first commit
This commit is contained in:
68
go-api/internal/authctx/authctx.go
Normal file
68
go-api/internal/authctx/authctx.go
Normal file
@@ -0,0 +1,68 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user