Go app for capturing and archiving HSA-eligible receipts: OIDC/PKCE auth against Authelia, SQLite storage with dual-write (filesystem + DB blob), mobile-first upload, and DB export. Adds AI receipt classification: a config.json catalog of people and categories (seeded into the DB on startup), a prompt builder that derives name-order/initial variants from the data (with same-surname ambiguity handling), and an Anthropic tool-use client behind POST /classify. Tests run against a mock endpoint; a live integration test is env-gated to the cheapest model. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
63 lines
1.5 KiB
Go
63 lines
1.5 KiB
Go
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
|
|
}
|
|
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,
|
|
})
|
|
}
|