RequireSession now stores the validated session in the request context
(auth.WithSession / auth.SessionFrom); auth.ActorFrom derives the actor
from it, so there is one context value.
Every /api/v1 request other than GET, HEAD and OPTIONS must carry
X-MealQuest-CSRF equal to the session's csrf_token (constant-time
compare), else 403 {"error":{"code":"csrf"}}. The check runs after
RequireSession, so unauthenticated requests still get 401. A session
with no token is a 500, never a match against a missing header.
GET /api/v1/me returns {name, email, household: {label: "Our kitchen"},
csrfToken} with Cache-Control: no-store.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
46 lines
1.2 KiB
Go
46 lines
1.2 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
)
|
|
|
|
// Actor is the signed-in household member a request acts for. Handlers
|
|
// record Actor.Subject as updated_by on edits.
|
|
type Actor struct {
|
|
Subject string
|
|
Email string
|
|
Name string
|
|
Groups []string
|
|
}
|
|
|
|
// Actor returns the household member s belongs to.
|
|
func (s Session) Actor() Actor {
|
|
return Actor{Subject: s.Subject, Email: s.Email, Name: s.Name, Groups: slices.Clone(s.Groups)}
|
|
}
|
|
|
|
type sessionKey struct{}
|
|
|
|
// WithSession returns a copy of ctx carrying s. The session middleware calls
|
|
// it for every authenticated request.
|
|
func WithSession(ctx context.Context, s Session) context.Context {
|
|
return context.WithValue(ctx, sessionKey{}, s)
|
|
}
|
|
|
|
// SessionFrom returns the session WithSession stored in ctx. ok is false when
|
|
// ctx has none, which behind the session middleware means a wiring bug.
|
|
func SessionFrom(ctx context.Context) (s Session, ok bool) {
|
|
s, ok = ctx.Value(sessionKey{}).(Session)
|
|
return s, ok
|
|
}
|
|
|
|
// ActorFrom returns the actor of the session WithSession stored in ctx. ok is
|
|
// false when ctx has none.
|
|
func ActorFrom(ctx context.Context) (actor Actor, ok bool) {
|
|
s, ok := SessionFrom(ctx)
|
|
if !ok {
|
|
return Actor{}, false
|
|
}
|
|
return s.Actor(), true
|
|
}
|