-
Notifications
You must be signed in to change notification settings - Fork 1
/
server.go
152 lines (132 loc) · 5.13 KB
/
server.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
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package server TODO(clap|kele) describe this as what the users would normally have to write.
package server
import (
"github.com/google/go-safeweb/safehttp"
"embed"
"github.com/empijei/go-safeweb-example-app/src/secure/auth"
"github.com/empijei/go-safeweb-example-app/src/secure/responses"
"github.com/empijei/go-safeweb-example-app/src/storage"
"github.com/google/go-safeweb/safehttp/plugins/htmlinject"
"github.com/google/safehtml/template"
)
//go:embed static
var staticFiles embed.FS
//go:embed templates
var templatesFS embed.FS
var templates *template.Template
func init() {
tplSrc := template.TrustedSourceFromConstant("templates/*.tpl.html")
var err error
// Automatically inject CSP nonces and XSRF tokens placeholders.
templates, err = htmlinject.LoadGlobEmbed(nil, htmlinject.LoadConfig{}, tplSrc, templatesFS)
if err != nil {
panic(err)
}
}
type serverDeps struct {
db *storage.DB
}
func Load(db *storage.DB, cfg *safehttp.ServeMuxConfig) {
deps := &serverDeps{
db: db,
}
// Private endpoints, only accessible to authenticated users (default).
cfg.Handle("/notes/", "GET", getNotesHandler(deps))
cfg.Handle("/notes", "POST", postNotesHandler(deps))
cfg.Handle("/logout", "POST", logoutHandler(deps))
// Public enpoints, no auth checks performed.
cfg.Handle("/login", "POST", postLoginHandler(deps), auth.Skip{})
cfg.Handle("/static/", "GET", safehttp.FileServerEmbed(staticFiles), auth.Skip{})
cfg.Handle("/", "GET", indexHandler(deps), auth.Skip{})
}
func getNotesHandler(deps *serverDeps) safehttp.Handler {
return safehttp.HandlerFunc(func(rw safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.Result {
user := auth.User(r)
notes := deps.db.GetNotes(user)
return safehttp.ExecuteNamedTemplate(rw, templates, "notes.tpl.html", map[string]interface{}{
"notes": notes,
"user": user,
})
})
}
func postNotesHandler(deps *serverDeps) safehttp.Handler {
noFormErr := responses.NewError(
safehttp.StatusBadRequest,
template.MustParseAndExecuteToHTML(`Please submit a valid form with "title" and "text" parameters.`),
)
noFieldsErr := responses.NewError(
safehttp.StatusBadRequest,
template.MustParseAndExecuteToHTML("Both title and text must be specified."),
)
return safehttp.HandlerFunc(func(rw safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.Result {
form, err := r.PostForm()
if err != nil {
return rw.WriteError(noFormErr)
}
title := form.String("title", "")
body := form.String("text", "")
if title == "" || body == "" {
return rw.WriteError(noFieldsErr)
}
user := auth.User(r)
deps.db.AddOrEditNote(user, storage.Note{Title: title, Text: body})
notes := deps.db.GetNotes(user)
return safehttp.ExecuteNamedTemplate(rw, templates, "notes.tpl.html", map[string]interface{}{
"notes": notes,
"user": user,
})
})
}
func indexHandler(deps *serverDeps) safehttp.Handler {
return safehttp.HandlerFunc(func(rw safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.Result {
user := auth.User(r)
if user != "" {
return safehttp.Redirect(rw, r, "/notes/", safehttp.StatusTemporaryRedirect)
}
return safehttp.ExecuteNamedTemplate(rw, templates, "index.tpl.html", nil)
})
}
// Logout and Login handlers would normally be centralized and provided by a separate package owned by the security team.
// Since this is a simple example application they are here together with the rest.
func logoutHandler(deps *serverDeps) safehttp.Handler {
return safehttp.HandlerFunc(func(rw safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.Result {
auth.ClearSession(r)
return safehttp.Redirect(rw, r, "/", safehttp.StatusSeeOther)
})
}
var invalidAuthErr responses.Error = responses.NewError(
safehttp.StatusBadRequest,
template.MustParseAndExecuteToHTML("Please specify a username and a password, both must be non-empty and your password must match the one you use to register."),
)
func postLoginHandler(deps *serverDeps) safehttp.Handler {
// Always return the same error to not leak the existence of a user.
return safehttp.HandlerFunc(func(rw safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.Result {
form, err := r.PostForm()
if err != nil {
return rw.WriteError(invalidAuthErr)
}
username := form.String("username", "")
password := form.String("password", "")
if username == "" || password == "" {
return rw.WriteError(invalidAuthErr)
}
if err := deps.db.AddOrAuthUser(username, password); err != nil {
return rw.WriteError(invalidAuthErr)
}
auth.CreateSession(r, username)
return safehttp.Redirect(rw, r, "/notes/", safehttp.StatusSeeOther)
})
}