71 lines
2.5 KiB
Go
71 lines
2.5 KiB
Go
// 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 read once per request by the middleware, out of the user row, so an
|
|
// authorization check never has to re-query. It is the authorization authority:
|
|
// httpserver.Server.authorize gates operations on it, the repository's ownership
|
|
// predicate narrows a talent caller's rows by it, and service/definitions.go
|
|
// checks it on every definition write. AccountType is NOT an authority — a user
|
|
// can change their own through PATCH /me.
|
|
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
|
|
}
|