Help us shape the future of Go! Take the 2026 Go Developer Survey

goauth

package module
v0.15.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 31 Imported by: 14

README

goauth

goauth is a PostgreSQL-first authentication Runtime for Go applications, with sessions, recovery, encrypted notification delivery, OIDC and RBAC. PostgreSQL stores canonical auth state; net/http and Fiber are optional HTTP adapters, and Redis is needed only for optional OIDC one-time state.

The latest published release is v0.6.0 (schema 8). Upgrading from v0.5.1 (schema 3) includes the intervening migrations and needs an explicit rollout and rollback plan. Read the v0.6 migration guide, release boundaries and RELEASING.md before adopting. The v0.5 migration guide describes the earlier v0.5 contract transition, not the complete schema 3→8 upgrade.

Pre-v1, public release. A Git tag is not proof of public availability, an independent security audit, or production adoption. See the release plan for the remaining acceptance criteria and RELEASING.md for exact-tag verification. The frozen v0.1 line ends at v0.1.7; consumers crossing that schema boundary must explicitly reset isolated development or test auth state. Existing tags remain immutable.

Documentation

The public-preview specification defines release acceptance criteria. Engineering review notes record historical work and checks; they do not replace consumer guidance or acceptance evidence at the release SHA.

Supported API

The supported imports are intentionally small:

  • github.com/assurrussa/goauth
  • github.com/assurrussa/goauth/postgres
  • github.com/assurrussa/goauth/redis
  • github.com/assurrussa/goauth/fiber
  • github.com/assurrussa/goauth/nethttp
  • github.com/assurrussa/goauth/oidc
  • github.com/assurrussa/goauth/oidc/provider
  • github.com/assurrussa/goauth/oidc/verifier
  • github.com/assurrussa/goauth/rbac
  • github.com/assurrussa/goauth/testkit

reference/externalconsumer.SupportedPackages is the machine-readable source of truth. Earlier v0.1 releases remain available at their immutable tags; the retired implementation is absent from this repository.

HTTP adapter capabilities

Capability net/http Fiber
Register, login, refresh Built in Built in
Password reset and email verification Built in Built in
Logout, logout-all, password change, email change Built in Host handlers call the root Runtime
Realm middleware with current-session checks Default Default
Offline JWT verification Explicit opt-in Explicit opt-in
Request body cap 1 MiB 1 MiB in auth handlers

Fiber hosts should also configure fiber.Config.BodyLimit to bound request buffering before handlers run. Each adapter returns HTTP 501 with notification_delivery_disabled for ErrNotificationDeliveryDisabled; hosts may choose to omit recovery/email routes in disabled delivery mode. See OIDC provider policy for client authentication, public-client S256 PKCE, confidential-client policy and persistent secret verification.

Runnable browser and JSON API example

examples/nethttp runs one PostgreSQL-backed Runtime with standard net/http, browser cookies, coordinated refresh, a bearer-only JSON API, and a local SMTP catcher. Its host owns CSRF, cookies, UI and worker lifetime. The nethttp middleware checks current sessions by default; OfflineJWT is an explicit opt-out. AuthContext(request.Context()) retrieves the checked identity. The profile contracts and required evidence separate browser cookies from explicit native/API credentials. The candidate gate includes a real HTTPS browser and backup/restore; see release verification for the required installed browser selection.

Passwords are bounded before hashing/verification, and each Runtime admits four concurrent hash operations by default (MaxConcurrentPasswordHashes is configurable). Saturation returns ErrPasswordHashOverloaded with 503/Retry-After in HTTP adapters.

The default 8–128-code-point password policy and twelve built-in blocklist entries remain unchanged. Hosts can opt into NewLocalCommonPasswordChecker through the existing PasswordPolicy.Blocklist field; WithBuiltInCommonPasswords() retains the built-ins alongside host entries. See local dictionaries for copying, matching, and explicit replacement/empty-set semantics.

CredentialVerificationRateLimit can set a separate bounded policy for repeated VerifyCredential/PostgreSQL PrepareCredential calls. Zero preserves the current LoginRateLimit behavior; browser login, password change and recovery keep their existing policies. Successful checks still count. See credential admission before using per-request Basic. Returned token lifetimes never exceed their canonical absolute session expiry. OIDC verifier defaults to the embedded access profile; selecting zitadel-jwt is explicit and does not enable acceptance of ID tokens.

PostgreSQL and Fiber quickstart

The host supplies a PostgreSQL DSN, three distinct 32-byte versioned key rings, and a goauth.NotificationSender implementation backed by its email or other delivery provider. This wiring mounts a minimal user-realm API:

import (
    "context"
    "net/url"

    "github.com/assurrussa/goauth"
    goauthfiber "github.com/assurrussa/goauth/fiber"
    "github.com/assurrussa/goauth/postgres"
    "github.com/gofiber/fiber/v3"
)

func wireAuth(
    dsn string,
    signingKeys, tokenKeys, notificationKeys goauth.KeyRing,
    sender goauth.NotificationSender,
) (*fiber.App, *postgres.Runtime, error) {
    auth, err := postgres.NewRuntime(postgres.Config{
        DSN:                dsn,
        AutoMigrate:        true, // use the host's migration lifecycle in production
        NotificationSender: sender,
        Runtime: goauth.Config{
            Signing: goauth.SigningConfig{
                Issuer: "https://auth.example.com", Audience: "example-app", Keys: signingKeys,
            },
            TokenHMACKeys:  tokenKeys,
            OutboxAEADKeys: notificationKeys,
            URLBuilder: goauth.URLBuilderFunc(func(_ context.Context, token string) (string, error) {
                return "https://app.example.com/reset?token=" + url.QueryEscape(token), nil
            }),
        },
    })
    if err != nil {
        return nil, nil, err
    }
    adapter, err := goauthfiber.New(auth)
    if err != nil {
        _ = auth.Close()
        return nil, nil, err
    }
    app := fiber.New()
    app.Post("/auth/register", adapter.Register)
    app.Post("/auth/login", adapter.Login)
    app.Post("/auth/refresh", adapter.Refresh)
    app.Post("/auth/password-reset", adapter.RequestPasswordReset)
    app.Post("/auth/email-challenge", adapter.RequireRealm(goauth.RealmUser,
        goauthfiber.RealmMiddlewareOptions{AllowConfirmation: true}),
        adapter.SendEmailChallenge)
    app.Post("/auth/email-challenge/verify", adapter.RequireRealm(goauth.RealmUser,
        goauthfiber.RealmMiddlewareOptions{AllowConfirmation: true}),
        adapter.VerifyEmailChallenge)
    app.Get("/me", adapter.RequireRealm(goauth.RealmUser,
        goauthfiber.RealmMiddlewareOptions{}), func(c fiber.Ctx) error {
        authContext, _ := goauthfiber.AuthContext(c)
        return c.JSON(fiber.Map{"subjectId": authContext.SubjectID.String()})
    })
    return app, auth, nil
}

To sign in, send POST /auth/login with JSON such as {"scheme":"email","identifier":"user@example.com","password":"...","realm":"user"}. The response contains account and tokens (accessToken, refreshToken, expiry, realm, and scope). Send Authorization: Bearer <accessToken> to /me; the middleware checks the realm and, by default, the current server-side session and subject status. Send POST /auth/refresh with {"refreshToken":"..."} to rotate the refresh token. Registration returns a confirmation-scoped session; use that access token to request an email code, then verify it with {"code":"..."}. After verification, log in again to obtain a full-scope session. Runtime.Logout and LogoutAll revoke sessions; the host must expose its chosen logout routes and token storage policy.

NotificationSender.SendNotification(ctx, delivery) receives the typed notification, a stable delivery ID, and its validity deadline. Return success only after the host delivery provider accepts responsibility; it does not mean that the recipient received the message. Retries may repeat an ID, and expiry or the attempt limit can end processing without a successful send. The sender must honor context cancellation and set network timeouts so shutdown can join the worker. NotificationWorkerConfig.OnBlocked reports a delivery ID and safe reason when decryption fails; alert on this signal without logging payloads. Run auth.RunNotifications(ctx) in a supervised worker, cancel and join it on shutdown, and close the Runtime when the process stops. Schedule broad auth.Cleanup(ctx, postgres.CleanupPolicy{}) only with a host retention policy. Canonical refresh replay evidence remains until family/session termination plus retention; see session security and release boundaries. For rate-event-only maintenance, use bounded auth.CleanupRateLimitEvents calls outside managed transactions; preserve the longest window across all shared consumers plus clock skew and request latency. See the maintenance contract. The host chooses how to serve the Fiber app. The queue checks one-time state before sending; an account change during an in-flight external send can still reach the provider. Token validity is always enforced by the Runtime when the recipient acts on the message. Use goauth.NewKeyRing with separate random material for signing, token HMAC, and notification encryption; retain prior key versions through rotation. Managed delivery cannot be combined with custom event, renderer, audit or transaction hooks. PostgreSQL assembles AuthTransaction for all participating auth writes, audit records and native encrypted events. Advanced direct root assembly requires an AuthTransaction and transactional AuditSink; custom stores and event sinks must join the supplied context, including reads. The old NotificationTransaction configuration is rejected instead of silently running without the full transaction contract. See migration details. Direct RuntimeStore implementations must guard reset issuance against the expected primary email, security version, and active status and invalidate outstanding reset records on password, email, or status changes. They must also reject email-challenge issuance when the expected primary email or security version no longer matches, and invalidate old challenges when the primary email changes.

Runtime contract

postgres.NewRuntime assembles the canonical store, Argon2id password hashing, realm sessions, refresh families, password reset, email confirmation, identity links, trusted bootstrap, password/profile/email lifecycle, logout, audit persistence, cleanup, atomic rate limiting, and a digest-only OIDC refresh store. The native PostgreSQL notification queue is selected by providing a goauth.NotificationSender; it stores encrypted deliveries and exposes a worker and cleanup operation. Optional OIDC and RBAC are assembled through Runtime methods. The host supplies:

  • a PostgreSQL connection or DSN;
  • distinct versioned keys for JWT signing, token HMAC, and outbox AES-256-GCM;
  • a notification sender and a supervised worker lifecycle;
  • a password-reset URL builder;
  • optional membership, claims, and identifier hooks.

Custom encrypted event sinks, rendering and transactional audit wiring require direct root Runtime assembly. postgres.NewRuntime rejects custom EventSink, AuditSink and AuthTransaction overrides before database side effects. All participants in direct root assembly must enlist in the same auth transaction; receiving its context alone does not guarantee that an external sink will roll back with the canonical writes.

The HTTP adapters own only JSON handlers, typed error mapping, and realm middleware. Hosts continue to own route prefixes, cookies, redirects, UI, projection tables, and application permission catalogs. auth_subjects is the canonical principal; host users and admin rows are projections or memberships keyed by its subject_id. Realm plus host membership is the admin boundary. RBAC role assignments use that same canonical ID; hosts extend the permission catalog and own their presentation DTOs.

See docs/v0.2-runtime.md for the underlying model, docs/compatibility.md for the verified consumer line, and AUTH_INVARIANTS.md for security invariants. See LICENSE and SECURITY.md for reuse and private security reporting.

Verification

Use the Go version/toolchain in go.mod and the tool versions in .github/workflows/ci.yml; see CONTRIBUTING.md.

make prepare
make check
make integration-local
make vulnerability-check

For an unreleased candidate, select the installed browser tools as described in release verification, then start disposable integration services:

make integration-up
make release-candidate-readiness
make integration-down

After a new public tag exists, check out that tag and run make public-module-check VERSION=<tag>. For candidate and public gates together, use make release-readiness VERSION=<tag> with integration services running. An explicit version is required; there is no stale default tag.

The published probe runs the Runtime/nethttp/Fiber/OIDC/Redis/RBAC wiring example in a fresh consumer environment through the public Go proxy and checksum service, without credentials, reused caches, a workspace, or a local replace. It checks the exact selected version after the test as well as before it. Database-backed acceptance runs through the separate local PostgreSQL consumer. Candidate checks do not establish publication; the public probe does not establish a security audit or production deployment.

Delivery-free operator administration

Config.NotificationDelivery = NotificationDeliveryDisabled explicitly disables password-reset issuance/consumption, email challenge issuance/verification, and email-change commands. Commands return ErrNotificationDeliveryDisabled before attempt bookkeeping or storage writes. Password login, profile updates, and password changes remain available; password changes still commit mandatory audit and security-state revocation. No sender, URL builder, or outbox AEAD keys are required in this mode. The zero policy retains required encrypted delivery.

PostgreSQL hosts can use Runtime.InAuthTransaction and Runtime.SQLExecutor(ctx) to commit host projections with canonical writes, audit, and RBAC. The executor exposes only SQL execution/query methods, never transaction ownership. Participating adapters must share Runtime.Database() exactly; a separate handle is rejected. Response-producing entry points can require ownership with Runtime.InOwnedAuthTransaction: an ambient same-handle transaction returns postgres.ErrAuthTransactionAlreadyActive before the callback or a second BEGIN. Ordinary InAuthTransaction and low-level calls still join inside its callback. Publish prepared secrets, tokens or authorization results only after the owned method returns nil; callbacks must not publish them early. Every error withholds these results, including ErrOperationOutcomeUnknown, which does not prove rollback and must not trigger a blind retry. See owned transactions.

