package web import ( "context" "net/http" "time" "maisym.com/hsa/internal/auth" ) type ctxKey int const sessionKey ctxKey = 0 // requireAuth rejects requests without a valid session cookie by redirecting to /login. // On success the decoded session is placed in the request context. func (s *Server) requireAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c, err := r.Cookie(sessionCookie) if err != nil { http.Redirect(w, r, "/login", http.StatusFound) return } sess, err := auth.DecodeSession(c.Value, s.cfg.SessionKey) if err != nil { s.clearCookie(w, sessionCookie) http.Redirect(w, r, "/login", http.StatusFound) return } // Server-side expiry: a cookie's MaxAge is client-enforced, so also reject a // session older than its TTL — a captured cookie value can't be replayed forever. if time.Since(sess.IssuedAt) > sessionTTL { s.clearCookie(w, sessionCookie) http.Redirect(w, r, "/login", http.StatusFound) return } ctx := context.WithValue(r.Context(), sessionKey, sess) next.ServeHTTP(w, r.WithContext(ctx)) }) } // sessionFrom returns the session stored in the context by requireAuth. func sessionFrom(ctx context.Context) auth.Session { sess, _ := ctx.Value(sessionKey).(auth.Session) return sess } func (s *Server) setCookie(w http.ResponseWriter, name, value string, ttl time.Duration) { http.SetCookie(w, &http.Cookie{ Name: name, Value: value, Path: "/", MaxAge: int(ttl.Seconds()), HttpOnly: true, Secure: s.cfg.SecureCookies, SameSite: http.SameSiteLaxMode, }) } func (s *Server) clearCookie(w http.ResponseWriter, name string) { http.SetCookie(w, &http.Cookie{ Name: name, Value: "", Path: "/", MaxAge: -1, HttpOnly: true, Secure: s.cfg.SecureCookies, SameSite: http.SameSiteLaxMode, }) }