From ae0e116d90cc9b2e7e268e497a7838bc756e2087 Mon Sep 17 00:00:00 2001 From: Rose Hogenson Date: Tue, 14 Oct 2025 11:50:58 -0700 Subject: Revert "Remove CSRF tokens" This reverts commit 21d7462b057838eb173145d79d2b9fd6c71e8053. This was a great idea, but tanjiro is still on Go 1.24 --- roseh.moe.go | 108 +++++++++++++++++++++++++++++------------- templates/login.html.template | 1 + templates/note.html.template | 1 + 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 @@

Incorrect password

{{end}}
+
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}}
+
{{block "saveIndicator" ""}} {{.}} -- cgit v1.3.1