Documentation
¶
Overview ¶
Package auth provides users, roles and permissions, sessions, CSRF, API keys, magic links, login rate limiting and the admin shell (login, setup, locale switch). The dashboard at /admin belongs to the admin module.
Index ¶
- Constants
- Variables
- func APIKeyMiddleware(deps *kernel.Deps) func(http.Handler) http.Handler
- func ActorFromContext(ctx context.Context) audit.Actor
- func CSRFMiddleware() func(http.Handler) http.Handler
- func CSRFToken(ctx context.Context) string
- func Can(ctx context.Context, perm string) bool
- func CanGrant(ctx context.Context, scopes []string) bool
- func HashPassword(password string) (string, error)
- func MCPKeyMiddleware(deps *kernel.Deps) func(http.Handler) http.Handler
- func NewMagicLink(ctx context.Context, deps *kernel.Deps, userID string, ttl time.Duration) (string, error)
- func RecentlyAuthenticated(ctx context.Context, window time.Duration) bool
- func ReloadRoles(ctx context.Context, d *sql.DB) error
- func RequirePerm(perm string) func(http.Handler) http.Handler
- func RequireRecentAuth(next http.Handler) http.Handler
- func SessionMiddleware(deps *kernel.Deps) func(http.Handler) http.Handler
- func StartUserSession(d *kernel.Deps, w http.ResponseWriter, r *http.Request, u *User) error
- func VerifyPassword(encoded, password string) bool
- func WithAPIKeyActor(ctx context.Context, d *kernel.Deps, keyID string) (context.Context, error)
- func WithUser(ctx context.Context, u *User) context.Context
- type APIKey
- type KeyService
- type ListQuery
- type Module
- type Role
- type RolePatch
- type RoleService
- type Service
- type User
- type UserPatch
- type ValidationError
Constants ¶
const ( KeyKindAPI = "api" KeyKindAgent = "agent" )
API key kinds. API keys authenticate on /api (and /mcp); agent keys only on /mcp, so an AI agent can never reach the REST API (for example to approve its own sensitive calls).
const ( StatusActive = "active" StatusDisabled = "disabled" RoleAdmin = "admin" RoleCustomer = "customer" )
User statuses and the customer role.
const DefaultMagicLinkTTL = 15 * time.Minute
DefaultMagicLinkTTL applies when NewMagicLink gets ttl == 0.
const MaxRotateGrace = 7 * 24 * time.Hour
MaxRotateGrace bounds how long a rotated key keeps working.
const ReauthWindow = 10 * time.Minute
ReauthWindow is how long a sign-in or password confirmation counts as recent for RequireRecentAuth.
Variables ¶
var ( ErrKeyRevoked = errors.New("auth: api key revoked") ErrKeyExpired = errors.New("auth: api key expired") ErrKeyOwnerInactive = errors.New("auth: api key owner is not active") )
Errors from WithAPIKeyActor (besides ErrNotFound for an unknown key).
var ( ErrRoleNotFound = errors.New("auth: role not found") ErrRoleInUse = errors.New("auth: role is assigned to users") ErrSystemRole = errors.New("auth: system role cannot be deleted or have its slug changed") )
Role errors returned by RoleService.
var ( ErrNotFound = errors.New("auth: not found") ErrEmailTaken = errors.New("auth: email already registered") ErrInvalidCredentials = errors.New("auth: invalid email or password") ErrLocked = errors.New("auth: too many failed attempts, try again later") ErrInvalidToken = errors.New("auth: invalid or expired token") ErrSetupDone = errors.New("auth: setup already completed") )
Errors returned by Service.
var AnonymousAPIPaths = []string{"/api/v1/openapi.json", "/api/docs"}
AnonymousAPIPaths are API paths served without a key or session (the api module may append during Register, before serving starts).
Functions ¶
func APIKeyMiddleware ¶
APIKeyMiddleware authenticates API requests: "Authorization: Bearer gp_…" (actor kind api_key, scopes, per-key rate limit → 429), or else a valid session cookie (unsafe methods then need X-CSRF-Token). Anything else gets the 401 envelope, except AnonymousAPIPaths. Agent keys (KeyKindAgent) get 403 agent_key_not_allowed: they work only on the MCP endpoint.
func ActorFromContext ¶
ActorFromContext returns the audit actor for ctx: the user, the API key, or audit.SystemActor.
func CSRFMiddleware ¶
CSRFMiddleware rejects non-GET/HEAD/OPTIONS requests (403) whose _csrf form value or X-CSRF-Token header does not match the session token. Without a session it uses a double-submit cookie (gp_csrf, issued on first visit), which protects anonymous forms such as login and setup. Run it after SessionMiddleware.
func CSRFToken ¶
CSRFToken returns the token forms must send as _csrf (or HTMX as X-CSRF-Token): the session's token, or for anonymous visitors the double-submit token from the gp_csrf cookie. Requires CSRFMiddleware.
func Can ¶
Can reports whether the principal in ctx holds perm: an active user whose role grants it, and for API keys additionally a matching scope (a key never exceeds its owner's role).
func CanGrant ¶
CanGrant reports whether the principal in ctx may hand out every scope in scopes (when creating or rotating an API key): each permission must pass Can, and "*" needs a role that holds "*" (and, for a key, "*" in the key's own scopes). Nobody can therefore mint a key stronger than themselves.
func HashPassword ¶
HashPassword returns a PHC-encoded Argon2id hash: $argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash> (raw std base64).
func MCPKeyMiddleware ¶
MCPKeyMiddleware is APIKeyMiddleware for the MCP endpoint: it accepts both API and agent keys.
func NewMagicLink ¶
func NewMagicLink(ctx context.Context, deps *kernel.Deps, userID string, ttl time.Duration) (string, error)
NewMagicLink creates a single-use login token for userID (sent by email as BaseURL + "/auth/magic/" + token). Only its SHA-256 is stored.
func RecentlyAuthenticated ¶
RecentlyAuthenticated reports whether ctx is a browser session whose user signed in or confirmed their password within the last window. API keys, CLI and job contexts are never recent: they have no session.
func ReloadRoles ¶
ReloadRoles reloads the role → permission mapping from role_permissions.
func RequirePerm ¶
RequirePerm answers 403 unless Can(ctx, perm): a JSON envelope for API requests, plain text otherwise.
func RequireRecentAuth ¶
RequireRecentAuth guards sensitive admin routes (backup downloads): a session older than ReauthWindow is sent to /admin/reauth, which asks for the password again and then returns to the original URL.
func SessionMiddleware ¶
SessionMiddleware loads the user from the gp_session cookie (sliding 14 day expiry) into the context. It never rejects a request.
func StartUserSession ¶
StartUserSession starts the native session after a caller has authenticated the user. It is not a password verifier; callers must verify credentials first.
func VerifyPassword ¶
VerifyPassword accepts native Argon2id and migrated WordPress password hashes. Malformed hashes and parameters outside bounded verification costs never match.
func WithAPIKeyActor ¶
WithAPIKeyActor returns ctx acting as the API key keyID, exactly as if the key had authenticated the request: Can checks the key's scopes and its owner's role, and audit records the key as the actor. It is for work done later on a key's behalf (e.g. an MCP call executed once a human approves it). The key must exist (ErrNotFound), not be revoked (ErrKeyRevoked) or expired (ErrKeyExpired), and its owner must be active (ErrKeyOwnerInactive).
Types ¶
type APIKey ¶
type APIKey struct {
ID, Name, Prefix, UserID string
Kind string // KeyKindAPI or KeyKindAgent
Scopes []string
RatePerMin int
ExpiresAt, LastUsedAt, CreatedAt, RevokedAt string
UseCount int64 // approximate: flushed at most once a minute
RotatedFrom string // id of the key this one replaced
}
APIKey is a bearer key: plaintext "gp_" + Prefix (8) + secret (32), stored as SHA-256. Scopes are permission strings; "*" grants all of the owner's permissions.
type KeyService ¶
type KeyService interface {
// Create creates an API key of kind KeyKindAPI.
Create(ctx context.Context, name, userID string, scopes []string, rate int, expires string) (plaintext string, k *APIKey, err error)
// CreateKind is Create with an explicit kind (KeyKindAPI or KeyKindAgent).
CreateKind(ctx context.Context, kind, name, userID string, scopes []string, rate int, expires string) (plaintext string, k *APIKey, err error)
List(ctx context.Context) ([]APIKey, error)
Get(ctx context.Context, id string) (*APIKey, error)
Revoke(ctx context.Context, id string) error
// Rotate issues a new key with the same name, kind, owner, scopes, rate
// and expiry, and retires the old one: revoked now when grace is 0,
// otherwise expiring after grace (so clients can switch without downtime).
Rotate(ctx context.Context, id string, grace time.Duration) (plaintext string, k *APIKey, err error)
}
KeyService manages API keys.
func NewKeyService ¶
func NewKeyService(d *sql.DB, a *audit.Writer, perms *kernel.PermissionRegistry) KeyService
NewKeyService returns the API key service on d. Create validates scopes against perms (plus "*"; nil skips the check); creation and revocation are recorded through a (nil: not audited). rate <= 0 means 60/min; expires is RFC3339 (normalised to UTC) or "" for never.
type ListQuery ¶
ListQuery filters Service.List. Sort: email, name, role, created_at, last_login_at (default created_at); Order: asc or desc (default desc).
type Module ¶
type Module struct{}
Module is the auth kernel.Module.
type Role ¶ added in v0.2.0
type Role struct {
Slug string
Name map[string]string // locale → label (en required)
System bool
Permissions []string
UserCount int
}
Role is a named permission pack. System roles are seeded; custom roles have System=false and may be deleted when no users reference them.
type RolePatch ¶ added in v0.2.0
RolePatch updates only its non-nil fields. Permissions replaces the full grant set when non-nil (empty slice clears all grants).
type RoleService ¶ added in v0.2.0
type RoleService interface {
List(ctx context.Context) ([]Role, error)
Get(ctx context.Context, slug string) (*Role, error)
Create(ctx context.Context, slug string, name map[string]string, permissions []string) (*Role, error)
Update(ctx context.Context, slug string, patch RolePatch) (*Role, error)
Delete(ctx context.Context, slug string) error
}
RoleService manages roles and role_permissions.
func NewRoleService ¶ added in v0.2.0
func NewRoleService(d *sql.DB, a *audit.Writer, perms *kernel.PermissionRegistry) RoleService
NewRoleService returns the role service. Create/Update validate permissions against perms (nil skips the known-permission check).
type Service ¶
type Service interface {
Create(ctx context.Context, email, name, password, role string) (*User, error)
GetByEmail(ctx context.Context, email string) (*User, error)
Get(ctx context.Context, id string) (*User, error)
List(ctx context.Context, q ListQuery) ([]User, int, error)
Update(ctx context.Context, id string, patch UserPatch) (*User, error)
Delete(ctx context.Context, id string) error
CheckPassword(ctx context.Context, email, password string) (*User, error) // rate-limited per IP (audit.IPFromContext) + email
Count(ctx context.Context) (int, error)
}
Service manages users.
type User ¶
type User struct {
ID, Email, Name, Role, Locale, Status string
LastLoginAt, CreatedAt, UpdatedAt string
}
User is an account. Customers have role "customer".
func ConsumeMagicLink ¶
ConsumeMagicLink marks token used and returns its active user, or ErrInvalidToken when it is unknown, used or expired.
func UserFromContext ¶
UserFromContext returns the acting user (an API key's owner for key requests), or nil.
type UserPatch ¶
type UserPatch struct {
Name, Email, Role, Locale, Status, Password *string
}
UserPatch updates only its non-nil fields.
type ValidationError ¶
ValidationError lists invalid fields; values are i18n keys.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string