The unreleased additive rbac.Service.Check(ctx, subjectID, key) (bool, error) distinguishes ordinary denial (false, nil) from a failed authorization check. A zero subject returns goauth.ErrInvalidSubjectID before permission-key validation; an invalid key returns an error matching rbac.ErrInvalidPermissionKey. Cache and store errors retain their causes for errors.Is/errors.As and always return false. Cache failures do not fall back to storage. The selected dependency receives the caller's context; cancellation and deadline errors it returns remain identifiable. Check does not independently reject a canceled context when that dependency returns success, preserving existing cache-hit and store behavior.

Existing Can remains a boolean fail-closed wrapper, and Require continues to return ErrPermissionDenied for every unsuccessful check, including dependency failures. Hosts can adopt Check explicitly when they need separate denial and dependency-failure handling; HTTP status policy remains host-owned. This API addition is not present in the published v0.6.0 tag.

RBAC authorization reads join the transaction and bypass caches. Snapshot needs a standalone repeatable-read transaction; invoking it in a managed auth transaction returns rbac.ErrSnapshotTransactionUnsupported. Supplied caches must implement rbac.CacheInvalidator; invalidation follows successful outer commit, never rollback. RBAC writes reserve cache bypass before SQL mutation, including standalone transactions. Managed operations use savepoints: an operation error restores its changes even if the host handles it and commits other writes. RBAC mutations on the same managed connection are serialized. Failure to restore a savepoint aborts the outer transaction. Rollback releases only its own reservations. While a write is pending, other authorization checks read committed PostgreSQL state. Each transaction/cache pair shares one guard and one invalidation regardless of write count. Each invalidation, including waiting for pending cache fills, has a two-second deadline detached from the request cancellation. Invalidators must honor that deadline and return after clearing the cache. Timeout leaves committed writes successful and the cache bypassed; an older completion cannot clear a newer failure. Failed cache invalidation bypasses the cache and reads authoritative PostgreSQL permissions; successful invalidation restores normal cached reads. An uncertain RBAC write commit returns ErrOperationOutcomeUnknown and keeps this cache wrapper bypassed for its lifetime. A read-only snapshot commit error keeps its classification without changing cache trust. Database read failures still deny authorization.

Repeated NewRBAC or Runtime.RBAC calls with the same live, comparable cache value and exact *sql.DB share the cache guard, invalidation lock and outcome state. Keep these services alive to retain cache acceleration. The registry uses weak references and does not own the database or cache. Mixing different cache values, adding an uncached service, repeating a noncomparable value cache, or reassembling after its wrapper is collected permanently switches cached reads to PostgreSQL for that DB handle. Invalidation still follows management commits. This conservative fallback cannot be cleared by later assembly or invalidation. Cache backends must belong to one canonical database; distinct DB handles and external SQL writers are outside this coordination contract.

Managed NotificationDelivery.EncryptedEvent contains the original sealed envelope for hosts that forward delivery into durable transports. The native queue remains the initiating auth transaction owner; downstream transports persist ciphertext only and deduplicate by delivery ID. Notification plaintext is ephemeral; never persist its reset URLs or codes. Acceptance is at least once, so SMTP or downstream transport acceptance may repeat after an uncertain acknowledgement. The worker renews its owned unexpired lease while reserving the attempt before sending; reservation and send share a bounded deadline. Expiry of that delivery deadline expires, retries or exhausts only the delivery; it does not stop the worker pool. After an ambiguous reservation timeout, the worker reads the persisted attempt count under the same lease token before settling the delivery. Parent cancellation stops the worker. Senders must honor cancellation and should deduplicate by delivery ID.

Privileged hosts can use Runtime.RequestPasswordResetWithReceipt to bind a PasswordResetReceipt (subject ID and public selector only) within the same transaction as the reset record, encrypted enqueue and mandatory audit. A callback failure rolls back those writes; no callback runs when issuance is suppressed. Use the PostgreSQL runtime's context SQL executor in the callback. Keep receipt presence private from unauthenticated clients. On ErrOperationOutcomeUnknown, preserve the host claim and reconcile durable state before attempting issuance again; never clear a cooldown and resend blindly.

PostgreSQL trusted bootstrap flows can call PrepareCredential before the host transaction. Its opaque, runtime-bound proof expires after five minutes. Inside InAuthTransaction, call RevalidateCredential before granting membership: it holds the canonical subject row lock through outer commit and checks active status, security version and normalized primary email. It creates no session and does not bypass durable rate admission. Fresh trusted provisioning can remain inside the transaction.

Email-less local identity foundation

Privileged hosts can provision or import canonical local identities using a registered non-email login scheme, without fabricating email. See the local identity contract for create-only imports, transactional host mappings, narrowly bounded legacy password compatibility, schema-4 migration, privileged SetTrustedLocalPassword, and host admission/SSO integration steps. The privileged setter preserves subject status, advances security version even for identical plaintext, and joins managed host transactions with mandatory audit.

Terminal authentication-subject retirement and scoped custom-login release are available through optional lifecycle APIs, with an explicit schema 6 upgrade. See the retirement contract for retained bindings, managed transaction receipts, compatibility and rollback requirements.

Session-bound confidential OIDC

Hosts with their own opaque SSO sessions can use the additive provider.NewSessionBound and postgres.NewSessionOIDCState integration. The session-bound profile documents owned transactions, request-specific reauthentication, client-aware live admission, fixed-deadline refresh, project claims and schema 8 rollout. It leaves generic realm/email policy and legacy provider DTO layouts unchanged.

OIDCRefreshTokenStore.Get is now read-only. A consumed token is reported as an inactive snapshot; authenticated replay revocation/audit occurs on the owning provider's mutation path. See the compatibility notes before upgrading direct adapter consumers.

Session-bound hosts can explicitly project a challenge-verified current email through oidc.EmailProjection, without changing identity, local account provisioning, or the existing admin-realm email gate.

Documentation

Index

Constants

View Source
const (
	DefaultPasswordMinLength           = 8
	DefaultPasswordMaxLength           = 128
	DefaultArgon2MemoryKiB             = 19 * 1024
	DefaultArgon2Iterations            = 2
	DefaultArgon2Parallelism           = 1
	DefaultMaxConcurrentPasswordHashes = 4
)
View Source
const AssistedRecoveryLifetime = 15 * time.Minute
View Source
const MFALoginLifetime = 3 * time.Minute
View Source
const (
	MaxAccessTokenTTL = 5 * time.Minute
)

Variables

View Source
var (
	ErrAssistedRecoveryUnavailable = errors.New("assisted recovery unavailable")
	ErrAssistedRecoveryDenied      = errors.New("assisted recovery denied")
)
View Source
var (
	ErrOperationOutcomeUnknown   = errors.New("operation commit outcome is unknown")
	ErrInvalidSubjectID          = errors.New("invalid subject ID")
	ErrInvalidSubjectStatus      = errors.New("invalid subject status")
	ErrInvalidIdentifierScheme   = errors.New("invalid identifier scheme")
	ErrInvalidIdentifier         = errors.New("invalid identifier")
	ErrIdentifierAlreadyExists   = errors.New("identifier already exists")
	ErrInvalidRealm              = errors.New("invalid realm")
	ErrRealmNotRegistered        = errors.New("realm is not registered")
	ErrInvalidChallengePurpose   = errors.New("invalid email challenge purpose")
	ErrAccountNotFound           = errors.New("account not found")
	ErrAccountUnavailable        = errors.New("account unavailable")
	ErrInvalidCredentials        = errors.New("invalid credentials")
	ErrCurrentPasswordInvalid    = errors.New("current password is invalid")
	ErrPasswordUnchanged         = errors.New("new password matches current password")
	ErrPasswordChangeConflict    = errors.New("password changed concurrently")
	ErrAuthenticationRateLimited = errors.New("authentication rate limited")
	ErrPasswordHashOverloaded    = errors.New("password hashing capacity exhausted")
	ErrMembershipDenied          = errors.New("realm membership denied")
	ErrEmailVerificationRequired = errors.New("verified email required")
	ErrInvalidPassword           = errors.New("invalid password")
	ErrCommonPassword            = errors.New("common password is not allowed")
	ErrInvalidToken              = errors.New("invalid token")
	ErrReservedAccessTokenClaim  = errors.New("claims enricher set a reserved access-token claim")
	ErrExpiredToken              = errors.New("token expired")
	ErrRefreshReplay             = errors.New("refresh token replay detected")
	ErrSessionRevoked            = errors.New("session revoked")
	ErrSecurityVersionMismatch   = errors.New("security version mismatch")
	ErrResetAlreadyUsed          = errors.New("password reset token already used")
	ErrInvalidConfirmationCode   = errors.New("invalid confirmation code")
	ErrConfirmationExpired       = errors.New("confirmation code expired")
	ErrConfirmationAttempts      = errors.New("confirmation attempts exhausted")
	ErrConfirmationRateLimited   = errors.New("confirmation rate limited")
	ErrConfirmationResendDelay   = errors.New("wait before resending confirmation")
	ErrEmailChangeNotFound       = errors.New("pending email change not found")
	ErrEmailChangeSameValue      = errors.New("new email matches current email")
	ErrKeyRingInvalid            = errors.New("invalid key ring")
	ErrKeyPurposeCollision       = errors.New("cryptographic key reused across purposes")
	ErrNotificationSinkRequired  = errors.New("encrypted notification sink is required")
	ErrURLBuilderRequired        = errors.New("URL builder is required")
	ErrIdentityLinkNotFound      = errors.New("identity link not found")
	ErrIdentityLinkConflict      = errors.New("identity link conflict")
	ErrExplicitIdentityLink      = errors.New("explicit authenticated identity linking is required")
)
View Source
var (
	ErrLocalIdentifierUnsupported     = errors.New("local identifier read capability is not supported")
	ErrLocalIdentityRenameUnsupported = errors.New("local identity rename capability is not supported")
	ErrLocalIdentifierConflict        = errors.New("local primary identifier is missing or changed")
	ErrIdentifierUnchanged            = errors.New("local identifier is unchanged")
)
View Source
var (
	ErrMFARequired    = errors.New("second factor required")
	ErrMFALoginDenied = errors.New("MFA login denied")
)
View Source
var (
	ErrSubjectLifecycleUnsupported        = errors.New("subject lifecycle read capability is not supported")
	ErrLocalIdentityRetirementUnsupported = errors.New("local identity retirement capability is not supported")
	ErrSubjectRetired                     = errors.New("authentication subject is permanently retired")
)
View Source
var ErrEmailChangeScopeUnavailable = errors.New("email change scope is unavailable")
View Source
var ErrLocalIdentityUnsupported = errors.New("local identity capability is not supported")
View Source
var ErrMFAUnavailable = errors.New("MFA unavailable")
View Source
var ErrNotificationDeliveryDisabled = errors.New("notification delivery is disabled")
View Source
var ErrNotificationRejected = errors.New("notification permanently rejected")

ErrNotificationRejected is an explicit terminal non-delivery disposition from a NotificationSender. Return it, optionally wrapped, only when the transport has definitively rejected or simulated the operation without accepting real mail. Never use it for timeouts, lost responses or unknown provider outcomes. PostgreSQL records an unsuccessful terminal receipt (exhausted/sender_rejected), scrubs the envelope, and leaves delivered_at NULL. Ordinary errors keep retrying.

View Source
var ErrPasswordVerificationUnavailable = errors.New("password verification unavailable")

ErrPasswordVerificationUnavailable marks an operational failure from a custom PasswordHasher. Wrap or join the underlying cause to preserve diagnostics.

View Source
var ErrRateLimitTransactionUnsupported = errors.New("rate-limit admission requires no active auth transaction")

ErrRateLimitTransactionUnsupported means attempt admission was invoked inside a managed auth transaction. Call the rate-limited Runtime operation before opening an outer AuthTransaction; do not strip the callback context to retry. The built-in stores reject this composition before consuming an attempt or verifying a password. This is a wiring error, not a throttling or credential failure. Transactional email-issue quotas are not affected.

View Source
var ErrTrustedLocalPasswordUnsupported = errors.New("trusted local password capability is not supported")
View Source
var NilSubjectID = SubjectID(uuid.Nil)

Functions

func AssistedRecoveryOwnerConfirmation added in v0.11.0

func AssistedRecoveryOwnerConfirmation(subject SubjectID, email string) string

func IsMFARecoveryCode added in v0.8.0

func IsMFARecoveryCode(code string) bool

func MFATOTPStep added in v0.8.0

func MFATOTPStep(secret, code string, now time.Time) (int64, bool, error)

MFATOTPStep uses six-digit SHA-1 TOTP, thirty-second periods and one-step skew. The store must atomically reject step <= the persisted LastStep.

func NewMFALoginID added in v0.8.0

func NewMFALoginID() (string, error)

func NewMFARecoveryCode added in v0.8.0

func NewMFARecoveryCode() (string, error)

func NormalizeEmail

func NormalizeEmail(value string) (string, error)

func PasswordResetNotificationContext added in v0.8.0

func PasswordResetNotificationContext(ctx context.Context, notification Notification) (context.Context, error)

