Skip to content

Commit f5ab02d

Browse files
committed
Added flash messages
1 parent 2083da7 commit f5ab02d

File tree

6 files changed

+191
-34
lines changed

6 files changed

+191
-34
lines changed

cmd/goauth2/main.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ func main() {
2727

2828
requestedOauth2Port := flag.Uint("port", 0, "port")
2929
templatesPath := flag.String("templates", "", "optional templates path")
30-
csrfAuthKey := flag.String("csrfAuthKey", shortuuid.New().String(), "optional templates path")
30+
csrfAuthKey := flag.String("csrfAuthKey", shortuuid.New().String(), "csrf authentication key")
31+
sessionAuthKey := flag.String("sessionAuthKey", shortuuid.New().String()+shortuuid.New().String(), "cookie session auth key (64 bytes)")
32+
sessionEncryptionKey := flag.String("sessionEncryptionKey", shortuuid.New().String(), "cookie session encryption key (32 bytes)")
3133
flag.Parse()
3234

3335
oAuth2Listener, err := getListener(*requestedOauth2Port)
@@ -47,6 +49,7 @@ func main() {
4749
web.WithGoAuth2App(goAuth2App),
4850
web.WithHost(oAuth2Listener.Addr().String()),
4951
web.WithCSRFAuthKey([]byte(*csrfAuthKey)),
52+
web.WithSessionKey([]byte(*sessionAuthKey), []byte(*sessionEncryptionKey)),
5053
}
5154

5255
if *templatesPath != "" {

go.mod

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ require (
99
github.com/gorilla/csrf v1.7.1
1010
github.com/gorilla/handlers v1.4.2 // indirect
1111
github.com/gorilla/mux v1.8.0
12+
github.com/gorilla/sessions v1.2.1
1213
github.com/inklabs/rangedb v0.12.1-0.20211102191110-c880f5f0baa1
1314
github.com/pmezard/go-difflib v1.0.0 // indirect
1415
github.com/stretchr/testify v1.7.0
1516
github.com/vmihailenco/msgpack/v4 v4.3.11 // indirect
1617
github.com/vmihailenco/tagparser v0.1.1 // indirect
1718
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
18-
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect
19+
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
1920
google.golang.org/appengine v1.6.6 // indirect
2021
google.golang.org/protobuf v1.25.0 // indirect
2122
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
4848
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
4949
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
5050
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
51+
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
52+
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
5153
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
5254
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
5355
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=

web/templates/layout/base.gohtml

+17
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,23 @@
3030
</div>
3131
</div>
3232

33+
{{block "flash" .}}
34+
{{ if index .Errors }}
35+
<div class="callout alert">
36+
{{ range index .Errors }}
37+
<p>{{ . }}</p>
38+
{{ end }}
39+
</div>
40+
{{ end }}
41+
{{ if index .Messages }}
42+
<div class="callout success">
43+
{{ range index .Messages }}
44+
<p>{{ . }}</p>
45+
{{ end }}
46+
</div>
47+
{{ end }}
48+
{{end}}
49+
3350
<div id="content">
3451
{{block "content" .}}{{end}}
3552
</div>

web/web_app.go

+109-24
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/gorilla/csrf"
1616
"github.com/gorilla/mux"
17+
"github.com/gorilla/sessions"
1718
"github.com/inklabs/rangedb"
1819
"github.com/inklabs/rangedb/pkg/shortuuid"
1920

@@ -22,9 +23,10 @@ import (
2223
)
2324

2425
const (
25-
accessTokenTODO = "f5bb89d486ee458085e476871b177ff4"
26-
expiresAtTODO = 1574371565
27-
AdminUserIDTODO = "873aeb9386724213b4c1410bce9f838c"
26+
accessTokenTODO = "f5bb89d486ee458085e476871b177ff4"
27+
expiresAtTODO = 1574371565
28+
AdminUserIDTODO = "873aeb9386724213b4c1410bce9f838c"
29+
flashSessionName = "fmsg"
2830
)
2931

3032
//go:embed static
@@ -36,13 +38,16 @@ var templates embed.FS
3638
const defaultHost = "0.0.0.0:8080"
3739

3840
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 {
4651
emailToUserID *projection.EmailToUserID
4752
clientApplications *projection.ClientApplications
4853
users *projection.Users
@@ -80,13 +85,21 @@ func WithUUIDGenerator(generator shortuuid.Generator) Option {
8085
}
8186
}
8287

83-
// WithCSRFAuthKey is a functional option to inject a CSRf authentication key
88+
// WithCSRFAuthKey is a functional option to inject a CSRF authentication key
8489
func WithCSRFAuthKey(csrfAuthKey []byte) Option {
8590
return func(app *webApp) {
8691
app.csrfAuthKey = csrfAuthKey
8792
}
8893
}
8994

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+
90103
// New constructs an webApp.
91104
func New(options ...Option) (*webApp, error) {
92105
goAuth2App, err := goauth2.New()
@@ -95,10 +108,12 @@ func New(options ...Option) (*webApp, error) {
95108
}
96109

97110
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,
102117
}
103118

104119
for _, option := range options {
@@ -110,6 +125,11 @@ func New(options ...Option) (*webApp, error) {
110125
return nil, err
111126
}
112127

128+
err = app.initSessionStore()
129+
if err != nil {
130+
return nil, err
131+
}
132+
113133
app.initRoutes()
114134
err = app.initProjections()
115135
if err != nil {
@@ -131,6 +151,19 @@ func (a *webApp) validateCSRFAuthKey() error {
131151
return nil
132152
}
133153

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+
134167
func (a *webApp) initRoutes() {
135168
r := mux.NewRouter().StrictSlash(true)
136169
r.HandleFunc("/authorize", a.authorize)
@@ -179,17 +212,19 @@ func (a *webApp) login(w http.ResponseWriter, r *http.Request) {
179212
scope := params.Get("scope")
180213

181214
a.renderTemplate(w, "login.gohtml", struct {
215+
flashMessageVars
182216
ClientId string
183217
RedirectURI string
184218
ResponseType string
185219
Scope string
186220
State string
187221
}{
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),
193228
})
194229
}
195230

@@ -200,6 +235,7 @@ type ClientApplication struct {
200235
}
201236

202237
type listClientApplicationsTemplateVars struct {
238+
flashMessageVars
203239
ClientApplications []ClientApplication
204240
}
205241

@@ -220,6 +256,11 @@ func (a *webApp) listClientApplications(w http.ResponseWriter, _ *http.Request)
220256
})
221257
}
222258

259+
type flashMessageVars struct {
260+
Errors []string
261+
Messages []string
262+
}
263+
223264
type User struct {
224265
UserID string
225266
Username string
@@ -230,10 +271,11 @@ type User struct {
230271
}
231272

232273
type listUsersTemplateVars struct {
274+
flashMessageVars
233275
Users []User
234276
}
235277

236-
func (a *webApp) listUsers(w http.ResponseWriter, _ *http.Request) {
278+
func (a *webApp) listUsers(w http.ResponseWriter, r *http.Request) {
237279

238280
var users []User
239281

@@ -249,19 +291,22 @@ func (a *webApp) listUsers(w http.ResponseWriter, _ *http.Request) {
249291
}
250292

251293
a.renderTemplate(w, "list-users.gohtml", listUsersTemplateVars{
252-
Users: users,
294+
Users: users,
295+
flashMessageVars: a.getFlashMessageVars(w, r),
253296
})
254297
}
255298

256299
type addUserTemplateVars struct {
300+
flashMessageVars
257301
Username string
258302
CSRFField template.HTML
259303
}
260304

261305
func (a *webApp) showAddUser(w http.ResponseWriter, r *http.Request) {
262306
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),
265310
})
266311
}
267312

@@ -280,6 +325,7 @@ func (a *webApp) submitAddUser(w http.ResponseWriter, r *http.Request) {
280325
redirectURI := url.URL{
281326
Path: "/admin/add-user",
282327
}
328+
a.FlashError(w, r, "username or password are required")
283329
http.Redirect(w, r, redirectURI.String(), http.StatusFound)
284330
return
285331
}
@@ -301,6 +347,8 @@ func (a *webApp) submitAddUser(w http.ResponseWriter, r *http.Request) {
301347
return
302348
}
303349

350+
a.FlashMessage(w, r, "User (%s) was added", username)
351+
304352
uri := url.URL{
305353
Path: "/admin/list-users",
306354
}
@@ -624,6 +672,43 @@ func (a *webApp) renderTemplate(w http.ResponseWriter, templateName string, data
624672
}
625673
}
626674

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+
627712
type errorResponse struct {
628713
Error string `json:"error"`
629714
}

0 commit comments

Comments
 (0)