authcore

package
v0.59.0 Latest Latest
Warning

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

Go to latest
Published: Jun 23, 2026 License: MIT Imports: 48 Imported by: 0

Documentation

Index

Constants

View Source
const (
	GroupInviteStatusPending  = "pending"
	GroupInviteStatusAccepted = "accepted"
	GroupInviteStatusDeclined = "declined"
	GroupInviteStatusRevoked  = "revoked"
	GroupInviteStatusExpired  = "expired"
)

Group-invite statuses (mirror the group_invites_status_chk constraint).

View Source
const (
	ErrCodeUsernameTooShort            = "username_too_short"
	ErrCodeUsernameTooLong             = "username_too_long"
	ErrCodeUsernameMustStartWithLetter = "username_must_start_with_letter"
	ErrCodeUsernameCannotContainAt     = "username_cannot_contain_at"
	ErrCodeUsernameCannotStartWithPlus = "username_cannot_start_with_plus"
	ErrCodeUsernameInvalidCharacters   = "username_invalid_characters"
	ErrCodeOwnerSlugTaken              = "owner_slug_taken"
	ErrCodeUsernameNotAllowed          = "username_not_allowed"
	ErrCodeRenameRateLimited           = "rename_rate_limited"
	ErrCodeInvalidEmail                = "invalid_email"
	ErrCodeInvalidPhoneNumber          = "invalid_phone_number"
	ErrCodePasswordTooShort            = "password_too_short"
)
View Source
const (
	// RootPersona is the single built-in permission-group persona. Every deployment has exactly ONE root group: the
	// parentless ancestor of every other group. Its namespace is `root:`.
	RootPersona = "root"

	// OwnerRoleName is the required role every persona ships. It holds the
	// persona's WHOLE namespace (`<persona>:*`) and nothing else — never a bare
	// `*`, never another persona. Widest reach within the persona, still
	// namespace-pure.
	OwnerRoleName = "owner"

	// MemberRoleName is the base-membership role authkit seeds on every group.
	// Minimal authority (no perms unless the app's catalog gives it some).
	MemberRoleName = "member"
)
View Source
const (
	// Identity / account directory.
	PermRootUsersRead    = "root:users:read"    // read the account directory
	PermRootUsersSuspend = "root:users:suspend" // suspend / unsuspend an account
	PermRootUsersBan     = "root:users:ban"     // ban / unban an account
	PermRootUsersUpdate  = "root:users:update"  // update account identity/password fields
	PermRootUsersDelete  = "root:users:delete"  // soft-delete / restore an account

	// Group lifecycle as ENTITIES (moderation — delete/restore a group, never run it).
	PermRootGroupsCreate = "root:groups:create" // create a top-level group as an operator
	PermRootGroupsDelete = "root:groups:delete" // soft-delete / restore any group

	// Operator management of roles/credentials/sessions.
	PermRootRolesManage      = "root:roles:manage"       // define/inspect platform-operator roles
	PermRootRemoteAppsManage = "root:remote-apps:manage" // manage federation issuers as an operator
	PermRootAPIKeysRevoke    = "root:api-keys:revoke"    // revoke any api-key
	PermRootSessionsRevoke   = "root:sessions:revoke"    // revoke any user session

	// SuperAdminRoleName is the root role authkit ships in addition to owner:
	// the apex operator. Like owner it holds root:* (they are equivalent on the
	// root persona); kept as a distinct name for the familiar "super-admin" slug.
	SuperAdminRoleName = "super-admin"
)
View Source
const (
	SubjectKindUser      = "user"
	SubjectKindRemoteApp = "remote_application"
)

SubjectKindUser / SubjectKindRemoteApplication select the concrete group-role table used for a principal.

View Source
const (
	// ServiceJWTTokenUse + DefaultServiceJWTLifetime are defined in authbase
	// (core-free) and re-exported here.
	ServiceJWTTokenUse = authbase.ServiceJWTTokenUse
	// ServiceJWTType is the JOSE typ header AuthKit stamps on minted service JWTs.
	ServiceJWTType            = "service+jwt"
	DefaultServiceJWTLifetime = authbase.DefaultServiceJWTLifetime
)
View Source
const (
	RemoteAppModeJWKS   = authbase.RemoteAppModeJWKS
	RemoteAppModeStatic = authbase.RemoteAppModeStatic
)

