Files
krow_backend/go-api/internal/authctx/authctx.go
2026-08-25 16:37:05 +05:30

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
}