PasswordResetNotificationContext restores only the authenticated notification scope. Use it after DecryptNotificationEvent, never on untrusted input. Missing metadata explicitly restores the legacy empty scope instead of inheriting a worker's ambient scope. Malformed metadata fails closed.

func PasswordResetScope added in v0.8.0

func PasswordResetScope(ctx context.Context) string

PasswordResetScope returns the trusted scope of the current recovery operation. Recipient policies must reject scopes they do not explicitly support.

func ValidateMFALoginPending added in v0.8.0

func ValidateMFALoginPending(pending MFALoginPending, account Account, factor MFAFactorRecord, realm Realm,
	binding [32]byte, now time.Time,
) error

ValidateMFALoginPending validates a locked snapshot; it does not consume it. Sample now after waiting for locks, then independently check realm membership.

func WithEmailChangeScope added in v0.8.0

func WithEmailChangeScope(ctx context.Context, scope EmailChangeScope) context.Context

WithEmailChangeScope selects a configured host projection for the existing email-change APIs. An unknown nonempty scope fails closed.

func WithPasswordResetScope added in v0.8.0

func WithPasswordResetScope(ctx context.Context, scope string) (context.Context, error)

WithPasswordResetScope binds a host-selected recovery authority to an operation. Callers must select scope from trusted route/configuration, never request fields. Empty scope preserves the legacy unscoped token purpose. A scoped token cannot be consumed in another scope, including the legacy empty scope.

Types

type Account

type Account struct {
	Subject Subject
	// PrimaryEmail is zero for deliberately provisioned email-less identities.
	PrimaryEmail Identifier
	Profile      BasicProfile
}

func (Account) EmailVerified

func (a Account) EmailVerified() bool

func (Account) IsZero

func (a Account) IsZero() bool

type AccountStore

type AccountStore interface {
	// LockAccount requires AuthTransaction and locks the canonical subject before dependent state.
	LockAccount(ctx context.Context, subjectID SubjectID) (Account, error)
	CreateLocalAccount(ctx context.Context, record LocalAccountRecord) (Account, error)
	FindAccount(ctx context.Context, identifier IdentifierInput) (Account, error)
	FindLocalAccount(ctx context.Context, identifier IdentifierInput) (LocalAccountRecord, error)
	GetLocalAccount(ctx context.Context, subjectID SubjectID) (LocalAccountRecord, error)
	GetAccount(ctx context.Context, subjectID SubjectID) (Account, error)
	UpdateBasicProfile(ctx context.Context, subjectID SubjectID, profile BasicProfile, now time.Time) (Account, error)
	ChangePassword(ctx context.Context, request PasswordChangeStoreRequest) (PasswordChangeStoreResult, error)
}

type Argon2idConfig

type Argon2idConfig struct {
	MemoryKiB   uint32
	Iterations  uint32
	Parallelism uint8
	SaltLength  uint32
	KeyLength   uint32
	Random      io.Reader
}

type Argon2idHasher

type Argon2idHasher struct {
	// contains filtered or unexported fields
}

func NewArgon2idHasher

func NewArgon2idHasher(config Argon2idConfig) (*Argon2idHasher, error)

func (*Argon2idHasher) HashPassword

func (h *Argon2idHasher) HashPassword(password string) (string, error)

func (*Argon2idHasher) VerifyPassword

func (h *Argon2idHasher) VerifyPassword(phc, password string) error

type AssistedRecovery added in v0.11.0

type AssistedRecovery struct {
	// contains filtered or unexported fields
}

AssistedRecovery is a transport-neutral, explicitly configured host service. Hosts must expose initiation only to authenticated administrators with CSRF protection and must never log request secrets or invitation URLs.

func NewAssistedRecovery added in v0.11.0

func NewAssistedRecovery(
	runtime *Runtime, authorization AssistedRecoveryAuthorization, completionURL string,
) (*AssistedRecovery, error)

func NewScopedAssistedRecovery added in v0.11.0

func NewScopedAssistedRecovery(
	runtime *Runtime, authorization AssistedRecoveryAuthorization, completionURL, scope string,
	target AssistedRecoveryTarget,
) (*AssistedRecovery, error)

NewScopedAssistedRecovery handles a host projection with distinct MFA and issuance policy. Scope and target are trusted assembly configuration.

func (*AssistedRecovery) Complete added in v0.11.0

func (*AssistedRecovery) Initiate added in v0.11.0

func (s *AssistedRecovery) Initiate(ctx context.Context, request AssistedRecoveryRequest) error

func (*AssistedRecovery) InitiateOwner added in v0.11.0

type AssistedRecoveryAuthorization added in v0.11.0

type AssistedRecoveryAuthorization interface {
	AuthorizeAssistedRecovery(ctx context.Context, auth AuthContext, subject SubjectID, reason, reference string) error
}

AssistedRecoveryAuthorization is evaluated inside the security transaction. The host must check its dedicated recovery permission and independently verified identity evidence. A name or possession of the new email is not identity evidence. Neither this interface nor Auth may come from request JSON.

type AssistedRecoveryCompletion added in v0.11.0

type AssistedRecoveryCompletion struct {
	Token       string
	NewPassword string
	Code        string
}

type AssistedRecoveryOwnerAuthorization added in v0.11.0

type AssistedRecoveryOwnerAuthorization interface {
	AuthorizeOwnerAssistedRecovery(ctx context.Context, subject SubjectID) error
}

AssistedRecoveryOwnerAuthorization is server-CLI authority only. Implementations must lock the owner population and verify this is the sole active owner. Never expose this capability through an HTTP route.

type AssistedRecoveryOwnerRequest added in v0.11.0

type AssistedRecoveryOwnerRequest struct {
	SubjectID    SubjectID
	NewEmail     string
	Reason       string
	Confirmation string
}

type AssistedRecoveryRecord added in v0.11.0

type AssistedRecoveryRecord struct {
	OwnerCLI        bool
	Scope           string
	TargetVersion   int64
	Selector        string
	SubjectID       SubjectID
	ActorID         SubjectID
	Digest          SecretDigest
	SecurityVersion int64
	OldEmail        string
	NewEmail        string
	CreatedAt       time.Time
	ExpiresAt       time.Time
}

AssistedRecoveryRecord contains only a keyed token digest. Stores must join AuthTransaction, lock subject before record, and bind the invitation to the exact security version and current primary email. Later issuance supersedes earlier invitations without modifying account security state.

type AssistedRecoveryRequest added in v0.11.0

type AssistedRecoveryRequest struct {
	Auth              AuthContext
	SubjectID         SubjectID
	NewEmail          string
	Password          string
	Code              string
	Reason            string
	EvidenceReference string
}

AssistedRecoveryRequest identifies an existing local account. Starting a recovery never changes its email, password, MFA, roles, or sessions.

type AssistedRecoveryStore added in v0.11.0

type AssistedRecoveryStore interface {
	CreateAssistedRecovery(ctx context.Context, record AssistedRecoveryRecord) error
	FindAssistedRecoverySubject(ctx context.Context, selector string) (SubjectID, error)
	LockAssistedRecovery(ctx context.Context, selector string) (AssistedRecoveryRecord, error)
	// CompleteAssistedRecovery performs exact-record CAS, preserves identity and
	// roles, changes email and password together, advances security version, and
	// invalidates all sessions, refresh families and recovery links atomically.
	CompleteAssistedRecovery(
		ctx context.Context, record AssistedRecoveryRecord, passwordPHC string, now time.Time,
	) (Account, error)
}

type AssistedRecoveryTarget added in v0.11.0

type AssistedRecoveryTarget interface {
	ReadAssistedRecoveryTarget(ctx context.Context, subject SubjectID) (AssistedRecoveryTargetSnapshot, error)
	ReplaceAssistedRecoveryTarget(
		ctx context.Context, subject SubjectID, snapshot AssistedRecoveryTargetSnapshot, email string, now time.Time,
	) error
	VerifyAssistedRecoveryFactor(ctx context.Context, subject SubjectID, code string, now time.Time) (bool, error)
	PrepareAssistedRecoveryPassword(ctx context.Context, subject SubjectID, password string) (string, error)
}

AssistedRecoveryTarget is a host-owned email projection on this exact auth database. Every method joins the managed transaction. Read must lock its target; Replace must use email/version CAS and invalidate host sessions. VerifyFactor must preserve enrollment and durably charge incorrect attempts. PreparePassword must apply the host's existing issuance policy and hash budget.

type AssistedRecoveryTargetSnapshot added in v0.11.0

type AssistedRecoveryTargetSnapshot struct {
	Email   string
	Version int64
}

type AuditSink

type AuditSink interface {
	RecordSecurityEvent(ctx context.Context, event SecurityEvent) error
}

type AuditSinkFunc

type AuditSinkFunc func(context.Context, SecurityEvent) error

func (AuditSinkFunc) RecordSecurityEvent

func (f AuditSinkFunc) RecordSecurityEvent(ctx context.Context, event SecurityEvent) error

type AuthContext

type AuthContext struct {
	SubjectID       SubjectID
	Realm           Realm
	Scope           SessionScope
	SessionID       string
	SecurityVersion int64
	Claims          map[string]any
}

type AuthTransaction added in v0.5.0

type AuthTransaction interface {
	InAuthTransaction(ctx context.Context, fn func(context.Context) error) error
}

AuthTransaction atomically commits all participating auth, audit and encrypted event writes. Callback errors roll back. A failed commit with uncertain server outcome must wrap ErrOperationOutcomeUnknown. Nested auth writes join the same scope. Credential-attempt admission is separate: built-in stores reject TakeRateLimit in this scope with ErrRateLimitTransactionUnsupported. Call rate-limited Runtime operations outside an outer transaction. Hooks must not perform irreversible external actions inside this callback.

type BasicProfile

type BasicProfile struct {
	Username    string
	DisplayName string
	GivenName   string
	FamilyName  string
}

type ChangePasswordRequest

type ChangePasswordRequest struct {
	SubjectID       SubjectID
	CurrentPassword string
	NewPassword     string
}

type ClaimsEnricher

type ClaimsEnricher interface {
	EnrichClaims(ctx context.Context, realm Realm, account Account, claims map[string]any) error
}

type ClaimsEnricherFunc

type ClaimsEnricherFunc func(context.Context, Realm, Account, map[string]any) error

func (ClaimsEnricherFunc) EnrichClaims

func (f ClaimsEnricherFunc) EnrichClaims(ctx context.Context, realm Realm, account Account, claims map[string]any) error

type CommonPasswordChecker

type CommonPasswordChecker interface {
	IsCommonPassword(password string) bool
}

func NewLocalCommonPasswordChecker added in v0.7.0

func NewLocalCommonPasswordChecker(
	passwords []string,
	options ...LocalCommonPasswordCheckerOption,
) (CommonPasswordChecker, error)

NewLocalCommonPasswordChecker copies passwords into an immutable in-memory dictionary implementing CommonPasswordChecker. It is safe for concurrent reads after construction; callers must not mutate passwords during construction.

Entries and queries use strings.ToLower(strings.TrimSpace(value)), matching the built-in checker. There is no Unicode normalization or full case folding. Invalid UTF-8 entries, entries empty after trimming, and nil options return a nil checker and an error that never contains the entry text. Duplicates are accepted. Invalid UTF-8 queries never match.

A nil or empty passwords slice is valid and blocks nothing unless WithBuiltInCommonPasswords is supplied. Assigning the result to PasswordPolicy.Blocklist replaces that policy's checker; built-ins are not included implicitly. Construction does not change any existing policy. Checks are synchronous and perform no I/O; the existing bool-only interface has no context or operational-error contract.

type CommonPasswordCheckerFunc

type CommonPasswordCheckerFunc func(string) bool

func (CommonPasswordCheckerFunc) IsCommonPassword

func (f CommonPasswordCheckerFunc) IsCommonPassword(password string) bool

type Config

type Config struct {
	// EmailChangeTargets registers explicitly scoped host-owned address projections.
	EmailChangeTargets map[EmailChangeScope]EmailChangeTarget

	// MFAKeys enables optional local-factor verification. Nil never bypasses stored enrollment.
	MFAKeys *KeyRing
	MFAMode MFAMode

	NotificationDelivery    NotificationDeliveryPolicy
	Store                   RuntimeStore
	Signing                 SigningConfig
	TokenHMACKeys           KeyRing
	OutboxAEADKeys          KeyRing
	EventSink               EncryptedEventSink
	NotificationTransaction NotificationTransaction // Deprecated: use AuthTransaction.
	AuthTransaction         AuthTransaction
	// Nil allows all trusted issuers; empty disables verified-email auto-linking.
	AutoLinkVerifiedEmailIssuers []string
	// ManagedNotificationDelivery enables typed, lease-owned PostgreSQL delivery.
	// A custom transaction alone keeps the configured renderer and event sink.
	ManagedNotificationDelivery bool
	IdentifierResolvers         map[IdentifierScheme]IdentifierResolver
	MembershipGate              MembershipGate
	ClaimsEnricher              ClaimsEnricher
	NotificationRenderer        NotificationRenderer
	URLBuilder                  URLBuilder
	AuditSink                   AuditSink
	PasswordHasher              PasswordHasher
	// EnableLegacyBytes256 opts into two sequential, equal-profile verification jobs.
	// Requires the built-in hasher and a profile preflight; see docs/local-identities.md.
	EnableLegacyBytes256 bool
	// Zero defaults to four concurrent hashes/verifications per shared Runtime.
	// Excess work fails immediately with ErrPasswordHashOverloaded.
	MaxConcurrentPasswordHashes int
	PasswordPolicy              PasswordPolicy
	AdditionalRealms            []Realm
	AccessTTL                   time.Duration
	SessionTTL                  time.Duration
	RefreshTTL                  time.Duration
	PasswordResetTTL            time.Duration
	ChallengeTTL                time.Duration
	EmailChangeTTL              time.Duration
	EnvelopeRetention           time.Duration
	ResetResponseFloor          time.Duration
	IdentityLinkAuthMaxAge      time.Duration
	LoginRateLimit              RateLimitPolicy

	// CredentialVerificationRateLimit bounds VerifyCredential and PostgreSQL
	// PrepareCredential separately from browser login. Zero inherits LoginRateLimit.
	// Every verification attempt, including success, still consumes admission.
	CredentialVerificationRateLimit RateLimitPolicy

	// PasswordResetRecipientResolver opts into host-authorized recovery addresses.
	// Nil retains primary-email-only recovery. See docs/password-reset-recipients.md.
	PasswordResetRecipientResolver PasswordResetRecipientResolver

	PasswordResetRateLimit RateLimitPolicy
	Now                    func() time.Time
	Random                 io.Reader
}

