favoritter/internal/middleware/auth.go
Ole-Morten Duesund aa5ab6b415 fix: address code review findings for Phase 7-8
Bugs fixed:
- Renderer.Error set WriteHeader before Content-Type, causing
  the header to be silently dropped. Now sets Content-Type first.
- truncate template function operated on bytes, not runes — could
  split multi-byte UTF-8 characters (Norwegian æøå). Now uses
  []rune for correct Unicode handling.

Performance:
- Skip session DB lookup (2 queries) on /static/ and /uploads/
  requests — these never use user context.

UX consistency:
- Replace all http.NotFound and http.Error("Forbidden") in
  handler layer with styled error pages via Renderer.Error.
- Add notFound/forbidden helper methods on Handler.

Deployment fixes:
- Remove false libc6/glibc deps from nfpm.yaml (binary is
  statically linked with CGO_ENABLED=0).
- Add CGO_ENABLED=0 to Makefile build target for consistency.
- Add .dockerignore to exclude .git, dist/, data/ from build
  context.
- Remove phantom 'lint' from Makefile .PHONY.
- Document ProtectSystem=strict constraint in systemd service.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:39:10 +02:00

68 lines
1.7 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-or-later
package middleware
import (
"context"
"errors"
"net/http"
"strings"
"kode.naiv.no/olemd/favoritter/internal/store"
)
const SessionCookieName = "session"
// ClearSessionCookie sets an expired session cookie to remove it from the client.
func ClearSessionCookie(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Name: SessionCookieName,
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
})
}
// SessionLoader loads the user from the session cookie on every request.
// If the session is valid, the user is attached to the request context.
func SessionLoader(sessions *store.SessionStore, users *store.UserStore) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Skip session lookup for static assets and uploads — they
// never use the user context and this avoids 2 DB queries
// per asset per page load.
if strings.HasPrefix(r.URL.Path, "/static/") ||
strings.HasPrefix(r.URL.Path, "/uploads/") {
next.ServeHTTP(w, r)
return
}
cookie, err := r.Cookie(SessionCookieName)
if err != nil {
next.ServeHTTP(w, r)
return
}
session, err := sessions.Validate(cookie.Value)
if err != nil {
if errors.Is(err, store.ErrSessionNotFound) {
ClearSessionCookie(w)
}
next.ServeHTTP(w, r)
return
}
user, err := users.GetByID(session.UserID)
if err != nil || user.Disabled {
sessions.Delete(cookie.Value)
ClearSessionCookie(w)
next.ServeHTTP(w, r)
return
}
ctx := context.WithValue(r.Context(), userKey, user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}