Remote-application trust modes (#74). A remote_application is a federation PRINCIPAL whose credential is a key, with exactly one trust source:

jwks   — keys fetched + refreshed from JWKSURI; rotation is publishing a new
         kid at the same URL.
static — authorized_keys-style human-managed PEM list for principals without
         a JWKS endpoint; manual rotation by design.

Remote-application trust modes are defined in authbase (core-free) and re-exported here.

View Source
const (
	AssuranceLevelPassword = "urn:authkit:loa:1"
	AssuranceLevelMFA      = "urn:authkit:loa:2"
)
View Source
const (
	SolanaSNSStatusDisabled = "disabled"
	SolanaSNSStatusPending  = "pending"
	SolanaSNSStatusResolved = "resolved"
	SolanaSNSStatusNotFound = "not_found"
	SolanaSNSStatusError    = "error"
	SolanaSNSStatusStale    = "stale"
)
View Source
const DefaultBootstrapManifestPath = "/etc/authkit/bootstrap.yaml"
View Source
const DelegatedAccessTokenType = jwtkit.DelegatedAccessTokenType

DelegatedAccessTokenType is the canonical JOSE `typ` header value for a delegated access token.

View Source
const HashAlgoLegacyResetRequired = "legacy-reset-required"

HashAlgoLegacyResetRequired marks profiles.user_passwords rows migrated from legacy systems whose stored hashes can never verify (DES crypt, md5-crypt, corrupted values). The raw legacy hash is preserved in password_hash for forensics only; the sole way forward for these accounts is a password reset.

View Source
const (
	// MaxCustomJWTLifetime caps the TTL of a custom-claims JWT. Custom tokens are
	// short-lived first-party tokens (capability/worker tokens, etc.); they share
	// the same 1h ceiling regardless of the requested TTL. Mirrors the bounded-TTL
	// guardrails on MintServiceJWT / MintDelegatedAccessToken.
	MaxCustomJWTLifetime = time.Hour
)
View Source
const RemoteApplicationAccessTokenType = jwtkit.RemoteApplicationAccessTokenType

RemoteApplicationAccessTokenType is the JOSE `typ` for a remote application access token.

View Source
const SensitiveActionFreshAuthWindow = 15 * time.Minute
View Source
const SolanaProviderSlug = "solana"

SolanaProviderSlug is the provider slug used for Solana wallets.

Variables

View Source
var (
	ErrInvalidAccessToken = authbase.ErrInvalidAccessToken
	ErrAccessTokenRevoked = authbase.ErrAccessTokenRevoked
	ErrAccessTokenExpired = authbase.ErrAccessTokenExpired
)

Token sentinel errors are defined in authbase and re-exported here for backward compatibility (so core.X callers and errors.Is checks are unaffected).

View Source
var (
	APIKeyMarker    = authbase.APIKeyMarker
	HasAPIKeyPrefix = authbase.HasAPIKeyPrefix
	FormatAPIKey    = authbase.FormatAPIKey
	ParseAPIKey     = authbase.ParseAPIKey
)

API-key marker/parse/format helpers are defined in authbase (core-free) and re-exported here for backward compatibility.

View Source
var (
	// ErrEmptyCustomClaims is returned when CustomJWTMintOptions.Claims is empty —
	// MintCustomJWT exists to carry host claims, so an empty set is a caller bug.
	ErrEmptyCustomClaims = errors.New("custom_jwt_empty_claims")
	// ErrTooManyCustomClaims is returned when the host claim set exceeds
	// maxCustomJWTClaims.
	ErrTooManyCustomClaims = errors.New("custom_jwt_too_many_claims")
	// ErrCustomClaimsReserved is returned when the host Claims map tries to set a
	// registered claim that AuthKit owns (`iss`/`iat`/`exp`) — those are set by
	// AuthKit and the raw map may not silently clobber them. Use the explicit
	// Issuer option to override `iss`.
	ErrCustomClaimsReserved = errors.New("custom_jwt_reserved_claim")
)
View Source
var (
	// ErrInviteNotFound indicates no live invite matched the lookup.
	ErrInviteNotFound = errors.New("group_invite_not_found")
	// ErrInviteNotPending indicates an action requiring a pending invite hit one
	// already accepted/declined/revoked/expired.
	ErrInviteNotPending = errors.New("group_invite_not_pending")
)
View Source
var (
	ErrPasskeyNotFound                 = errors.New("passkey_not_found")
	ErrPasskeyUserVerificationRequired = errors.New("passkey_user_verification_required")
	ErrPasskeyCloneDetected            = errors.New("passkey_clone_detected")
)
View Source
var (
	// ErrAttributeDefNotFound is defined in authbase (core-free) and re-exported here.
	ErrAttributeDefNotFound = authbase.ErrAttributeDefNotFound
	// ErrInvalidAttributeDef indicates a malformed definition registration.
	ErrInvalidAttributeDef = errors.New("invalid_attribute_def")
)
View Source
var (
	// ErrUserBanned indicates the account is blocked from authenticating.
	ErrUserBanned = errors.New("user_banned")
	// ErrPasswordResetRequired indicates the account's stored password hash is
	// flagged HashAlgoLegacyResetRequired: no plaintext can ever verify against
	// it, so the user must complete a password reset before password auth (login,
	// reauth, change-password) can succeed. HTTP layers map this to the stable
	// code "password_reset_required".
	ErrPasswordResetRequired = errors.New("password_reset_required")
	// ErrUserNotFound indicates a user does not exist (or is not visible).
	ErrUserNotFound = errors.New("user_not_found")
	// ErrEmailAlreadyVerified indicates an email verification request targeted an already-verified email.
	ErrEmailAlreadyVerified = errors.New("email_already_verified")
	// ErrPhoneAlreadyVerified indicates a phone verification request targeted an already-verified phone.
	ErrPhoneAlreadyVerified = errors.New("phone_already_verified")
	// ErrPendingRegistrationNotFound indicates a registration resend request did not match a pending registration.
	ErrPendingRegistrationNotFound = errors.New("pending_registration_not_found")
	// ErrRegistrationDisabled indicates a public user-creation path was attempted
	// while native-user registration is bootstrap-only. Existing-user
	// authentication is unaffected; only NEW account creation through
	// public/auto-registration is blocked.
	ErrRegistrationDisabled = errors.New("registration_disabled")
	// ErrVerificationLinkExpired indicates a verification link/token no longer has a pending verification record.
	ErrVerificationLinkExpired = errors.New("verification_link_expired")
	ErrEmailInUse              = errors.New("email_in_use")
	ErrPhoneInUse              = errors.New("phone_in_use")
	ErrEmailSenderUnavailable  = errors.New("email_sender_unavailable")
	ErrSMSSenderUnavailable    = errors.New("sms_unavailable")
)
View Source
var (
	ErrEmailDeliveryFailed = errors.New("email_delivery_failed")
	ErrSMSDeliveryFailed   = errors.New("sms_delivery_failed")
)
View Source
var (
	// ErrInvalidServiceJWT is defined in authbase and re-exported here.
	ErrInvalidServiceJWT = authbase.ErrInvalidServiceJWT
	ErrMissingSigner     = errors.New("missing_signer")
)
View Source
var (
	// ErrRemoteApplicationNotFound indicates no remote_application matched.
	ErrRemoteApplicationNotFound = errors.New("remote_application_not_found")
	// ErrInvalidRemoteApplication is defined in authbase and re-exported here.
	ErrInvalidRemoteApplication = authbase.ErrInvalidRemoteApplication
	// ErrReservedIssuer indicates an attempt to register a remote_application
	// under the platform's own issuer string. The platform issuer is the local,
	// first-party signing identity; allowing a federated remote_application to
	// claim it would overwrite the trusted local issuer entry (key-swap / auth
	// DoS — see AK-AUTH-01).
	ErrReservedIssuer = errors.New("reserved_issuer")
)
View Source
var (
	NormalizeAllowedOrigin  = authbase.NormalizeAllowedOrigin
	NormalizeAllowedOrigins = authbase.NormalizeAllowedOrigins
	OriginAllowed           = authbase.OriginAllowed
)

Origin helpers are defined in authbase (core-free) and re-exported here.

View Source
var ErrCannotRemoveLastAdminRole = errors.New("cannot_remove_last_admin_role")

ErrCannotRemoveLastAdminRole is retained for the admin HTTP adapter's error mapping. The root layer has no "last admin" lock (super-admin is seeded out-of-band via the bootstrap manifest), so core no longer returns it, but the exported symbol stays so dependents keep compiling.

View Source
var ErrEntitlementFilterUnavailable = errors.New("authkit: entitlement filtering requires an EntitlementFilterProvider")

ErrEntitlementFilterUnavailable is returned by AdminListUsers/AdminCountUsers when an Entitlement filter is requested but no EntitlementFilterProvider is configured — fail loud rather than silently return everyone.

View Source
var ErrGroupNotFound = errors.New("permission group not found")

ErrGroupNotFound is returned when a (persona, resource_slug) or id resolves to no live permission-group.

View Source
var ErrInvalidBootstrapManifest = errors.New("invalid_bootstrap_manifest")
View Source
var ErrNotGroupMember = errors.New("not_group_member")

ErrNotGroupMember is returned when a remote_application holds no role in its controlling permission-group.

View Source
var ErrOwnerSlugTaken = errors.New("owner_slug_taken")

ErrOwnerSlugTaken is retained as a stable sentinel for identity-policy error mapping. Under the permission-group model usernames are unique on their own (the owner-slug reservation plane was removed); kept so dependents' errors.Is checks keep compiling.

View Source
var ErrReauthenticationRequired = errors.New("reauth_required")
View Source
var ErrRenameRateLimited = errors.New("rename_rate_limited")

ErrRenameRateLimited is returned when a username rename is attempted before the renameCooldown window has elapsed.

View Source
var ErrReservedRoleSlug = errors.New("reserved_role_slug")
View Source
var ErrTwoFAEnrollmentRequired = errors.New("2fa_enrollment_required")
View Source
var ErrUserRoleNotFound = errors.New("user_role_not_found")

Functions

func IntrinsicRootPermissions

func IntrinsicRootPermissions() []string

IntrinsicRootPermissions returns the authkit-built-in root: permission set (every deployment ships these). Apps add their own root: moderation perms on top via the root persona's roles.

func IsDevEnvironment

func IsDevEnvironment(environment string) bool

IsDevEnvironment reports whether a host-provided environment string is non-production.

func MintDelegatedAccessToken

func MintDelegatedAccessToken(ctx context.Context, signer jwtkit.Signer, p DelegatedAccessParams) (string, error)

MintDelegatedAccessToken signs a canonical delegated access token with an explicit signer. It stamps the `typ=delegated-access+jwt` JOSE header, writes the canonical `delegated_sub`/`permissions`/`attributes` claims, and NEVER sets `sub` — the sub-XOR-delegated_sub invariant is enforced by construction. Receiving services authorize by issuer/resource-account trust plus `permissions`. A top-level `roles` claim is never minted; delegated-subject role UUIDs, when carried, ride under `attributes.roles` (see the Roles param).

Hosts embedding core.Service should prefer (*Service).MintDelegatedAccessToken so they never construct their own signer or read the PEM.

func MintRemoteApplicationAccessToken

func MintRemoteApplicationAccessToken(ctx context.Context, signer jwtkit.Signer, p RemoteApplicationAccessParams) (string, error)

MintRemoteApplicationAccessToken signs a remote application access token with an explicit signer. It stamps the `typ=remote-application-access+jwt` header and writes NO `sub`/`delegated_sub` — identity is the validated `iss` and authority is STORED, resolved at verify. A non-nil p.Permissions is written as the `permissions` claim: a down-scoping request the verifier intersects with the stored ceiling (#76 amendment); never a widening.

func NormalizeEmail

func NormalizeEmail(email string) string

func NormalizePhone

func NormalizePhone(phone string) string

func NormalizePreferredLanguage

func NormalizePreferredLanguage(language string) (string, error)

func NormalizeRemoteAppTrustSource

func NormalizeRemoteAppTrustSource(jwksURI string, mode string, keys []RemoteAppKey) (string, error)

NormalizeRemoteAppTrustSource validates the mutually-exclusive trust source of a registration and returns the normalized mode. Empty mode is inferred: a key list means static, otherwise jwks. It is the single validation gate so the XOR rule cannot be bypassed.

func OwnerGrant

func OwnerGrant(persona string) string

OwnerGrant is the namespace-pure owner grant for a persona: `<persona>:*`. Never a bare `*`. The owner role of every persona holds exactly this.

func PermAPIKeysManage

func PermAPIKeysManage(t string) string

func PermAPIKeysRead

func PermAPIKeysRead(t string) string

func PermInvitesManage

func PermInvitesManage(t string) string

func PermInvitesRead

func PermInvitesRead(t string) string

func PermMembersManage

func PermMembersManage(t string) string

Built-in per-persona group-management permissions (authkit-provisioned in every persona's catalog). All are 3-segment <persona>:<area>:<action>. The owner role (=<persona>:*) covers them all; an app may grant them to other roles.

func PermMembersRead

func PermMembersRead(t string) string

func PermRemoteAppsManage

func PermRemoteAppsManage(t string) string

func PermRemoteAppsRead

func PermRemoteAppsRead(t string) string

func PermRolesManage

func PermRolesManage(t string) string

func PermRolesRead

func PermRolesRead(t string) string

func PermissionPersona

func PermissionPersona(perm string) string

PermissionPersona returns a permission/grant's first segment (its persona ≡ namespace). PermissionPersona("merchant:catalog:update") == "merchant".

func ValidateEmail

func ValidateEmail(email string) error

func ValidateGrantPattern

func ValidateGrantPattern(g string) error

ValidateGrantPattern checks a GRANT token (what a role holds). Grants may be concrete perms OR namespace-anchored globs, but NEVER a bare `*`:

<persona>:<resource>:<action>   a concrete perm
<persona>:<resource>:*          all actions on a resource
<persona>:*                     the whole persona namespace (the owner grant)

The persona segment is always a literal — a bare `*` or `*`-persona is rejected, which is what makes reach != capability structural (a `merchant:*` grant can never name a `root:`/`customer:` perm). Mirrors authbase.PermMatches semantics but is STRICTER: it forbids mid-glob forms like `persona:*:action`.

func ValidatePassword

func ValidatePassword(value string) error

func ValidatePermission

func ValidatePermission(p string) error

ValidatePermission checks a CONCRETE catalog permission: EXACTLY three lowercase segments `<persona>:<resource>:<action>` (e.g. `merchant:catalog:update`, `root:users:ban`). Two-part (`repo:update`) and four-part perms are rejected — a two-part perm must grow a resource (`repo:contents:update`); a persona may use a `:self:` resource for "the thing itself" actions (`endpoint:self:invoke`).

func ValidatePhone

func ValidatePhone(phone string) error

func ValidateUsername

func ValidateUsername(username string) error

func ValidationErrorCode

func ValidationErrorCode(err error) string

ValidationErrorCode returns a stable validation code from err when possible.

func WithSessionRevokeReason

func WithSessionRevokeReason(ctx context.Context, reason SessionRevokeReason) context.Context

WithSessionRevokeReason annotates ctx so revoke paths can emit a structured reason to the auth logger.

Types

type APIKey

type APIKey struct {
	ID          string
	KeyID       string
	Name        string
	Role        string
	Permissions []string
	Resources   []APIKeyResource
	CreatedBy   string
	CreatedAt   time.Time
	LastUsedAt  *time.Time
	ExpiresAt   *time.Time
	RevokedAt   *time.Time
}

APIKey is the non-secret metadata view of an API key. The secret is never stored or returned after creation. Role is the single group role the key holds; Permissions is that role's RESOLVED effective permission set (a convenience projection — the role is the source of truth, edit it to change the key).

type APIKeyMintOptions

type APIKeyMintOptions struct {
	Name      string
	Role      string
	Resources []APIKeyResource
	CreatedBy string
	ExpiresAt *time.Time
}

APIKeyMintOptions is the resource-aware API-key mint request. The key references exactly ONE role (Role) that must be valid for the owning group's persona catalog (or a group custom role); its permissions are resolved from that role at use time. Resource-scope is a separate binding.

type APIKeyResource

type APIKeyResource = authbase.APIKeyResource

APIKeyResource is one opaque, host-defined resource scope carried by an API key. Defined in authbase (core-free) and re-exported here.

type APIKeysConfig

type APIKeysConfig struct {
	// Prefix is the issuing application's brand prefix for generated API keys
	// (single value per deployment). Empty defaults to the bare `st_` marker.
	// Must be lowercase alphanumeric, 1-16 chars.
	Prefix string
	// MaxTTL caps how far in the future a minted API key may expire. 0 (default)
	// means no cap (keys may be non-expiring); when set, a requested expiry
	// beyond now+MaxTTL (incl. no-expiry) is capped at mint time.
	MaxTTL time.Duration
}

APIKeysConfig configures opaque permission-group-owned machine credentials.

type AdminListUsersResult

type AdminListUsersResult struct {
	Users  []AdminUser `json:"users"`
	Total  int64       `json:"total"`
	Limit  int         `json:"limit"`
	Offset int         `json:"offset"`
}

AdminListUsersResult contains paginated user list with total count

type AdminRecoverUserInput

type AdminRecoverUserInput struct {
	Email       string
	PhoneNumber string
}

type AdminUser

type AdminUser struct {
	ID              string     `json:"id"`
	Email           *string    `json:"email"` // Nullable for phone-only users
	PhoneNumber     *string    `json:"phone_number"`
	Username        *string    `json:"username"`
	DiscordUsername *string    `json:"discord_username"`
	EmailVerified   bool       `json:"email_verified"`
	PhoneVerified   bool       `json:"phone_verified"`
	BannedAt        *time.Time `json:"banned_at,omitempty"`
	BannedUntil     *time.Time `json:"banned_until,omitempty"`
	BanReason       *string    `json:"ban_reason,omitempty"`
	BannedBy        *string    `json:"banned_by,omitempty"`
	DeletedAt       *time.Time `json:"deleted_at"`
	Biography       *string    `json:"biography"`
	CreatedAt       time.Time  `json:"created_at"`
	UpdatedAt       time.Time  `json:"updated_at"`
	LastLogin       *time.Time `json:"last_login"`
	Roles           []string   `json:"roles"`
	Entitlements    []string   `json:"entitlements"`
}

Admin listing/get/delete

type AdminUserListOptions

type AdminUserListOptions struct {
	Page        int
	PageSize    int
	Search      string          // ILIKE over username/email/phone_number
	Role        string          // root_role slug (e.g. "admin"); empty = no role filter
	Status      AdminUserStatus // empty = non-deleted (historical default)
	Sort        AdminUserSort   // empty = created_at
	Desc        bool            // true = descending
	Entitlement string          // empty = no entitlement filter; else provider-backed
}

AdminUserListOptions is the admin dashboard user-directory query. It carries no host product knowledge: Role is the root_role query param, a singleton-root permission-group role slug. Status/Sort are closed enums. Entitlement filtering delegates to the billing provider, never a cross-schema join.

type AdminUserSort

type AdminUserSort string

AdminUserSort selects the directory ordering column.

const (
	AdminUserSortCreatedAt AdminUserSort = "created_at" // default
	AdminUserSortLastLogin AdminUserSort = "last_login"
	AdminUserSortUsername  AdminUserSort = "username"
	AdminUserSortEmail     AdminUserSort = "email"
)

type AdminUserStatus

type AdminUserStatus string

AdminUserStatus filters the directory by account state.

const (
	AdminUserStatusActive  AdminUserStatus = "active"  // not deleted, not banned
	AdminUserStatusBanned  AdminUserStatus = "banned"  // not deleted, currently banned
	AdminUserStatusDeleted AdminUserStatus = "deleted" // soft-deleted
	AdminUserStatusAny     AdminUserStatus = "any"     // no deleted/banned predicate

)

type AuthEventLogReader

type AuthEventLogReader interface {
	// ListSessionEvents returns session events matching any of the given event types.
	// If userID is empty, returns events for all users.
	ListSessionEvents(ctx context.Context, userID string, eventTypes ...SessionEventType) ([]AuthSessionEvent, error)
}

AuthEventLogReader allows listing session events filtered by event types and optional userID.

type AuthEventLogger

type AuthEventLogger interface {
	LogSessionEvent(ctx context.Context, e AuthSessionEvent) error
}

type AuthSessionEvent

type AuthSessionEvent struct {
	OccurredAt time.Time
	Issuer     string
	UserID     string
	SessionID  string
	Event      SessionEventType
	Method     *string
	Reason     *string
	IPAddr     *string
	UserAgent  *string
}

AuthSessionEvent is a best-effort, append-only session lifecycle record intended for external sinks.

ClickHouse schema expectation (see migrations/clickhouse): - issuer, user_id, session_id, event are required - method is typically set for SessionEventCreated - reason is typically set for SessionEventRevoked

type BatchEntitlementsProvider

type BatchEntitlementsProvider interface {
	ListEntitlementsBatch(ctx context.Context, userIDs []string) (map[string][]string, error)
}

BatchEntitlementsProvider is an optional upgrade of EntitlementsProvider: one call answers many users, so list renders (AdminListUsers) cost one provider round trip instead of one per row. Detected by type assertion; providers without it get the per-user fallback. Unknown user ids may be absent from the result.

type BootstrapManifest

type BootstrapManifest struct {
	Users       []BootstrapManifestUser       `json:"users" yaml:"users"`
	GlobalRoles []BootstrapManifestGlobalRole `json:"global_roles" yaml:"global_roles"`
}

BootstrapManifest is AuthKit's first-class closed-deployment authority manifest. It owns AuthKit state only: users, root permission-group roles, and password seeding.

Operator authority is a role assignment in the singleton root group. The manifest uses the `global_roles` field names for bootstrap compatibility, but they seed root-group roles: a role named "admin" maps onto the root super-admin (root:*), and any other name must be a catalog role of the root persona (declared in core.Config). Role name/description fields no longer have a target and are accepted-but-ignored.

func LoadBootstrapManifestFile

func LoadBootstrapManifestFile(path string) (BootstrapManifest, error)

func ParseBootstrapManifestYAML

func ParseBootstrapManifestYAML(raw []byte) (BootstrapManifest, error)

type BootstrapManifestGlobalRole

type BootstrapManifestGlobalRole struct {
	Name        string  `json:"name" yaml:"name"`
	Slug        string  `json:"slug" yaml:"slug"`
	Description *string `json:"description" yaml:"description"`
}

BootstrapManifestGlobalRole declares a root-group role to ensure exists. Only Slug is meaningful (the role name); Name/Description are accepted for backward compatibility but ignored. "admin" seeds the super-admin role (root:*).

type BootstrapManifestResult

type BootstrapManifestResult struct {
	DryRun                bool `json:"dry_run"`
	UsersCreated          int  `json:"users_created"`
	UsersUpdated          int  `json:"users_updated"`
	PasswordsSet          int  `json:"passwords_set"`
	PasswordsKept         int  `json:"passwords_kept"`
	GlobalRoles           int  `json:"global_roles"`
	GlobalRoleAssignments int  `json:"global_role_assignments"`
}

type BootstrapManifestUser

type BootstrapManifestUser struct {
	Ref           string                 `json:"ref" yaml:"ref"`
	Email         string                 `json:"email" yaml:"email"`
	PhoneNumber   string                 `json:"phone_number" yaml:"phone_number"`
	Username      string                 `json:"username" yaml:"username"`
	EmailVerified bool                   `json:"email_verified" yaml:"email_verified"`
	PhoneVerified bool                   `json:"phone_verified" yaml:"phone_verified"`
	Banned        bool                   `json:"banned" yaml:"banned"`
	BannedAt      *time.Time             `json:"banned_at" yaml:"banned_at"`
	BannedUntil   *time.Time             `json:"banned_until" yaml:"banned_until"`
	BanReason     *string                `json:"ban_reason" yaml:"ban_reason"`
	BannedBy      *string                `json:"banned_by" yaml:"banned_by"`
	Metadata      map[string]any         `json:"metadata" yaml:"metadata"`
	Password      *BootstrapUserPassword `json:"password" yaml:"password"`
	// GlobalRoles assigns root permission-group roles to this user by name (#111).
	// "admin" mints the root super-admin (root:*); any other name is assigned as a
	// same-named catalog role of the root persona.
	GlobalRoles []string `json:"global_roles" yaml:"global_roles"`
}

type BootstrapReconcileOptions

type BootstrapReconcileOptions struct {
	DryRun bool
}

type BootstrapUserPassword

type BootstrapUserPassword struct {
	Plaintext     string         `json:"plaintext" yaml:"plaintext"`
	Hash          string         `json:"hash" yaml:"hash"`
	HashAlgo      string         `json:"hash_algo" yaml:"hash_algo"`
	HashParams    map[string]any `json:"hash_params" yaml:"hash_params"`
	ResetRequired bool           `json:"reset_required" yaml:"reset_required"`
	// Enforce makes the password DESIRED-STATE (#89): re-asserted on every
	// reconcile. Default false = SEED-ONCE — the password is applied only when
	// the user is first created, so a password rotated out of band (via the
	// admin API) is never reverted to the manifest value on a later reconcile.
	// Must not be combined with ResetRequired (forcing a reset every run is
	// nonsensical).
	Enforce bool `json:"enforce" yaml:"enforce"`
}

type Config

type Config struct {
	// Token is the JWT issuing/verification contract and session limits.
	Token TokenConfig
	// Frontend describes host-owned frontend routes used for absolute-URL and
	// full-page OIDC callback construction.
	Frontend FrontendConfig
	// Registration controls verification policy and public self-registration.
	Registration RegistrationConfig
	// Keys controls signing-key resolution (or verify-only mode).
	Keys KeysConfig
	// Identity declares external OAuth2/OIDC identity providers.
	Identity IdentityConfig
	// APIKeys configures opaque permission-group-owned machine credentials.
	APIKeys APIKeysConfig
	// TwoFactor configures optional MFA features.
	TwoFactor TwoFactorConfig
	// Passkeys configures WebAuthn/FIDO2 passkey ceremonies.
	Passkeys PasskeyConfig
	// RBAC declares the app permission catalog, default roles, and owner policy.
	RBAC RBACConfig

	// Environment is a host-provided runtime mode string used for dev/prod
	// behavior checks. "prod"/"production" mean production; anything else is
	// treated as non-prod.
	Environment string

	// Schema is the Postgres schema AuthKit's tables live in. Empty defaults to
	// "profiles" (the historical hard-coded name). Set it when multiple apps
	// embed AuthKit against the same database and must not share auth tables
	// (authkit issue 69). The name must match ^[a-z_][a-z0-9_]*$ (max 63 bytes);
	// NewFromConfig rejects anything else. Hosts that set a non-default schema
	// must also run the migrations rendered for that schema — see
	// migrations/postgres.FSForSchema.
	Schema string

	// SolanaNetwork is the SIWS chain selector ("mainnet"/"testnet"/"devnet").
	// Empty derives a default from Environment. Solana Name Service (SNS)
	// resolution turns on automatically when a resolver is supplied via the
	// WithSolanaSNSResolver option; its lookup timeout (3s) and cache TTL (24h)
	// are fixed constants, not configurable.
	SolanaNetwork string
}

Config is the host-provided configuration for an AuthKit Service. Fields are grouped by concern into typed sub-structs (#108). It carries DATA/POLICY only; runtime dependencies (Postgres, Redis, senders, loggers) are injected via the constructor's functional options, not here.

type CreatePermissionGroupRequest

type CreatePermissionGroupRequest struct {
	Persona            string
	ResourceSlug       string
	ParentPersona      string
	ParentResourceSlug string
	OwnerSubjectID     string
}

CreatePermissionGroupRequest creates a permission group. Parent is addressed by (ParentPersona, ParentResourceSlug); for a single-allowed-parent persona ParentPersona may be omitted. OwnerSubjectID, when set, is seeded with the owner role.

type CustomJWTMintOptions

type CustomJWTMintOptions struct {
	// Claims is the host's claim set, e.g. {"cap_kind": "...", "grants": [...],
	// "release_id": "..."}. Required and non-empty. It may carry `sub`/`aud`
	// (unless overridden by the Subject/Audiences options) but may NOT carry the
	// AuthKit-owned registered claims `iss`/`iat`/`exp`.
	Claims map[string]any
	// TTL is the token lifetime. Required (must be > 0); capped at
	// MaxCustomJWTLifetime.
	TTL time.Duration
	// Type is the JOSE `typ` header (e.g. "worker-capability+jwt"). When empty the
	// header is left unset — unlike the opinionated minters, MintCustomJWT does
	// not impose a default `typ`; the host owns the token shape.
	Type string
	// Subject, when set, becomes the `sub` claim and wins over any `sub` in Claims.
	Subject string
	// Audiences, when set, becomes the `aud` claim and wins over any `aud` in Claims.
	Audiences []string
	// Issuer, when set, becomes the `iss` claim; otherwise `iss` defaults to the
	// Service's configured Issuer. This is the ONLY way to override `iss`.
	Issuer string
}

CustomJWTMintOptions controls minting of a JWT with an arbitrary first-party claim set. This is AuthKit's documented escape hatch: the HOST owns the claim semantics, and the verifier side MUST understand them. Prefer the constrained, opinionated paths — MintServiceJWT (machine-to-machine service JWT) and MintDelegatedAccessToken (cross-service delegated access) — whenever they fit; reach for MintCustomJWT only for token shapes those can't express (e.g. tensorhub capability/worker tokens with `cap_kind`/`grants`/`release_id`).

Claim precedence (documented + enforced):

  • AuthKit ALWAYS sets the registered claims it owns: `iss`, `iat`, `exp` (and the `kid`/`alg` JOSE headers, via the signer). The host Claims map may NOT set `iss`/`iat`/`exp` — doing so returns ErrCustomClaimsReserved rather than silently dropping or clobbering them.
  • `iss` is overridable ONLY via the explicit Issuer option (defaults to the Service's configured Issuer). `sub`/`aud` are set from the explicit Subject/Audiences options when provided; otherwise the host Claims map may carry its own `sub`/`aud` (the host owns those for custom tokens). When an explicit Subject/Audiences IS provided, it wins over any `sub`/`aud` in the Claims map.

type CustomRoleResolver

type CustomRoleResolver func(groupID, role string) ([]string, bool)

CustomRoleResolver returns the grant tokens of a per-group custom role, or (nil, false) if no such custom role exists. Consulted only for personas whose AllowCustomRoles is set; pass nil when the deployment defines no custom roles.

type DelegatedAccessParams

type DelegatedAccessParams struct {
	// Issuer becomes the `iss` claim: the AuthKit issuer that signed the token.
	// Must match a remote_application registered with the validating resource server.
	// Required when minting via the free function; the *Service mint method
	// defaults it to the Service's configured Issuer when empty.
	Issuer string
	// Audiences becomes the `aud` claim: the target resource API(s), e.g.
	// "openrails", "tensorhub", or "gen-orchestrator".
	Audiences []string
	// DelegatedSubject becomes `delegated_sub`: the issuer-side subject id.
	// Required. No local account is implied in the receiving service.
	DelegatedSubject string
	// Permissions becomes the `permissions` claim: an array of resource-defined
	// permission strings (NOT OAuth's space-delimited `scope`). Receiving
	// services validate these against their own permission set.
	Permissions []string
	// Attributes becomes the `attributes` claim: the canonical app-specific
	// ESCAPE HATCH (#75). An object of issuer-asserted, NAMESPACED, OPAQUE
	// key/values that AuthKit transports + optionally shape-validates but NEVER
	// interprets — the semantics belong to the consuming app (tensorhub etc.).
	// Each value is set in ONE of two modes, per key:
	//   INLINE    — the value carries the full definition, e.g.
	//               {"tier":{"endpoints":[...],"caps":[...]}}. No lookup.
	//   REFERENCE — the value is a short string key, e.g. {"tier":"tier-1"},
	//               resolved by the consumer against a definition the
	//               remote_application registered ahead of time (see the
	//               attribute-def registry: Service.RegisterRemoteAppAttributeDef
	//               / ResolveRemoteAppAttributeDef). Keeps tokens small.
	// Reserved well-known keys: `tier` (opaque entitlement-tier string) and
	// `roles` (a uuid array; prefer the typed Roles field below). Everything
	// else is free-form per consuming app. Values are arbitrary JSON.
	Attributes map[string]any
	// Roles is a convenience for emitting the delegated subject's role UUIDs into
	// `attributes.roles` (a JSON array of UUID strings). Equivalent to setting
	// Attributes["roles"] yourself; when both are set this typed field wins.
	Roles []string
	// TTL is the token lifetime. Defaults to 15m when zero.
	TTL time.Duration
	// JTI, when set, becomes the `jti` claim (token identifier). Optional.
	JTI string
	// NotBefore, when set, becomes the `nbf` claim. Optional.
	NotBefore time.Time
}

DelegatedAccessParams describes a delegated access token to mint.

A delegated access token is AuthKit's standard primitive for resource-service federation: one AuthKit issuer signs a short-lived JWT for an external delegated subject, and a resource service accepts it after issuer/JWKS/ audience validation. The token represents a delegated subject (DelegatedSubject) acting under the resource account that the VALIDATED `iss` resolves to in the receiver's issuer registry. It NEVER carries a normal `sub` — no local account is implied in the receiving service.

type EmailSender

type EmailSender interface {
	SendVerification(ctx context.Context, email, username string, msg VerificationMessage) error
	SendPasswordResetLink(ctx context.Context, email, username, resetURL string) error
	SendLoginCode(ctx context.Context, email, username, code string) error
	SendWelcome(ctx context.Context, email, username string) error
}

EmailSender sends verification/login/reset emails.

type EntitlementFilterProvider

type EntitlementFilterProvider interface {
	ListSubjectsWithEntitlement(ctx context.Context, entitlement string) ([]string, error)
}

EntitlementFilterProvider is the REVERSE of EntitlementsProvider: given an entitlement key, it returns the subject ids that currently hold it. AuthKit owns the user DIRECTORY; the billing system (OpenRails) owns "who is entitled", so filtering the directory BY entitlement delegates here instead of joining across schemas. Subject ids ARE user ids (UUID-only payable identity). Detected by type assertion on the entitlements provider; when absent, AdminListUsers with an Entitlement filter fails with ErrEntitlementFilterUnavailable so the misconfiguration is loud rather than silently returning everyone.

type EntitlementsProvider

type EntitlementsProvider interface {
	ListEntitlements(ctx context.Context, userID string) ([]string, error)
}

EntitlementsProvider returns the names of a user's currently active application entitlements (e.g., billing tiers). Names are the ONLY shape AuthKit consumes — they are baked verbatim into the `entitlements` claim of access tokens and surfaced on admin user views. Providers should return active grants only; expired/revoked entitlements are the provider's concern, not AuthKit's.

type EphemeralMode

type EphemeralMode string
const (
	EphemeralMemory EphemeralMode = "memory"
	EphemeralRedis  EphemeralMode = "redis"
)

type EphemeralStore

type EphemeralStore interface {
	Get(ctx context.Context, key string) ([]byte, bool, error)
	Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
	Del(ctx context.Context, key string) error
}

EphemeralStore is a minimal key-value interface used for short-lived auth state. Implementations should honor TTL on Set and treat missing keys as (found=false, err=nil).

type FrontendConfig

type FrontendConfig struct {
	// BaseURL, if set, is used for building absolute URLs (e.g. password
	// reset/verify links). If empty and Token.Issuer is a well-formed URL,
	// NewFromConfig defaults it to the issuer.
	BaseURL string
	// CallbackPath is the host-owned frontend route that receives full-page OIDC
	// login results. Empty defaults to "/login/callback".
	CallbackPath string
	// VerifyPath is the host-owned frontend route that receives scanner-safe
	// verification link landings. Empty defaults to "/verify".
	VerifyPath string
	// PasswordResetPath is the host-owned frontend route that receives
	// scanner-safe password reset link landings. Empty defaults to "/reset".
	PasswordResetPath string
}

FrontendConfig describes host-owned frontend routes.

type GeneratedRoute

type GeneratedRoute struct {
	Persona string
	Method  string
	Path    string // e.g. /merchant/:resource_slug/members
	Perm    string
}

GeneratedRoute is one auto-generated management endpoint: addressed by the RESOURCE's own id (:resource_slug), gated by Perm (a concrete <persona>:<res>:<act>).

type GroupAssignment

type GroupAssignment struct {
	Persona string   // the declared persona of the group this assignment lives in
	GroupID string   // opaque group id; used ONLY to scope custom-role lookups
	Roles   []string // role names the subject holds in this group
}

GroupAssignment is a subject's role-assignment set within ONE permission-group, tagged with that group's persona. The engine produces a slice of these by walking a target group's parent chain (resolving the subject's roles at each level); the slice order is irrelevant — the union is additive and order-independent.

type GroupInvite

type GroupInvite struct {
	ID        string
	GroupID   string
	UserID    string
	InvitedBy string
	Role      string
	Status    string
	ExpiresAt *time.Time
	ActedAt   *time.Time
	CreatedAt time.Time
	UpdatedAt time.Time
}

GroupInvite is the non-secret view of a pending/acted invite. Role resolves via the persona catalog / custom roles (not a DB FK); on accept it is assigned to UserID.

type GroupMember

type GroupMember struct {
	SubjectID   string
	SubjectKind string
	Role        string
}

GroupMember is one role-assignment in a group (roster listing).

type GroupSchema

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

GroupSchema is the validated, immutable set of declared group personas — the containment schema + catalogs + management profiles. Construct via NewGroupSchema, which validates everything once.

func BuildSchema

func BuildSchema(appTypes ...PersonaDef) (*GroupSchema, error)

BuildSchema assembles the deployment's GroupSchema from authkit's intrinsic root persona plus the app's declared personas, and validates the whole. If the app passes its OWN root persona (to add moderation roles) it is used as-is; otherwise the bare IntrinsicRootPersona() is injected. This is the consumer entry point: an app declares only its non-root personas (+ optional extra root roles) and gets a validated schema, or a clear error.

func NewGroupSchema

func NewGroupSchema(types ...PersonaDef) (*GroupSchema, error)

NewGroupSchema validates an app's declared personas and returns the schema, or an error describing the first problem. It enforces: a single root persona (named RootPersona, parentless); every persona has an `owner` role == `<persona>:*`; every role grant is a valid pattern in the persona's own namespace; allowed-parent edges reference declared personas and form an acyclic tree rooted at root; and CustomRoleCreation routes imply AllowCustomRoles.

func (*GroupSchema) Can

func (s *GroupSchema) Can(assignments []GroupAssignment, custom CustomRoleResolver, perm string) bool

Can reports whether the subject (via its assignments across a target group's parent chain) holds a grant covering perm. ALLOW if any granted token covers perm under authkit's namespace-anchored glob semantics (a bare `*` never matches). Additive walk-up union; the caller constructs the exact perm to check (e.g. for a resource of persona RT acted on from an ancestor of persona LT, the perm is `LT:RT:<action>` — the two-persona rule, decision #5).

func (*GroupSchema) GeneratedRoutes

func (s *GroupSchema) GeneratedRoutes() []GeneratedRoute

GeneratedRoutes returns the full management surface implied by the schema's per-persona management profiles. The HTTP layer mounts exactly these; anything a profile disables is simply absent (→ 404). Reads gate on <area>:read; mutations on the matching <area>:manage built-in.

func (*GroupSchema) IsRoot

func (s *GroupSchema) IsRoot(name string) bool

IsRoot reports whether name is the root persona.

func (*GroupSchema) Persona

func (s *GroupSchema) Persona(name string) (PersonaDef, bool)

Persona returns a declared persona's effective definition.

func (*GroupSchema) Personas

func (s *GroupSchema) Personas() []string

Personas returns the declared persona names, sorted.

func (*GroupSchema) ResolveGrants

func (s *GroupSchema) ResolveGrants(assignments []GroupAssignment, custom CustomRoleResolver) []string

ResolveGrants computes the additive, de-duplicated UNION of grant tokens a subject holds across the given assignments. For each (persona, role): a catalog role contributes the persona's catalog grants; otherwise, if the persona allows custom roles, the per-group custom role's grants are used. Unknown personas and unknown roles contribute NOTHING (fail-closed). Every returned token is a grant pattern already validated at schema-construction time.

func (*GroupSchema) Role

func (s *GroupSchema) Role(persona, roleName string) (RoleDef, bool)

Role returns a single role from a persona's catalog.

func (*GroupSchema) Roles

func (s *GroupSchema) Roles(persona string) ([]RoleDef, bool)

Roles returns a persona's effective roles (app-declared + seeded owner/member).

func (*GroupSchema) ValidateParent

func (s *GroupSchema) ValidateParent(childPersona, parentPersona string) error

ValidateParent enforces the containment schema at INSTANCE-create time: a proposed (childPersona, parentPersona) edge. root is parentless; every non-root group needs a parent whose persona is in the child persona's AllowedParents — so e.g. `root -> repo` is structurally impossible, not merely discouraged.

type IdentityConfig

type IdentityConfig struct {
	// Providers – identity providers by name ("google"/"apple"/"github"/
	// "discord"). Only client id/secret are required; standard scopes derive
	// from defaults.
	Providers map[string]oidckit.RPConfig
	// ProviderDescriptors define OAuth2/OIDC providers using config-first
	// descriptors. They augment/override built-in Providers entries and are the
	// preferred path for adding custom providers.
	ProviderDescriptors map[string]authprovider.Provider
}

IdentityConfig declares external OAuth2/OIDC identity providers.

type ImportUserInput

type ImportUserInput struct {
	Email         string
	PhoneNumber   string
	Username      string
	EmailVerified bool
	PhoneVerified bool
	BannedAt      *time.Time
	BannedUntil   *time.Time
	BanReason     *string
	BannedBy      *string
	Metadata      map[string]any
	CreatedAt     *time.Time
	UpdatedAt     *time.Time

	// Optional pre-hashed credential to import alongside the user (bulk legacy
	// migration). When PasswordHash is non-empty and the user row is inserted,
	// ImportUsers stores it verbatim. The verify-time whitelist (argon2id/bcrypt,
	// else legacy-reset-required) still governs login; bulk import does not
	// re-validate the hash, matching single-row UpsertPasswordHash.
	PasswordHash string
	HashAlgo     string
	HashParams   []byte
}

type ImportUserResult

type ImportUserResult struct {
	Index  int
	UserID string // set when Status == inserted
	Status ImportUserStatus
	Reason string // set for skipped/rejected (machine-ish: "duplicate_in_batch", "already_exists", or a validation code)
}

ImportUserResult is the outcome for one input row, addressed by its original index in the input slice.

type ImportUserStatus

type ImportUserStatus string

ImportUserStatus is the per-row outcome of ImportUsers.

const (
	// ImportStatusInserted: the user row was created.
	ImportStatusInserted ImportUserStatus = "inserted"
	// ImportStatusSkipped: a matching user already existed (by username/email/
	// phone), or the row duplicated an earlier row in the same batch. Skipped
	// rows are left untouched — bulk import is insert-or-skip, never overwrite,
	// so a re-run is idempotent and never clobbers data a user changed after
	// import.
	ImportStatusSkipped ImportUserStatus = "skipped"
	// ImportStatusRejected: the row failed validation/normalization (bad email,
	// username, phone) and was not imported.
	ImportStatusRejected ImportUserStatus = "rejected"
)

type ImportUsersResult

type ImportUsersResult struct {
	Results  []ImportUserResult
	Inserted int
	Skipped  int
	Rejected int
}

ImportUsersResult aggregates the per-row outcomes plus rollup counts.

type KeysConfig

type KeysConfig struct {
	// Source can be nil — if nil, authkit auto-discovers keys: (1) env vars
	// (ACTIVE_KEY_ID, ACTIVE_PRIVATE_KEY_PEM, PUBLIC_KEYS); (2) filesystem
	// <Path>/keys.json (default /vault/auth); (3) auto-generated keys in
	// .runtime/authkit/ (dev fallback; prod hard-fail). Hosts NEVER handle the
	// private key — they delegate the signing OPERATION to authkit; there is no
	// API that returns a private key or PEM (a future Vault-Transit backend,
	// authkit future #72, drops in behind the same Signer seam).
	Source jwtkit.KeySource
	// Path overrides the filesystem DIRECTORY the local key resolver scans for
	// keys.json when Source is nil. Empty defaults to AUTHKIT_KEYS_PATH, then
	// /vault/auth.
	Path string
	// VerifyOnly constructs the Service with NO active signer (#87): token
	// MINTING returns ErrMissingSigner, while VERIFICATION and all RBAC reads
	// work fully and the JWKS endpoint serves an empty key set. When true, key
	// auto-discovery is SKIPPED. Ignored when Source is non-nil. Use it for a
	// pure resource-server / control-plane deployment that only verifies inbound
	// tokens.
	VerifyOnly bool
}

KeysConfig controls signing-key resolution.

type Keyset

type Keyset struct {
	Active     jwtkit.Signer
	PublicKeys map[string]crypto.PublicKey // kid -> pub
}

Keyset holds the active signer and the public keys exposed via JWKS.

type MFAStatus

type MFAStatus struct {
	Enabled        bool
	Satisfied      bool
	AllowedMethods []string
}

type ManagementProfile

type ManagementProfile struct {
	MemberAssignment      bool // api-routes.member-assignment
	CustomRoleCreation    bool // api-routes.custom-role-creation (requires AllowCustomRoles)
	APIKeyMinting         bool // api-routes.api-key-minting
	RemoteAppRegistration bool // api-routes.remote-app-registration
	Invitation            bool // api-routes.invitation
}

ManagementProfile chooses which group-management operations authkit exposes as AUTO-GENERATED routes for a persona's groups (the `api-routes.*` block). Each flag gates ROUTE GENERATION, not the capability: the host can always do the op via core even with the route off (false ⇒ no public route / 404, not "impossible").

type Option

type Option func(*Service)

Option configures a Service at construction time. Options are applied inside NewFromConfig / NewService, after the base service is built — the replacement for the removed chainable WithX builder methods (#108). Data/policy belongs in Config; every runtime DEPENDENCY is an Option.

func WithAuthLogger

func WithAuthLogger(l AuthEventLogger) Option

WithAuthLogger sets the session-event audit sink.

func WithDBTXWrapper

func WithDBTXWrapper(wrap func(db.DBTX) db.DBTX) Option

WithDBTXWrapper re-binds the querier through wrap (a decorator over the schema-rewriting db.DBTX). Test seam for counting/spy queriers; must be applied after WithPostgres (NewFromConfig applies pg first).

func WithEmailSender

func WithEmailSender(sender EmailSender) Option

WithEmailSender sets the email provider.

func WithEntitlements

func WithEntitlements(p EntitlementsProvider) Option

WithEntitlements sets the entitlements provider.

func WithEphemeralStore

func WithEphemeralStore(store EphemeralStore, mode EphemeralMode) Option

WithEphemeralStore sets the ephemeral store + mode (empty mode => memory).

func WithPostgres

func WithPostgres(pool *pgxpool.Pool) Option

WithPostgres attaches the pgx pool and binds the schema-qualified querier. NewFromConfig applies this automatically from its required pg argument.

func WithSMSSender

func WithSMSSender(sender SMSSender) Option

WithSMSSender sets the SMS provider.

func WithSolanaSNSResolver

func WithSolanaSNSResolver(r SolanaSNSResolver) Option

WithSolanaSNSResolver turns on Solana Name Service resolution using the host-provided resolver (SNS is off when no resolver is supplied).

type Options

type Options struct {
	Issuer               string
	IssuedAudiences      []string // JWT audiences - tokens issued will contain ALL of these audiences
	ExpectedAudiences    []string
	AccessTokenDuration  time.Duration
	RefreshTokenDuration time.Duration
	SessionMaxPerUser    int
	// Optional link building.
	BaseURL string
	// FrontendCallbackPath is the host-owned frontend route that receives full-page OIDC login results.
	FrontendCallbackPath      string
	FrontendVerifyPath        string
	FrontendPasswordResetPath string
	PasskeyRPID               string
	PasskeyRPDisplayName      string
	PasskeyOrigins            []string
	PasskeyUserVerification   string
	// Schema is the Postgres schema AuthKit's tables live in. Empty defaults to
	// "profiles". Must match ^[a-z_][a-z0-9_]*$ (max 63 bytes); NewService
	// panics on an invalid non-empty value because a malformed name would be
	// spliced into SQL text (see internal/db.ForSchema). Prefer NewFromConfig,
	// which returns the validation error instead.
	Schema string
	// RegistrationVerification controls whether registration verification is disabled,
	// non-blocking, or required.
	RegistrationVerification RegistrationVerificationPolicy

	// VerificationSendTimeout bounds each in-line email/SMS provider send
	// (verification codes, password-reset links, login codes) so a configured
	// but misconfigured/unreachable provider cannot hang the request that
	// triggered it (e.g. registration). Empty/<=0 defaults to 15 seconds.
	VerificationSendTimeout time.Duration

	// NativeUserRegistrationMode controls public native-user self-registration.
	NativeUserRegistrationMode RegistrationMode

	// Environment is host-provided runtime mode used for dev/prod behavior checks.
	Environment string
	// SolanaNetwork is host-provided chain selector for SIWS flows.
	SolanaNetwork string
	// SolanaSNSEnabled enables AuthKit-owned Solana Name Service resolution for SIWS-linked wallets.
	SolanaSNSEnabled bool
	// SolanaSNSResolver resolves a verified Solana wallet address to its primary .sol name.
	SolanaSNSResolver SolanaSNSResolver
	// SolanaSNSLookupTimeout bounds resolver calls. Empty defaults to 3 seconds.
	SolanaSNSLookupTimeout time.Duration
	// SolanaSNSCacheTTL controls when cached SNS metadata is considered stale. Empty defaults to 24 hours.
	SolanaSNSCacheTTL time.Duration

	// APIKeyPrefix is the issuing application's brand prefix for generated API
	// keys (validated lowercase-alnum, 1-16 chars; empty -> bare st_).
	APIKeyPrefix string
	// APIKeyMaxTTL caps a minted API key's expiry (0 = no cap).
	APIKeyMaxTTL time.Duration
	// TOTPSecretKey encrypts persisted authenticator-app shared secrets.
	TOTPSecretKey []byte
	// Permissions is the app's permission vocabulary (merged with authkit's
	// base permission-group permissions).
	Permissions []PermissionDef
}

Options configures issued tokens and identifiers.

func (Options) PublicNativeUserRegistrationEnabled

func (o Options) PublicNativeUserRegistrationEnabled() bool

PublicNativeUserRegistrationEnabled reports whether public native-user self-registration / auto-registration is allowed.

func (Options) RegistrationVerificationEnabled

func (o Options) RegistrationVerificationEnabled() bool

func (Options) RegistrationVerificationPolicy

func (o Options) RegistrationVerificationPolicy() RegistrationVerificationPolicy

func (Options) RegistrationVerificationRequired

func (o Options) RegistrationVerificationRequired() bool

type Passkey

type Passkey struct {
	ID                      string     `json:"id"`
	UserID                  string     `json:"user_id,omitempty"`
	Label                   *string    `json:"label,omitempty"`
	Transports              []string   `json:"transports,omitempty"`
	AuthenticatorAttachment string     `json:"authenticator_attachment,omitempty"`
	BackupEligible          bool       `json:"backup_eligible"`
	BackupState             bool       `json:"backup_state"`
	CreatedAt               time.Time  `json:"created_at"`
	LastUsedAt              *time.Time `json:"last_used_at,omitempty"`
}

type PasskeyConfig

type PasskeyConfig struct {
	RPID             string
	RPDisplayName    string
	Origins          []string
	UserVerification string
}

PasskeyConfig configures WebAuthn relying-party identity and UV policy.

type PasskeyLoginResult

type PasskeyLoginResult struct {
	UserID       string
	SessionID    string
	RefreshToken string
	AccessToken  string
	ExpiresAt    time.Time
}

type PendingChangeKind

type PendingChangeKind string

PendingChangeKind identifies one of the four verification-gated "deferred change" flows. They all share the same shape — "hold a change until an emailed/texted code is verified, then finalize it" — so they share one record type, one ephemeral storage namespace, and one set of generic operations, differing only in their per-kind finalizer.

const (
	KindRegisterEmail PendingChangeKind = "register_email"
	KindRegisterPhone PendingChangeKind = "register_phone"
	KindChangeEmail   PendingChangeKind = "change_email"
	KindChangePhone   PendingChangeKind = "change_phone"
)

type PendingRegistration

type PendingRegistration struct {
	Email             string
	Username          string
	PasswordHash      string
	PreferredLanguage string
}

PendingRegistration represents an unverified registration

type PermissionDef

type PermissionDef struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
}

PermissionDef is one entry in the permission set: an opaque permission string plus a human-readable description (surfaced to admin UIs).

type PermissionGroupStore

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

PermissionGroupStore is the database access layer for permission-groups. It holds a db.DBTX (a *pgxpool.Pool or a pgx.Tx), so callers choose the txn scope.

func NewPermissionGroupStore

func NewPermissionGroupStore(q db.DBTX) *PermissionGroupStore

NewPermissionGroupStore wraps a db.DBTX (pool or transaction).

func (*PermissionGroupStore) AssignRole

func (st *PermissionGroupStore) AssignRole(ctx context.Context, groupID, subjectID, subjectKind, role string) error

AssignRole grants subject a role in a group, replacing any previous role in that same group. The role NAME is validated against the persona catalog / custom roles by the caller before assignment.

func (*PermissionGroupStore) CanOnGroup

func (st *PermissionGroupStore) CanOnGroup(ctx context.Context, schema *GroupSchema, subjectID, subjectKind, groupID, perm string) (bool, error)

CanOnGroup is the end-to-end DB-backed authorization check: walk the target group's chain, preload any custom roles, and test perm coverage against the schema. The caller constructs perm per the two-persona rule (e.g. for an action on a persona-RT resource reached from an ancestor of persona LT, the perm is `LT:RT:<action>`).

func (*PermissionGroupStore) CreateGroup

func (st *PermissionGroupStore) CreateGroup(ctx context.Context, persona, parentID, parentPersona, resourceSlug string) (string, error)

CreateGroup inserts a permission-group and returns its internal id. parentID/ parentPersona are empty for the root group. The containment trigger + CHECK enforce shape at the DB; callers SHOULD also pre-validate via GroupSchema.ValidateParent for a clear error before hitting the DB.

func (*PermissionGroupStore) CustomRolesFor

func (st *PermissionGroupStore) CustomRolesFor(ctx context.Context, groupIDs []string) (CustomRoleResolver, error)

CustomRolesFor preloads the custom roles for a set of group ids and returns a CustomRoleResolver backed by the result — so the pure decision core resolves custom-role grants without per-call DB access.

func (*PermissionGroupStore) DeleteCustomRole

func (st *PermissionGroupStore) DeleteCustomRole(ctx context.Context, groupID, role string) error

DeleteCustomRole removes a per-group custom role (and its permissions).

func (*PermissionGroupStore) GroupByResourceSlug

func (st *PermissionGroupStore) GroupByResourceSlug(ctx context.Context, persona, resourceSlug string) (string, error)

GroupByResourceSlug resolves a group by its API addressing key (persona, resource_slug) — the route layer's (persona, resource_slug). Returns the internal id, which never leaves authkit.

func (*PermissionGroupStore) GroupMembers

func (st *PermissionGroupStore) GroupMembers(ctx context.Context, groupID string) ([]GroupMember, error)

GroupMembers lists the live role-assignments in a group.

func (*PermissionGroupStore) RootGroupID

func (st *PermissionGroupStore) RootGroupID(ctx context.Context) (string, error)

RootGroupID returns the singleton root group's internal id (ErrGroupNotFound if the deployment has not seeded one yet).

func (*PermissionGroupStore) SeedContainment

func (st *PermissionGroupStore) SeedContainment(ctx context.Context, schema *GroupSchema) error

SeedContainment upserts the containment schema (group_persona_parents) from a validated GroupSchema. Idempotent; call once at bootstrap so the DB trigger can enforce the declared tree shape. root has no rows (parentless).

func (*PermissionGroupStore) SubjectGroups

func (st *PermissionGroupStore) SubjectGroups(ctx context.Context, subjectID, subjectKind string) ([]SubjectGroupMembership, error)

SubjectGroups lists every group membership a subject holds (cross-persona), the data behind /me/groups.

func (*PermissionGroupStore) UnassignRole

func (st *PermissionGroupStore) UnassignRole(ctx context.Context, groupID, subjectID, subjectKind, role string) error

UnassignRole soft-deletes a role assignment.

func (*PermissionGroupStore) UnassignSubject

func (st *PermissionGroupStore) UnassignSubject(ctx context.Context, groupID, subjectID, subjectKind string) error

UnassignSubject soft-deletes every active role assignment a subject holds in a group.

func (*PermissionGroupStore) UpsertCustomRole

func (st *PermissionGroupStore) UpsertCustomRole(ctx context.Context, groupID, role string, permissions []string) error

UpsertCustomRole defines/updates a per-group custom role's permission set. Only meaningful for personas whose AllowCustomRoles is set; the caller enforces that + validates each grant pattern against the group's persona.

func (*PermissionGroupStore) WalkAssignments

func (st *PermissionGroupStore) WalkAssignments(ctx context.Context, groupID, subjectID, subjectKind string) ([]GroupAssignment, error)

WalkAssignments walks the target group's parent chain to the root and returns the subject's assignments at each ancestor where it holds at least one role — exactly the []GroupAssignment that GroupSchema.ResolveGrants/Can consume. This is the additive walk-up made concrete.

type PersonaDef

type PersonaDef struct {
	Name             string
	Roles            []RoleDef // app-declared; owner (=<persona>:*) + member are injected if absent
	AllowedParents   []string  // declared personas; empty ⇒ root (parentless). Non-root needs >=1.
	AllowCustomRoles bool      // may a group owner define ADDITIONAL custom roles?
	Routes           ManagementProfile
}

PersonaDef declares one permission-group persona, which is also the first permission segment. `Name == RootPersona` is the parentless singleton.

func IntrinsicRootPersona

func IntrinsicRootPersona(extraRootRoles ...RoleDef) PersonaDef

IntrinsicRootPersona returns the base `root` PersonaDef authkit ships: the parentless singleton persona whose owner/super-admin hold root:*. An app passes this to BuildSchema along with EXTRA root roles (moderation bundles) and its other personas; the extra root roles may hold any root: perm (intrinsic or app-declared). Custom roles are OFF on root (operators are not end users).

type PreferredLanguage

type PreferredLanguage struct {
	Language string
}

type RBACConfig

type RBACConfig struct {
	// Permissions is the embedding application's set of valid permission strings
	// (e.g. `endpoint:revise`, `repo:create`); authkit stores and validates them
	// as opaque catalog entries. The permission-group personas declared in Groups
	// (#111) are the current model for scoping permissions.
	Permissions []PermissionDef

	// Groups declares the app's permission-group personas (#111): the containment
	// schema + per-persona role catalogs + management profiles. authkit injects the
	// intrinsic `root` persona when absent, so an empty slice yields a valid
	// root-only deployment. Validated by NewFromConfig via BuildSchema.
	Groups []PersonaDef
}

RBACConfig declares the app permission catalog, default roles, and owner policy.

type RegistrationConfig

type RegistrationConfig struct {
	// Verification controls registration verification: "none"|"optional"|
	// "required". Empty defaults to "none".
	Verification RegistrationVerificationPolicy
	// NativeUserMode controls public native-user self-registration. Empty
	// defaults to "open". Non-open modes disable every public user-creation path
	// while leaving embedded admin/bootstrap core APIs available.
	NativeUserMode RegistrationMode
}

RegistrationConfig controls verification policy and public self-registration.

type RegistrationMode

type RegistrationMode string
const (
	RegistrationModeOpen               RegistrationMode = "open"
	RegistrationModeInviteOnly         RegistrationMode = "invite_only"
	RegistrationModeAdminOnly          RegistrationMode = "admin_only"
	RegistrationModeAdminBootstrapOnly RegistrationMode = "admin_bootstrap_only"
	RegistrationModeManifestOnly       RegistrationMode = "manifest_only"
	RegistrationModeClosed             RegistrationMode = "closed"
)

type RegistrationVerificationPolicy

type RegistrationVerificationPolicy string
const (
	RegistrationVerificationNone     RegistrationVerificationPolicy = "none"
	RegistrationVerificationOptional RegistrationVerificationPolicy = "optional"
	RegistrationVerificationRequired RegistrationVerificationPolicy = "required"
)

type RemoteAppAttributeDef

type RemoteAppAttributeDef = authbase.RemoteAppAttributeDef

RemoteAppAttributeDef is one REFERENCE-mode attribute definition (#75): a remote_application registers (key, version) -> definition, and a platform resolves a token's `attributes.<key>: "<ref>"` reference back to it. The Definition is an OPAQUE JSON doc — AuthKit stores and serves it but NEVER interprets its semantics (same agnosticism as the token attributes bag). RemoteAppAttributeDef is defined in authbase (core-free) and re-exported here.

type RemoteAppKey

type RemoteAppKey = authbase.RemoteAppKey

RemoteAppKey is defined in authbase (core-free) and re-exported here.

type RemoteApplication

type RemoteApplication = authbase.RemoteApplication

RemoteApplication is a federation principal: an external system that authenticates by signing JWTs verified against its JWKS/public keys. Defined in authbase (core-free) and re-exported here.

type RemoteApplicationAccessParams

type RemoteApplicationAccessParams struct {
	// Issuer becomes the `iss` claim: the remote_application's OIDC issuer,
	// registered with the validating resource server. Required when minting via
	// the free function; the *Service mint method defaults it to the Service's
	// configured Issuer when empty.
	Issuer string
	// Audiences becomes the `aud` claim: the target resource API(s).
	Audiences []string
	// TTL is the token lifetime. Defaults to 15m when zero.
	TTL time.Duration
	// JTI, when set, becomes the `jti` claim. Optional.
	JTI string
	// NotBefore, when set, becomes the `nbf` claim. Optional.
	NotBefore time.Time
	// Permissions, when non-nil, becomes the `permissions` claim: a DOWN-SCOPING
	// request for least-privilege (#76 amendment). The stored grant is the
	// ceiling; effective = this claim, but EVERY claimed perm must be within the
	// stored grant — an out-of-grant claimed perm REJECTS the token at verify (a
	// remote application access token can never widen). nil/absent => no claim
	// => full stored ceiling (backward-compatible with v0.28.0 tokens).
	Permissions []string
}

RemoteApplicationAccessParams describes a remote application access token to mint (#76): a remote_application signs a short-lived JWT that authenticates it AS ITSELF. The principal's authority is the STORED set AuthKit assigned it (permission-group role membership only), resolved at verify from the validated `iss`. The token therefore carries NO authority role claims of its own — and even if a caller adds them, the verifier ignores them.

type RemovedMFARoleAssignment

type RemovedMFARoleAssignment struct {
	GroupID      string
	Persona      string
	ResourceSlug string
	Role         string
	RemovedAt    time.Time
}

type ResolvedAPIKey

type ResolvedAPIKey = authbase.ResolvedAPIKey

ResolvedAPIKey is defined in authbase (core-free) and re-exported here.

type RoleDef

type RoleDef struct {
	Name        string
	Permissions []string
	RequiresMFA bool
}

RoleDef is a named permission bundle within a persona's catalog. Its permissions are grant patterns, all in the owning persona namespace.

type SMSHealthChecker

type SMSHealthChecker interface {
	CheckHealth(ctx context.Context) error
}

SMSHealthChecker is an optional capability for SMS senders that can verify, without sending a message, that they are configured to actually deliver (valid credentials, an attached sender, and a verified/registered number). CheckHealth returns nil when delivery is expected to succeed, or a descriptive error explaining why it will not (e.g. an unverified toll-free sender that would otherwise fail silently with Twilio error 30032).

type SMSSender

type SMSSender interface {
	SendVerification(ctx context.Context, phone string, msg VerificationMessage) error
	SendPasswordResetLink(ctx context.Context, phone, resetURL string) error
	SendLoginCode(ctx context.Context, phone, code string) error
}

SMSSender sends verification/login/reset SMS messages.

type Service

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

Service is the core auth service used by HTTP adapters.

func NewFromConfig

func NewFromConfig(cfg Config, pg *pgxpool.Pool, extraOpts ...Option) (*Service, error)

NewFromConfig creates a Service from high-level Config + Stores. If Keys is nil, auto-discovers keys from environment variables, filesystem, or generates development keys.

func NewService

func NewService(opts Options, keys Keyset, coreOpts ...Option) *Service

func (*Service) AcceptGroupInvite

func (s *Service) AcceptGroupInvite(ctx context.Context, inviteID, userID string) error

AcceptGroupInvite flips a pending invite (addressed by id) to accepted and, in the same transaction, assigns the invited role to userID. userID must be the invited user. ErrInviteNotFound if no live pending invite for (id, userID) exists. Idempotency: a second accept finds no pending row and returns ErrInviteNotPending.

func (*Service) AddRemoteApplicationMember

func (s *Service) AddRemoteApplicationMember(ctx context.Context, appID, role string) error

AddRemoteApplicationMember grants a remote_application a role in its own controlling permission-group. role defaults to the base member role.

func (*Service) AdminCountUsers

func (s *Service) AdminCountUsers(ctx context.Context, opts AdminUserListOptions) (int64, error)

AdminCountUsers returns the number of users matching opts (same filters as AdminListUsers, ignoring pagination/sort).

func (*Service) AdminDeleteUser

func (s *Service) AdminDeleteUser(ctx context.Context, id string) error

func (*Service) AdminGetUser

func (s *Service) AdminGetUser(ctx context.Context, id string) (*AdminUser, error)

func (*Service) AdminListUserSessions

func (s *Service) AdminListUserSessions(ctx context.Context, userID string) ([]Session, error)

Helper exposed for admin endpoints

func (*Service) AdminListUsers

func (s *Service) AdminListUsers(ctx context.Context, opts AdminUserListOptions) (*AdminListUsersResult, error)

AdminListUsers is the generic admin user-directory list (issue #91): generic role/status filter + search + sort + offset pagination, with optional provider-backed entitlement filtering. Each row is enriched with role slugs and (via the entitlements provider) entitlement names.

func (*Service) AdminRecoverUser

func (s *Service) AdminRecoverUser(ctx context.Context, userID string, input AdminRecoverUserInput) error

AdminRecoverUser locks down a compromised account and replaces its primary recovery identifier before sending a password-reset link/code to that new identifier.

func (*Service) AdminRevokeUserSessions

func (s *Service) AdminRevokeUserSessions(ctx context.Context, userID string) error

func (*Service) AdminSetPassword

func (s *Service) AdminSetPassword(ctx context.Context, userID, new string) error

AdminSetPassword force-sets a user's password (admin only, no current password required)

func (*Service) AssignGroupRole

func (s *Service) AssignGroupRole(ctx context.Context, persona, resourceSlug, subjectID, subjectKind, role string) error

AssignGroupRole grants a subject a role in the group addressed by (persona, resourceSlug). The role must be a catalog role (or any role for custom-enabled types).

func (*Service) AssignRoleBySlug

func (s *Service) AssignRoleBySlug(ctx context.Context, userID, slug string) error

Exported wrappers for admin endpoints

func (*Service) BanUser

func (s *Service) BanUser(ctx context.Context, userID string, reason *string, until *time.Time, bannedBy string) error

BanUser disables a user account and stores ban metadata.

func (*Service) BeginPasskeyLogin

func (s *Service) BeginPasskeyLogin(ctx context.Context, identifier string) (*protocol.CredentialAssertion, error)

func (*Service) BeginPasskeyRegistration

func (s *Service) BeginPasskeyRegistration(ctx context.Context, userID string) (*protocol.CredentialCreation, error)

func (*Service) BeginPasswordReset

func (s *Service) BeginPasswordReset(ctx context.Context, token string, sessionTTL time.Duration) (string, error)

BeginPasswordReset validates and consumes a password reset token, then issues a short-lived one-time reset session for browser handoff.

func (*Service) Can

func (s *Service) Can(ctx context.Context, subjectID, subjectKind, persona, resourceSlug, perm string) (bool, error)

Can is the Service-level authorization check: resolve the group addressed by (persona, resourceSlug), then test perm coverage via the additive walk-up. The caller constructs perm per the two-persona rule (LT:RT:action).

func (*Service) CancelEmailChange

func (s *Service) CancelEmailChange(ctx context.Context, userID string) error

CancelEmailChange aborts a pending email-change for the user, clearing the unified pending-change record. The new email is applied only on confirmation, so there is nothing to roll back. Idempotent: a no-op when none is pending.

func (*Service) CancelPhoneChange

func (s *Service) CancelPhoneChange(ctx context.Context, userID, phone string) error

CancelPhoneChange aborts a pending phone-change for the user, clearing the unified pending-change record. Because the new phone is held only in the pending record and never optimistically applied to the profile, there is nothing to roll back. Idempotent: a no-op when no pending change exists.

func (*Service) ChangePassword

func (s *Service) ChangePassword(ctx context.Context, userID, current, new string, keepSessionID *string) error

ChangePassword sets or changes a user's password. If the user already has a password, current must verify; otherwise current is ignored. Always Argon2id-hashes the new password and upserts it, then revokes all other sessions for the user; caller may keep one active session via keepSessionID.

func (*Service) CheckPendingRegistrationConflict

func (s *Service) CheckPendingRegistrationConflict(ctx context.Context, email, username string) (bool, bool, error)

CheckPendingRegistrationConflict checks if email or username exists in users or pending registration cache. Returns (emailTaken, usernameTaken, error)

func (*Service) CheckPhoneRegistrationConflict

func (s *Service) CheckPhoneRegistrationConflict(ctx context.Context, phone, username string) (bool, bool, error)

CheckPhoneRegistrationConflict checks if phone or username exists in users OR pending tables. Returns (phoneTaken, usernameTaken, error)

func (*Service) CheckSMSHealth

func (s *Service) CheckSMSHealth(ctx context.Context) error

CheckSMSHealth probes whether the configured SMS sender can actually deliver, without sending a message, when the sender implements SMSHealthChecker. The result is cached and gates phone-based flows via SMSAvailable. It returns the probe error (nil = healthy) so callers can log it. When no sender is configured or the sender cannot self-check, it records healthy=true (delivery readiness is then governed solely by sender presence, as before).

func (*Service) CheckUserPassword

func (s *Service) CheckUserPassword(ctx context.Context, userID, pass string) error

CheckUserPassword is the error-returning form of VerifyUserPassword: nil on success, ErrPasswordResetRequired when the stored hash is flagged HashAlgoLegacyResetRequired (no plaintext can verify; the user must reset), and a generic unauthorized error otherwise. Callers that need to route reset-required users (reauth, change-password) should use this form.

func (*Service) CleanupExpiredAuthState

func (s *Service) CleanupExpiredAuthState(ctx context.Context) error

CleanupExpiredAuthState removes expired transient AuthKit state that lives in postgres. Short-lived verification state — pending registrations, pending email/phone changes, email/phone verifications, and password resets — now lives entirely in the ephemeral store (Redis when multi-instance, in-memory otherwise) and expires automatically by TTL, so no database sweep is needed for it. The only persistent auth state requiring a sweep is revoked/expired refresh sessions.

func (*Service) Clear2FAChallenge

func (s *Service) Clear2FAChallenge(ctx context.Context, userID string) error

Clear2FAChallenge removes the stored challenge after successful 2FA verification.

func (*Service) ClearEmailVerifyCodeAttempts

func (s *Service) ClearEmailVerifyCodeAttempts(ctx context.Context, email string)

ClearEmailVerifyCodeAttempts resets the per-email failed-attempt counter after a successful confirmation.

func (*Service) ConfirmEmailChange

func (s *Service) ConfirmEmailChange(ctx context.Context, userID, email, code string) error

ConfirmEmailChange verifies the code and updates the user's email address. This is called when the user enters the verification code sent to their new email.

func (*Service) ConfirmEmailChangeByToken added in v0.57.0

func (s *Service) ConfirmEmailChangeByToken(ctx context.Context, token string) (string, error)

ConfirmEmailChangeByToken applies a pending email change using its high-entropy link token.

func (*Service) ConfirmEmailVerification

func (s *Service) ConfirmEmailVerification(ctx context.Context, email, code string) (userID string, err error)

ConfirmEmailVerification verifies a short typed code for a SPECIFIC email and marks email_verified = true. The code is only 6 digits, so it is brute-force resistant ONLY because it is scoped to the address it was issued to (a guessed code that happens to match another account's record is rejected here without being consumed) and the HTTP layer caps attempts per-identifier (AK security audit F1). For the unguessable 256-bit emailed link token use ConfirmEmailVerificationByToken instead.

func (*Service) ConfirmEmailVerificationByToken

func (s *Service) ConfirmEmailVerificationByToken(ctx context.Context, token string) (userID string, err error)

ConfirmEmailVerificationByToken verifies the 256-bit emailed link token and marks email_verified = true. The token's own entropy is the security boundary (it is unguessable), so this path is global-lookup and needs no email scoping.

func (*Service) ConfirmPasswordReset

func (s *Service) ConfirmPasswordReset(ctx context.Context, token, newPassword string) (string, error)

ConfirmPasswordReset verifies token and sets a new password.

func (*Service) ConfirmPasswordResetWithSession

func (s *Service) ConfirmPasswordResetWithSession(ctx context.Context, resetSession, newPassword string) (string, error)

ConfirmPasswordResetWithSession consumes a reset session and sets the new password.

func (*Service) ConfirmPendingPhoneRegistration

func (s *Service) ConfirmPendingPhoneRegistration(ctx context.Context, phone, code string) (userID string, err error)

ConfirmPendingPhoneRegistration verifies code and creates the actual user account. Implements "first to verify wins" - whoever verifies first gets the username/phone.

func (*Service) ConfirmPendingPhoneRegistrationByToken

func (s *Service) ConfirmPendingPhoneRegistrationByToken(ctx context.Context, token string) (string, error)

ConfirmPendingPhoneRegistrationByToken verifies a pending phone registration using either a manual code or a high-entropy link token.

func (*Service) ConfirmPendingRegistration

func (s *Service) ConfirmPendingRegistration(ctx context.Context, email, code string) (userID string, err error)

ConfirmPendingRegistration finalizes a pending email registration from a short typed code scoped to a SPECIFIC email. Like ConfirmEmailVerification, the 6-digit code is brute-force resistant only because it is bound to the target address (a guessed code matching another pending signup is rejected without being consumed) and the HTTP layer caps attempts per-identifier (AK security audit F1). For the 256-bit emailed link token use ConfirmPendingRegistrationByToken instead.

func (*Service) ConfirmPendingRegistrationByToken

func (s *Service) ConfirmPendingRegistrationByToken(ctx context.Context, token string) (userID string, err error)

ConfirmPendingRegistrationByToken finalizes a pending email registration from the 256-bit emailed link token. The token's entropy is the security boundary, so this path is global-lookup and needs no email scoping.

func (*Service) ConfirmPhoneChange

func (s *Service) ConfirmPhoneChange(ctx context.Context, userID, phone, code string) error

ConfirmPhoneChange verifies the code and updates the user's phone number. This is called when the user enters the verification code sent to their new phone.

func (*Service) ConfirmPhoneChangeByToken added in v0.57.0

func (s *Service) ConfirmPhoneChangeByToken(ctx context.Context, token string) (string, error)

ConfirmPhoneChangeByToken applies a pending phone change using its high-entropy link token.

func (*Service) ConfirmPhoneVerification

func (s *Service) ConfirmPhoneVerification(ctx context.Context, phone, code string) error

ConfirmPhoneVerification verifies a token and marks phone_verified = true.

func (*Service) ConfirmPhoneVerificationByToken

func (s *Service) ConfirmPhoneVerificationByToken(ctx context.Context, token string) error

ConfirmPhoneVerificationByToken verifies phone ownership using a one-click token.

func (*Service) ConfirmPhoneVerificationByTokenUserID

func (s *Service) ConfirmPhoneVerificationByTokenUserID(ctx context.Context, token string) (string, error)

ConfirmPhoneVerificationByTokenUserID verifies phone ownership using a one-click token and returns the user ID.

func (*Service) ConfirmPhoneVerificationUserID

func (s *Service) ConfirmPhoneVerificationUserID(ctx context.Context, phone, code string) (string, error)

ConfirmPhoneVerificationUserID verifies a token, marks phone_verified = true, and returns the user ID.

func (s *Service) CountProviderLinks(ctx context.Context, userID string) int

Public wrappers

func (*Service) Create2FAChallenge

func (s *Service) Create2FAChallenge(ctx context.Context, userID string) (string, error)

Create2FAChallenge creates a short-lived challenge to prove password verification before 2FA.

func (*Service) CreateGroupInvite

func (s *Service) CreateGroupInvite(ctx context.Context, persona, resourceSlug, userID, role, invitedBy string) (string, error)

CreateGroupInvite records a pending invite for userID to hold role in the group addressed by (persona, resourceSlug), attributed to invitedBy. The role is validated against the persona catalog (catalog role, or any role for custom-enabled personas) exactly as AssignGroupRole does. Returns the new invite's id. A pending invite for the same (group, user) is unique at the DB; a duplicate is rejected.

func (*Service) CreatePendingPhoneRegistration

func (s *Service) CreatePendingPhoneRegistration(ctx context.Context, phone, username, passwordHash string) (string, error)

CreatePendingPhoneRegistration creates a pending phone registration and sends SMS verification code. Returns 6-digit code for verification. Code expires in 10 minutes (shorter than email).

func (*Service) CreatePendingPhoneRegistrationWithLanguage

func (s *Service) CreatePendingPhoneRegistrationWithLanguage(ctx context.Context, phone, username, passwordHash, preferredLanguage string) (string, error)

func (*Service) CreatePendingRegistration

func (s *Service) CreatePendingRegistration(ctx context.Context, email, username, passwordHash string, ttl time.Duration) (string, error)

CreatePendingRegistration creates a pending registration and sends verification email. Returns token for verification. Allows duplicate pending registrations (last one wins).

func (*Service) CreatePendingRegistrationWithLanguage

func (s *Service) CreatePendingRegistrationWithLanguage(ctx context.Context, email, username, passwordHash string, ttl time.Duration, preferredLanguage string) (string, error)

func (*Service) CreatePermissionGroup

func (s *Service) CreatePermissionGroup(ctx context.Context, req CreatePermissionGroupRequest) (string, error)

CreatePermissionGroup validates containment against the schema, resolves the parent group, creates the group, and (atomically) seeds the owner assignment. Returns the INTERNAL group id (for the caller's own bookkeeping; never exposed over the wire).

func (*Service) CreateUser

func (s *Service) CreateUser(ctx context.Context, email, username string) (*User, error)

func (*Service) DeclineGroupInvite

func (s *Service) DeclineGroupInvite(ctx context.Context, inviteID, userID string) error

DeclineGroupInvite flips a pending invite (addressed by id) to declined. userID must be the invited user. No role is assigned. ErrInviteNotFound if no live pending invite for (id, userID) exists; ErrInviteNotPending if already acted.

func (*Service) DefineGroupCustomRole

func (s *Service) DefineGroupCustomRole(ctx context.Context, persona, resourceSlug, role string, permissions []string) error

DefineGroupCustomRole creates/updates a custom role in the group addressed by (persona, resourceSlug). Requires the persona to allow custom roles; every permission must be a valid grant pattern in that persona's namespace (namespace purity) and must not collide with a catalog role name.

func (*Service) DeleteGroupCustomRole

func (s *Service) DeleteGroupCustomRole(ctx context.Context, persona, resourceSlug, role string) error

DeleteGroupCustomRole removes a custom role from a group.

func (*Service) DeletePasskey

func (s *Service) DeletePasskey(ctx context.Context, userID, id string) error

func (*Service) DeletePendingPhoneRegistrationByPhone

func (s *Service) DeletePendingPhoneRegistrationByPhone(ctx context.Context, phone string) error

DeletePendingPhoneRegistrationByPhone removes a pending phone registration (and all its verification tokens) for the given phone, if one exists. No-op when none exists.

func (*Service) DeletePendingRegistrationByEmail

func (s *Service) DeletePendingRegistrationByEmail(ctx context.Context, email string) error

DeletePendingRegistrationByEmail removes a pending email registration (and all its verification tokens) for the given email, if one exists. Used to abandon a pending registration the user explicitly cancelled. No-op when none exists.

func (*Service) DeleteRemoteAppAttributeDef

func (s *Service) DeleteRemoteAppAttributeDef(ctx context.Context, appID, key string) error

DeleteRemoteAppAttributeDef removes ALL versions of a key for the remote_application. Returns ErrAttributeDefNotFound when nothing matched.

func (*Service) DeleteRemoteApplication

func (s *Service) DeleteRemoteApplication(ctx context.Context, issuer string) error

DeleteRemoteApplication removes a remote_application by OIDC issuer URL.

func (*Service) DeriveUsername

func (s *Service) DeriveUsername(email string) string

func (*Service) DeriveUsernameForOAuth

func (s *Service) DeriveUsernameForOAuth(ctx context.Context, provider, preferred, email, displayName string) string

DeriveUsernameForOAuth prefers provider-preferred usernames; falls back to email local part or display name.

func (*Service) Disable2FA

func (s *Service) Disable2FA(ctx context.Context, userID string) error

Disable2FA disables two-factor authentication for a user.

func (*Service) Disable2FAFactor

func (s *Service) Disable2FAFactor(ctx context.Context, userID, factorID string) error

func (*Service) Disable2FAFactorWithRemovedRoles

func (s *Service) Disable2FAFactorWithRemovedRoles(ctx context.Context, userID, factorID string) ([]RemovedMFARoleAssignment, error)

func (*Service) Disable2FAWithRemovedRoles

func (s *Service) Disable2FAWithRemovedRoles(ctx context.Context, userID string) ([]RemovedMFARoleAssignment, error)

Disable2FAWithRemovedRoles disables account MFA and removes active user role assignments whose catalog role requires MFA.

func (*Service) Enable2FA

func (s *Service) Enable2FA(ctx context.Context, userID, method string, phoneNumber *string) ([]string, error)

Enable2FA enables two-factor authentication for a user and generates backup codes. Returns the plaintext backup codes (caller must show these to user ONCE).

func (*Service) Enable2FADefault

func (s *Service) Enable2FADefault(ctx context.Context, userID, method string, phoneNumber *string) ([]string, error)

func (*Service) EnableTOTP2FA

func (s *Service) EnableTOTP2FA(ctx context.Context, userID, code string) ([]string, error)

EnableTOTP2FA verifies the pending secret before enabling authenticator-app 2FA.

func (*Service) EnableTOTP2FADefault

func (s *Service) EnableTOTP2FADefault(ctx context.Context, userID, code string, makeDefault bool) ([]string, error)

func (*Service) EnsureRootGroup

func (s *Service) EnsureRootGroup(ctx context.Context) (string, error)

EnsureRootGroup creates the singleton root group if absent (idempotent) and returns its internal id.

func (*Service) EntitlementsProvider

func (s *Service) EntitlementsProvider() EntitlementsProvider

func (*Service) EphemeralMode

func (s *Service) EphemeralMode() EphemeralMode

func (*Service) ExchangeRefreshToken

func (s *Service) ExchangeRefreshToken(ctx context.Context, refreshToken string, ua string, ip net.IP) (idToken string, expiresAt time.Time, newRefresh string, err error)

ExchangeRefreshToken rotates a refresh token and returns a new ID token + refresh token.

func (*Service) FinishPasskeyLogin

func (s *Service) FinishPasskeyLogin(ctx context.Context, response []byte, userAgent string, ip net.IP) (PasskeyLoginResult, error)

func (*Service) FinishPasskeyRegistration

func (s *Service) FinishPasskeyRegistration(ctx context.Context, userID string, response []byte) (Passkey, error)

func (*Service) GenerateAvailableUsername

func (s *Service) GenerateAvailableUsername(ctx context.Context, base string) string

GenerateAvailableUsername tries base, then minimal numeric suffixes, then a short fallback.

func (*Service) GenerateSIWSChallenge

func (s *Service) GenerateSIWSChallenge(ctx context.Context, cache siws.ChallengeCache, domain, address, username string) (siws.SignInInput, error)

GenerateSIWSChallenge creates a new SIWS challenge for the given address. The challenge is stored in the cache and must be verified within 15 minutes.

func (*Service) Get2FASettings

func (s *Service) Get2FASettings(ctx context.Context, userID string) (*TwoFactorSettings, error)

Get2FASettings retrieves a user's 2FA settings

func (*Service) GetDiscordUsername

func (s *Service) GetDiscordUsername(ctx context.Context, userID string) (string, error)

Convenience: Discord username

func (*Service) GetEmailByUserID

func (s *Service) GetEmailByUserID(ctx context.Context, id string) (string, error)

func (*Service) GetPendingEmailChange

func (s *Service) GetPendingEmailChange(ctx context.Context, userID string) (string, error)

GetPendingEmailChange retrieves the pending email change for a user, if any. A unified change_email record exists only for an actual change (verifying the current address uses a separate store), so its presence already means "change".

func (*Service) GetPendingPhoneRegistrationByPhone

func (s *Service) GetPendingPhoneRegistrationByPhone(ctx context.Context, phone string) (*PendingRegistration, error)

GetPendingPhoneRegistrationByPhone looks up a pending phone registration by phone number. (PendingRegistration.Email carries the phone for phone registrations, preserving prior behavior.)

func (*Service) GetPendingRegistrationByEmail

func (s *Service) GetPendingRegistrationByEmail(ctx context.Context, email string) (*PendingRegistration, error)

GetPendingRegistrationByEmail looks up a pending registration by email.

func (*Service) GetPreferredLanguage

func (s *Service) GetPreferredLanguage(ctx context.Context, userID string) (PreferredLanguage, error)
func (s *Service) GetProviderLink(ctx context.Context, providerSlug, subject string) (string, *string, error)

Additional public helpers used by OIDC flow

func (*Service) GetProviderLinkByIssuer

func (s *Service) GetProviderLinkByIssuer(ctx context.Context, issuer, subject string) (string, *string, error)

Issuer-based provider link helpers (preferred)

func (*Service) GetProviderUsername

func (s *Service) GetProviderUsername(ctx context.Context, userID, provider string) (string, error)

func (*Service) GetRemoteApplication

func (s *Service) GetRemoteApplication(ctx context.Context, issuer string) (*RemoteApplication, error)

GetRemoteApplication returns a remote_application by OIDC issuer URL.

func (*Service) GetRemoteApplicationBySlug

func (s *Service) GetRemoteApplicationBySlug(ctx context.Context, slug string) (*RemoteApplication, error)

GetRemoteApplicationBySlug returns a remote_application by slug.

func (*Service) GetSolanaAddress

func (s *Service) GetSolanaAddress(ctx context.Context, userID string) (string, error)

GetSolanaAddress retrieves the Solana wallet address linked to a user, if any.

func (*Service) GetSolanaLinkedAccount

func (s *Service) GetSolanaLinkedAccount(ctx context.Context, userID string) (*SolanaLinkedAccount, error)

GetSolanaLinkedAccount retrieves the SIWS-linked wallet and its AuthKit-owned metadata.

func (*Service) GetUserByEmail

func (s *Service) GetUserByEmail(ctx context.Context, email string) (*User, error)

func (*Service) GetUserByPhone

func (s *Service) GetUserByPhone(ctx context.Context, phone string) (*User, error)

GetUserByPhone looks up a user by phone number.

func (*Service) GetUserBySolanaAddress

func (s *Service) GetUserBySolanaAddress(ctx context.Context, address string) (*User, error)

GetUserBySolanaAddress looks up a user by their Solana wallet address.

func (*Service) GetUserByUsername

func (s *Service) GetUserByUsername(ctx context.Context, username string) (*User, error)

func (*Service) GetUserMetadata

func (s *Service) GetUserMetadata(ctx context.Context, userID string) (map[string]any, error)

GetUserMetadata returns a user's arbitrary metadata (internal/admin flags).

func (*Service) HardDeleteUser

func (s *Service) HardDeleteUser(ctx context.Context, userID string) error

HardDeleteUser permanently deletes the user row and dependent AuthKit rows via ON DELETE CASCADE.

func (*Service) HasEmailSender

func (s *Service) HasEmailSender() bool

HasEmailSender returns true if an email sender is configured.

func (*Service) HasPassword

func (s *Service) HasPassword(ctx context.Context, userID string) bool

func (*Service) HasSMSSender

func (s *Service) HasSMSSender() bool

HasSMSSender returns true if an SMS sender is configured.

func (*Service) HostDeleteUser

func (s *Service) HostDeleteUser(ctx context.Context, id string, soft bool) error

HostDeleteUser performs deletion on behalf of the host application. If soft is true, it performs a soft delete (see SoftDeleteUser). If false, it hard-deletes the user and all dependent rows via ON DELETE CASCADE.

func (*Service) ImportUser

func (s *Service) ImportUser(ctx context.Context, input ImportUserInput) (*User, error)

func (*Service) ImportUsers

func (s *Service) ImportUsers(ctx context.Context, inputs []ImportUserInput) (ImportUsersResult, error)

ImportUsers bulk-imports users for fast legacy migration (target: 500k+). It is the sole import API: validate/normalize happens in Go (identical to the legacy single-row path) so accuracy is preserved, then clean rows load via chunked multi-row INSERTs — no per-row round-trips.

Semantics are INSERT-OR-SKIP (not upsert): a row whose username/email/phone already exists, or which duplicates an earlier row in the same batch, is skipped and reported, never overwritten. This makes a re-run idempotent (resume a partial import) without clobbering changes a user made after they were imported. Invalid rows are rejected individually and never abort the batch.

Each input may carry an optional pre-hashed PasswordHash; for inserted rows it is stored verbatim (the verify-time hash whitelist still governs login).

func (*Service) IsUserAllowed

func (s *Service) IsUserAllowed(ctx context.Context, userID string) (bool, error)

func (*Service) IsUserReserved

func (s *Service) IsUserReserved(ctx context.Context, userID string) (bool, error)

IsUserReserved reports whether a user is a reserved, non-loginable placeholder (the `reserved` metadata flag). The login gate (ensureUserAccess) consults it so reserved placeholders cannot authenticate. The owner-namespace reservation FLOW that set this flag was removed in the permission-group hard cut (#111); the read gate stays as defense-in-depth for any externally-set flag.

func (*Service) Issue2FAEnrollmentToken

func (s *Service) Issue2FAEnrollmentToken(ctx context.Context, userID string) (token string, expiresAt time.Time, err error)

func (*Service) IssueAccessToken

func (s *Service) IssueAccessToken(ctx context.Context, userID, email string, extra map[string]any) (token string, expiresAt time.Time, err error)

IssueAccessToken builds and signs an access token (JWT) for the given user. Includes core registered claims plus: - entitlements (authoritative short-lived snapshot) Extra claims in `extra` are merged into the token body (e.g., sid).

func (*Service) IssueRefreshSession

func (s *Service) IssueRefreshSession(ctx context.Context, userID, userAgent string, ip net.IP) (sessionID, refreshToken string, expiresAt *time.Time, err error)

IssueRefreshSession creates a session row and returns a new refresh token string.

func (*Service) IssueRefreshSessionWithAuthMethods

func (s *Service) IssueRefreshSessionWithAuthMethods(ctx context.Context, userID, userAgent string, ip net.IP, authMethods []string) (sessionID, refreshToken string, expiresAt *time.Time, err error)

IssueRefreshSessionWithAuthMethods creates a refresh session and records the authentication methods that established it. Callers minting a session after MFA should pass e.g. []string{"pwd", "otp", "mfa"}.

func (*Service) JWKS

func (s *Service) JWKS() jwtkit.JWKS

JWKS returns a JWKS built from configured public keys.

func (*Service) Keyfunc

func (s *Service) Keyfunc() func(token *jwt.Token) (any, error)

Keyfunc looks up a public key by KID, falling back to the active key if missing.

func (*Service) LinkProvider

func (s *Service) LinkProvider(ctx context.Context, userID, provider, subject string, email *string) error

func (*Service) LinkProviderByIssuer

func (s *Service) LinkProviderByIssuer(ctx context.Context, userID, issuer, providerSlug, subject string, email *string) error

func (*Service) LinkSolanaWallet

func (s *Service) LinkSolanaWallet(ctx context.Context, cache siws.ChallengeCache, userID string, output siws.SignInOutput) error

LinkSolanaWallet links a Solana wallet to an existing user account.

func (*Service) List2FAFactors

func (s *Service) List2FAFactors(ctx context.Context, userID string) ([]TwoFactorFactor, error)

func (*Service) ListAPIKeys

func (s *Service) ListAPIKeys(ctx context.Context, persona, resourceSlug string) ([]APIKey, error)

ListAPIKeys returns metadata for every API key of the permission-group addressed by (persona, resourceSlug), including revoked/expired ones. The secret is never returned.

func (*Service) ListEntitlements

func (s *Service) ListEntitlements(ctx context.Context, userID string) []string

ListEntitlements returns current entitlement names for a user (fresh from the provider). A provider failure is logged and returned as none — callers (admin user views) degrade rather than fail.

func (*Service) ListGroupInvites

func (s *Service) ListGroupInvites(ctx context.Context, persona, resourceSlug string) ([]GroupInvite, error)

ListGroupInvites returns every (non-deleted) invite of the group addressed by (persona, resourceSlug), including acted ones, newest first.

func (*Service) ListGroupMembers

func (s *Service) ListGroupMembers(ctx context.Context, persona, resourceSlug string) ([]GroupMember, error)

ListGroupMembers returns the role-assignments in the group addressed by (persona, resourceSlug).

func (*Service) ListPasskeys

func (s *Service) ListPasskeys(ctx context.Context, userID string) ([]Passkey, error)

func (*Service) ListRemoteAppAttributeDefs

func (s *Service) ListRemoteAppAttributeDefs(ctx context.Context, appID string) ([]RemoteAppAttributeDef, error)

ListRemoteAppAttributeDefs returns all definitions a remote_application has registered (every key + version), newest version first within each key.

func (*Service) ListRemoteApplications

func (s *Service) ListRemoteApplications(ctx context.Context, activeOnly bool) ([]RemoteApplication, error)

ListRemoteApplications returns registered remote_applications. When activeOnly is true, only enabled rows are returned.

func (*Service) ListRemoteApplicationsForGroup

func (s *Service) ListRemoteApplicationsForGroup(ctx context.Context, persona, resourceSlug string) ([]RemoteApplication, error)

ListRemoteApplicationsForGroup returns the remote_applications whose controlling permission_group_id is the group addressed by (persona, resourceSlug) (#111). It resolves the group via the store, then filters remote_applications by permission_group_id so a per-persona management caller sees only the issuers it controls (ListRemoteApplications lists ALL groups').

func (*Service) ListRoleSlugsByUser

func (s *Service) ListRoleSlugsByUser(ctx context.Context, userID string) []string

Public helpers for HTTP adapters

func (*Service) ListSubjectGroups

func (s *Service) ListSubjectGroups(ctx context.Context, subjectID, subjectKind string) ([]SubjectGroupMembership, error)

ListSubjectGroups returns every group membership a subject holds (the cross-persona discovery behind /me/groups).

func (*Service) ListUserSessions

func (s *Service) ListUserSessions(ctx context.Context, userID string) ([]Session, error)

ListUserSessions lists active sessions for a user and issuer.

func (*Service) ListUsersDeletedBefore

func (s *Service) ListUsersDeletedBefore(ctx context.Context, cutoff time.Time, limit int) ([]string, error)

ListUsersDeletedBefore returns user IDs for users soft-deleted before the cutoff. It is intended for retention/purge workflows in the host application.

func (*Service) LogPasswordChanged

func (s *Service) LogPasswordChanged(ctx context.Context, userID string, sessionID string, ip *string, ua *string)

LogPasswordChanged records a password change event for a user (best-effort).

func (*Service) LogPasswordRecovery

func (s *Service) LogPasswordRecovery(ctx context.Context, userID string, method, sessionID string, ip *string, ua *string)

func (*Service) LogSessionCreated

func (s *Service) LogSessionCreated(ctx context.Context, userID string, method string, sessionID string, ip *string, ua *string)

LogSessionCreated records a session creation event via the configured AuthEventLogger (best-effort).

func (*Service) LogSessionFailed

func (s *Service) LogSessionFailed(ctx context.Context, userID string, sessionID string, reason *string, ip *string, ua *string)

func (*Service) MFAStatus

func (s *Service) MFAStatus(ctx context.Context, userID string) (MFAStatus, error)

func (*Service) MarkSessionAuthenticated

func (s *Service) MarkSessionAuthenticated(ctx context.Context, userID, sessionID string) error

func (*Service) MarkSessionAuthenticatedWithMethods

func (s *Service) MarkSessionAuthenticatedWithMethods(ctx context.Context, userID, sessionID string, authMethods []string) error

MarkSessionAuthenticatedWithMethods refreshes the session's sensitive-action auth window and records how the user re-proved identity.

func (*Service) MintAPIKey

func (s *Service) MintAPIKey(ctx context.Context, persona, resourceSlug, name, role, createdBy string, expiresAt *time.Time) (APIKey, string, error)

MintAPIKey inserts a new API key for the permission-group addressed by (persona, resourceSlug), bound to role, and returns its metadata plus the full plaintext token (shown ONCE). The role must be valid for the group's persona; no-escalation is enforced by the HTTP handler / host hook. expiresAt is optional (nil = no expiry) and is capped to APIKeyMaxTTL when set.

func (*Service) MintAPIKeyWithOptions

func (s *Service) MintAPIKeyWithOptions(ctx context.Context, persona, resourceSlug string, opts APIKeyMintOptions) (APIKey, string, error)

MintAPIKeyWithOptions inserts a new API key using the resource-aware mint contract. The key references exactly ONE role (opts.Role) valid for the owning group's TYPE; its effective permissions are resolved from the role at use time. No-escalation is the caller's responsibility. Resources are a separate binding.

func (*Service) MintCustomJWT

func (s *Service) MintCustomJWT(ctx context.Context, opts CustomJWTMintOptions) (string, error)

MintCustomJWT signs a JWT carrying an arbitrary first-party claim set using the Service's internal signer — the SAME signing path as MintServiceJWT / MintDelegatedAccessToken. The host passes a claim map (+ a few controlled headers) and NEVER touches the private key, the PEM, or a raw signer; the #70 hard boundary holds.

AuthKit sets the `kid`/`alg` JOSE headers (via the signer) and the registered `iss`/`iat`/`exp` claims; everything else comes from the host. See CustomJWTMintOptions for the claim-precedence rules. The host Claims map may not set `iss`/`iat`/`exp` (ErrCustomClaimsReserved).

func (*Service) MintDelegatedAccessToken

func (s *Service) MintDelegatedAccessToken(ctx context.Context, p DelegatedAccessParams) (string, error)

MintDelegatedAccessToken signs a canonical delegated access token using the Service's internal signer. The host passes claims/params only and NEVER touches the private key. When p.Issuer is empty it defaults to the Service's configured Issuer. See the package-level MintDelegatedAccessToken for the claim contract.

func (*Service) MintRemoteApplicationAccessToken

func (s *Service) MintRemoteApplicationAccessToken(ctx context.Context, p RemoteApplicationAccessParams) (string, error)

MintRemoteApplicationAccessToken signs a remote application access token using the Service's internal signer. When p.Issuer is empty it defaults to the Service's configured Issuer.

func (*Service) MintServiceJWT

func (s *Service) MintServiceJWT(ctx context.Context, opts ServiceJWTMintOptions) (string, ServiceJWTClaims, error)

MintServiceJWT creates a short-lived signed service JWT from AuthKit's active signing key. It defaults to a 15-minute lifetime and stamps `token_use=service`; it does not grant host permissions by itself.

func (*Service) Options

func (s *Service) Options() Options

Options exposes immutable configuration for callers that need to validate claims.

func (*Service) PasswordLogin

func (s *Service) PasswordLogin(ctx context.Context, email, pass string, extra map[string]any) (string, time.Time, error)

PasswordLogin verifies credentials and issues an ID token.

func (*Service) PasswordLoginByUserID

func (s *Service) PasswordLoginByUserID(ctx context.Context, userID, pass string, extra map[string]any) (string, time.Time, error)

PasswordLoginByUserID verifies credentials for a specific user ID and issues an ID token. This supports login flows where the identifier is a phone number or username and email may be NULL.

func (*Service) PatchUserMetadata

func (s *Service) PatchUserMetadata(ctx context.Context, userID string, patch map[string]any) error

PatchUserMetadata merges patch into a user's metadata.

func (*Service) PermissionGroupSchema

func (s *Service) PermissionGroupSchema() *GroupSchema

PermissionGroupSchema returns the validated schema this Service was built with (the intrinsic root-only schema if constructed without Config groups).

func (*Service) Postgres

func (s *Service) Postgres() *pgxpool.Pool

Postgres returns the attached pgx pool (may be nil).

func (*Service) PublicKeysByKID

func (s *Service) PublicKeysByKID() map[string]crypto.PublicKey

PublicKeysByKID returns the public keys indexed by key ID.

func (*Service) ReconcileBootstrapManifest

func (s *Service) ReconcileBootstrapManifest(ctx context.Context, manifest BootstrapManifest, opts BootstrapReconcileOptions) (BootstrapManifestResult, error)

func (*Service) RecordFailedEmailVerifyCode

func (s *Service) RecordFailedEmailVerifyCode(ctx context.Context, email string)

RecordFailedEmailVerifyCode increments the per-email failed-attempt counter for the typed email-verification code. After maxEmailVerifyCodeAttempts failures it invalidates every outstanding code/pending-registration for that address so the short numeric code cannot be brute-forced within its TTL (AK security audit F1). No-op without an ephemeral store.

func (*Service) RegenerateBackupCodes

func (s *Service) RegenerateBackupCodes(ctx context.Context, userID string) ([]string, error)

RegenerateBackupCodes generates new backup codes for a user (invalidating old ones). Returns the plaintext codes (caller must show these to user ONCE).

func (*Service) RegisterRemoteAppAttributeDef

func (s *Service) RegisterRemoteAppAttributeDef(ctx context.Context, appID, key string, version int32, definition json.RawMessage) (*RemoteAppAttributeDef, error)

RegisterRemoteAppAttributeDef stores (or updates) a definition for the remote_application. version defaults to 1 when zero. The caller authority is the remote_application itself (it owns its users' restrictions); the http layer enforces that.

func (*Service) RemoteApplicationRoles

func (s *Service) RemoteApplicationRoles(ctx context.Context, appID string) ([]string, error)

RemoteApplicationRoles returns the roles a remote_application holds in its controlling permission-group, or ErrNotGroupMember when it holds none.

func (*Service) RemoveGroupSubject

func (s *Service) RemoveGroupSubject(ctx context.Context, persona, resourceSlug, subjectID, subjectKind string) error

RemoveGroupSubject revokes every role a subject holds in a group.

func (*Service) RemoveRemoteApplicationMember

func (s *Service) RemoveRemoteApplicationMember(ctx context.Context, appID, role string) error

RemoveRemoteApplicationMember soft-deletes a remote_application's role in its controlling permission-group.

func (*Service) RemoveRoleBySlug

func (s *Service) RemoveRoleBySlug(ctx context.Context, userID, slug string) error

func (*Service) RenamePasskey

func (s *Service) RenamePasskey(ctx context.Context, userID, id, label string) error

func (*Service) RequestEmailChange

func (s *Service) RequestEmailChange(ctx context.Context, userID, newEmail string) error

RequestEmailChange initiates an email change by sending a verification code to the new email. The current email is NOT changed until the user confirms via ConfirmEmailChange. Also sends a notification to the old email for security.

func (*Service) RequestEmailVerification

func (s *Service) RequestEmailVerification(ctx context.Context, email string, ttl time.Duration) error

RequestEmailVerification creates a verification code and dispatches an email.

func (*Service) RequestPasswordReset

func (s *Service) RequestPasswordReset(ctx context.Context, email string, ttl time.Duration, ip *string, ua *string) error

RequestPasswordReset creates a password reset token and dispatches a reset link via email. Returns nil for unknown emails to prevent user enumeration (202-like behavior).

func (*Service) RequestPhoneChange

func (s *Service) RequestPhoneChange(ctx context.Context, userID, newPhone string) error

RequestPhoneChange initiates a phone number change by sending a verification code to the new phone. The current phone is NOT changed until the user confirms via ConfirmPhoneChange.

func (*Service) RequestPhonePasswordReset

func (s *Service) RequestPhonePasswordReset(ctx context.Context, phone string, ttl time.Duration, ip *string, ua *string) error

RequestPhonePasswordReset creates a password reset token and sends a reset link via SMS. Always returns nil for unknown phone numbers to prevent user enumeration (202-like behavior).

func (*Service) RequestPhoneVerification

func (s *Service) RequestPhoneVerification(ctx context.Context, phone string, ttl time.Duration) error

RequestPhoneVerification looks up the user by phone number and sends a verification code. This mirrors the RequestEmailVerification pattern - caller only needs to provide the phone number.

func (*Service) Require2FAForLogin

func (s *Service) Require2FAForLogin(ctx context.Context, userID string) (string, error)

Require2FAForLogin sends a 2FA code to the user's configured method. Returns the destination (email/phone) where the code was sent. This should be called after successful password verification.

func (*Service) Require2FAForLoginFactor

func (s *Service) Require2FAForLoginFactor(ctx context.Context, userID, factorID string) (destination, method string, factor TwoFactorFactor, err error)

func (*Service) Require2FAForReauth

func (s *Service) Require2FAForReauth(ctx context.Context, userID, sessionID string) (destination, method string, err error)

Require2FAForReauth sends a 2FA code for authenticated step-up reauth.

func (*Service) Require2FAForReauthFactor

func (s *Service) Require2FAForReauthFactor(ctx context.Context, userID, sessionID, factorID string) (destination, method string, factor TwoFactorFactor, err error)

func (*Service) Require2FAForReauthMethod

func (s *Service) Require2FAForReauthMethod(ctx context.Context, userID, sessionID, method string) (destination, selectedMethod string, factor TwoFactorFactor, err error)

func (*Service) RequireFreshSession

func (s *Service) RequireFreshSession(ctx context.Context, userID, sessionID string, now time.Time) (SessionFreshness, error)

func (*Service) ResendEmailChangeCode

func (s *Service) ResendEmailChangeCode(ctx context.Context, userID string) error

ResendEmailChangeCode resends the verification code for a pending email change.

func (*Service) ResendPhoneChangeCode

func (s *Service) ResendPhoneChangeCode(ctx context.Context, userID, phone string) error

ResendPhoneChangeCode resends the verification code for a pending phone change.

func (*Service) ResolveAPIKey

func (s *Service) ResolveAPIKey(ctx context.Context, keyID, secret string) (groupRef string, permissions []string, err error)

ResolveAPIKey validates a presented API key (key_id + secret) and returns the owning permission-group id and the key's effective permissions resolved from its role at verify time (a role edit is reflected immediately — perms are never frozen into the key).

func (*Service) ResolveAPIKeyWithResources

func (s *Service) ResolveAPIKeyWithResources(ctx context.Context, keyID, secret string) (ResolvedAPIKey, error)

ResolveAPIKeyWithResources validates a presented API key and returns the full resource-aware result.

func (*Service) ResolveAndStoreSolanaSNS

func (s *Service) ResolveAndStoreSolanaSNS(ctx context.Context, userID, address string) (SolanaLinkedAccount, error)

ResolveAndStoreSolanaSNS refreshes cached SNS metadata for an existing SIWS link. Resolver failures are recorded as stable metadata and do not invalidate the wallet link.

func (*Service) ResolveGroupIDForSlug

func (s *Service) ResolveGroupIDForSlug(ctx context.Context, persona, resourceSlug string) (string, error)

ResolveGroupIDForSlug maps the API addressing key (persona, resourceSlug) to the group's INTERNAL id. The id never goes on the wire — this is for callers that must thread the controlling permission_group_id into a sibling resource (e.g. a remote_application's permission_group_id, #111). ErrGroupNotFound if no live group matches.

func (*Service) ResolveRemoteAppAttributeDef

func (s *Service) ResolveRemoteAppAttributeDef(ctx context.Context, appID, key string, version int32) (*RemoteAppAttributeDef, error)

ResolveRemoteAppAttributeDef returns the definition for (appID, key, version). version <= 0 resolves the LATEST version. The returned Definition is opaque.

func (*Service) ResolveRemoteApplicationAuthority

func (s *Service) ResolveRemoteApplicationAuthority(ctx context.Context, appID string) ([]string, error)

ResolveRemoteApplicationAuthority resolves a remote_application's effective permissions: the additive walk-up of every role it holds across its controlling permission-group's parent chain (#111). Returns an empty slice (no error) when the app holds no roles.

func (*Service) ResolveRemoteApplicationGroup

func (s *Service) ResolveRemoteApplicationGroup(ctx context.Context, issuer string) (string, error)

ResolveRemoteApplicationGroup returns the controlling permission_group_id of the remote_application registered for issuer (#111). ErrRemoteApplicationNotFound if unknown.

func (*Service) ResolveSessionByRefresh

func (s *Service) ResolveSessionByRefresh(ctx context.Context, refreshToken string) (string, error)

ResolveSessionByRefresh finds the session id for a presented refresh token, if valid and active.

func (*Service) RestoreUser

func (s *Service) RestoreUser(ctx context.Context, id string) error

RestoreUser clears deleted_at and re-enables the account.

func (*Service) RevokeAPIKey

func (s *Service) RevokeAPIKey(ctx context.Context, persona, resourceSlug, tokenID string) (bool, error)

RevokeAPIKey marks the API key revoked. It is scoped to the group so a token cannot be revoked from a different group. Returns false if no matching, not-already-revoked token exists.

func (*Service) RevokeAllSessions

func (s *Service) RevokeAllSessions(ctx context.Context, userID string, keepSessionID *string) error

func (*Service) RevokeGroupInvite

func (s *Service) RevokeGroupInvite(ctx context.Context, persona, resourceSlug, inviteID string) error

RevokeGroupInvite flips a pending invite to revoked. It is scoped to the group addressed by (persona, resourceSlug) so a manager cannot revoke an invite from another group. ErrInviteNotFound if absent; ErrInviteNotPending if already acted.

func (*Service) RevokeSessionByID

func (s *Service) RevokeSessionByID(ctx context.Context, sessionID string) error

func (*Service) RevokeSessionByIDForUser

func (s *Service) RevokeSessionByIDForUser(ctx context.Context, userID, sessionID string) error

RevokeSessionByIDForUser revokes a session by id ensuring it belongs to the user.

func (*Service) SMSAvailable

func (s *Service) SMSAvailable() bool

SMSAvailable reports whether phone-based flows should be offered: a sender is configured and (if a health check has run) it was found able to deliver.

func (*Service) SMSHealthReason

func (s *Service) SMSHealthReason() string

SMSHealthReason returns the reason SMS was last found unhealthy, if any.

func (*Service) SMSHealthy

func (s *Service) SMSHealthy() bool

SMSHealthy reports the last CheckSMSHealth result. It is true until a check has run (legacy behavior: assume healthy when a sender is present).

func (*Service) Schema

func (s *Service) Schema() string

Schema returns the Postgres schema AuthKit's tables live in ("profiles" unless configured otherwise via Config.Schema/Options.Schema).

func (*Service) SeedPermissionGroupContainment

func (s *Service) SeedPermissionGroupContainment(ctx context.Context) error

SeedPermissionGroupContainment writes the declared containment schema into group_persona_parents so the DB trigger can enforce tree shape. Idempotent; call once at bootstrap.

func (*Service) SendPhone2FASetupCode

func (s *Service) SendPhone2FASetupCode(ctx context.Context, userID, phone, code string) error

SendPhone2FASetupCode generates and sends a 6-digit code for 2FA setup to the user's phone.

func (*Service) SendPhoneVerificationToUser

func (s *Service) SendPhoneVerificationToUser(ctx context.Context, phone, userID string, ttl time.Duration) error

SendPhoneVerificationToUser creates a verification code and sends it via SMS to a known user. Use RequestPhoneVerification if you only have a phone number and need to look up the user. Always returns nil for security.

func (*Service) SendWelcome

func (s *Service) SendWelcome(ctx context.Context, userID string)

SendWelcome triggers the welcome email if an EmailSender is configured.

func (*Service) SessionFreshness

func (s *Service) SessionFreshness(ctx context.Context, userID, sessionID string, now time.Time) (SessionFreshness, error)

func (*Service) SetDefault2FAFactor

func (s *Service) SetDefault2FAFactor(ctx context.Context, userID, factorID string) error

func (*Service) SetEmailVerified

func (s *Service) SetEmailVerified(ctx context.Context, id string, v bool) error

func (*Service) SetEntitlementsProvider

func (s *Service) SetEntitlementsProvider(p EntitlementsProvider)

SetEntitlementsProvider installs the entitlements provider AFTER construction.

This is the ONE sanctioned post-construction setter — #108 otherwise removed every mutating builder in favor of constructor options. It exists for a genuine initialization CYCLE: an embedded billing engine (e.g. OpenRails) authenticates through this Service — it needs the Verifier/Core, so the Service must exist first — yet that same engine is the SOURCE of the entitlements provider, so the provider cannot exist at construction time. The host builds the Service, builds the engine with it, then installs the engine's provider here. Safe because entitlements are read LAZILY at token-mint time; call it during wiring, before serving requests. Hosts WITHOUT this cycle should prefer the WithEntitlements construction option instead.

func (*Service) SetPasswordAfterFreshAuth

func (s *Service) SetPasswordAfterFreshAuth(ctx context.Context, userID, new string, keepSessionID *string) error

func (*Service) SetPreferredLanguage

func (s *Service) SetPreferredLanguage(ctx context.Context, userID, language string) error

func (*Service) SetProviderUsername

func (s *Service) SetProviderUsername(ctx context.Context, userID, provider, subject, username string) error

func (*Service) SoftDeleteUser

func (s *Service) SoftDeleteUser(ctx context.Context, id string) error

SoftDeleteUser marks the user deleted and sets deleted_at without dropping rows. Also revokes all refresh sessions for this issuer.

func (*Service) StartTOTPEnrollment

func (s *Service) StartTOTPEnrollment(ctx context.Context, userID string) (secret, otpauthURI string, err error)

StartTOTPEnrollment creates a short-lived pending authenticator-app secret.

func (*Service) TimeUntilUsernameRenameAvailable

func (s *Service) TimeUntilUsernameRenameAvailable(ctx context.Context, userID string, now time.Time) (int64, error)

func (*Service) UnassignGroupRole

func (s *Service) UnassignGroupRole(ctx context.Context, persona, resourceSlug, subjectID, subjectKind, role string) error

UnassignGroupRole revokes a subject's role in a group.

func (*Service) UnbanUser

func (s *Service) UnbanUser(ctx context.Context, userID string) error

UnbanUser clears ban metadata and re-enables the account.

func (*Service) UnlinkProvider

func (s *Service) UnlinkProvider(ctx context.Context, userID, provider string) error

func (*Service) UpdateBiography

func (s *Service) UpdateBiography(ctx context.Context, id string, bio *string) error

func (*Service) UpdateEmail

func (s *Service) UpdateEmail(ctx context.Context, id, email string) error

func (*Service) UpdateImportedUser

func (s *Service) UpdateImportedUser(ctx context.Context, userID string, input ImportUserInput) (*User, error)

func (*Service) UpdateUsername

func (s *Service) UpdateUsername(ctx context.Context, id, username string) error

func (*Service) UpdateUsernameForce

func (s *Service) UpdateUsernameForce(ctx context.Context, id, username string) error

UpdateUsernameForce is the admin override that skips the 72h cooldown check. Otherwise identical to UpdateUsername. Caller is responsible for gating this behind admin scope upstream.

func (*Service) UpsertPasswordHash

func (s *Service) UpsertPasswordHash(ctx context.Context, userID, hash, algo string, params []byte) error

func (*Service) UpsertRemoteApplication

func (s *Service) UpsertRemoteApplication(ctx context.Context, in RemoteApplication) (*RemoteApplication, error)

UpsertRemoteApplication registers or updates a remote_application keyed by its issuer.

func (*Service) UpsertRoleBySlug

func (s *Service) UpsertRoleBySlug(ctx context.Context, name, slug string, description *string) error

func (*Service) ValidateUsernameForRegistration

func (s *Service) ValidateUsernameForRegistration(ctx context.Context, username string) (string, error)

func (*Service) ValidateUsernameForUser

func (s *Service) ValidateUsernameForUser(ctx context.Context, username, userID string) (slug, excludeGroupID string, err error)

ValidateUsernameForUser validates a desired username and confirms no OTHER live user already holds it, so username uniqueness is the only constraint. The returned slug is the lowercased username; excludeGroupID is retained in the signature for dependent adapters but is always empty under the permission-group model.

func (*Service) ValidateVerificationConfiguration

func (s *Service) ValidateVerificationConfiguration() error

ValidateVerificationConfiguration ensures registration verification policy can be satisfied by currently configured delivery senders.

func (*Service) Verify2FAChallenge

func (s *Service) Verify2FAChallenge(ctx context.Context, userID, challenge string) (bool, error)

Verify2FAChallenge verifies the challenge created during the password step.

func (*Service) Verify2FACode

func (s *Service) Verify2FACode(ctx context.Context, userID, code string) (bool, error)

Verify2FACode verifies a 2FA code entered by the user during login. Returns true if code is valid, false otherwise.

func (*Service) Verify2FAFactorCode

func (s *Service) Verify2FAFactorCode(ctx context.Context, userID, factorID, code string) (bool, error)

func (*Service) Verify2FAReauthCode

func (s *Service) Verify2FAReauthCode(ctx context.Context, userID, sessionID, code string) (bool, error)

Verify2FAReauthCode verifies a session-scoped 2FA reauth code.

func (*Service) Verify2FAReauthFactorCode

func (s *Service) Verify2FAReauthFactorCode(ctx context.Context, userID, sessionID, factorID, code string) (bool, error)

func (*Service) Verify2FAReauthMethodCode

func (s *Service) Verify2FAReauthMethodCode(ctx context.Context, userID, sessionID, method, code string) (bool, error)

func (*Service) VerifyBackupCode

func (s *Service) VerifyBackupCode(ctx context.Context, userID, backupCode string) (bool, error)

VerifyBackupCode verifies a 2FA backup code for account recovery. On success, removes the used backup code from the user's backup codes.

func (*Service) VerifyPendingPassword

func (s *Service) VerifyPendingPassword(ctx context.Context, email, pass string) bool

VerifyPendingPassword checks if the provided password matches the pending registration's hash. Returns true if password is correct, false otherwise.

func (*Service) VerifyPendingPhonePassword

func (s *Service) VerifyPendingPhonePassword(ctx context.Context, phone, pass string) bool

VerifyPendingPhonePassword checks if the provided password matches the pending phone registration's hash. Returns true if password is correct, false otherwise.

func (*Service) VerifyPhone2FASetupCode

func (s *Service) VerifyPhone2FASetupCode(ctx context.Context, userID, phone, code string) (bool, error)

VerifyPhone2FASetupCode checks the code for 2FA phone setup.

func (*Service) VerifySIWSAndLogin

func (s *Service) VerifySIWSAndLogin(ctx context.Context, cache siws.ChallengeCache, output siws.SignInOutput, extra map[string]any) (accessToken string, expiresAt time.Time, refreshToken, userID string, created bool, err error)

VerifySIWSAndLogin verifies a SIWS signature and logs in or creates a user. Returns access token, expiry, refresh token, user ID, and whether a new user was created.

func (*Service) VerifyUserPassword

func (s *Service) VerifyUserPassword(ctx context.Context, userID, pass string) bool

VerifyUserPassword checks a user's password without issuing tokens or updating last-login. Returns true if the password is correct, false otherwise.

func (*Service) WithEmailSender

func (s *Service) WithEmailSender(sender EmailSender) *Service

WithEmailSender sets the email sender dependency.

func (*Service) WithSMSSender

func (s *Service) WithSMSSender(sender SMSSender) *Service

WithSMSSender sets the SMS sender dependency.

type ServiceJWTClaims

type ServiceJWTClaims = authbase.ServiceJWTClaims

ServiceJWTClaims is defined in authbase (core-free) and re-exported here.

func MintServiceJWT

func MintServiceJWT(ctx context.Context, signer jwtkit.Signer, issuer string, opts ServiceJWTMintOptions) (string, ServiceJWTClaims, error)

MintServiceJWT signs a service JWT with an explicit signer and issuer. Hosts can use this helper when they manage the signing key outside core.Service.

type ServiceJWTMintOptions

type ServiceJWTMintOptions struct {
	Subject     string
	Audiences   []string
	Permissions []string
	Resources   []APIKeyResource
	Lifetime    time.Duration
	NotBefore   time.Time
	IssuedAt    time.Time
	JTI         string
}

ServiceJWTMintOptions controls service-JWT minting for embedded hosts.

type Session

type Session struct {
	ID                  string
	FamilyID            string
	CreatedAt           time.Time
	LastAuthenticatedAt *time.Time
	LastUsedAt          time.Time
	ExpiresAt           *time.Time
	RevokedAt           *time.Time
	UserAgent           *string
	IPAddr              *string
}

Session represents a sanitized session view (no tokens).

type SessionEventType

type SessionEventType string

SessionEventType identifies a session lifecycle event.

const (
	SessionEventCreated          SessionEventType = "session_created"
	SessionEventRevoked          SessionEventType = "session_revoked"
	SessionEventPasswordChange   SessionEventType = "password_changed"
	SessionEventPasswordRecovery SessionEventType = "password_recovery"
	SessionEventFailed           SessionEventType = "session_failed"
)

type SessionFreshness

type SessionFreshness struct {
	LastAuthenticatedAt           time.Time
	TimeUntilReauthRequired       time.Duration
	ReauthRequiredForSensitiveOps bool
	AuthMethods                   []string
}

func (SessionFreshness) AssuranceClaims

func (f SessionFreshness) AssuranceClaims() (authTime int64, amr []string, acr string)

type SessionRevokeReason

type SessionRevokeReason string

SessionRevokeReason identifies why a session (or set of sessions) was revoked.

const (
	SessionRevokeReasonUnknown              SessionRevokeReason = ""
	SessionRevokeReasonLogout               SessionRevokeReason = "logout"
	SessionRevokeReasonUserRevoke           SessionRevokeReason = "user_revoke"
	SessionRevokeReasonUserRevokeAll        SessionRevokeReason = "user_revoke_all"
	SessionRevokeReasonAdminRevoke          SessionRevokeReason = "admin_revoke"
	SessionRevokeReasonAdminRevokeAll       SessionRevokeReason = "admin_revoke_all"
	SessionRevokeReasonPasswordChange       SessionRevokeReason = "password_change"
	SessionRevokeReasonAdminSetPassword     SessionRevokeReason = "admin_set_password"
	SessionRevokeReasonUserDisabled         SessionRevokeReason = "user_disabled"
	SessionRevokeReasonBanned               SessionRevokeReason = "banned"
	SessionRevokeReasonSoftDeleted          SessionRevokeReason = "soft_deleted"
	SessionRevokeReasonEvicted              SessionRevokeReason = "evicted"
	SessionRevokeReasonRefreshReuseDetected SessionRevokeReason = "refresh_reuse_detected"
)

type SolanaConfig

type SolanaConfig struct {
	// Network is a host-provided chain selector ("mainnet"/"testnet"/"devnet").
	// If empty, AuthKit derives a default from Environment.
	Network string
	// SNSEnabled enables AuthKit-owned Solana Name Service resolution for
	// SIWS-linked wallets.
	SNSEnabled bool
	// SNSResolver resolves a verified Solana wallet address to its primary .sol name.
	SNSResolver SolanaSNSResolver
	// SNSLookupTimeout bounds resolver calls. Empty defaults to 3 seconds.
	SNSLookupTimeout time.Duration
	// SNSCacheTTL controls when cached SNS metadata is stale. Empty defaults to 24h.
	SNSCacheTTL time.Duration
}

SolanaConfig controls SIWS chain selection and optional SNS resolution.

type SolanaLinkedAccount

type SolanaLinkedAccount struct {
	Provider            string     `json:"provider"`
	Issuer              string     `json:"issuer"`
	Address             string     `json:"address"`
	Verified            bool       `json:"verified"`
	VerifiedAt          *time.Time `json:"verified_at"`
	PrimarySNSName      *string    `json:"primary_sns_name"`
	SNSResolutionStatus string     `json:"sns_resolution_status"`
	SNSResolvedAt       *time.Time `json:"sns_resolved_at"`
	SNSStale            bool       `json:"sns_stale"`
	SNSError            *string    `json:"sns_error"`
}

SolanaLinkedAccount is the AuthKit-owned normalized metadata for a SIWS-linked wallet.

type SolanaSNSResolver

type SolanaSNSResolver interface {
	ResolvePrimaryName(ctx context.Context, address string) (string, error)
}

SolanaSNSResolver resolves a verified Solana wallet address to its primary .sol name.

type SubjectGroupMembership

type SubjectGroupMembership struct {
	Persona      string
	ResourceSlug string
	Role         string
}

SubjectGroupMembership is one (persona, resource, role) a subject holds.

type TokenConfig

type TokenConfig struct {
	Issuer               string
	IssuedAudiences      []string // tokens issued will contain ALL of these audiences
	ExpectedAudiences    []string
	AccessTokenDuration  time.Duration
	RefreshTokenDuration time.Duration
	// SessionMaxPerUser caps concurrent refresh sessions per user. 0 = unlimited
	// (default 3 if unset by the service); eviction is always evict-oldest.
	SessionMaxPerUser int
}

TokenConfig is the JWT issuing/verification contract plus session limits.

type TwoFactorConfig

type TwoFactorConfig struct {
	// TOTPSecretKey encrypts persisted authenticator-app shared secrets. It must
	// be 16, 24, or 32 bytes. Without it, TOTP enrollment fails closed.
	TOTPSecretKey []byte
}

TwoFactorConfig configures optional 2FA methods.

type TwoFactorFactor

type TwoFactorFactor struct {
	ID           string
	UserID       string
	Method       string
	PhoneNumber  *string
	TOTPSecret   []byte
	LastTOTPStep *int64
	IsDefault    bool
	Enabled      bool
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

type TwoFactorSettings

type TwoFactorSettings struct {
	UserID       string
	Enabled      bool
	Method       string // "email", "sms", or "totp"
	PhoneNumber  *string
	TOTPSecret   []byte
	LastTOTPStep *int64
	BackupCodes  []string // Hashed backup codes
	Factors      []TwoFactorFactor
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

TwoFactorSettings represents a user's 2FA configuration

type User

type User struct {
	ID              string
	Email           *string // Nullable - phone-only users have NULL email
	PhoneNumber     *string
	Username        *string
	DiscordUsername *string
	EmailVerified   bool
	PhoneVerified   bool
	BannedAt        *time.Time
	BannedUntil     *time.Time
	BanReason       *string
	BannedBy        *string
	DeletedAt       *time.Time
	Biography       *string
	CreatedAt       time.Time
	UpdatedAt       time.Time
	LastLogin       *time.Time
}

type ValidationError

type ValidationError struct {
	Code              string
	RetryAfterSeconds int64
}

ValidationError is the stable identity-policy error returned by AuthKit validation helpers. Code is intended to be exposed directly in route responses as {"error":"code"}.

func (*ValidationError) Error

func (e *ValidationError) Error() string

type VerificationMessage

type VerificationMessage struct {
	// Fixed-length numeric code for manual entry (optional).
	Code string
	// AuthKit-built scanner-safe verification link (optional).
	LinkURL string
	// Purpose lets senders vary copy without adding new sender methods.
	Purpose string
}

func (VerificationMessage) Validate

func (m VerificationMessage) Validate() error

Jump to

Keyboard shortcuts

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