type Credential

type Credential struct {
	Identifier IdentifierInput
	Password   string
}

type EmailChallengeIssueResult

type EmailChallengeIssueResult struct {
	Status  EmailChallengeIssueStatus
	RetryAt time.Time
}

type EmailChallengeIssueStatus

type EmailChallengeIssueStatus string
const (
	EmailChallengeIssued      EmailChallengeIssueStatus = "issued"
	EmailChallengeWait        EmailChallengeIssueStatus = "wait"
	EmailChallengeHourlyLimit EmailChallengeIssueStatus = "hourly_limit"
	EmailChallengeDailyLimit  EmailChallengeIssueStatus = "daily_limit"
)

type EmailChallengeLimits

type EmailChallengeLimits struct {
	MinResendInterval time.Duration
	PerHour           int
	PerDay            int
}

type EmailChallengePurpose

type EmailChallengePurpose string
const EmailChallengePurposeVerification EmailChallengePurpose = "verification"

func (EmailChallengePurpose) Validate

func (p EmailChallengePurpose) Validate() error

type EmailChallengeRecord

type EmailChallengeRecord struct {
	ID           string
	SubjectID    SubjectID
	IdentifierID string
	// Runtime supplies these together so a store can reject a challenge if
	// the account changes after the caller reads its email destination.
	ExpectedNormalizedEmail string
	ExpectedSecurityVersion int64
	Purpose                 EmailChallengePurpose
	Digest                  SecretDigest
	RateDigest              SecretDigest
	MaxAttempts             int
	ExpiresAt               time.Time
	CreatedAt               time.Time
}

type EmailChallengeStore

type EmailChallengeStore interface {
	IssueEmailChallenge(
		ctx context.Context,
		record EmailChallengeRecord,
		limits EmailChallengeLimits,
	) (EmailChallengeIssueResult, error)
	VerifyEmailChallenge(ctx context.Context, request EmailChallengeVerifyRequest) (EmailChallengeVerifyResult, error)
}

type EmailChallengeVerifyRequest

type EmailChallengeVerifyRequest struct {
	SubjectID SubjectID
	Purpose   EmailChallengePurpose
	Digests   []SecretDigest
	Now       time.Time
}

type EmailChallengeVerifyResult

type EmailChallengeVerifyResult struct {
	Status   EmailChallengeVerifyStatus
	Account  Account
	Attempts int
}

type EmailChallengeVerifyStatus

type EmailChallengeVerifyStatus string
const (
	EmailChallengeVerified        EmailChallengeVerifyStatus = "verified"
	EmailChallengeInvalid         EmailChallengeVerifyStatus = "invalid"
	EmailChallengeExpired         EmailChallengeVerifyStatus = "expired"
	EmailChallengeAttemptsUsed    EmailChallengeVerifyStatus = "attempts_used"
	EmailChallengeAlreadyVerified EmailChallengeVerifyStatus = "already_verified"
)

type EmailChangeIssueResult

type EmailChangeIssueResult struct {
	Status  EmailChangeIssueStatus
	RetryAt time.Time
}

type EmailChangeIssueStatus

type EmailChangeIssueStatus string
const (
	EmailChangeIssued      EmailChangeIssueStatus = "issued"
	EmailChangeWait        EmailChangeIssueStatus = "wait"
	EmailChangeHourlyLimit EmailChangeIssueStatus = "hourly_limit"
	EmailChangeDailyLimit  EmailChangeIssueStatus = "daily_limit"
	EmailChangeSameValue   EmailChangeIssueStatus = "same_value"
)

type EmailChangeRecord

type EmailChangeRecord struct {
	Scope                   EmailChangeScope
	ExpectedSecurityVersion int64
	TargetSnapshot          EmailChangeTargetSnapshot
	ID                      string
	SubjectID               SubjectID
	NewDisplayValue         string
	NewNormalizedValue      string
	Digest                  SecretDigest
	RateDigest              SecretDigest
	MaxAttempts             int
	ExpiresAt               time.Time
	CreatedAt               time.Time
}

type EmailChangeScope added in v0.8.0

type EmailChangeScope string

EmailChangeScope identifies a host-owned recovery-address projection. The empty scope retains the primary-email contract. A scope is trusted host authority and must never be selected from an untrusted form or URL.

func EmailChangeScopeFromContext added in v0.8.0

func EmailChangeScopeFromContext(ctx context.Context) EmailChangeScope

type EmailChangeStore

type EmailChangeStore interface {
	IssueEmailChange(
		ctx context.Context,
		record EmailChangeRecord,
		limits EmailChallengeLimits,
	) (EmailChangeIssueResult, error)
	GetPendingEmailChange(ctx context.Context, subjectID SubjectID, now time.Time) (PendingEmailChange, error)
	VerifyEmailChange(ctx context.Context, request EmailChangeVerifyRequest) (EmailChangeVerifyResult, error)
}

type EmailChangeTarget added in v0.8.0

type EmailChangeTarget interface {
	SnapshotEmailChangeTarget(ctx context.Context, subject SubjectID) (EmailChangeTargetSnapshot, error)
	ApplyVerifiedEmailChange(ctx context.Context, subject SubjectID,
		expected EmailChangeTargetSnapshot, newEmail string, now time.Time) error
}

EmailChangeTarget must join the Runtime's auth transaction. Snapshot is read under the canonical subject lock. Apply must CAS the complete old snapshot, write only this projection, and invalidate dependent host security state. It must not mutate the subject, primary email, credentials, or issue sessions.

type EmailChangeTargetSnapshot added in v0.8.0

type EmailChangeTargetSnapshot struct {
	NormalizedEmail string
	Version         int64
}

EmailChangeTargetSnapshot is a versioned host-owned address. An absent address may be empty, but its version must remain positive and monotonic.

type EmailChangeVerifyRequest

type EmailChangeVerifyRequest struct {
	Scope                   EmailChangeScope
	RecordID                string
	ExpectedSecurityVersion int64
	SubjectID               SubjectID
	Digests                 []SecretDigest
	Now                     time.Time
}

type EmailChangeVerifyResult

type EmailChangeVerifyResult struct {
	Status   EmailChangeVerifyStatus
	Account  Account
	Attempts int
}

type EmailChangeVerifyStatus

type EmailChangeVerifyStatus string
const (
	EmailChangeVerified     EmailChangeVerifyStatus = "verified"
	EmailChangeInvalid      EmailChangeVerifyStatus = "invalid"
	EmailChangeExpired      EmailChangeVerifyStatus = "expired"
	EmailChangeAttemptsUsed EmailChangeVerifyStatus = "attempts_used"
	EmailChangeNotFound     EmailChangeVerifyStatus = "not_found"
)

type EncryptedEnvelope

type EncryptedEnvelope struct {
	KeyID          string
	Nonce          []byte
	Ciphertext     []byte
	AdditionalData []byte
	CreatedAt      time.Time
	DeleteAfter    time.Time
}

type EncryptedEvent

type EncryptedEvent struct {
	ID          string
	Type        string
	SubjectID   SubjectID
	ReferenceID string
	ValidUntil  time.Time
	Envelope    EncryptedEnvelope
}

type EncryptedEventSink

type EncryptedEventSink interface {
	EnqueueEncrypted(ctx context.Context, event EncryptedEvent) error
	DeleteEncrypted(ctx context.Context, eventID string) error
	DeleteExpiredEncrypted(ctx context.Context, before time.Time) (int64, error)
}

type ExternalIdentity

type ExternalIdentity struct {
	Issuer        string
	Subject       string
	Email         string
	EmailVerified bool
	Profile       BasicProfile
}

type ExternalLoginRequest

type ExternalLoginRequest struct {
	Identity ExternalIdentity
	Realm    Realm
}

type Identifier

type Identifier struct {
	ID              string
	SubjectID       SubjectID
	Scheme          IdentifierScheme
	DisplayValue    string
	NormalizedValue string
	VerifiedAt      *time.Time
	CreatedAt       time.Time
	UpdatedAt       time.Time
}

Identifier stores both a display value and its scheme-specific canonical value. VerifiedAt is the only source for verification claims.

func (Identifier) IsVerified

func (i Identifier) IsVerified() bool

type IdentifierInput

type IdentifierInput struct {
	Scheme IdentifierScheme
	Value  string
}

type IdentifierResolver

type IdentifierResolver interface {
	NormalizeIdentifier(ctx context.Context, input IdentifierInput) (IdentifierInput, error)
}

type IdentifierResolverFunc

type IdentifierResolverFunc func(context.Context, IdentifierInput) (IdentifierInput, error)

func (IdentifierResolverFunc) NormalizeIdentifier

func (f IdentifierResolverFunc) NormalizeIdentifier(ctx context.Context, input IdentifierInput) (IdentifierInput, error)

type IdentifierScheme

type IdentifierScheme string
const IdentifierSchemeEmail IdentifierScheme = "email"

func (IdentifierScheme) Validate

func (s IdentifierScheme) Validate() error
type IdentityLink struct {
	ID              string
	SubjectID       SubjectID
	Issuer          string
	ExternalSubject string
	EmailNormalized string
	EmailVerified   bool
	CreatedAt       time.Time
	UpdatedAt       time.Time
}

type IdentityLinkStore

type IdentityLinkStore interface {
	ResolveIdentityLink(ctx context.Context, issuer, externalSubject string) (Account, IdentityLink, error)
	CreateSSOAccount(ctx context.Context, record SSOAccountRecord) (Account, IdentityLink, error)
	LinkIdentity(ctx context.Context, link IdentityLink) (IdentityLink, error)
}

type ImportLocalIdentityRequest added in v0.6.0

type ImportLocalIdentityRequest struct {
	SubjectID           SubjectID
	Identifier          IdentifierInput
	PasswordPHC         string
	PasswordInputPolicy PasswordInputPolicy
	Profile             BasicProfile
	Status              SubjectStatus
}

ImportLocalIdentityRequest imports one existing email-less local identity. SubjectID is assigned by the host's durable migration mapping. Status must be explicit. PHCs must be canonical and match the configured current profile, or the fixed legacy profile when EnableLegacyBytes256 is enabled.

type Key

type Key struct {
	ID       string
	Material []byte
}

type KeyRing

type KeyRing struct {
	// contains filtered or unexported fields
}

KeyRing is an immutable versioned set of 256-bit symmetric keys.

func NewKeyRing

func NewKeyRing(activeKeyID string, keys ...Key) (KeyRing, error)

func (KeyRing) Active

func (r KeyRing) Active() (Key, error)

func (KeyRing) ActiveKeyID

func (r KeyRing) ActiveKeyID() string

func (KeyRing) Get

func (r KeyRing) Get(keyID string) (Key, error)

func (KeyRing) IDs

func (r KeyRing) IDs() []string

type LocalAccountRecord

type LocalAccountRecord struct {
	Account             Account
	PasswordPHC         string
	PasswordInputPolicy PasswordInputPolicy
}

type LocalCommonPasswordCheckerOption added in v0.7.0

type LocalCommonPasswordCheckerOption func(*localCommonPasswordCheckerConfig)

LocalCommonPasswordCheckerOption configures NewLocalCommonPasswordChecker. Use WithBuiltInCommonPasswords to include the default dictionary.

func WithBuiltInCommonPasswords added in v0.7.0

func WithBuiltInCommonPasswords() LocalCommonPasswordCheckerOption

WithBuiltInCommonPasswords adds the existing built-in dictionary to a local checker. It does not modify the default dictionary or any other checker. Repeating this option has the same effect as supplying it once.

type LocalIdentifierReader added in v0.6.0

type LocalIdentifierReader interface {
	GetLocalIdentifier(ctx context.Context, subjectID SubjectID, scheme IdentifierScheme) (Identifier, error)
}

