-
Notifications
You must be signed in to change notification settings - Fork 0
/
mongo.go
204 lines (177 loc) · 5.8 KB
/
mongo.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// Package vagorillasessionsstores is a Gorilla sessions.Store implementation for BadgerDB, MongoDB and Dgraph
package vagorillasessionsstores
import (
"context"
"encoding/base32"
"net/http"
"strings"
"time"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// NewMongoStore returns a new Mongo backed store.
// opts expects options.Client() mongo driver client options.
// If databaseName is left empty "" a default "sessions" named database will be created.
// If collectionName is left empty "" a default "store" named collection will be created.
//
// Keys are defined in pairs to allow key rotation, but the common case is
// to set a single authentication key and optionally an encryption key.
//
// The first key in a pair is used for authentication and the second for
// encryption. The encryption key can be set to nil or omitted in the last
// pair, but the authentication key is required in all pairs.
//
// It is recommended to use an authentication key with 32 or 64 bytes.
// The encryption key, if set, must be either 16, 24, or 32 bytes to select
// AES-128, AES-192, or AES-256 modes.
func NewMongoStore(client *mongo.Client, databaseName string, collectionName string, keyPairs ...[]byte) (*MongoStore, error) {
if databaseName == "" {
databaseName = "sessions"
}
if collectionName == "" {
collectionName = "store"
}
collection := client.Database(databaseName).Collection(collectionName)
store := &MongoStore{
Codecs: securecookie.CodecsFromPairs(keyPairs...),
Options: &sessions.Options{
Path: "/",
MaxAge: 86400 * 30,
},
db: collection,
}
store.MaxAge(store.Options.MaxAge)
return store, nil
}
// MongoStore stores sessions using MongoDB
type MongoStore struct {
Codecs []securecookie.Codec
Options *sessions.Options
db *mongo.Collection
}
// Get returns a session for the given name after adding it to the registry.
//
// It returns a new session if the sessions doesn't exist. Access IsNew on
// the session to check if it is an existing session or a new one.
//
// It returns a new session and an error if the session exists but could
// not be decoded.
func (s *MongoStore) Get(r *http.Request, name string) (*sessions.Session, error) {
return sessions.GetRegistry(r).Get(s, name)
}
// New returns a session for the given name without adding it to the registry.
//
// The difference between New() and Get() is that calling New() twice will
// decode the session data twice, while Get() registers and reuses the same
// decoded session after the first call.
func (s *MongoStore) New(r *http.Request, name string) (*sessions.Session, error) {
session := sessions.NewSession(s, name)
opts := *s.Options
session.Options = &opts
session.IsNew = true
var err error
if c, errCookie := r.Cookie(name); errCookie == nil {
err = securecookie.DecodeMulti(name, c.Value, &session.ID,
s.Codecs...)
err = s.load(session)
if err == nil {
session.IsNew = false
}
}
return session, err
}
// Save adds a single session to the response.
//
// If the Options.MaxAge of the session is <= 0 then the session file will be
// deleted from the store path. With this process it enforces the properly
// session cookie handling so no need to trust in the cookie management in the
// web browser.
func (s *MongoStore) Save(r *http.Request, w http.ResponseWriter,
session *sessions.Session) error {
// Delete if max-age is <= 0
if session.Options.MaxAge <= 0 {
if err := s.erase(session); err != nil {
return err
}
http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
return nil
}
if session.ID == "" {
session.ID = strings.TrimRight(
base32.StdEncoding.EncodeToString(
securecookie.GenerateRandomKey(32)), "=")
}
if err := s.save(session); err != nil {
return err
}
encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
s.Codecs...)
if err != nil {
return err
}
http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
return nil
}
// MaxAge sets the maximum age for the store and the underlying cookie
// implementation. Individual sessions can be deleted by setting Options.MaxAge
// = -1 for that session.
func (s *MongoStore) MaxAge(age int) {
s.Options.MaxAge = age
// Set the maxAge for each securecookie instance.
for _, codec := range s.Codecs {
if sc, ok := codec.(*securecookie.SecureCookie); ok {
sc.MaxAge(age)
}
}
}
type SessionEntry struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
SessionID string `bson:"sessionid,omitempty"`
Value string `bson:"value,omitempty"`
}
func (s *MongoStore) save(session *sessions.Session) error {
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
s.Codecs...)
if err != nil {
return err
}
filt := SessionEntry{
SessionID: session.ID,
}
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
opts := options.Update().SetUpsert(true)
_, err = s.db.UpdateOne(
ctx,
filt,
bson.D{
{"$set", bson.D{{"value", encoded}, {"sessionid", session.ID}}},
},
opts,
)
return err
}
func (s *MongoStore) load(session *sessions.Session) error {
var result SessionEntry
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
entry := SessionEntry{
SessionID: session.ID,
}
res := s.db.FindOne(ctx, entry)
err := res.Decode(&result)
if err != nil {
return (err)
}
return securecookie.DecodeMulti(session.Name(), string(result.Value), &session.Values, s.Codecs...)
}
func (s *MongoStore) erase(session *sessions.Session) error {
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
entry := SessionEntry{
SessionID: session.ID,
}
_, err := s.db.DeleteOne(ctx, entry)
return err
}