// Package authctx carries the authenticated identity of a request. // // It is the successor to the development identity that used to be injected by // httpserver.devOrgMiddleware. The difference is not the shape — both put a // value on the request context — but the provenance: everything here was read // out of a server-side session row, and nothing in it can be influenced by the // request that carries it. // // That is the whole point of the package existing separately from the handlers. // A handler that wants to know who is calling has exactly one place to ask, and // that place cannot be reached from a request body, a query string or a header. // There is deliberately no setter that takes a user id from a client. package authctx import ( "context" "errors" "time" ) type key struct{} // ErrNoIdentity means a protected operation was reached without an // authenticated identity. That is a routing or middleware bug rather than a // client error: an unauthenticated request should have been refused before it // got this far. var ErrNoIdentity = errors.New("no authenticated identity in context") // Identity is who the request is, as resolved from the session row. // // Role is carried because Phase 3D will need it, and because carrying it now // means the middleware reads it once per request instead of every future // authorization check re-querying the user. It is NOT consulted anywhere in // Phase 3C: authentication only. type Identity struct { UserID string OrgID string Email string FullName string Role string AccountType string Status string // SessionID is the row this identity came from, so logout and per-session // diagnostics do not have to re-hash the cookie. SessionID string // ExpiresAt is the session's sliding deadline as of this request. ExpiresAt time.Time } // With returns a context carrying the authenticated identity. func With(ctx context.Context, id Identity) context.Context { return context.WithValue(ctx, key{}, id) } // From reads the identity, reporting whether one was present. func From(ctx context.Context) (Identity, bool) { v, ok := ctx.Value(key{}).(Identity) return v, ok && v.UserID != "" } // MustFrom reads the identity or returns ErrNoIdentity. func MustFrom(ctx context.Context) (Identity, error) { if v, ok := From(ctx); ok { return v, nil } return Identity{}, ErrNoIdentity }