LocalIdentifierReader is an optional read-only RuntimeStore capability. Return only the subject's primary custom identifier for the explicit non-email scheme, never a secondary alias or credential. Missing rows return ErrLocalIdentifierConflict. Reads must join an existing AuthTransaction and reject foreign transaction scopes.

type LocalIdentityRecord added in v0.6.0

type LocalIdentityRecord struct {
	LocalAccountRecord
	Identifier Identifier
}

LocalIdentityRecord is a create-only, email-less account with one custom login identifier. The identifier is not an email or a verification assertion.

type LocalIdentityRenameStore added in v0.6.0

type LocalIdentityRenameStore interface {
	RenameLocalIdentity(ctx context.Context, request RenameLocalIdentityStoreRequest) (LocalIdentityView, error)
}

LocalIdentityRenameStore is an optional capability; required store interfaces remain unchanged. Lock subject before its identifier and credential; require an existing credential, compare security version then old normalized identifier. Preserve status, identifier ID/creation time, profile, email, links and credential. A real edit advances security version exactly once and invalidates sessions, local/OIDC families, resets and pending email security state in AuthTransaction. Exact no-ops return ErrIdentifierUnchanged after CAS without any writes. Collisions (including same-subject aliases) return ErrIdentifierAlreadyExists, never merge. Overflow fails closed. The Runtime adds mandatory audit to the same transaction.

type LocalIdentityRetirementStore added in v0.6.0

type LocalIdentityRetirementStore interface {
	RetireLocalIdentity(ctx context.Context, request RetireLocalIdentityStoreRequest) (SubjectLifecycleView, error)
}

LocalIdentityRetirementStore is an optional capability; required store interfaces remain unchanged. Lock subject first, reject retirement, then check version, exact primary custom login and existing local credential. Atomically mark disabled/retired, increment once (including ordinary-disabled subjects), delete ONLY the selected identifier and the local credential, revoke sessions, local/OIDC families, resets and pending email security state. Keep UUID, profile, audit, RBAC, every other identifier and external link. Overflow denies. Return persisted timestamps. The Runtime adds mandatory transactional audit.

Implementing this capability requires terminality across ALL store mutation paths: status/rename/password/credential/link writes cannot revive or attach authentication to a retired subject. Never clear the marker or reuse its ID. Retained bindings are deny-only, never reassigned or silently linked. Stores that cannot enforce these requirements must not advertise this capability.

type LocalIdentityStore added in v0.6.0

type LocalIdentityStore interface {
	CreateLocalIdentity(ctx context.Context, record LocalIdentityRecord) (Account, error)
}

LocalIdentityStore is an optional capability for privileged local identities. Implementations must persist PasswordInputPolicy, return it on every credential read, and clear it to Unicode on password change/reset. Creates must reject any existing subject or identifier, never merge, and join AuthTransaction.

type LocalIdentityView added in v0.6.0

type LocalIdentityView struct {
	Account    Account
	Identifier Identifier
}

LocalIdentityView is a safe identity read model, without credential material.

type LocalLoginResult added in v0.8.0

type LocalLoginResult struct {
	Login   LoginResult
	Pending *MFALoginChallenge
}

type LoginRequest

type LoginRequest struct {
	Credential Credential
	Realm      Realm
}

type LoginResult

type LoginResult struct {
	Account Account
	Tokens  TokenPair
}

type MFAChangeRequest added in v0.8.0

type MFAChangeRequest struct {
	Auth     AuthContext
	Password string
	Code     string
}

type MFAEnrollment added in v0.8.0

type MFAEnrollment struct {
	// RecoveryCodes are returned for pre-auth setup only, inactive until confirmation.
	RecoveryCodes []string
	ID            string
	Secret        string
	URL           string
	ExpiresAt     time.Time
}

type MFAEnrollmentConfirmRequest added in v0.8.0

type MFAEnrollmentConfirmRequest struct {
	Auth         AuthContext
	Password     string
	EnrollmentID string
	Code         string
	Binding      [32]byte
}

type MFAEnrollmentRecord added in v0.8.0

type MFAEnrollmentRecord struct {
	SubjectID       SubjectID
	ID              string
	Revision        int64
	SecurityVersion int64
	SessionID       string
	Binding         [32]byte
	KeyID           string
	Ciphertext      []byte
	CreatedAt       time.Time
	ExpiresAt       time.Time
}

type MFAEnrollmentRequest added in v0.8.0

type MFAEnrollmentRequest struct {
	Auth        AuthContext
	Password    string
	CurrentCode string
	Binding     [32]byte
}

Auth must come from verified host authentication, never request JSON.

type MFAEnrollmentStore added in v0.8.0

type MFAEnrollmentStore interface {
	EnsureMFAFactor(ctx context.Context, subject SubjectID) (MFAFactorRecord, error)
	SaveMFAEnrollment(ctx context.Context, enrollment MFAEnrollmentRecord) error
	LockMFAEnrollment(ctx context.Context, subject SubjectID) (MFAEnrollmentRecord, error)
	ReplaceMFAFactor(ctx context.Context, subject SubjectID, previous, next MFAFactorRecord) (bool, error)
	ReplaceMFARecovery(ctx context.Context, subject SubjectID, digests [][]byte) error
	CancelMFAEnrollment(ctx context.Context, subject SubjectID, sessionID string, binding [32]byte) error
	InvalidateMFAState(ctx context.Context, subject SubjectID) error
}

type MFAFactorCodec added in v0.8.0

type MFAFactorCodec struct {
	// contains filtered or unexported fields
}

MFAFactorCodec retains an explicit persistent domain. Existing AuthHub rows use authhub/mfa; native local factors use goauth/mfa. Keep historical keys.

func NewMFAFactorCodec added in v0.8.0

func NewMFAFactorCodec(keys KeyRing, domain string) (*MFAFactorCodec, error)

func (*MFAFactorCodec) CheckKey added in v0.8.0

func (c *MFAFactorCodec) CheckKey(keyID string) error

func (*MFAFactorCodec) Open added in v0.8.0

func (c *MFAFactorCodec) Open(subject SubjectID, factor, keyID string, ciphertext []byte) ([]byte, error)

Open returns caller-owned secret bytes; clear them after use.

func (*MFAFactorCodec) RecoveryDigest added in v0.8.0

func (c *MFAFactorCodec) RecoveryDigest(subject SubjectID, factor, code string) ([]byte, error)

func (*MFAFactorCodec) Seal added in v0.8.0

func (c *MFAFactorCodec) Seal(subject SubjectID, factor, secret string) (string, []byte, error)

type MFAFactorDecision added in v0.8.0

type MFAFactorDecision uint8
const (
	MFAFactorDenied MFAFactorDecision = iota
	MFAFactorVerified
	MFAFactorRateLimited
)

func ConsumeMFAFactorLocked added in v0.8.0

func ConsumeMFAFactorLocked(ctx context.Context, store MFAFactorStore, codec *MFAFactorCodec,
	subject SubjectID, factor MFAFactorRecord, code string, now time.Time,
) (MFAFactorDecision, error)

ConsumeMFAFactorLocked is for adapters that already charged a durable attempt in this same transaction. Never use it without that admission.

func VerifyMFAFactorLocked added in v0.8.0

func VerifyMFAFactorLocked(ctx context.Context, store MFAFactorStore, codec *MFAFactorCodec,
	subject SubjectID, factor MFAFactorRecord, code string, now time.Time,
) (MFAFactorDecision, error)

VerifyMFAFactorLocked only consumes a factor; it is not a login proof. The caller validates the subject, session/challenge, binding, revision and expiry, and commits factor, challenge, session and audit together before publishing.

type MFAFactorRecord added in v0.8.0

type MFAFactorRecord struct {
	ID         string
	Revision   int64
	Enabled    bool
	KeyID      string
	Ciphertext []byte
	LastStep   int64
}

MFAFactorRecord is durable encrypted state. Revision advances on every enrollment replacement, disable, or recovery-code regeneration.

type MFAFactorStore added in v0.8.0

type MFAFactorStore interface {
	LockMFAFactor(ctx context.Context, subject SubjectID) (MFAFactorRecord, error)
	TakeMFAAttempt(ctx context.Context, subject SubjectID) (bool, error)
	ConsumeMFARecovery(ctx context.Context, subject SubjectID, factorID string, revision int64, digest []byte) (bool, error)
	AdvanceMFAStep(ctx context.Context, subject SubjectID, factorID string, revision, previous, next int64) (bool, error)
}

MFAFactorStore joins a subject-locked auth transaction. Expected business denials commit their attempt budget. Consume operations must compare the exact enabled factor ID and revision and atomically consume the digest/step.

type MFALocalAdmissionStore added in v0.8.0

type MFALocalAdmissionStore interface {
	LocalMFARequired(ctx context.Context, subject SubjectID) (bool, error)
}

MFALocalAdmissionStore must remain enabled even when MFA keys are absent. A legacy custom store may omit it only if it cannot contain enrolled factors.

type MFALoginChallenge added in v0.8.0

type MFALoginChallenge struct {
	Kind      MFALoginKind
	ID        string
	ExpiresAt time.Time
}

type MFALoginKind added in v0.8.0

type MFALoginKind string
const (
	MFALoginKindFactor     MFALoginKind = "factor"
	MFALoginKindEnrollment MFALoginKind = "enrollment"
)

type MFALoginPending added in v0.8.0

type MFALoginPending struct {
	Kind            MFALoginKind
	Setup           MFALoginSetup
	ID              string
	SubjectID       SubjectID
	Realm           Realm
	SecurityVersion int64
	FactorID        string
	FactorRevision  int64
	BindingDigest   [32]byte
	AuthenticatedAt time.Time
	ExpiresAt       time.Time
}

MFALoginPending is server-side continuation state, not authorization. ID is a random selector. BindingDigest hashes independent server browser state.

type MFALoginSetup added in v0.8.0

type MFALoginSetup struct {
	ID              string
	KeyID           string
	Ciphertext      []byte
	RecoveryDigests [][]byte
}

MFALoginSetup is encrypted setup material bound to one password-verified continuation. It has no lifetime independent of its parent pending record.

type MFAMode added in v0.8.0

type MFAMode string

MFAMode is trusted runtime configuration, never a browser-supplied login option.

const (
	MFAModeOptional MFAMode = "optional"
	MFAModeRequired MFAMode = "required"
)

type MFARequiredStore added in v0.8.0

type MFARequiredStore interface {
	MFAStore
	SaveMFALoginSetup(ctx context.Context, pending MFALoginPending, setup MFALoginSetup) error
	MarkMFASessionAdmission(ctx context.Context, session Session, admission MFASessionAdmission) error
	AdmitRequiredMFASession(ctx context.Context, session Session) error
}

MFARequiredStore extends the optional module without silently upgrading third-party stores that cannot enforce required enrollment and live admission.

type MFASessionAdmission added in v0.8.0

type MFASessionAdmission string

MFASessionAdmission is immutable server-written provenance. External admission is only written by LoginExternal after the host has verified that authority.

const (
	MFASessionAdmissionFactor   MFASessionAdmission = "factor"
	MFASessionAdmissionExternal MFASessionAdmission = "external"
)

type MFAStore added in v0.8.0

type MFAStore interface {
	MFAFactorStore
	MFALocalAdmissionStore
	MFAEnrollmentStore
	FindMFALoginSubject(ctx context.Context, id string) (SubjectID, error)
	CreateMFALogin(ctx context.Context, pending MFALoginPending) error
	LockMFALogin(ctx context.Context, id string) (MFALoginPending, error)
	ConsumeMFALogin(ctx context.Context, pending MFALoginPending) (bool, error)
}

MFAStore operations join the caller's transaction with subject, factor, then pending locks. Consumption is exact-record CAS, never a read followed by delete.

type MembershipGate

type MembershipGate interface {
	AllowMembership(ctx context.Context, realm Realm, account Account) error
}

type MembershipGateFunc

type MembershipGateFunc func(context.Context, Realm, Account) error

func (MembershipGateFunc) AllowMembership

func (f MembershipGateFunc) AllowMembership(ctx context.Context, realm Realm, account Account) error

type Notification

type Notification struct {
	Template string            `json:"template"`
	To       string            `json:"to"`
	Data     map[string]string `json:"data,omitempty"`
}

type NotificationDelivery

type NotificationDelivery struct {
	ID string
	// EncryptedEvent is the exact sealed event claimed by the managed worker.
	// Delivery transports may persist this envelope, but never Notification's
	// plaintext codes/tokens. Use ID for downstream idempotency; delivery is at-least-once.
	EncryptedEvent EncryptedEvent
	Notification   Notification
	ValidUntil     time.Time
}

NotificationDelivery may be sent more than once before it is accepted, expires, or exhausts the attempt limit. A sender must use ID as its idempotency key when the underlying transport supports deduplication.

type NotificationDeliveryPolicy added in v0.5.0

type NotificationDeliveryPolicy uint8

NotificationDeliveryPolicy selects whether recovery and email commands are available. The zero value retains mandatory encrypted delivery.

const (
	NotificationDeliveryRequired NotificationDeliveryPolicy = iota
	NotificationDeliveryDisabled
)

type NotificationDiagnostic added in v0.10.0

type NotificationDiagnostic struct {
	Outcome    string
	Code       string
	HTTPStatus int
}

