Comments only, no behaviour changed. modernc.org/sqlite v1.60.1 (the _pragma list on every connection, busy_timeout first; extended result codes; a deferred BEGIN; multi-statement Exec) and SQLite 3.53.4's documentation (transactions, WAL, foreign keys, NOCASE, upsert, julianday, partial indexes); htmx 2.0.4 src/htmx.js; the Fetch and HTML standards for Sec-Fetch-Site, Origin, SameSite, __Host-, CSP and defer order; Go's html/template. Comments the source contradicts are corrected - the driver does not serialise writes (SetMaxOpenConns(1) does), htmx:load fires per swapped element, no-store does not keep a page out of history. Left for their own commits: D-S3 a migration two processes can both run, D-S20 recipient case, D-B3 Origin: null under Referrer-Policy: no-referrer, D-H5/H6/H7/H9 (audit M11, stage 4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
52 lines
1.6 KiB
Go
52 lines
1.6 KiB
Go
package auth
|
|
|
|
import (
|
|
"net/http"
|
|
)
|
|
|
|
// RequireAuth wraps a handler so only requests with a valid session cookie
|
|
// reach it; everyone else is redirected to the login page. The authenticated
|
|
// principal is stashed in the request context for downstream handlers.
|
|
func (m *Module) RequireAuth(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
token, ok := m.sessionToken(r)
|
|
if !ok {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
userID, ok := m.sessions.Lookup(token)
|
|
if !ok {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
u, err := m.store.GetUser(userID)
|
|
if err != nil {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
// Only a session whose user exists is kept alive by being used.
|
|
if isSessionActivity(r) && m.sessions.Touch(token) {
|
|
m.setSessionCookie(w, token)
|
|
}
|
|
p := principalFromUser(u)
|
|
ctx := withPrincipal(r.Context(), p)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
|
|
// isSessionActivity: a GET from htmx is a background poll - every panel hx-get
|
|
// is a load or a poll - and does not renew the session. htmx 2.0.4, src/htmx.js
|
|
// getHeaders(): every request it issues carries HX-Request: true (and
|
|
// loadHistoryFromServer() sets it too).
|
|
func isSessionActivity(r *http.Request) bool {
|
|
return !(r.Method == http.MethodGet && r.Header.Get("HX-Request") != "")
|
|
}
|
|
|
|
// CurrentUser returns the authenticated username from the request context.
|
|
func CurrentUser(r *http.Request) string {
|
|
if v, ok := r.Context().Value(usernameKey).(string); ok {
|
|
return v
|
|
}
|
|
return ""
|
|
}
|