@@ -14,6 +14,7 @@ import (
14
14
15
15
"github.com/gorilla/csrf"
16
16
"github.com/gorilla/mux"
17
+ "github.com/gorilla/sessions"
17
18
"github.com/inklabs/rangedb"
18
19
"github.com/inklabs/rangedb/pkg/shortuuid"
19
20
@@ -22,9 +23,10 @@ import (
22
23
)
23
24
24
25
const (
25
- accessTokenTODO = "f5bb89d486ee458085e476871b177ff4"
26
- expiresAtTODO = 1574371565
27
- AdminUserIDTODO = "873aeb9386724213b4c1410bce9f838c"
26
+ accessTokenTODO = "f5bb89d486ee458085e476871b177ff4"
27
+ expiresAtTODO = 1574371565
28
+ AdminUserIDTODO = "873aeb9386724213b4c1410bce9f838c"
29
+ flashSessionName = "fmsg"
28
30
)
29
31
30
32
//go:embed static
@@ -36,13 +38,16 @@ var templates embed.FS
36
38
const defaultHost = "0.0.0.0:8080"
37
39
38
40
type webApp struct {
39
- router http.Handler
40
- templateFS fs.FS
41
- goAuth2App * goauth2.App
42
- uuidGenerator shortuuid.Generator
43
- csrfAuthKey []byte
44
- host string
45
- projections struct {
41
+ router http.Handler
42
+ templateFS fs.FS
43
+ goAuth2App * goauth2.App
44
+ uuidGenerator shortuuid.Generator
45
+ sessionStore sessions.Store
46
+ sessionAuthKey []byte
47
+ sessionEncryptionKey []byte
48
+ csrfAuthKey []byte
49
+ host string
50
+ projections struct {
46
51
emailToUserID * projection.EmailToUserID
47
52
clientApplications * projection.ClientApplications
48
53
users * projection.Users
@@ -80,13 +85,21 @@ func WithUUIDGenerator(generator shortuuid.Generator) Option {
80
85
}
81
86
}
82
87
83
- // WithCSRFAuthKey is a functional option to inject a CSRf authentication key
88
+ // WithCSRFAuthKey is a functional option to inject a CSRF authentication key
84
89
func WithCSRFAuthKey (csrfAuthKey []byte ) Option {
85
90
return func (app * webApp ) {
86
91
app .csrfAuthKey = csrfAuthKey
87
92
}
88
93
}
89
94
95
+ // WithSessionKey is a functional option to inject a session auth and encryption key
96
+ func WithSessionKey (authenticationKey , encryptionKey []byte ) Option {
97
+ return func (app * webApp ) {
98
+ app .sessionAuthKey = authenticationKey
99
+ app .sessionEncryptionKey = encryptionKey
100
+ }
101
+ }
102
+
90
103
// New constructs an webApp.
91
104
func New (options ... Option ) (* webApp , error ) {
92
105
goAuth2App , err := goauth2 .New ()
@@ -95,10 +108,12 @@ func New(options ...Option) (*webApp, error) {
95
108
}
96
109
97
110
app := & webApp {
98
- templateFS : templates ,
99
- goAuth2App : goAuth2App ,
100
- uuidGenerator : shortuuid .NewUUIDGenerator (),
101
- host : defaultHost ,
111
+ templateFS : templates ,
112
+ goAuth2App : goAuth2App ,
113
+ uuidGenerator : shortuuid .NewUUIDGenerator (),
114
+ sessionAuthKey : []byte (shortuuid .NewUUIDGenerator ().New () + shortuuid .NewUUIDGenerator ().New ()),
115
+ sessionEncryptionKey : []byte (shortuuid .NewUUIDGenerator ().New ()),
116
+ host : defaultHost ,
102
117
}
103
118
104
119
for _ , option := range options {
@@ -110,6 +125,11 @@ func New(options ...Option) (*webApp, error) {
110
125
return nil , err
111
126
}
112
127
128
+ err = app .initSessionStore ()
129
+ if err != nil {
130
+ return nil , err
131
+ }
132
+
113
133
app .initRoutes ()
114
134
err = app .initProjections ()
115
135
if err != nil {
@@ -131,6 +151,19 @@ func (a *webApp) validateCSRFAuthKey() error {
131
151
return nil
132
152
}
133
153
154
+ func (a * webApp ) initSessionStore () error {
155
+ if len (a .sessionAuthKey ) != 64 {
156
+ return fmt .Errorf ("invalid session authentication key length" )
157
+ }
158
+
159
+ if len (a .sessionEncryptionKey ) != 32 {
160
+ return fmt .Errorf ("invalid session encryption key length" )
161
+ }
162
+
163
+ a .sessionStore = sessions .NewCookieStore (a .sessionAuthKey , a .sessionEncryptionKey )
164
+ return nil
165
+ }
166
+
134
167
func (a * webApp ) initRoutes () {
135
168
r := mux .NewRouter ().StrictSlash (true )
136
169
r .HandleFunc ("/authorize" , a .authorize )
@@ -179,17 +212,19 @@ func (a *webApp) login(w http.ResponseWriter, r *http.Request) {
179
212
scope := params .Get ("scope" )
180
213
181
214
a .renderTemplate (w , "login.gohtml" , struct {
215
+ flashMessageVars
182
216
ClientId string
183
217
RedirectURI string
184
218
ResponseType string
185
219
Scope string
186
220
State string
187
221
}{
188
- ClientId : clientId ,
189
- RedirectURI : redirectURI ,
190
- ResponseType : responseType ,
191
- Scope : scope ,
192
- State : state ,
222
+ ClientId : clientId ,
223
+ RedirectURI : redirectURI ,
224
+ ResponseType : responseType ,
225
+ Scope : scope ,
226
+ State : state ,
227
+ flashMessageVars : a .getFlashMessageVars (w , r ),
193
228
})
194
229
}
195
230
@@ -200,6 +235,7 @@ type ClientApplication struct {
200
235
}
201
236
202
237
type listClientApplicationsTemplateVars struct {
238
+ flashMessageVars
203
239
ClientApplications []ClientApplication
204
240
}
205
241
@@ -220,6 +256,11 @@ func (a *webApp) listClientApplications(w http.ResponseWriter, _ *http.Request)
220
256
})
221
257
}
222
258
259
+ type flashMessageVars struct {
260
+ Errors []string
261
+ Messages []string
262
+ }
263
+
223
264
type User struct {
224
265
UserID string
225
266
Username string
@@ -230,10 +271,11 @@ type User struct {
230
271
}
231
272
232
273
type listUsersTemplateVars struct {
274
+ flashMessageVars
233
275
Users []User
234
276
}
235
277
236
- func (a * webApp ) listUsers (w http.ResponseWriter , _ * http.Request ) {
278
+ func (a * webApp ) listUsers (w http.ResponseWriter , r * http.Request ) {
237
279
238
280
var users []User
239
281
@@ -249,19 +291,22 @@ func (a *webApp) listUsers(w http.ResponseWriter, _ *http.Request) {
249
291
}
250
292
251
293
a .renderTemplate (w , "list-users.gohtml" , listUsersTemplateVars {
252
- Users : users ,
294
+ Users : users ,
295
+ flashMessageVars : a .getFlashMessageVars (w , r ),
253
296
})
254
297
}
255
298
256
299
type addUserTemplateVars struct {
300
+ flashMessageVars
257
301
Username string
258
302
CSRFField template.HTML
259
303
}
260
304
261
305
func (a * webApp ) showAddUser (w http.ResponseWriter , r * http.Request ) {
262
306
a .renderTemplate (w , "add-user.gohtml" , addUserTemplateVars {
263
- Username : "" , // TODO: Add when form post fails on redirect
264
- CSRFField : csrf .TemplateField (r ),
307
+ Username : "" , // TODO: Add when form post fails on redirect
308
+ CSRFField : csrf .TemplateField (r ),
309
+ flashMessageVars : a .getFlashMessageVars (w , r ),
265
310
})
266
311
}
267
312
@@ -280,6 +325,7 @@ func (a *webApp) submitAddUser(w http.ResponseWriter, r *http.Request) {
280
325
redirectURI := url.URL {
281
326
Path : "/admin/add-user" ,
282
327
}
328
+ a .FlashError (w , r , "username or password are required" )
283
329
http .Redirect (w , r , redirectURI .String (), http .StatusFound )
284
330
return
285
331
}
@@ -301,6 +347,8 @@ func (a *webApp) submitAddUser(w http.ResponseWriter, r *http.Request) {
301
347
return
302
348
}
303
349
350
+ a .FlashMessage (w , r , "User (%s) was added" , username )
351
+
304
352
uri := url.URL {
305
353
Path : "/admin/list-users" ,
306
354
}
@@ -624,6 +672,43 @@ func (a *webApp) renderTemplate(w http.ResponseWriter, templateName string, data
624
672
}
625
673
}
626
674
675
+ func (a * webApp ) FlashError (w http.ResponseWriter , r * http.Request , format string , vars ... interface {}) {
676
+ a .flashMessage (w , r , "error" , fmt .Sprintf (format , vars ... ))
677
+ }
678
+
679
+ func (a * webApp ) FlashMessage (w http.ResponseWriter , r * http.Request , format string , vars ... interface {}) {
680
+ a .flashMessage (w , r , "message" , fmt .Sprintf (format , vars ... ))
681
+ }
682
+
683
+ func (a * webApp ) flashMessage (w http.ResponseWriter , r * http.Request , key , message string ) {
684
+ session , _ := a .sessionStore .Get (r , flashSessionName )
685
+ session .AddFlash (message , key )
686
+ _ = session .Save (r , w )
687
+ }
688
+
689
+ func (a * webApp ) getFlashMessageVars (w http.ResponseWriter , r * http.Request ) flashMessageVars {
690
+ session , _ := a .sessionStore .Get (r , flashSessionName )
691
+ fErrors := session .Flashes ("error" )
692
+ fMessages := session .Flashes ("message" )
693
+
694
+ var flashErrors , flashMessages []string
695
+ for _ , flash := range fErrors {
696
+ flashErrors = append (flashErrors , flash .(string ))
697
+ }
698
+ for _ , flash := range fMessages {
699
+ flashMessages = append (flashMessages , flash .(string ))
700
+ }
701
+
702
+ if len (fErrors ) > 0 || len (fMessages ) > 0 {
703
+ _ = session .Save (r , w )
704
+ }
705
+
706
+ return flashMessageVars {
707
+ Errors : flashErrors ,
708
+ Messages : flashMessages ,
709
+ }
710
+ }
711
+
627
712
type errorResponse struct {
628
713
Error string `json:"error"`
629
714
}
0 commit comments