NotificationDiagnostic is a bounded, transport-safe explanation of a sender failure. It must never contain an address, URL, response body, credential or raw error. PostgreSQL independently validates every tuple before persisting it. HTTPStatus is zero when no HTTP response was observed. This is an observation of one attempt, never proof of delivery or a change to retry policy.

type NotificationDiagnosticError added in v0.10.0

type NotificationDiagnosticError interface {
	error
	NotificationDiagnostic() NotificationDiagnostic
}

NotificationDiagnosticError optionally enriches a sender error. Existing NotificationSender implementations remain compatible. Wrapped errors are supported via errors.As; rejection still requires ErrNotificationRejected.

type NotificationRenderer

type NotificationRenderer interface {
	RenderNotification(ctx context.Context, notification Notification) ([]byte, error)
}

type NotificationRendererFunc

type NotificationRendererFunc func(context.Context, Notification) ([]byte, error)

func (NotificationRendererFunc) RenderNotification

func (f NotificationRendererFunc) RenderNotification(ctx context.Context, notification Notification) ([]byte, error)

type NotificationSender

type NotificationSender interface {
	SendNotification(ctx context.Context, delivery NotificationDelivery) error
}

NotificationSender is the only delivery integration required from a host using the PostgreSQL managed notification queue.

type NotificationSenderFunc

type NotificationSenderFunc func(context.Context, NotificationDelivery) error

func (NotificationSenderFunc) SendNotification

func (f NotificationSenderFunc) SendNotification(ctx context.Context, delivery NotificationDelivery) error

type NotificationTransaction deprecated

type NotificationTransaction interface {
	InNotificationTransaction(ctx context.Context, fn func(context.Context) error) error
}

NotificationTransaction is the previous notification-only contract.

Deprecated: implement AuthTransaction for every Runtime operation.

type PasswordChangeStoreRequest

type PasswordChangeStoreRequest struct {
	SubjectID           SubjectID
	ExpectedPasswordPHC string
	NewPasswordPHC      string
	Now                 time.Time
}

type PasswordChangeStoreResult

type PasswordChangeStoreResult struct {
	Status  PasswordChangeStoreStatus
	Account Account
}

type PasswordChangeStoreStatus

type PasswordChangeStoreStatus string
const (
	PasswordChangeStoreSucceeded PasswordChangeStoreStatus = "succeeded"
	PasswordChangeStoreConflict  PasswordChangeStoreStatus = "conflict"
	PasswordChangeStoreMissing   PasswordChangeStoreStatus = "missing"
)

type PasswordEmailChangeRequest added in v0.5.0

type PasswordEmailChangeRequest struct {
	SubjectID       SubjectID
	CurrentPassword string
	NewEmail        string
}

PasswordEmailChangeRequest binds reauthentication to the actor selected by the trusted host. Never populate SubjectID from an unauthenticated request body.

type PasswordHasher

type PasswordHasher interface {
	HashPassword(password string) (string, error)
	// VerifyPassword returns nil on a match and an error on a mismatch. Runtime
	// preserves native mismatch errors as invalid credentials. Operational errors
	// must wrap ErrPasswordVerificationUnavailable, ErrPasswordHashOverloaded,
	// context.Canceled or context.DeadlineExceeded instead.
	VerifyPassword(phc, password string) error
}

type PasswordInputPolicy added in v0.6.0

type PasswordInputPolicy string

PasswordInputPolicy describes verification of an existing credential, never issuance of a new password. The zero value retains the standard Unicode policy.

const (
	PasswordInputPolicyUnicode PasswordInputPolicy = "unicode_v1"
	// PasswordInputPolicyLegacyBytes256 is only for explicitly imported Argon2id
	// v19 credentials with m=65536,t=3,p=1, a 16-byte salt and a 32-byte digest.
	PasswordInputPolicyLegacyBytes256 PasswordInputPolicy = "legacy_bytes_256" //nolint:gosec // Policy identifier, not a credential.
)

type PasswordPolicy

type PasswordPolicy struct {
	MinLength        int
	MaxLength        int
	Blocklist        CommonPasswordChecker
	DisableBlocklist bool
}

func DefaultPasswordPolicy

func DefaultPasswordPolicy() PasswordPolicy

func (PasswordPolicy) Validate

func (p PasswordPolicy) Validate(password string) error

type PasswordResetConsumeRequest

type PasswordResetConsumeRequest struct {
	Selector    string
	Digest      SecretDigest
	PasswordPHC string
	Now         time.Time
}

type PasswordResetConsumeResult

type PasswordResetConsumeResult struct {
	Status  PasswordResetConsumeStatus
	Account Account
}

type PasswordResetConsumeStatus

type PasswordResetConsumeStatus string
const (
	PasswordResetConsumed PasswordResetConsumeStatus = "consumed"
	PasswordResetInvalid  PasswordResetConsumeStatus = "invalid"
	PasswordResetExpired  PasswordResetConsumeStatus = "expired"
	PasswordResetUsed     PasswordResetConsumeStatus = "used"
)

type PasswordResetReceipt added in v0.5.0

type PasswordResetReceipt struct {
	SubjectID SubjectID
	Selector  string
}

PasswordResetReceipt contains only public correlation identifiers, never a reset credential.

type PasswordResetRecipientResolver added in v0.7.0

type PasswordResetRecipientResolver interface {
	LookupPasswordResetSubject(ctx context.Context, normalizedEmail string) (SubjectID, error)
	ResolvePasswordResetRecipient(ctx context.Context, account Account, requestedEmail string) (string, error)
	// PreparePasswordResetPassword applies host policy and returns a prepared PHC.
	// Empty PHC with nil error selects the unchanged Runtime policy and hasher.
	// It runs only after token authentication under the same subject lock. It
	// must not mutate state or log/persist the supplied plaintext password.
	PreparePasswordResetPassword(ctx context.Context, account Account, newPassword string) (string, error)
}

PasswordResetRecipientResolver is an optional, privileged host policy for recovery addresses. Lookup must find exactly one canonical subject across primary email and every host-authorized alias; missing or colliding ownership must return ErrAccountNotFound. Neither method may provision a local credential.

Resolve runs with the active canonical subject locked in the auth transaction. It must recheck current ownership and return the current authorized destination. A nonempty requestedEmail must still belong unambiguously to account.Subject.ID. Empty requestedEmail asks for the current password-reset success destination. Use the supplied context for all reads. Hosts must serialize alias mutations through the same subject lock and invalidate all reset records in that transaction, including change-away-and-back, before publishing the mutation.

type PasswordResetRecord

type PasswordResetRecord struct {
	SubjectID SubjectID
	Selector  string
	Digest    SecretDigest
	// Expected fields reject a reset issued from a stale account lookup.
	ExpectedNormalizedEmail string
	ExpectedSecurityVersion int64
	ExpiresAt               time.Time
	CreatedAt               time.Time
}

type PasswordResetStore

type PasswordResetStore interface {
	CreatePasswordReset(ctx context.Context, record PasswordResetRecord) error
	ConsumePasswordReset(ctx context.Context, request PasswordResetConsumeRequest) (PasswordResetConsumeResult, error)
}

type PasswordResetSubjectStore added in v0.7.0

type PasswordResetSubjectStore interface {
	CreatePasswordResetForSubject(ctx context.Context, record PasswordResetRecord) error
	// Authenticate and lock subject then token before prepare. Recheck expiry
	// with the current configured clock after preparation and before mutation.
	ConsumePasswordResetWithPreparation(ctx context.Context, request PasswordResetConsumeRequest,
		prepare func(context.Context, Account) (string, error)) (PasswordResetConsumeResult, error)
}

PasswordResetSubjectStore is the opt-in storage capability required by PasswordResetRecipientResolver. The ordinary PasswordResetStore contract is unchanged. Implementations must lock the subject, require active status, a nonempty current local credential and the exact ExpectedSecurityVersion. ExpectedNormalizedEmail must be empty; it is not an alternate identity field. Replacement, single-use, expiry and transaction guarantees match CreatePasswordReset.

type PendingEmailChange

type PendingEmailChange struct {
	Scope                   EmailChangeScope          `json:"-"`
	ExpectedSecurityVersion int64                     `json:"-"`
	TargetSnapshot          EmailChangeTargetSnapshot `json:"-"`
	NewNormalizedValue      string                    `json:"-"`
	ID                      string
	SubjectID               SubjectID
	NewDisplayValue         string
	Attempts                int
	MaxAttempts             int
	CreatedAt               time.Time
	ExpiresAt               time.Time
}

type PreparedPasswordChange added in v0.8.0

type PreparedPasswordChange struct {
	// contains filtered or unexported fields
}

PreparedPasswordChange is an opaque, runtime-bound proof of password verification and replacement hashing. It expires after five minutes. Hosts must keep it in process and must never log, serialize or send it to a client. Completion still requires host-owned session authorization.

func (PreparedPasswordChange) GoString added in v0.8.0

func (PreparedPasswordChange) GoString() string

GoString avoids exposing credential hashes through Go-syntax formatting.

func (PreparedPasswordChange) String added in v0.8.0

func (PreparedPasswordChange) String() string

String avoids exposing credential hashes through ordinary formatting.

type ProvisionLocalIdentityRequest added in v0.6.0

type ProvisionLocalIdentityRequest struct {
	Identifier IdentifierInput
	Password   string
	Profile    BasicProfile
}

ProvisionLocalIdentityRequest creates an active email-less local identity. Identifier must use an explicitly registered non-email scheme.

type RateLimitPolicy

type RateLimitPolicy struct {
	Window time.Duration
	Limit  int
}

func (RateLimitPolicy) Validate

func (p RateLimitPolicy) Validate() error

type RateLimitRequest

type RateLimitRequest struct {
	SubjectID SubjectID
	Action    string
	Bucket    SecretDigest
	Window    time.Duration
	Limit     int
	Now       time.Time
}

type RateLimitResult

type RateLimitResult struct {
	Allowed bool
	RetryAt time.Time
}

type RateLimitStore

type RateLimitStore interface {
	TakeRateLimit(ctx context.Context, request RateLimitRequest) (RateLimitResult, error)
}

type Realm

type Realm string
const (
	RealmUser  Realm = "user"
	RealmAdmin Realm = "admin"
)

func (Realm) Validate

func (r Realm) Validate() error

type RefreshRotationRequest

type RefreshRotationRequest struct {
	ExpectedSecurityVersion int64
	ExpectedNormalizedEmail string
	ExpectedEmailVerified   bool
	CurrentSelector         string
	CurrentDigest           SecretDigest
	NextSelector            string
	NextDigest              SecretDigest
	NextExpiresAt           time.Time
	Now                     time.Time
}

type RefreshRotationResult

type RefreshRotationResult struct {
	Status   RefreshRotationStatus
	Account  Account
	Session  Session
	FamilyID string
}

type RefreshRotationStatus

type RefreshRotationStatus string
const (
	RefreshRotationSucceeded RefreshRotationStatus = "succeeded"
	RefreshRotationInvalid   RefreshRotationStatus = "invalid"
	RefreshRotationExpired   RefreshRotationStatus = "expired"
	RefreshRotationRevoked   RefreshRotationStatus = "revoked"
	RefreshRotationReplayed  RefreshRotationStatus = "replayed"
)

type RegisterRequest

type RegisterRequest struct {
	Email    string
	Password string
	Profile  BasicProfile
}

type RegisterResult

type RegisterResult struct {
	Account Account
	Tokens  TokenPair
}

type RenameLocalIdentityRequest added in v0.6.0

type RenameLocalIdentityRequest struct {
	SubjectID               SubjectID
	ExpectedIdentifier      IdentifierInput
	ExpectedSecurityVersion int64
	NewIdentifier           IdentifierInput
}

RenameLocalIdentityRequest binds a custom login edit to the exact subject and security version the host authorized. Both identifiers use the same explicit, registered non-email scheme. ExpectedIdentifier is compared after normalization.

type RenameLocalIdentityStoreRequest added in v0.6.0

type RenameLocalIdentityStoreRequest struct {
	SubjectID               SubjectID
	Scheme                  IdentifierScheme
	ExpectedNormalizedValue string
	ExpectedSecurityVersion int64
	NewDisplayValue         string
	NewNormalizedValue      string
	Now                     time.Time
}

RenameLocalIdentityStoreRequest contains already normalized, bounded values. Stores must validate these bounds and the expected state under a subject lock.

type RetireLocalIdentityRequest added in v0.6.0

type RetireLocalIdentityRequest struct {
	SubjectID               SubjectID
	ExpectedIdentifier      IdentifierInput
	ExpectedSecurityVersion int64
}

RetireLocalIdentityRequest authorizes terminal retirement of the ENTIRE authentication subject, releasing only this primary custom login. Other identifiers and external links remain reserved to the disabled old subject.

type RetireLocalIdentityStoreRequest added in v0.6.0

type RetireLocalIdentityStoreRequest struct {
	SubjectID               SubjectID
	Scheme                  IdentifierScheme
	ExpectedNormalizedValue string
	ExpectedSecurityVersion int64
	Now                     time.Time
}

RetireLocalIdentityStoreRequest contains an explicit non-email scheme and normalized, bounded expected login. Now must be nonzero.

type Runtime

type Runtime struct {
	// contains filtered or unexported fields
}

func NewRuntime

func NewRuntime(config Config) (*Runtime, error)

