summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-10-14 11:50:58 -0700
committerRose Hogenson <rosehogenson@posteo.net>2025-10-14 11:50:58 -0700
commitae0e116d90cc9b2e7e268e497a7838bc756e2087 (patch)
treeb6e759ca2d9491225abf4679d9817425a76ba956
parent21d7462b057838eb173145d79d2b9fd6c71e8053 (diff)
downloadroseh.moe-ae0e116d90cc9b2e7e268e497a7838bc756e2087.tar.zst
Revert "Remove CSRF tokens"
This reverts commit 21d7462b057838eb173145d79d2b9fd6c71e8053. This was a great idea, but tanjiro is still on Go 1.24
-rw-r--r--roseh.moe.go108
-rw-r--r--templates/login.html.template1
-rw-r--r--templates/note.html.template1
3 files changed, 78 insertions, 32 deletions
diff --git a/roseh.moe.go b/roseh.moe.go
index 1783a03..481da6b 100644
--- a/roseh.moe.go
+++ b/roseh.moe.go
@@ -371,31 +371,63 @@ func attachCookie(w http.ResponseWriter) error {
return nil
}
-func cookieAuth(w http.ResponseWriter, r *http.Request) bool {
+func cookieAuth(w http.ResponseWriter, r *http.Request) (string, bool) {
+ const csrfTokenLen = 32
+ var csrfToken string
+ if csrfCookie, err := r.Cookie("csrf-token"); err == nil {
+ csrfToken = csrfCookie.Value
+ } else {
+ buf := make([]byte, csrfTokenLen)
+ rand.Read(buf)
+ csrfToken = base64.RawURLEncoding.EncodeToString(buf)
+ http.SetCookie(w, &http.Cookie{
+ Name: "csrf-token",
+ Value: csrfToken,
+ Path: "/",
+ Secure: true,
+ HttpOnly: true,
+ SameSite: http.SameSiteStrictMode,
+ Partitioned: true,
+ })
+ }
+
cookie, err := r.Cookie("auth")
if err != nil {
- return false
+ return csrfToken, false
}
authCookie, err := base64.RawURLEncoding.DecodeString(cookie.Value)
if err != nil {
- return false
+ return csrfToken, false
}
msg, ok := verify(authCookie)
if !ok {
- return false
+ return csrfToken, false
}
var t time.Time
if err := t.UnmarshalBinary(msg); err != nil {
- return false
+ return csrfToken, false
}
cookieAge := time.Since(t)
if cookieAge > cookieExpiration {
- return false
+ return csrfToken, false
}
if cookieAge > 24*time.Hour {
attachCookie(w)
}
- return true
+ return csrfToken, true
+}
+
+func checkCSRFToken(r *http.Request) error {
+ cookie, err := r.Cookie("csrf-token")
+ if err != nil {
+ return err
+ }
+ cookieHash := sha256.Sum256([]byte(cookie.Value))
+ formValueHash := sha256.Sum256([]byte(r.FormValue("csrf-token")))
+ if subtle.ConstantTimeCompare(cookieHash[:], formValueHash[:]) == 0 {
+ return fmt.Errorf("bad CSRF token")
+ }
+ return nil
}
func checkPassword(password string) bool {
@@ -411,18 +443,26 @@ var (
)
type loginTemplateArgs struct {
- Error bool
+ CSRFToken string
+ Error bool
}
-func executeLoginTemplate(w io.Writer) {
- if err := loginTemplate.Execute(w, loginTemplateArgs{}); err != nil {
+func executeLoginTemplate(w io.Writer, csrfToken string) {
+ if err := loginTemplate.Execute(w, loginTemplateArgs{CSRFToken: csrfToken}); err != nil {
log.Printf("Warning: login: %s", err)
}
}
func login(w http.ResponseWriter, r *http.Request) {
+ if err := checkCSRFToken(r); err != nil {
+ http.Error(w, "bad CSRF token", http.StatusBadRequest)
+ return
+ }
if !checkPassword(r.FormValue("password")) {
- if err := loginTemplate.Execute(w, loginTemplateArgs{Error: true}); err != nil {
+ if err := loginTemplate.Execute(w, loginTemplateArgs{
+ Error: true,
+ CSRFToken: r.FormValue("csrf-token"),
+ }); err != nil {
log.Printf("Warning: login: %s", err)
}
return
@@ -441,24 +481,29 @@ var (
)
type notepadTemplateArgs struct {
- Content string
+ Content string
+ CSRFToken string
}
func notepad(w http.ResponseWriter, r *http.Request) {
- if !cookieAuth(w, r) {
- executeLoginTemplate(w)
+ csrfToken, ok := cookieAuth(w, r)
+ if !ok {
+ executeLoginTemplate(w, csrfToken)
return
}
notepadContentsMu.Lock()
currentContent := notepadContents
notepadContentsMu.Unlock()
- if err := notepadTemplate.Execute(w, notepadTemplateArgs{Content: currentContent}); err != nil {
+ if err := notepadTemplate.Execute(w, notepadTemplateArgs{Content: currentContent, CSRFToken: csrfToken}); err != nil {
log.Printf("Warning: notepad: %s", err)
}
}
func saveNote(w http.ResponseWriter, r *http.Request) error {
- if !cookieAuth(w, r) {
+ if err := checkCSRFToken(r); err != nil {
+ return err
+ }
+ if _, ok := cookieAuth(w, r); !ok {
return fmt.Errorf("not logged in")
}
newContent := r.FormValue("content")
@@ -521,23 +566,22 @@ func main() {
log.Fatal(err)
}
- mux := http.NewServeMux()
- mux.HandleFunc("GET /pong", pong)
- mux.HandleFunc("GET /wormhole", wormhole)
- mux.HandleFunc("POST /wormhole/{hole}", wormholeSend)
- mux.HandleFunc("GET /wormhole/{hole}", wormholeRecv)
- mux.HandleFunc("GET /wormhole/{hole}/ready", wormholeReady)
- mux.HandleFunc("POST /login", login)
- mux.HandleFunc("GET /notepad", notepad)
- mux.HandleFunc("POST /notepad", autosave)
- mux.HandleFunc("GET /cmd/{cmd}", cmd)
- mux.HandleFunc("GET /pkg/{pkg}", pkg)
- mux.HandleFunc("GET /static/", static)
- mux.HandleFunc("GET /favicon.ico", favicon)
- mux.HandleFunc("GET /{$}", index)
- mux.HandleFunc("GET /", notFound)
+ http.HandleFunc("GET /pong", pong)
+ http.HandleFunc("GET /wormhole", wormhole)
+ http.HandleFunc("POST /wormhole/{hole}", wormholeSend)
+ http.HandleFunc("GET /wormhole/{hole}", wormholeRecv)
+ http.HandleFunc("GET /wormhole/{hole}/ready", wormholeReady)
+ http.HandleFunc("POST /login", login)
+ http.HandleFunc("GET /notepad", notepad)
+ http.HandleFunc("POST /notepad", autosave)
+ http.HandleFunc("GET /cmd/{cmd}", cmd)
+ http.HandleFunc("GET /pkg/{pkg}", pkg)
+ http.HandleFunc("GET /static/", static)
+ http.HandleFunc("GET /favicon.ico", favicon)
+ http.HandleFunc("GET /{$}", index)
+ http.HandleFunc("GET /", notFound)
addr := fmt.Sprintf(":%d", *port)
log.Printf("Listening on %q", addr)
- log.Fatal(http.ListenAndServe(addr, http.NewCrossOriginProtection().Handler(mux)))
+ log.Fatal(http.ListenAndServe(addr, nil))
}
diff --git a/templates/login.html.template b/templates/login.html.template
index 0cf3469..f08caf7 100644
--- a/templates/login.html.template
+++ b/templates/login.html.template
@@ -5,6 +5,7 @@
<p class="login-error">Incorrect password</p>
{{end}}
<form class="password-form" action="/login" method="post">
+ <input type="hidden" name="csrf-token" value="{{.CSRFToken}}">
<label class="password-label" for="password">Enter password</label>
<input id="password" class="password" type="password" name="password" autofocus>
</form>
diff --git a/templates/note.html.template b/templates/note.html.template
index 19da416..07c2d8e 100644
--- a/templates/note.html.template
+++ b/templates/note.html.template
@@ -6,6 +6,7 @@
{{end}}
<form id="form" method="post">
+ <input type="hidden" name="csrf-token" value="{{.CSRFToken}}">
<div class="save-indicator-container">
{{block "saveIndicator" ""}}
<span id="save-indicator">{{.}}</span>