/
session.go
155 lines (123 loc) · 3.18 KB
/
session.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package sessions
import (
"crypto/rand"
"errors"
"net/http"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"simpaix.net/simpa/v2/engine/crypt"
)
var ErrCipher = errors.New("simpa-sessions: SID is not signed by backend, but for compability created a new session")
// Session Cookie cofig
type Config struct {
Name string `bson:"name" json:"name"`
Value string `bson:"value" json:"value"`
MaxAge int `bson:"maxage" json:"maxage"`
Expires time.Time `bson:"expires" json:"expires"`
Secure bool `bson:"secure" json:"secure"`
HttpOnly bool `bson:"httponly" json:"httponly"`
SameSite http.SameSite `bson:"samesite" json:"samesite"`
}
// Session object
type Session struct {
ID string `bson:"_id" json:"_id"`
Values map[string]interface{} `bson:"values" json:"values"` // when marshaled should be encrypted through engine's crypter, when unmarshalled should decrypt and parse json
Opts *Config `bson:"options" json:"options"`
store Store
crypt crypt.CrypterI
}
type SessionI interface {
// Creates or loads session from the database
New(r *http.Request, config http.Cookie) (*Session, error)
// generateres unique SID
genSID() (string, error)
// saves session back to store
Save() error
// returns SID
SID() string
// sets key to map
Set(key string, val interface{})
// gets ley from map
Get(key string) interface{}
// sets store driver
SetStore(Store)
// sets crypter, should set it from engine
SetCrypter(crypt.CrypterI)
}
func (s *Session) New(r *http.Request, config *Config) (*Session, error) {
var err error
if c, noCookie := r.Cookie(config.Name); noCookie == nil {
// cookie on user's browser found
sid, err := s.crypt.Decrypt(c.Value)
if err == nil {
sess, err := s.store.Get(sid)
if err != nil {
return nil, err
}
s.ID = sess.ID
s.Values = sess.Values
s.Opts = sess.Opts
return s, err
}
}
gid, err := s.genSID()
if err != nil {
return nil, err
}
s.ID = gid
s.Opts = config
return s, err
}
func (s *Session) Save(w http.ResponseWriter) error {
if err := s.store.Set(s); err != nil {
return err
}
sid, err := s.crypt.Encrypt(s.ID)
if err != nil {
return err
}
s.Opts.Value = sid
http.SetCookie(w, &http.Cookie{
Name: s.Opts.Name,
Value: s.Opts.Value,
MaxAge: s.Opts.MaxAge,
Expires: s.Opts.Expires,
Secure: s.Opts.Secure,
HttpOnly: s.Opts.HttpOnly,
SameSite: s.Opts.SameSite,
})
return nil
}
func (s *Session) SID() string {
return s.ID
}
func (s *Session) genSID() (string, error) {
var sid string
for {
buff := make([]byte, 12)
if _, err := rand.Read(buff); err != nil {
return sid, err
}
sid = primitive.ObjectID(buff).Hex()
if _, err := s.store.Get(sid); err != nil {
if err == mongo.ErrNoDocuments {
break
}
return sid, err
}
}
return sid, nil
}
func (s *Session) SetStore(store Store) {
s.store = store
}
func (s *Session) SetCrypter(crypt crypt.CrypterI) {
s.crypt = crypt
}
func (s *Session) Set(key string, val interface{}) {
s.Values[key] = val
}
func (s *Session) Get(key string) interface{} {
return s.Values[key]
}