func (*Runtime) AcknowledgeEncryptedEvent

func (r *Runtime) AcknowledgeEncryptedEvent(ctx context.Context, eventID string) error

func (*Runtime) AuthenticateSession added in v0.5.0

func (r *Runtime) AuthenticateSession(ctx context.Context, token string) (AuthContext, error)

AuthenticateSession validates current session/security state and realm membership.

func (*Runtime) BeginLocalLogin added in v0.8.0

func (r *Runtime) BeginLocalLogin(ctx context.Context, request LoginRequest, binding [32]byte) (LocalLoginResult, error)

BeginLocalLogin returns either final local tokens or a non-authorizing pending selector. Hosts keep it separate from authenticated browser state and enforce their authoritative browser-flow publication fence before exposing tokens.

func (*Runtime) BeginLocalLoginEnrollment added in v0.8.0

func (r *Runtime) BeginLocalLoginEnrollment(ctx context.Context, id string, binding [32]byte) (MFAEnrollment, error)

BeginLocalLoginEnrollment only accepts a live password-verified enrollment continuation. It never creates a session or extends the original deadline.

func (*Runtime) BeginMFAEnrollment added in v0.8.0

func (r *Runtime) BeginMFAEnrollment(ctx context.Context, request MFAEnrollmentRequest) (MFAEnrollment, error)

func (*Runtime) CancelMFAEnrollment added in v0.8.0

func (r *Runtime) CancelMFAEnrollment(ctx context.Context, auth AuthContext, binding [32]byte) error

func (*Runtime) ChangePassword

func (r *Runtime) ChangePassword(ctx context.Context, request ChangePasswordRequest) (Account, error)

func (*Runtime) CleanupExpiredEncryptedEvents

func (r *Runtime) CleanupExpiredEncryptedEvents(ctx context.Context) (int64, error)

func (*Runtime) CompleteLocalLogin added in v0.8.0

func (r *Runtime) CompleteLocalLogin(ctx context.Context, id string, binding [32]byte, code string) (LoginResult, error)

CompleteLocalLogin prepares tokens before writes, then rechecks and consumes the factor/challenge with session and audit in one owned commit. Expected denials commit their durable attempt effects; errors release no usable result.

func (*Runtime) CompleteLocalLoginEnrollment added in v0.8.0

func (r *Runtime) CompleteLocalLoginEnrollment(
	ctx context.Context, id, enrollmentID string, binding [32]byte, code string,
) (LoginResult, error)

CompleteLocalLoginEnrollment atomically activates the displayed setup, invalidates previous authority and publishes a fresh post-enrollment session. No authority or recovery codes are returned for an uncertain commit.

func (*Runtime) CompletePasswordChange added in v0.8.0

func (r *Runtime) CompletePasswordChange(ctx context.Context, subjectID SubjectID,
	proof *PreparedPasswordChange,
) (Account, error)

CompletePasswordChange joins the host auth transaction, revalidates the proof under the subject lock, and performs the existing atomic password update, session invalidation, notification and audit. The host must authorize its current live session and any required second factor in this same transaction. The expected subject prevents a proof from being applied to a different actor. Success inside an outer transaction is provisional until that owner commits.

func (*Runtime) ConfirmEmailChange

func (r *Runtime) ConfirmEmailChange(ctx context.Context, subjectID SubjectID, code string) (Account, error)

func (*Runtime) ConfirmMFAEnrollment added in v0.8.0

func (r *Runtime) ConfirmMFAEnrollment(ctx context.Context, request MFAEnrollmentConfirmRequest) ([]string, error)

func (*Runtime) DecryptNotificationEvent

func (r *Runtime) DecryptNotificationEvent(event EncryptedEvent) (Notification, error)

func (*Runtime) DisableMFA added in v0.8.0

func (r *Runtime) DisableMFA(ctx context.Context, request MFAChangeRequest) error

func (*Runtime) FindAccount

func (r *Runtime) FindAccount(ctx context.Context, identifier IdentifierInput) (Account, error)

func (*Runtime) GetAccount

func (r *Runtime) GetAccount(ctx context.Context, subjectID SubjectID) (Account, error)

func (*Runtime) GetLocalIdentifier added in v0.6.0

func (r *Runtime) GetLocalIdentifier(ctx context.Context, subjectID SubjectID, scheme IdentifierScheme) (Identifier, error)

GetLocalIdentifier reads a primary custom login by immutable subject ID. It exposes neither passwords nor password policy, and does not require a credential. The scheme must be explicitly registered and non-email; absence is a conflict.

func (*Runtime) GetSubjectLifecycle added in v0.6.0

func (r *Runtime) GetSubjectLifecycle(ctx context.Context, id SubjectID) (SubjectLifecycleView, error)

GetSubjectLifecycle reads lifecycle by immutable subject ID. It is suitable for host reconciliation, but is not an operation receipt or proof of which request committed. It exposes no credentials.

func (*Runtime) ImportLocalIdentity added in v0.6.0

func (r *Runtime) ImportLocalIdentity(ctx context.Context, request ImportLocalIdentityRequest) (Account, error)

ImportLocalIdentity is a privileged, create-only migration API. It never overwrites passwords, re-enables subjects, or merges identities by identifier. The host must atomically persist its import mapping and grants using the same AuthTransaction and skip already mapped imports, preserving later mutations. A conflict is an error, including a repeated call with the same subject ID.

func (*Runtime) LinkExternalIdentity

func (r *Runtime) LinkExternalIdentity(ctx context.Context, auth AuthContext, external ExternalIdentity) (IdentityLink, error)

func (*Runtime) Login

func (r *Runtime) Login(ctx context.Context, request LoginRequest) (LoginResult, error)

func (*Runtime) LoginExternal

func (r *Runtime) LoginExternal(ctx context.Context, request ExternalLoginRequest) (LoginResult, error)

LoginExternal resolves or provisions an SSO-only account and creates a realm-bound session. An unverified email can receive only the user realm's limited confirmation session.

func (*Runtime) Logout

func (r *Runtime) Logout(ctx context.Context, subjectID SubjectID, sessionID string) error

func (*Runtime) LogoutAll

func (r *Runtime) LogoutAll(ctx context.Context, subjectID SubjectID) (int64, error)

func (*Runtime) MFAMode added in v0.8.0

func (r *Runtime) MFAMode() MFAMode

MFAMode returns the normalized trusted admission policy.

func (*Runtime) MFAStatus added in v0.8.0

func (r *Runtime) MFAStatus(ctx context.Context, auth AuthContext) (bool, error)

MFAStatus reports enrollment and whether local MFA management is available. A missing codec still returns the authorized enrollment state with ErrMFAUnavailable.

func (*Runtime) PendingEmailChange

func (r *Runtime) PendingEmailChange(ctx context.Context, subjectID SubjectID) (PendingEmailChange, error)

func (*Runtime) PreparePasswordChange added in v0.8.0

func (r *Runtime) PreparePasswordChange(ctx context.Context, request ChangePasswordRequest) (*PreparedPasswordChange, error)

PreparePasswordChange performs durable password-attempt admission and hashing outside a host transaction. SubjectID must come from the authenticated actor, never from an unauthenticated form. No plaintext password is retained.

func (*Runtime) PreparePasswordChangeWithIssuer added in v0.8.0

func (r *Runtime) PreparePasswordChangeWithIssuer(ctx context.Context, request ChangePasswordRequest,
	issuer *Runtime,
) (*PreparedPasswordChange, error)

PreparePasswordChangeWithIssuer uses a trusted host-selected runtime only for replacement policy and bounded hashing. Current-credential verification, durable admission, proof binding, notification and audit remain with r. Never select the issuer from request input. It grants no account-write authority.

func (*Runtime) ProvisionExternalIdentity added in v0.12.0

func (r *Runtime) ProvisionExternalIdentity(ctx context.Context, external ExternalIdentity) (Account, error)

ProvisionExternalIdentity resolves an exact existing issuer/subject binding or creates a new SSO-only account. Unlike ResolveExternalIdentity, it NEVER links by email, regardless of AutoLinkVerifiedEmailIssuers. Email collisions require the separate authenticated explicit-link workflow.

This is a trusted host API, not an HTTP signup endpoint. The caller MUST verify the provider proof, project admission and enrollment policy before calling it. It grants no membership, roles, password or session. Hosts must join this call, their enrollment record and mandatory audit in one owned auth transaction, and withhold a successful result until the outer commit is confirmed.

func (*Runtime) ProvisionLocalIdentity added in v0.6.0

func (r *Runtime) ProvisionLocalIdentity(ctx context.Context, request ProvisionLocalIdentityRequest) (Account, error)

ProvisionLocalIdentity is a privileged host API. Authorize the operator before calling it; do not bind public signup input to it. It creates no session and asserts no verified email. Account creation and audit join AuthTransaction.

func (*Runtime) ProvisionTrustedLocalAccount

func (r *Runtime) ProvisionTrustedLocalAccount(ctx context.Context, request RegisterRequest) (Account, error)

ProvisionTrustedLocalAccount creates a verified local account for an operator-controlled bootstrap or seed path. Interactive registration must use Register so that the account starts in confirmation scope.

func (*Runtime) Refresh

func (r *Runtime) Refresh(ctx context.Context, rawRefreshToken string) (TokenPair, error)

func (*Runtime) RegenerateMFARecovery added in v0.8.0

func (r *Runtime) RegenerateMFARecovery(ctx context.Context, request MFAChangeRequest) ([]string, error)

func (*Runtime) Register

func (r *Runtime) Register(ctx context.Context, request RegisterRequest) (RegisterResult, error)

func (*Runtime) RenameLocalIdentity added in v0.6.0

func (r *Runtime) RenameLocalIdentity(ctx context.Context, request RenameLocalIdentityRequest) (LocalIdentityView, error)

RenameLocalIdentity is a privileged host operation, with no public endpoint or notification. Authorize the exact subject and rate-limit the operator first. Original display spelling is preserved; the registered resolver defines equality. Display-only edits also advance security version; an exact no-op is an error. Old login is immediately released, so reuse must create a fresh subject.

Call before host project locks. Results inside an outer AuthTransaction are provisional until that transaction succeeds. ErrOperationOutcomeUnknown returns no success; reconcile a durable host receipt before retrying. Version increments invalidate existing bindings but do not close OIDC provider final-write races.

func (*Runtime) RequestEmailChange

func (r *Runtime) RequestEmailChange(ctx context.Context, subjectID SubjectID, newEmail string) error

RequestEmailChange is a trusted host API. The host must authorize the actor and require fresh authentication before calling it. Local-password HTTP flows should use RequestEmailChangeWithPassword instead.

func (*Runtime) RequestEmailChangeWithPassword added in v0.5.0

func (r *Runtime) RequestEmailChangeWithPassword(ctx context.Context, request PasswordEmailChangeRequest) error

RequestEmailChangeWithPassword verifies this subject's current local password before issuing an email-change challenge. The caller still owns session and actor-to-target authorization. SSO-only accounts need an explicit host-owned recent-IdP-authentication flow through RequestEmailChange; there is no fallback.

func (*Runtime) RequestEmailChangeWithPreviousEmailNotice added in v0.14.0

func (r *Runtime) RequestEmailChangeWithPreviousEmailNotice(
	ctx context.Context, subjectID SubjectID, newEmail, previousEmail string,
) error

RequestEmailChangeWithPreviousEmailNotice is a trusted host API. Like RequestEmailChange, the host must authorize the actor and require fresh authentication. previousEmail must be a canonical, already verified host address read under the same auth transaction, never an untrusted form value. A nonempty value must exactly match the locked pre-change target snapshot. Empty retains the existing RequestEmailChange behavior. A successful issuance atomically queues a credential-free notice; no mail is sent in the transaction. An uncertain commit must not be blindly retried as a new issuance.

func (*Runtime) RequestPasswordReset

func (r *Runtime) RequestPasswordReset(ctx context.Context, email string) error

func (*Runtime) RequestPasswordResetWithReceipt added in v0.5.0

func (r *Runtime) RequestPasswordResetWithReceipt(
	ctx context.Context, email string, bind func(context.Context, PasswordResetReceipt) error,
) error

RequestPasswordResetWithReceipt calls bind inside the initiating auth transaction after reset, encrypted notification and audit writes. A bind error rolls back all participating writes. No callback runs when no reset is issued. Privileged hosts must not expose whether bind ran to unauthenticated clients.

func (*Runtime) ResetPassword

func (r *Runtime) ResetPassword(ctx context.Context, rawToken, newPassword string) error

func (*Runtime) ResolveExternalIdentity

func (r *Runtime) ResolveExternalIdentity(ctx context.Context, external ExternalIdentity) (Account, error)

ResolveExternalIdentity accepts only identity claims verified by a trusted provider adapter. Never bind unverified client JSON directly to this method.

func (*Runtime) RetireLocalIdentity added in v0.6.0

func (r *Runtime) RetireLocalIdentity(ctx context.Context, request RetireLocalIdentityRequest) (SubjectLifecycleView, error)

RetireLocalIdentity is a privileged host operation, without an HTTP endpoint or email notification. Authorize the exact subject, login and security version and rate-limit the operator first. This permanently retires the whole auth subject, not only an alias. Reusing the released login creates a fresh subject with no inherited grants, roles, sessions or links. Retained emails can prevent reuse and must never silently bind the replacement to the former subject.

Call before host project locks and compose grants/audit/operation receipt in the same AuthTransaction. Nested results are provisional until outer commit. Every error returns zero success. ErrOperationOutcomeUnknown needs durable receipt reconciliation; do not blindly retry. Repeated retirement returns ErrSubjectRetired without writes. This does not close OIDC final-write races.

func (*Runtime) SendEmailChallenge

func (r *Runtime) SendEmailChallenge(
	ctx context.Context,
	subjectID SubjectID,
	purpose EmailChallengePurpose,
) error

func (*Runtime) SetSubjectStatus

func (r *Runtime) SetSubjectStatus(ctx context.Context, subjectID SubjectID, status SubjectStatus) (Subject, error)

func (*Runtime) SetTrustedLocalPassword added in v0.6.0

func (r *Runtime) SetTrustedLocalPassword(ctx context.Context, request SetTrustedLocalPasswordRequest) (Account, error)

SetTrustedLocalPassword is a privileged host API, never a public reset endpoint. The host must authorize and rate-limit its operator and supply actor audit. Strict issuance policy and the shared hash budget apply; authentication-attempt admission does not. Even identical plaintext advances security version and revokes security state. Active, suspended and disabled status are preserved.

Hashing precedes new locks. Call before taking host project locks. This joins an outer AuthTransaction: returned state is provisional until that outer call succeeds. Reconcile a durable host operation record on ErrOperationOutcomeUnknown before retrying, since a repeated successful call advances security state again. Required delivery enqueues password_changed only when a primary email exists; disabled delivery/email-less identities still require transactional audit.

func (*Runtime) UpdateBasicProfile

func (r *Runtime) UpdateBasicProfile(
	ctx context.Context,
	subjectID SubjectID,
	profile BasicProfile,
) (Account, error)

func (*Runtime) VerifyAccessToken deprecated

func (r *Runtime) VerifyAccessToken(ctx context.Context, token string, introspect bool) (AuthContext, error)

VerifyAccessToken is retained for source compatibility.

Deprecated: use VerifyJWT or AuthenticateSession explicitly.

func (*Runtime) VerifyCredential

func (r *Runtime) VerifyCredential(ctx context.Context, credential Credential) (Account, error)

VerifyCredential verifies a local credential without creating a session. It is intended for recent-authentication checks and trusted bootstrap flows.

func (*Runtime) VerifyEmailChallenge

func (r *Runtime) VerifyEmailChallenge(
	ctx context.Context,
	subjectID SubjectID,
	purpose EmailChallengePurpose,
	code string,
) (Account, error)

func (*Runtime) VerifyJWT added in v0.5.0

func (r *Runtime) VerifyJWT(ctx context.Context, token string) (AuthContext, error)

VerifyJWT validates an access JWT offline. Revocation takes effect only when it expires; use AuthenticateSession for current authorization.

type SSOAccountRecord

type SSOAccountRecord struct {
	Account Account
	Link    IdentityLink
}

type ScopedEmailChangeStore added in v0.8.0

type ScopedEmailChangeStore interface {
	IssueScopedEmailChange(ctx context.Context, record EmailChangeRecord,
		limits EmailChallengeLimits) (EmailChangeIssueResult, error)
	GetPendingScopedEmailChange(ctx context.Context, subject SubjectID,
		scope EmailChangeScope, now time.Time) (PendingEmailChange, error)
	VerifyScopedEmailChange(ctx context.Context, request EmailChangeVerifyRequest) (EmailChangeVerifyResult, error)
}

ScopedEmailChangeStore is an explicit opt-in. Legacy RuntimeStore adapters cannot silently treat a host projection as the primary email. Implementations share the ordinary subject-wide issuance quota and record-attempt machinery.

type SecretDigest

type SecretDigest struct {
	KeyID  string
	Digest []byte
}

type SecurityEvent

type SecurityEvent struct {
	Type       SecurityEventType
	SubjectID  SubjectID
	Realm      Realm
	At         time.Time
	Attributes map[string]string
}

type SecurityEventType

type SecurityEventType string
const (
	SecurityEventLocalIdentityProvisioned SecurityEventType = "local_identity.provisioned"
	SecurityEventLocalIdentityImported    SecurityEventType = "local_identity.imported"
	SecurityEventLoginFailed              SecurityEventType = "login.failed"
	SecurityEventRefreshReplay            SecurityEventType = "refresh.replay"
	SecurityEventPasswordResetIssued      SecurityEventType = "password_reset.issued"
	SecurityEventPasswordResetUsed        SecurityEventType = "password_reset.used"
	SecurityEventPasswordChanged          SecurityEventType = "password.changed"
	SecurityEventTrustedLocalPasswordSet  SecurityEventType = "password.trusted_set"
	SecurityEventLocalIdentityRenamed     SecurityEventType = "local_identity.renamed"
	SecurityEventSubjectRetired           SecurityEventType = "subject.retired"
	SecurityEventEmailChallengeIssued     SecurityEventType = "email_challenge.issued"
	SecurityEventEmailVerified            SecurityEventType = "email.verified"
	SecurityEventEmailChangeIssued        SecurityEventType = "email_change.issued"
	SecurityEventEmailChanged             SecurityEventType = "email.changed"
	SecurityEventSessionRevoked           SecurityEventType = "session.revoked"
	SecurityEventSessionsRevoked          SecurityEventType = "sessions.revoked"
	SecurityEventSubjectStatusChanged     SecurityEventType = "subject.status_changed"
	SecurityEventIdentityLinked           SecurityEventType = "identity.linked"
)

type Session

type Session struct {
	ID              string
	SubjectID       SubjectID
	Realm           Realm
	Scope           SessionScope
	SecurityVersion int64
	CreatedAt       time.Time
	ExpiresAt       time.Time
	RevokedAt       *time.Time
}

func (Session) Active

func (s Session) Active(now time.Time) bool

type SessionRecord

type SessionRecord struct {
	Session          Session
	FamilyID         string
	RefreshSelector  string
	RefreshDigest    SecretDigest
	RefreshExpiresAt time.Time
}

type SessionScope

type SessionScope string
const (
	SessionScopeAuthenticated SessionScope = "authenticated"
	SessionScopeConfirmation  SessionScope = "confirmation"
)

type SessionSecurity

type SessionSecurity struct {
	Session        Session
	SubjectStatus  SubjectStatus
	CurrentVersion int64
}

type SessionStore

type SessionStore interface {
	// PeekRefresh authenticates a token without consuming it. Rotation revalidates all state.
	PeekRefresh(ctx context.Context, request RefreshRotationRequest) (RefreshRotationResult, error)
	// CreateSession locks the canonical subject and rejects stale status, security
	// version, or email-verification/scope state. It must not rewrite signed scope.
	CreateSession(ctx context.Context, record SessionRecord) error
	RotateRefresh(ctx context.Context, request RefreshRotationRequest) (RefreshRotationResult, error)
	IntrospectSession(ctx context.Context, sessionID string) (SessionSecurity, error)
	RevokeSession(ctx context.Context, subjectID SubjectID, sessionID string, now time.Time) (bool, error)
	RevokeSubjectSessions(ctx context.Context, subjectID SubjectID, now time.Time) (int64, error)
	SetSubjectStatus(ctx context.Context, subjectID SubjectID, status SubjectStatus, now time.Time) (Subject, error)
}

type SetTrustedLocalPasswordRequest added in v0.6.0

type SetTrustedLocalPasswordRequest struct {
	SubjectID   SubjectID
	NewPassword string
}

SetTrustedLocalPasswordRequest replaces an existing local credential without its old password. It never creates a credential or changes subject status.

type SigningConfig

type SigningConfig struct {
	Issuer   string
	Audience string
	Keys     KeyRing
}

type Subject

type Subject struct {
	ID              SubjectID
	Status          SubjectStatus
	SecurityVersion int64
	CreatedAt       time.Time
	UpdatedAt       time.Time
}

Subject is the canonical security principal. Profile, identifiers, credentials, memberships, and permissions deliberately live elsewhere.

func (Subject) IsZero

func (s Subject) IsZero() bool

type SubjectID

type SubjectID uuid.UUID

SubjectID is the canonical identifier shared by every goauth relation.

func MustParseSubjectID

func MustParseSubjectID(value string) SubjectID

func NewSubjectID

func NewSubjectID() SubjectID

func ParseSubjectID

func ParseSubjectID(value string) (SubjectID, error)

func (SubjectID) IsZero

func (id SubjectID) IsZero() bool

func (SubjectID) MarshalText

func (id SubjectID) MarshalText() ([]byte, error)

func (*SubjectID) Scan

func (id *SubjectID) Scan(source any) error

func (SubjectID) String

func (id SubjectID) String() string

func (*SubjectID) UnmarshalText

func (id *SubjectID) UnmarshalText(value []byte) error

func (SubjectID) Validate

func (id SubjectID) Validate() error

func (SubjectID) Value

func (id SubjectID) Value() (driver.Value, error)

type SubjectLifecycleReader added in v0.6.0

type SubjectLifecycleReader interface {
	GetSubjectLifecycle(ctx context.Context, subjectID SubjectID) (SubjectLifecycleView, error)
}

SubjectLifecycleReader is an optional capability. Read account and marker from ONE consistent snapshot by immutable subject ID, including retired accounts. Reads join AuthTransaction and reject foreign transaction scopes.

type SubjectLifecycleView added in v0.6.0

type SubjectLifecycleView struct {
	Account   Account
	RetiredAt *time.Time
}

SubjectLifecycleView is a non-secret, consistent snapshot of an account and its terminal retirement marker. Subject's existing struct layout is unchanged. RetiredAt is nil for ordinary active, suspended and disabled accounts.

type SubjectStatus

type SubjectStatus string
const (
	SubjectStatusActive    SubjectStatus = "active"
	SubjectStatusSuspended SubjectStatus = "suspended"
	SubjectStatusDisabled  SubjectStatus = "disabled"
)

func (SubjectStatus) Validate

func (s SubjectStatus) Validate() error

type TokenPair

type TokenPair struct {
	AccessToken      string
	RefreshToken     string
	AccessExpiresAt  time.Time
	RefreshExpiresAt time.Time
	Session          Session
}

type TrustedLocalPasswordStore added in v0.6.0

type TrustedLocalPasswordStore interface {
	SetTrustedLocalPassword(ctx context.Context, request TrustedLocalPasswordStoreRequest) (Account, error)
}

TrustedLocalPasswordStore is an optional RuntimeStore capability. A successful write must preserve subject status, replace only an existing credential, set its input policy to Unicode, increment security version, and invalidate all sessions, local/OIDC refresh families, resets and pending email security state. Writes must join AuthTransaction, including mandatory Runtime audit/enqueue. Missing subjects/credentials return ErrAccountNotFound. Stale security version returns ErrSecurityVersionMismatch; stale PHC/policy returns ErrPasswordChangeConflict.

type TrustedLocalPasswordStoreRequest added in v0.6.0

type TrustedLocalPasswordStoreRequest struct {
	SubjectID                   SubjectID
	ExpectedPasswordPHC         string
	ExpectedPasswordInputPolicy PasswordInputPolicy
	ExpectedSecurityVersion     int64
	NewPasswordPHC              string
	Now                         time.Time
}

TrustedLocalPasswordStoreRequest binds a privileged replacement to the exact credential and security state read before hashing. All expected fields must be checked under the canonical subject lock, then the credential lock.

type URLBuilder

type URLBuilder interface {
	PasswordResetURL(ctx context.Context, token string) (string, error)
}

type URLBuilderFunc

type URLBuilderFunc func(context.Context, string) (string, error)

func (URLBuilderFunc) PasswordResetURL

func (f URLBuilderFunc) PasswordResetURL(ctx context.Context, token string) (string, error)

Directories

Path Synopsis
cmd
releaseevidence command
Command releaseevidence validates a complete public-preview evidence manifest.
Command releaseevidence validates a complete public-preview evidence manifest.
examples
hostsession command
Command hostsession demonstrates email-less credentials with host-owned sessions.
Command hostsession demonstrates email-less credentials with host-owned sessions.
nethttp command
Command nethttp demonstrates host-owned browser transport and bearer-only API routes backed by a single PostgreSQL Runtime.
Command nethttp demonstrates host-owned browser transport and bearer-only API routes backed by a single PostgreSQL Runtime.
internal
authclock
Package authclock carries the Runtime clock across its transaction boundary.
Package authclock carries the Runtime clock across its transaction boundary.
identifierbounds
Package identifierbounds enforces portable custom identifier storage bounds.
Package identifierbounds enforces portable custom identifier storage bounds.
retryafter
Package retryafter extracts a transport-independent retry deadline.
Package retryafter extracts a transport-independent retry deadline.
Package nethttp adapts goauth to standard-library JSON HTTP handlers.
Package nethttp adapts goauth to standard-library JSON HTTP handlers.
reference
externalconsumer
Package externalconsumer is a compile-checked manifest of the goauth packages that are currently supported for external consumers such as backend and goadmin.
Package externalconsumer is a compile-checked manifest of the goauth packages that are currently supported for external consumers such as backend and goadmin.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL