iam

package
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Sep 30, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package iam holds AuthKit's shared identity and access vocabulary: users, subjects, groups, roles and permissions, actors, remote applications, credential parsing, naming policy, and the one error catalog with its wire envelope. It depends only on the standard library, so the engine, the DB-less verify package and hosts can all share it.

A persona is a type of permission group (channel, org, merchant). A permission group is one instance of a persona, addressed by ID: it holds roles for an entity the host app owns (the channel /c/golang). root is the persona with exactly one group, the whole site. A permission is `<persona>:<resource>:<action>`; `*` may replace the action or everything after the persona.

Index

Constants

View Source
const (
	AssuranceLevelPassword = "urn:authkit:loa:1"
	AssuranceLevelMFA      = "urn:authkit:loa:2"
)
View Source
const (
	// RefreshCookieName is the refresh cookie on HTTPS deployments. Browsers
	// accept a __Host- cookie only when it is Secure, host-only and Path=/, so
	// a sibling subdomain can neither plant nor shadow it.
	RefreshCookieName = "__Host-authkit_rt"
	// InsecureRefreshCookieName is used only on plain-HTTP deployments (local
	// development), where browsers refuse __Host- cookies.
	InsecureRefreshCookieName = "authkit_rt"
)
View Source
const (
	DefaultPageLimit = 50
	MaxPageLimit     = 500
)

DefaultPageLimit and MaxPageLimit bound PageRequest.Limit.

View Source
const (
	// ServiceJWTTokenUse is the required `token_use` claim for service JWTs.
	ServiceJWTTokenUse = "service"
	// DefaultServiceJWTLifetime is the recommended lifetime for first-party
	// machine-to-machine service JWTs.
	DefaultServiceJWTLifetime = 15 * time.Minute
)
View Source
const JWKSPath = "/.well-known/jwks.json"

JWKSPath serves the issuer's public signing keys beneath the issuer's path (the mount's BasePath), so verifiers derive it: issuer + JWKSPath.

View Source
const MaxBatch = 500

MaxBatch bounds the ids (users, groups, subjects) accepted by one batch call.

View Source
const PermWildcard = "*"

PermWildcard is the wildcard CHARACTER used inside namespace-anchored globs (`org:*`, `org:members:*`, `org:*:read`, `root:*`). A bare standalone `*` is NOT a valid grant — it is rejected everywhere.

View Source
const UserRecoveryPeriod = 30 * 24 * time.Hour

UserRecoveryPeriod is the fixed interval in which an accepted account deletion can be restored. Repeated deletion does not extend it.

Variables

View Source
var ErrUnknownPermission = errors.New("iam: unknown permission")

ErrUnknownPermission reports a permission no persona catalog registers. It is a programming error, so it has no wire code.

Functions

func DecodeError

func DecodeError(resp *http.Response) error

DecodeError is WriteError's inverse for an HTTP client of AuthKit: nil for a 2xx response, else the Error the response carries, with the response's status and the envelope's code, message, param and metadata. errors.Is matches it against the Err* sentinels by code. A body that is not an AuthKit envelope (a proxy's error page, an unmounted route) decodes to an Error with the status and an empty code. DecodeError reads the body but does not close it.

func PublicDisplayName

func PublicDisplayName(users map[string]PublicUser, id string) string

PublicDisplayName renders id against a PublicUsers result, including ids the batch did not resolve.

func WriteError

func WriteError(w http.ResponseWriter, err error)

WriteError writes err as the error envelope with the catalog's status for its code. Anything that is not an AuthKit error, and every server failure, is written as 500 internal_error.

Types

type APIKey

type APIKey struct {
	ID          string     `json:"id"`        // the key's identity: APIKeyActor(ID), verify Claims.APIKeyID
	LookupID    string     `json:"lookup_id"` // the public lookup id embedded in the token
	GroupID     string     `json:"group_id"`
	Name        string     `json:"name"`
	Role        Role       `json:"role"`
	Permissions []Perm     `json:"permissions"`
	CreatedBy   *string    `json:"created_by"` // nil = issued by the system
	CreatedAt   time.Time  `json:"created_at"`
	LastUsedAt  *time.Time `json:"last_used_at"`
	ExpiresAt   *time.Time `json:"expires_at"`
	RevokedAt   *time.Time `json:"revoked_at"`
}

APIKey is an API key's metadata; the secret is shown only by CreateAPIKey. A key holds one role of its group; Permissions is that role resolved now, so editing the role changes every key holding it.

type APIKeyCreated

type APIKeyCreated struct {
	APIKey APIKey `json:"api_key"`
	Secret string `json:"secret"`
}

APIKeyCreated is a new key and its token, shown this once.

type APIKeyPrincipal

type APIKeyPrincipal struct {
	ID          string     `json:"id"`
	LookupID    string     `json:"lookup_id"`
	Group       Group      `json:"group"`
	Issuer      string     `json:"issuer"` // the issuer of the AuthKit deployment holding the key
	Role        Role       `json:"role"`
	Permissions []Perm     `json:"permissions"`
	ExpiresAt   *time.Time `json:"expires_at"`
}

APIKeyPrincipal is a resolved, live API key: the group it acts in and the permissions of its role at resolution time.

type AccessTokenOptions

type AccessTokenOptions struct {
	SessionID string
	TTL       time.Duration // 0 = the configured access-token lifetime
	Claims    map[string]any
}

AccessTokenOptions shapes a host-minted access token. Claims are the host's own: one named like an AuthKit claim is refused.

type AccountSessionRevocation

type AccountSessionRevocation struct {
	// Issuers is the exact issuer scope covered, this deployment's first.
	Issuers []string `json:"issuers"`
	// RevokedSessions counts revoked refresh sessions per covered issuer.
	RevokedSessions map[string]int `json:"revoked_sessions"`
	// RevokedDeviceKeys counts revoked device keys; they are not issuer-bound.
	RevokedDeviceKeys int `json:"revoked_device_keys"`
	// UnlistedIssuerSessions counts live sessions left under issuers outside
	// Issuers; nonzero means the account issuer configuration is incomplete.
	UnlistedIssuerSessions int `json:"unlisted_issuer_sessions"`
}

AccountSessionRevocation reports an account-wide emergency revocation across the configured account issuers (TokenConfig.AccountIssuers). Access tokens minted from the revoked sessions and device keys are refused at once by every session check (permission checks, Sensitive, account changes); plain stateless verification admits them until they expire.

type Actor

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

Actor is who performs an operation. Its fields are unexported: the zero Actor is invalid and every operation refuses it, and SystemActor is the only way to build the system actor. Authority is resolved live per operation; nothing is cached in the value.

func APIKeyActor

func APIKeyActor(apiKeyID string) Actor

APIKeyActor acts as an API key: APIKey.ID, the same value as verify Claims.APIKeyID.

func DelegatedActor

func DelegatedActor(g DelegatedGrant) Actor

DelegatedActor acts under a verified delegated grant. A grant from a foreign platform carries no AuthKit authority.

func RemoteApplicationActor

func RemoteApplicationActor(appID string) Actor

RemoteApplicationActor acts as a registered remote application.

func SystemActor

func SystemActor() Actor

SystemActor is your application's own code acting, with no user: trusted host authority. It skips authority rules but never invariants (last owner, MFA-required roles). Never derive it from request input.

func UserActor

func UserActor(userID string) Actor

UserActor acts as a native user.

func (Actor) Bounded

func (a Actor) Bounded() bool

Bounded reports whether a ceiling narrows the actor.

func (Actor) CeilingCovers

func (a Actor) CeilingCovers(perm Perm) bool

CeilingCovers reports whether every ceiling permits perm (true when unbounded).

func (Actor) Delegation

func (a Actor) Delegation() (DelegatedGrant, bool)

Delegation returns the delegated grant of a delegated actor.

func (Actor) ID

func (a Actor) ID() string

ID is the user, API key, application or delegated subject id; "" for the system.

func (Actor) InSession

func (a Actor) InSession(r SessionRef) Actor

InSession binds a user or delegated actor to the sign-in its token was minted from. Every authority check then also requires that session or device key to be active, in the same query as the account check, and refuses a revoked one with ErrSessionRevoked. verify.ActorFromClaims binds every actor it builds from an AuthKit user or delegated token; an unbound actor (UserActor in trusted server code) is checked at account level only. The zero ref leaves a unchanged; any other actor, or a ref naming both, is the zero Actor.

func (Actor) IsZero

func (a Actor) IsZero() bool

IsZero reports whether a is the invalid zero Actor.

func (Actor) Kind

func (a Actor) Kind() ActorKind

func (Actor) Session

func (a Actor) Session() (SessionRef, bool)

Session is the sign-in a is bound to (InSession).

func (Actor) String

func (a Actor) String() string

String is "<kind>:<id>" (or "system"), for logs and audit.

func (Actor) Within

func (a Actor) Within(perms ...Perm) Actor

Within narrows the actor to permissions covered by perms (an intersection with any existing ceiling). On the system or the zero Actor it returns the zero Actor.

type ActorKind

type ActorKind string

ActorKind is the class of authority an Actor carries.

const (
	ActorUser              ActorKind = "user"
	ActorAPIKey            ActorKind = "api_key"
	ActorRemoteApplication ActorKind = "remote_application"
	ActorDelegated         ActorKind = "delegated"
	ActorSystem            ActorKind = "system"
)

type AppRef

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

AppRef addresses one remote application: by id or by issuer. Build it with AppByID or AppByIssuer; the zero AppRef finds nothing.

func AppByID

func AppByID(id string) AppRef

func AppByIssuer

func AppByIssuer(issuer string) AppRef

func (AppRef) ID

func (r AppRef) ID() string

ID is the id of a by-id reference, "" otherwise.

func (AppRef) IsZero

func (r AppRef) IsZero() bool

func (AppRef) Issuer

func (r AppRef) Issuer() string

Issuer is the issuer of a by-issuer reference, "" otherwise.

func (AppRef) String

func (r AppRef) String() string

type ApplicationTrustRoot

type ApplicationTrustRoot string

ApplicationTrustRoot is the authority that changes an application's keys.

const (
	ApplicationTrustRootManual ApplicationTrustRoot = "manual"
	ApplicationTrustRootUser   ApplicationTrustRoot = "user"
)

type Ban

type Ban struct {
	Reason       string
	Until        *time.Time
	KeepExisting bool
}

Ban bans an account. Until nil bans indefinitely. KeepExisting leaves a ban already in force unchanged, so a repeat call is a no-op.

type BanState

type BanState struct {
	At     time.Time  `json:"at" yaml:"at"`
	Until  *time.Time `json:"until" yaml:"until"`
	Reason *string    `json:"reason" yaml:"reason"`
	By     *string    `json:"by" yaml:"by"`
}

BanState is a ban in force: when it began, until when (nil = indefinitely), why, and the account that banned (By, nil for the system or a machine).

type BootstrapManifest

type BootstrapManifest struct {
	Users              []BootstrapManifestUser              `json:"users" yaml:"users"`
	RemoteApplications []BootstrapManifestRemoteApplication `json:"remote_applications" yaml:"remote_applications"`
}

BootstrapManifest is genesis seed data: accounts, their root roles and remote applications. ApplyBootstrapManifest applies it as a host operation; authkit.ParseBootstrapManifestYAML reads one from its YAML file format, whose keys are the yaml tags below.

type BootstrapManifestRemoteApplication

type BootstrapManifestRemoteApplication struct {
	Issuer     string                 `json:"issuer" yaml:"issuer"`
	JWKSURI    string                 `json:"jwks_uri" yaml:"jwks_uri"`
	PublicKeys []RemoteApplicationKey `json:"public_keys" yaml:"public_keys"`
	Enabled    *bool                  `json:"enabled" yaml:"enabled"`
	RootRole   Role                   `json:"root_role" yaml:"root_role"`
}

BootstrapManifestRemoteApplication seeds one application controlled by root.

type BootstrapManifestUser

type BootstrapManifestUser struct {
	Username      string `json:"username" yaml:"username"`
	Email         string `json:"email" yaml:"email"`
	Phone         string `json:"phone_number" yaml:"phone_number"`
	EmailVerified bool   `json:"email_verified" yaml:"email_verified"`
	PhoneVerified bool   `json:"phone_verified" yaml:"phone_verified"`
	// Ban bans a new account from now: only Until and Reason may be set.
	Ban      *BanState              `json:"ban" yaml:"ban"`
	Metadata map[string]any         `json:"metadata" yaml:"metadata"`
	Password *BootstrapUserPassword `json:"password" yaml:"password"`
	// RootRole is the account's root role (`root:admin`). The root owner role
	// is seeded only while the root group has no usable owner.
	RootRole Role `json:"root_role" yaml:"root_role"`
}

BootstrapManifestUser seeds one account. A new account is created as declared. An existing account is used only when the email or phone the manifest names is verified on it, and its identity, contacts, ban and metadata are left as they are. An account found any other way (unverified contact, or username when no contact is named) is refused unless the apply would change nothing on it.

type BootstrapOptions

type BootstrapOptions struct {
	// DryRun validates and counts without writing.
	DryRun bool
	// StartupOnly applies the manifest at most once per database schema; leave
	// it false for host or CLI applies.
	StartupOnly bool
	// Name labels the StartupOnly receipt ("" is "default"). Another name does
	// not rerun genesis.
	Name string
}

BootstrapOptions tunes ApplyBootstrapManifest.

type BootstrapResult

type BootstrapResult struct {
	DryRun                     bool `json:"dry_run"`
	AlreadyApplied             bool `json:"already_applied"`
	UsersCreated               int  `json:"users_created"`
	UsersMatched               int  `json:"users_matched"`
	PasswordsSet               int  `json:"passwords_set"`
	PasswordsKept              int  `json:"passwords_kept"`
	RootRoleAssignments        int  `json:"root_role_assignments"`
	RemoteApplications         int  `json:"remote_applications"`
	RemoteApplicationRootRoles int  `json:"remote_application_root_roles"`
}

BootstrapResult counts what an apply did.

type BootstrapUserPassword

type BootstrapUserPassword struct {
	Plaintext    string `json:"plaintext" yaml:"plaintext"`
	PasswordHash `yaml:",inline"`
	// ResetRequired stores a password that never verifies: the account must
	// reset it.
	ResetRequired bool `json:"reset_required" yaml:"reset_required"`
	// Enforce re-asserts the password on every apply. Without it the password
	// is set only when the account is created, so a rotated password is never
	// reverted. It cannot be combined with ResetRequired.
	Enforce bool `json:"enforce" yaml:"enforce"`
}

BootstrapUserPassword is exactly one of Plaintext, a PasswordHash, or ResetRequired.

type ContactChange

type ContactChange struct {
	Field ContactField
	// NewValue is the replacement address as stored.
	NewValue string
}

ContactChange is delivered to the PREVIOUS address after a recovery identifier (email or phone) was replaced, so a hijacked change is visible to the account's real owner.

type ContactField

type ContactField string

ContactField names a recovery identifier.

const (
	ContactEmail ContactField = "email"
	ContactPhone ContactField = "phone"
)

type DelegatedAccess

type DelegatedAccess struct {
	// Subject becomes delegated_sub. A user actor mints only for itself (empty
	// means the actor); the system must name the subject.
	Subject string
	// Audiences becomes aud: the resource APIs the token is for.
	Audiences []string
	// Permissions becomes the permissions claim. A permission in an AuthKit
	// persona's namespace must be held live by the subject on the root group;
	// the host's own vocabulary is the host's decision.
	Permissions []string
	// Attributes carries app-specific JSON; AuthKit assigns no key a meaning.
	Attributes map[string]any
	// TTL is clamped to the Config.Delegated bounds (default 15m, at most 1h).
	TTL time.Duration
	// JTI becomes jti; empty mints a fresh one.
	JTI       string
	NotBefore time.Time
	// CertificateThumbprint binds the token to an X.509 certificate (RFC 8705
	// cnf.x5t#S256), JWKThumbprint to a DPoP key (RFC 9449 cnf.jkt), each the
	// unpadded base64url SHA-256. At most one; neither mints a bearer token.
	CertificateThumbprint string
	JWKThumbprint         string
}

DelegatedAccess is a delegated access token to mint: signed by this deployment, it carries delegated_sub and never sub.

type DelegatedGrant

type DelegatedGrant struct {
	Issuer  string
	Subject string // delegated_sub
	// Permissions are always a ceiling.
	Permissions []Perm
	// RemoteApplicationID is set when the token is bound to an application.
	RemoteApplicationID string
	// GroupID is that application's controlling group.
	GroupID string
}

DelegatedGrant is copied from a verified delegated access token.

type DelegationAuthorizer

type DelegationAuthorizer func(context.Context, DelegationRequest) (DelegationGrant, error)

DelegationAuthorizer is the single host seam of the delegated mint route.

type DelegationGrant

type DelegationGrant struct {
	Permissions []string
	Attributes  map[string]any
}

DelegationGrant is the complete authority AuthKit signs for one request.

type DelegationRequest

type DelegationRequest struct {
	UserID    string
	Audiences []string
	TTL       time.Duration
	// DelegateCertificate is the delegate's X.509 leaf (DER) and
	// CertificateThumbprint its x5t#S256; both empty on the DPoP path.
	DelegateCertificate   []byte
	CertificateThumbprint string
	// JWKThumbprint is the DPoP key's jkt; empty on the certificate path.
	JWKThumbprint  string
	RequestedGrant json.RawMessage
}

DelegationRequest is what POST /delegated/token asks the host to authorize (ak#277). Audiences and TTL are already clamped; the sender proof is validated; RequestedGrant is the client's opaque, host-schema object that AuthKit never copies into the token. The token is bound to exactly one of the delegate's certificate and its DPoP key.

type DeviceKey

type DeviceKey struct {
	ID         string            `json:"id"`
	Label      *string           `json:"label"`
	PublicKey  ed25519.PublicKey `json:"public_key"`
	CreatedAt  time.Time         `json:"created_at"`
	LastUsedAt *time.Time        `json:"last_used_at"`
	RevokedAt  *time.Time        `json:"revoked_at"`
	Current    bool              `json:"current"`
}

DeviceKey is an Ed25519 key a native client signs in with. Current marks the key behind the access token of the request that listed it.

type DeviceKeyNotice

type DeviceKeyNotice struct {
	Label     string
	CreatedAt time.Time
}

DeviceKeyNotice describes a native-client device key just enrolled on an EXISTING account, so a key added through a compromised mailbox is visible to the account's real owner.

type EmailMessage

type EmailMessage struct {
	Kind MessageKind
	To   string
	// Username is the account's username, "" when it has none.
	Username string
	// Language is a two-letter code: the account's preferred language, else
	// the request's, else the configured default.
	Language      string
	Code          string
	Link          string
	Purpose       VerificationPurpose
	ContactChange *ContactChange
	DeviceKey     *DeviceKeyNotice
}

EmailMessage is one email AuthKit asks Deps.Email to deliver. Fields a kind does not use are empty.

type Error

type Error interface {
	error
	// Code is the stable snake_case wire code (internal_error for a server
	// failure; empty for a decoded response that is not an AuthKit error).
	Code() string
	// Status is the HTTP status the catalog fixes for the code.
	Status() int
	// Param names the offending request field, when there is one.
	Param() string
	// Metadata is machine-readable context, such as next_rename_at.
	Metadata() map[string]any
}

Error is an AuthKit error as the wire sees it. Every error AuthKit returns carries one, as does every error DecodeError reads from a response; match identities with errors.Is against the sentinels below.

var (
	// ErrAPIKeyInvalid indicates an API key that is malformed, does not exist,
	// has a bad secret, or whose owning permission group is gone: one answer so
	// callers learn nothing from the error.
	ErrAPIKeyInvalid Error = errmodel.E(errmodel.CodeAPIKeyInvalid)
	// ErrAPIKeyRevoked indicates the API key was revoked, or its creator
	// can no longer act (banned, deleted or reserved).
	ErrAPIKeyRevoked Error = errmodel.E(errmodel.CodeAPIKeyRevoked)
	// ErrAPIKeyExpired indicates the API key is past its expires_at.
	ErrAPIKeyExpired Error = errmodel.E(errmodel.CodeAPIKeyExpired)
	// ErrAPIKeyNotFound indicates no key with the id exists in the group.
	ErrAPIKeyNotFound Error = errmodel.E(errmodel.CodeAPIKeyNotFound)
)
var (
	ErrUserNotFound              Error = errmodel.E(errmodel.CodeUserNotFound)
	ErrGroupNotFound             Error = errmodel.E(errmodel.CodeGroupNotFound)
	ErrRemoteApplicationNotFound Error = errmodel.E(errmodel.CodeRemoteApplicationNotFound)
)

Lookup.

var (
	ErrInsufficientAuthority      Error = errmodel.E(errmodel.CodeInsufficientAuthority)
	ErrRoleAssignmentEscalation   Error = errmodel.E(errmodel.CodeRoleAssignmentEscalation)
	ErrAccountAuthorityEscalation Error = errmodel.E(errmodel.CodeAccountAuthorityEscalation)
	ErrLastOwner                  Error = errmodel.E(errmodel.CodeLastOwner)
	ErrCannotTargetSelf           Error = errmodel.E(errmodel.CodeCannotTargetSelf)
	ErrTwoFAEnrollmentRequired    Error = errmodel.E(errmodel.CodeTwoFAEnrollmentRequired)
	// ErrSubjectMFARequired refuses a role that needs MFA for an account with
	// no usable second factor.
	ErrSubjectMFARequired Error = errmodel.E(errmodel.CodeSubjectMFARequired)
	// ErrSessionRevoked refuses an actor bound to a session or device key
	// (Actor.InSession) that was revoked or expired, as logout, revoke-all, a
	// password change, a ban and deletion all do.
	ErrSessionRevoked Error = errmodel.E(errmodel.CodeSessionRevoked)
	// ErrTokenExpired refuses a token past its exp (beyond the verifier's
	// clock skew).
	ErrTokenExpired Error = errmodel.E(errmodel.CodeTokenExpired)
)

Authority.

var (
	ErrRenameRateLimited       Error = errmodel.E(errmodel.CodeRenameRateLimited)
	ErrRenamesDisabled         Error = errmodel.E(errmodel.CodeRenamesDisabled)
	ErrRoleNotAssignable       Error = errmodel.E(errmodel.CodeRoleNotAssignable)
	ErrUnknownGroupPersona     Error = errmodel.E(errmodel.CodeUnknownGroupPersona)
	ErrExternalInvitesDisabled Error = errmodel.E(errmodel.CodeExternalInvitesDisabled)
)

Groups and naming.

var (
	ErrEmailInUse             Error = errmodel.E(errmodel.CodeEmailInUse)
	ErrUsernameInUse          Error = errmodel.E(errmodel.CodeUsernameInUse)
	ErrPhoneInUse             Error = errmodel.E(errmodel.CodePhoneInUse)
	ErrInvalidUntil           Error = errmodel.E(errmodel.CodeInvalidUntil)
	ErrAccountRecoveryExpired Error = errmodel.E(errmodel.CodeAccountRecoveryExpired)
	ErrContactNotVerified     Error = errmodel.E(errmodel.CodeContactNotVerified)
	// ErrEntitlementFilterUnavailable refuses UserQuery.Entitlement when the
	// host set no Deps.EntitlementHolders.
	ErrEntitlementFilterUnavailable Error = errmodel.E(errmodel.CodeEntitlementFilterUnavailable)
)

Users.

var (
	ErrSigningNotConfigured            Error = errmodel.Internal("signing_not_configured", nil)
	ErrDeviceKeysDisabled              Error = errmodel.E(errmodel.CodeDeviceKeysDisabled)
	ErrRemoteApplicationIssuerConflict Error = errmodel.E(errmodel.CodeRemoteApplicationIssuerConflict)
	ErrReservedIssuer                  Error = errmodel.E(errmodel.CodeReservedIssuer)
)

Credentials and applications (the API-key, service-JWT, delegation and remote-application sentinels live beside their types).

var ErrDelegationRefused Error = errmodel.E(errmodel.CodeDelegationRefused)

ErrDelegationRefused is returned (or wrapped) by a DelegationAuthorizer to refuse a mint as a policy decision; any other error is an authorizer outage.

var ErrGroupConflict Error = errmodel.E(errmodel.CodeGroupConflict)

ErrGroupConflict refuses to create a group whose id is taken by a deleted group or a group of another persona.

var ErrInvalidRemoteApplication Error = errmodel.E(errmodel.CodeInvalidRemoteApplication)

ErrInvalidRemoteApplication indicates a malformed remote_application registration payload.

var ErrInvalidServiceJWT Error = errmodel.E(errmodel.CodeInvalidServiceJWT)

ErrInvalidServiceJWT indicates a presented service JWT failed verification.

var ErrInvitationNotFound Error = errmodel.E(errmodel.CodeInvitationNotFound)

ErrInvitationNotFound indicates no invitation with the id or code exists in the group, or none the caller may redeem.

func AsError

func AsError(err error) (Error, bool)

AsError returns the Error in err's chain.

type ErrorEnvelope

type ErrorEnvelope struct {
	Error ErrorObject `json:"error"`
}

ErrorEnvelope is every AuthKit error response: {"error": {...}}.

func ErrorResponse

func ErrorResponse(err error) (int, ErrorEnvelope)

ErrorResponse is WriteError's status and envelope, for any router: Gin `c.JSON(iam.ErrorResponse(err))`, Fiber `status, body := iam.ErrorResponse(err); return c.Status(status).JSON(body)`.

type ErrorObject

type ErrorObject struct {
	Type     string         `json:"type"`
	Code     string         `json:"code"`
	Message  string         `json:"message"`
	Param    *string        `json:"param"`
	Metadata map[string]any `json:"metadata"`
}

ErrorObject is the error detail under the envelope's "error" key: a stable code, its type category (derived from the status), a human-readable message, the offending request field (null for none) and machine-readable metadata (null for none).

type Event

type Event struct {
	// ID is the same on every delivery: the host's idempotency key.
	ID         string    `json:"id"`
	Kind       EventKind `json:"kind"`
	OccurredAt time.Time `json:"occurred_at"`
	// ActorKind and ActorID name who made the change (ActorID is empty for
	// the system). Both are empty for a change AuthKit made on its own: the
	// end of a recovery window, or a role retired with its grantor's cover.
	ActorKind ActorKind `json:"actor_kind"`
	ActorID   string    `json:"actor_id"`
	// UserID is the account of a user event and the user subject of a role
	// event.
	UserID string `json:"user_id"`
	// GroupID and Persona name the group of a group or role event.
	GroupID string  `json:"group_id"`
	Persona Persona `json:"persona"`
	// ApplicationID is the remote-application subject of a role event.
	ApplicationID string `json:"application_id"`
	// Previous and Current are the changed value before and after: the
	// email, phone or username, or the role (`channel:moderator`); "" when
	// none.
	Previous string `json:"previous"`
	Current  string `json:"current"`
	// Reason and Until describe a ban; Until is nil for an indefinite one.
	Reason string     `json:"reason"`
	Until  *time.Time `json:"until"`
}

Event is one committed account or group change. It is recorded in the transaction that makes the change, so a refused or rolled-back change records nothing, and delivered after commit, at least once. It names what changed and never carries a credential, hash, token or code.

type EventKind

type EventKind string

EventKind names a committed change delivered to Deps.OnEvent. Hosts ignore kinds they do not know: later versions add kinds.

const (
	// EventUserRegistered: an account was created (sign-up, sign-in that
	// creates an account, CreateUser, EnsureUserRole, a bootstrap manifest).
	// ImportUsers records no events.
	EventUserRegistered EventKind = "user.registered"
	// EventUserEmailChanged, EventUserPhoneChanged, EventUserUsernameChanged:
	// Previous and Current are the old and new value ("" when none).
	EventUserEmailChanged    EventKind = "user.email_changed"
	EventUserPhoneChanged    EventKind = "user.phone_changed"
	EventUserUsernameChanged EventKind = "user.username_changed"
	// EventUserBanned carries Reason and Until. EventUserUnbanned: a ban in
	// force was lifted; a temporary ban running out records nothing.
	EventUserBanned   EventKind = "user.banned"
	EventUserUnbanned EventKind = "user.unbanned"
	// EventUserDeleted starts the recovery window, EventUserRestored ends it
	// early and EventUserPurged follows the removal of the account row.
	EventUserDeleted  EventKind = "user.deleted"
	EventUserRestored EventKind = "user.restored"
	EventUserPurged   EventKind = "user.purged"
	// Role events carry GroupID, Persona (RootPersona for root roles), the
	// subject (UserID or ApplicationID) and the role as Previous → Current.
	EventRoleGranted EventKind = "role.granted"
	EventRoleChanged EventKind = "role.changed"
	EventRoleRevoked EventKind = "role.revoked"
	// Group events carry GroupID and Persona. A purge records no role events
	// for the assignments it removes.
	EventGroupCreated EventKind = "group.created"
	EventGroupDeleted EventKind = "group.deleted"
	EventGroupPurged  EventKind = "group.purged"
)

type Grant

type Grant interface {
	// contains filtered or unexported methods
}

Grant is what a role holds: a permission or pattern, or another role of the same persona whose permissions it includes. Only Perm and Role are Grants.

type Group

type Group struct {
	ID        string    `json:"id"`
	Persona   Persona   `json:"persona"`
	CreatedAt time.Time `json:"created_at"`
	// DeletedAt is set on a soft-deleted group.
	DeletedAt *time.Time `json:"deleted_at"`
}

Group is one permission group: an instance of a persona, or the root group, the whole site. A group has no name: it only holds roles. The entity it guards (a channel, /c/golang) lives in the host app, which stores the group's ID.

type GroupMember

type GroupMember struct {
	Subject Subject     `json:"subject"`
	Role    Role        `json:"role"`
	User    *PublicUser `json:"user"`
}

GroupMember is a subject holding a role in a group. User is the account of a user member when MemberQuery.WithUsers asked for it.

type GroupQuery

type GroupQuery struct {
	Persona        Persona
	IncludeDeleted bool
	Ownerless      bool
	Page           PageRequest
}

GroupQuery lists the groups of a persona (zero = every persona but root), oldest first. Ownerless keeps only live groups that no owner counts for under the last-owner rule: created without one, or left without one by the credential sweep at boot. An owner whose required MFA enrollment is pending does not count.

type GroupRef

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

GroupRef addresses one permission group: by id, or the root group. Build it with GroupByID or RootGroup; the zero GroupRef addresses nothing.

func GroupByID

func GroupByID(id string) GroupRef

GroupByID addresses a group by its uuid.

func RootGroup

func RootGroup() GroupRef

RootGroup addresses the deployment's root group.

func (GroupRef) ID

func (g GroupRef) ID() string

ID is the group id of a by-id reference; "" for RootGroup().

func (GroupRef) IsRoot

func (g GroupRef) IsRoot() bool

IsRoot reports whether g is RootGroup(). A by-id reference is never root until resolved.

func (GroupRef) IsZero

func (g GroupRef) IsZero() bool

func (GroupRef) String

func (g GroupRef) String() string

type HashAlgo

type HashAlgo string

HashAlgo names a password hash algorithm.

const (
	HashArgon2id HashAlgo = "argon2id"
	HashBcrypt   HashAlgo = "bcrypt"
	// HashLegacyResetRequired marks a migrated password that can never verify
	// (DES crypt, md5-crypt, a corrupted value). The raw hash is kept for
	// forensics only; the account must reset its password.
	HashLegacyResetRequired HashAlgo = "legacy-reset-required"
)

type ImportConflict

type ImportConflict string

ImportConflict is what ImportUsers does with a row that finds an account.

const (
	// ImportSkip leaves the account unchanged. It is the default.
	ImportSkip ImportConflict = "skip"
	// ImportMerge updates an account the row is bound to: found by ID, or by an
	// email or phone verified on the account. It merges Metadata, keeps the
	// earlier CreatedAt and the later LastLogin, and fills a PreferredLanguage
	// or AvatarURL the account lacks. Only a row bound by ID or by a contact
	// verified on both sides links Providers, and stores PasswordHash when the
	// account has no password. It never changes identity, contacts,
	// verification, bans or deletion. A row that is not bound is skipped with
	// ImportUnboundMatch.
	ImportMerge ImportConflict = "merge"
)

type ImportMatch

type ImportMatch string

ImportMatch names the identifier that found an existing account, the first of id, email, phone and username that did.

const (
	ImportMatchID       ImportMatch = "id"
	ImportMatchEmail    ImportMatch = "email"
	ImportMatchPhone    ImportMatch = "phone"
	ImportMatchUsername ImportMatch = "username"
)

type ImportOptions

type ImportOptions struct {
	OnConflict ImportConflict
}

ImportOptions tunes ImportUsers.

type ImportReason

type ImportReason string

ImportReason explains a skipped or rejected import row: one of the constants below, or the validation code of the rejected field (such as "invalid_email").

const (
	ImportAlreadyExists                ImportReason = "already_exists"
	ImportDuplicateInBatch             ImportReason = "duplicate_in_batch"
	ImportUnboundMatch                 ImportReason = "unbound_match"
	ImportDeleted                      ImportReason = "deleted"
	ImportIdentifierConflict           ImportReason = "identifier_conflict"
	ImportUsernameUnavailable          ImportReason = "username_unavailable"
	ImportProviderAlreadyLinked        ImportReason = "provider_already_linked"
	ImportProviderChangeRequiresUnlink ImportReason = "provider_change_requires_unlink"
	ImportInvalidID                    ImportReason = "invalid_id"
	ImportInvalidText                  ImportReason = "invalid_text"
	ImportInvalidPasswordHash          ImportReason = "invalid_password_hash"
	ImportInvalidBan                   ImportReason = "invalid_ban"
	ImportInvalidProvider              ImportReason = "invalid_provider"
	ImportInvalidDeletedAt             ImportReason = "invalid_deleted_at"
	// Solana link rows.
	ImportInvalidUserID           ImportReason = "invalid_user_id"
	ImportInvalidAddress          ImportReason = "invalid_address"
	ImportMissingSource           ImportReason = "missing_source"
	ImportMissingSourceID         ImportReason = "missing_source_id"
	ImportMissingUser             ImportReason = "missing_user"
	ImportAddressOwnedByOtherUser ImportReason = "address_owned_by_other_user"
	ImportAlreadyVerified         ImportReason = "already_verified"
	ImportAlreadyImported         ImportReason = "already_imported"
	ImportUserHasDifferentAddress ImportReason = "user_has_different_address"
	ImportProviderLinkConflict    ImportReason = "provider_link_conflict"
)

type ImportResult

type ImportResult struct {
	Rows     []ImportRow `json:"rows"`
	Inserted int         `json:"inserted"`
	Skipped  int         `json:"skipped"`
	Merged   int         `json:"merged"`
	Rejected int         `json:"rejected"`
}

ImportResult reports every row, in input order.

type ImportRow

type ImportRow struct {
	Index     int          `json:"index"`
	UserID    string       `json:"user_id"`
	MatchedBy ImportMatch  `json:"matched_by"`
	Status    ImportStatus `json:"status"`
	Reason    ImportReason `json:"reason"`
}

ImportRow is one row's outcome. Every row but a rejected one has UserID; skipped and merged rows say which identifier found the account, and Reason explains skipped and rejected rows.

type ImportSolanaLink struct {
	UserID          string
	Address         string
	Source          string
	SourceID        string
	SourceCreatedAt *time.Time
}

ImportSolanaLink reserves a legacy wallet address for an account. It is not a login method until the owner proves the wallet through Sign-In with Solana.

type ImportSolanaLinkRow

type ImportSolanaLinkRow struct {
	Index   int          `json:"index"`
	UserID  string       `json:"user_id"`
	Address string       `json:"address"`
	Status  ImportStatus `json:"status"`
	Reason  ImportReason `json:"reason"`
}

ImportSolanaLinkRow is one wallet row's outcome: inserted, skipped or rejected.

type ImportSolanaLinksResult

type ImportSolanaLinksResult struct {
	Rows     []ImportSolanaLinkRow `json:"rows"`
	Inserted int                   `json:"inserted"`
	Skipped  int                   `json:"skipped"`
	Rejected int                   `json:"rejected"`
}

ImportSolanaLinksResult reports every wallet row, in input order.

type ImportStatus

type ImportStatus string

ImportStatus is one row's outcome.

const (
	ImportInserted ImportStatus = "inserted"
	ImportSkipped  ImportStatus = "skipped"
	ImportMerged   ImportStatus = "merged"
	ImportRejected ImportStatus = "rejected"
)

type ImportUser

type ImportUser struct {
	// ID, when set, is the new account's id, or the existing account a Merge
	// updates. It must be a UUID.
	ID            string
	Email         string
	Phone         string
	Username      string
	EmailVerified bool
	PhoneVerified bool
	// PasswordHash is validated before it is stored; HashLegacyResetRequired
	// keeps any value and makes the account reset its password.
	PasswordHash *PasswordHash
	// Ban imports a ban as it stood: At is required, By (the banning
	// account) is optional.
	Ban       *BanState
	Metadata  map[string]any
	CreatedAt *time.Time
	UpdatedAt *time.Time
	LastLogin *time.Time
	// PreferredLanguage and AvatarURL are validated as UpdateUser validates them.
	PreferredLanguage string
	AvatarURL         string
	// DeletedAt, not in the future, imports the account as the system's
	// DeleteUsers at that time would have left it: the 30-day recovery window
	// runs from DeletedAt (RestoreUsers restores it, signing in does not), and
	// once the window has passed the account is purged after Deps.OnPurge.
	// Such rows need River, as DeleteUsers does.
	DeletedAt *time.Time
	// Providers are external identities the account signs in with, linked as
	// LinkProvider links them, at most one per issuer; Solana wallets import
	// only through ImportSolanaLinks. A row naming an identity another account
	// holds, or an earlier row of the batch names, is rejected with
	// "provider_already_linked".
	Providers []ProviderLink
}

ImportUser is one account to import. It finds an existing account by ID, Email, Phone or Username (canonical or a live alias); ImportOptions decides what happens then.

type Invitation

type Invitation struct {
	ID         string     `json:"id"`
	GroupID    string     `json:"group_id"`
	Role       Role       `json:"role"`
	Email      *string    `json:"email"`      // nil = a link, not an email invitation
	CreatedBy  *string    `json:"created_by"` // nil = issued by the system
	CreatedAt  time.Time  `json:"created_at"`
	ExpiresAt  *time.Time `json:"expires_at"`
	RedeemedAt *time.Time `json:"redeemed_at"`
	RevokedAt  *time.Time `json:"revoked_at"`
}

Invitation is an invitation's metadata, never its code. Email is "" for a link.

type InvitationCreated

type InvitationCreated struct {
	Invitation Invitation `json:"invitation"`
	Code       string     `json:"code"`
	URL        string     `json:"url"`
}

InvitationCreated is a new invitation with its code and link, shown this once; only the code's hash is stored.

type ListPage

type ListPage[T any] struct {
	Items []T
	Next  string
	Total *int
}

ListPage is one page of a list. Next is the opaque cursor of the following page; "" marks the last page. Total, when the query asked for it, counts every item of the whole list.

On the wire it is {"data": [...], "next_cursor": string|null, "total": number|null}: the one list envelope.

func (ListPage[T]) MarshalJSON added in v0.148.0

func (p ListPage[T]) MarshalJSON() ([]byte, error)

func (*ListPage[T]) UnmarshalJSON added in v0.148.0

func (p *ListPage[T]) UnmarshalJSON(b []byte) error

type MemberQuery

type MemberQuery struct {
	Kinds     []SubjectKind
	Roles     []Role
	LiveOnly  bool
	WithUsers bool
	Page      PageRequest
}

MemberQuery filters a group's members; empty filters match everything. LiveOnly keeps members that can act now: users not deleted, banned or reserved, applications enabled in a live group. WithUsers fills GroupMember.User, which carries contact details.

type Membership

type Membership struct {
	Group Group `json:"group"`
	Role  Role  `json:"role"`
}

Membership is a group a subject holds a role in.

type MessageKind

type MessageKind string

MessageKind names what an email or SMS is for. Later versions add kinds: a sender ignores or refuses a kind it does not know.

const (
	// MessageVerification proves a contact: Code, an optional Link, and
	// Purpose says what for.
	MessageVerification MessageKind = "verification"
	// MessageLoginCode is a second-factor sign-in Code.
	MessageLoginCode MessageKind = "login_code"
	// MessagePasswordReset carries a reset Link.
	MessagePasswordReset MessageKind = "password_reset"
	// MessageInvite invites an address to create an account (email only):
	// Link.
	MessageInvite MessageKind = "invite"
	// MessageWelcome follows a completed registration (email only).
	MessageWelcome MessageKind = "welcome"
	// MessageContactChanged goes to the address or number an account just
	// replaced: ContactChange.
	MessageContactChanged MessageKind = "contact_changed"
	// MessageDeviceKeyEnrolled tells an existing account that a device key can
	// now sign in as it (email only): DeviceKey.
	MessageDeviceKeyEnrolled MessageKind = "device_key_enrolled"
	// MessageMFAReset tells an account that the system removed its passkeys,
	// second factors and device keys and signed it out everywhere (email only).
	MessageMFAReset MessageKind = "mfa_reset"
)

type NameAdmissionRequest

type NameAdmissionRequest struct {
	UserID        string // Empty only before a new account is created.
	ActorID       string
	CurrentName   string
	RequestedName string
	Operation     NameOperation
}

NameAdmissionRequest is the username admission hook's operation context.

type NameOperation

type NameOperation string
const (
	NameCreate NameOperation = "create"
	NameRename NameOperation = "rename"
)

type NameResolution

type NameResolution struct {
	ID             string     `json:"id"`
	CanonicalName  string     `json:"canonical_name"`
	IsAlias        bool       `json:"is_alias"`
	AliasExpiresAt *time.Time `json:"alias_expires_at,omitempty"`
}

NameResolution always addresses one immutable owner. An alias points directly to that owner; CanonicalName reflects its current spelling, never an alias chain. AliasExpiresAt is nil for canonical names and permanent aliases; IsAlias tells them apart. Expired aliases are not resolutions.

type NewAPIKey

type NewAPIKey struct {
	Name      string
	Role      Role
	ExpiresAt *time.Time
}

NewAPIKey is the input of CreateAPIKey. ExpiresAt nil means no expiry, capped by Config.APIKeys.MaxTTL when set.

type NewGroup

type NewGroup struct {
	ID      string
	Persona Persona
	Owner   *Subject
}

NewGroup describes a group to create. ID, when set, is the group's id: any uuid the host keys the group by, such as its own row's id or a user's id. Creating an id that exists returns that group unchanged when it is a live group of Persona, and is iam.ErrGroupConflict otherwise. Owner, when set, is seeded with the owner role of a new group: a live account, or an enabled remote application.

type NewInvitation

type NewInvitation struct {
	Role      Role
	Email     string
	ExpiresAt *time.Time
}

NewInvitation is the input of CreateInvitation.

Without Email it is an invite link: a single-use code granting Role (which it needs) to the signed-in account that redeems it. With Email, AuthKit emails a registration invite to that address: with Role, registering also grants it, and an existing account that has verified the address may redeem it instead; without Role, in the root group, it only lets the address register (root:users:invite).

ExpiresAt nil is the default lifetime: 72 hours for a link, 7 days for an email invite. A link lives at most 30 days.

type NewUser

type NewUser struct {
	Email, Phone, Username, Password string
	EmailVerified, PhoneVerified     bool
}

NewUser creates a native account. Verified flags are the system's assertion that the address was proven elsewhere.

type OpResult

type OpResult struct {
	ID  string
	Err error
}

OpResult is the per-item outcome of a batch mutation (#219/#222): batch writes return one OpResult per requested ID so partial failure is expressible — a bare single error on a bulk write would hide which item failed. Err == nil means the item succeeded.

As JSON, Err marshals as its wire code (#197), so errors.Is against authkit sentinels survives the round-trip; a non-Error (or a 500) degrades to internal_error.

func (OpResult) MarshalJSON

func (r OpResult) MarshalJSON() ([]byte, error)

func (*OpResult) UnmarshalJSON

func (r *OpResult) UnmarshalJSON(b []byte) error

type PageRequest

type PageRequest struct {
	Cursor string
	Limit  int
}

PageRequest asks for one page of a keyset-paged list. Limit 0 means DefaultPageLimit; larger values are capped at MaxPageLimit.

func (PageRequest) PageLimit

func (p PageRequest) PageLimit() int

PageLimit is the effective limit.

type Passkey added in v0.148.0

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

Passkey is a WebAuthn credential an account signs in with.

type PasswordHash

type PasswordHash struct {
	Hash string   `json:"hash" yaml:"hash"`
	Algo HashAlgo `json:"algo" yaml:"algo"`
}

PasswordHash is a password hash made elsewhere (an import, a bootstrap manifest, UpdateUser), validated before it is stored.

type Perm

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

Perm is a permission `<persona>:<resource>:<action>` (`channel:posts:edit`) or a grant pattern, where `*` replaces the action (`channel:posts:*`) or everything after the persona (`channel:*`, the owner).

func (Perm) IsZero

func (p Perm) IsZero() bool

func (Perm) MarshalText

func (p Perm) MarshalText() ([]byte, error)

func (Perm) Matches

func (p Perm) Matches(grant Perm) bool

Matches reports whether grant authorizes p. A grant is a literal (`org:members:read`) or a pattern whose segments after the persona may be `*`: `org:*` covers every `org:` permission; any other pattern covers only permissions with as many segments (`org:members:*` covers `org:members:read`, not `org:members:read:x`). The persona is always literal, so a bare `*` matches nothing. When p is itself a pattern, Matches reports whether grant covers all of it. Malformed text on either side matches nothing, and nothing is trimmed. testdata/perm_vectors.json pins this rule.

func (Perm) Persona

func (p Perm) Persona() Persona

Persona returns the permission's first segment: its namespace.

func (Perm) String

func (p Perm) String() string

String is the permission's text, "" for the zero Perm.

func (*Perm) UnmarshalText

func (p *Perm) UnmarshalText(b []byte) error

UnmarshalText reads a permission or pattern: a persona, then one or more segments, each a name or `*` (catalogs use `<persona>:<resource>:<action>`; tokens from other platforms may not). Empty is the zero Perm.

type Persona

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

Persona is a type of permission group (`channel`, `org`, `merchant`). A permission group is one instance of a persona. root is the persona with exactly one group, the whole site. The persona is the first segment of every permission its groups use.

func RootPersona

func RootPersona() Persona

RootPersona is the persona with exactly one group, the whole site. It always exists.

func (Persona) IsZero

func (p Persona) IsZero() bool

func (Persona) MarshalText

func (p Persona) MarshalText() ([]byte, error)

func (Persona) OwnerGrant

func (p Persona) OwnerGrant() Perm

OwnerGrant is the owner's grant `<persona>:*`.

func (Persona) OwnerRole

func (p Persona) OwnerRole() Role

OwnerRole is the role every persona has: it holds the whole namespace, OwnerGrant. The root owner also holds every other persona's OwnerGrant.

func (Persona) String

func (p Persona) String() string

String is the persona's name, "" for the zero Persona.

func (*Persona) UnmarshalText

func (p *Persona) UnmarshalText(b []byte) error

UnmarshalText reads a persona name; empty is the zero Persona.

type ProviderLink struct {
	Issuer   string
	Subject  string
	Provider string
	Email    string
}

ProviderLink is an external identity to sign in with: the provider's issuer and subject, its configured name, and the email it reported.

type PublicUser

type PublicUser struct {
	ID        string  `json:"id"`
	Username  string  `json:"username"`
	AvatarURL *string `json:"avatar_url"`
	Deleted   bool    `json:"deleted"`
	// Metadata holds the account's metadata keys the host made public
	// (Config.PublicUserMetadata), and no others.
	Metadata map[string]any `json:"metadata"`
}

PublicUser is what other people may see of an account: never its contacts, ban or sign-in data. A deleted account is a tombstone: Deleted is set and every other field but ID is empty.

func (PublicUser) DisplayName

func (u PublicUser) DisplayName() string

DisplayName is the username, or "user-<first 8 of id>" for tombstoned and unnamed users.

type RegistrationMode

type RegistrationMode string

RegistrationMode is the public self-registration policy: open (anyone), invite_only (with an account invitation) or closed. Host operations (CreateUser, bootstrap, import) create users in every mode.

const (
	RegistrationModeOpen       RegistrationMode = "open"
	RegistrationModeInviteOnly RegistrationMode = "invite_only"
	RegistrationModeClosed     RegistrationMode = "closed"
)

type RegistrationVerificationPolicy

type RegistrationVerificationPolicy string

RegistrationVerificationPolicy controls whether a newly-registered contact must be verified.

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

type RemoteApplication

type RemoteApplication struct {
	ID string `json:"id"`
	// GroupID is the controlling group. Registration takes it from the group
	// the application is registered in, never from this field.
	GroupID string `json:"group_id"`
	Issuer  string `json:"issuer"`   // OIDC iss
	JWKSURI string `json:"jwks_uri"` // OIDC jwks_uri (jwks mode only)
	// Mode is the trust source; empty infers static from PublicKeys, else jwks.
	Mode RemoteApplicationMode `json:"mode"`
	// PublicKeys is the static-mode key list (empty in jwks mode).
	PublicKeys []RemoteApplicationKey `json:"public_keys"`
	Enabled    bool                   `json:"enabled"`
	// TrustRoot is what may change the application's keys: the system
	// (manual) or a credentials manager of its controlling group (user).
	// Never the keypair alone.
	TrustRoot   ApplicationTrustRoot `json:"trust_root"`
	Role        Role                 `json:"role"`
	Permissions []Perm               `json:"permissions"`
	CreatedAt   time.Time            `json:"created_at"`
	UpdatedAt   time.Time            `json:"updated_at"`
}

RemoteApplication is a registered federation principal: an external issuer AuthKit trusts to mint delegated and remote-application tokens. Role and Permissions are read, never written: its role in its controlling group and the permissions that role confers now (none when it needs MFA, which an application cannot present).

type RemoteApplicationKey

type RemoteApplicationKey struct {
	KID          string `json:"kid,omitempty" yaml:"kid,omitempty"`
	PublicKeyPEM string `json:"public_key_pem" yaml:"public_key_pem"`
}

RemoteApplicationKey is one entry of a static-mode principal's human-managed key list (stored as jsonb; edited like an authorized_keys file).

type RemoteApplicationMode

type RemoteApplicationMode string

RemoteApplicationMode is a remote application's one trust source:

jwks   — keys fetched and refreshed from JWKSURI; rotation is publishing a
         new kid at the same URL.
static — a human-managed PEM list for principals without a JWKS endpoint.
const (
	RemoteApplicationModeJWKS   RemoteApplicationMode = "jwks"
	RemoteApplicationModeStatic RemoteApplicationMode = "static"
)

type Role

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

Role is a role of a persona: its owner role or one the app declares (`moderator`). A role bundles permissions; where it is held is its scope, and a role held on root applies in every group. Its text form is `<persona>:<name>` (`channel:moderator`).

func (Role) IsOwner

func (r Role) IsOwner() bool

IsOwner reports whether r is its persona's owner role.

func (Role) IsZero

func (r Role) IsZero() bool

func (Role) MarshalText

func (r Role) MarshalText() ([]byte, error)

func (Role) Name

func (r Role) Name() string

Name is the role's name within its persona (`moderator`).

func (Role) Persona

func (r Role) Persona() Persona

Persona is the persona whose groups the role is held in.

func (Role) String

func (r Role) String() string

String is `<persona>:<name>`, "" for the zero Role.

func (*Role) UnmarshalText

func (r *Role) UnmarshalText(b []byte) error

UnmarshalText reads `<persona>:<name>`; empty is the zero Role.

type Route

type Route struct {
	Method     string
	Path       string
	Group      RouteGroup
	Auth       RouteAuthTier
	Permission string // `<persona>` stands for the group's persona
}

Route is one mounted endpoint. Path is the full net/http pattern path ("/api/v1/admin/users/{id}"); Auth and Permission describe the route-level gate, not every handler-specific authorization check.

func (Route) Pattern

func (r Route) Pattern() string

Pattern is the route's net/http ServeMux pattern: "GET /api/v1/me".

type RouteAuthTier

type RouteAuthTier string

RouteAuthTier is the authentication a route enforces before its handler runs.

const (
	AuthPublic     RouteAuthTier = "public"     // no principal
	AuthOptional   RouteAuthTier = "optional"   // principal used when present
	AuthRequired   RouteAuthTier = "required"   // valid principal
	AuthSession    RouteAuthTier = "session"    // valid principal whose session or device key is still active
	AuthPermission RouteAuthTier = "permission" // valid principal holding Route.Permission, its session checked
)

type RouteGroup

type RouteGroup string

RouteGroup names one capability of AuthKit's HTTP surface. Hosts mount the default groups or select exactly the ones they expose.

const (
	RouteAuth RouteGroup = "auth"
	// RouteDeviceKeys is the refreshless native-client login surface: email
	// enrollment plus Ed25519 challenge authentication.
	RouteDeviceKeys       RouteGroup = "device_keys"
	RouteRegistration     RouteGroup = "registration"
	RouteAccount          RouteGroup = "account"
	RouteAdmin            RouteGroup = "admin"
	RoutePermissionGroups RouteGroup = "groups"
	RouteBrowserOIDC      RouteGroup = "browser_oidc"
	// RouteDelegated is the delegated-token mint surface (POST
	// /delegated/token), mounted only when Config.Delegated declares audiences.
	RouteDelegated RouteGroup = "delegated"
)

type SMSMessage

type SMSMessage struct {
	Kind MessageKind
	To   string
	// Language is a two-letter code, chosen as for EmailMessage.
	Language      string
	Code          string
	Link          string
	Purpose       VerificationPurpose
	ContactChange *ContactChange
}

SMSMessage is one text message AuthKit asks Deps.SMS to deliver. Fields a kind does not use are empty.

type ServiceJWT

type ServiceJWT struct {
	Subject     string
	Audiences   []string
	Permissions []string
	// TTL defaults to, and is capped at, DefaultServiceJWTLifetime.
	TTL       time.Duration
	NotBefore time.Time
	IssuedAt  time.Time
	JTI       string
}

ServiceJWT is a first-party machine-to-machine JWT to mint. It grants nothing AuthKit enforces; the receiver authorizes its permissions.

type ServiceJWTClaims

type ServiceJWTClaims struct {
	Issuer      string    `json:"issuer"`
	Subject     string    `json:"subject"`
	Audiences   []string  `json:"audiences"`
	IssuedAt    time.Time `json:"issued_at"`
	NotBefore   time.Time `json:"not_before"`
	ExpiresAt   time.Time `json:"expires_at"`
	JTI         string    `json:"jti"`
	TokenUse    string    `json:"token_use"`
	Permissions []string  `json:"permissions"`
}

ServiceJWTClaims is the claim shape of a service JWT (iss, sub, aud, iat, nbf, exp, jti, token_use, permissions). Permissions are requested capabilities; receivers intersect them with their own grants.

type Session

type Session struct {
	ID         string     `json:"id"`
	CreatedAt  time.Time  `json:"created_at"`
	LastUsedAt time.Time  `json:"last_used_at"`
	ExpiresAt  *time.Time `json:"expires_at"`
	UserAgent  *string    `json:"user_agent"`
	IP         *string    `json:"ip"`
	Current    bool       `json:"current"`
}

Session is one refresh session on this deployment's issuer. Current marks the session behind the access token of the request that listed it.

type SessionEvent

type SessionEvent struct {
	Kind       SessionEventKind `json:"kind"`
	OccurredAt time.Time        `json:"occurred_at"`
	Issuer     string           `json:"issuer"`
	SessionID  *string          `json:"session_id"`
	Method     *string          `json:"method"`
	Reason     *string          `json:"reason"`
	IP         *string          `json:"ip"`
	UserAgent  *string          `json:"user_agent"`
}

SessionEvent is one entry of an account's sign-in and session history, kept for Config.SessionEventRetention. Method is how a session was created; Reason why one was revoked or a sign-in failed.

type SessionEventKind

type SessionEventKind string

SessionEventKind names an entry of an account's session history.

const (
	SessionEventCreated          SessionEventKind = "session_created"
	SessionEventFailed           SessionEventKind = "session_failed"
	SessionEventRevoked          SessionEventKind = "session_revoked"
	SessionEventPasswordChange   SessionEventKind = "password_changed"
	SessionEventPasswordRecovery SessionEventKind = "password_recovery"
	// SessionEventAccountSessionsRevoked is one account-wide revocation; each
	// session it ended has its own SessionEventRevoked.
	SessionEventAccountSessionsRevoked SessionEventKind = "account_sessions_revoked"
)

type SessionEventQuery

type SessionEventQuery struct {
	Kinds []SessionEventKind
	Page  PageRequest
}

SessionEventQuery pages an account's session history, newest first. No Kinds means every kind.

type SessionRef

type SessionRef struct {
	SessionID   string
	DeviceKeyID string
}

SessionRef names the sign-in an access token was minted from: a refresh session (claim sid) or a device key (claim device_key_id), never both.

func (SessionRef) IsZero

func (r SessionRef) IsZero() bool

IsZero reports whether r names no sign-in.

type SolanaNetwork

type SolanaNetwork string

SolanaNetwork is the chain Sign In With Solana signs for. The zero value leaves SIWS off.

const (
	SolanaMainnet SolanaNetwork = "mainnet"
	SolanaTestnet SolanaNetwork = "testnet"
	SolanaDevnet  SolanaNetwork = "devnet"
)

type Subject

type Subject struct {
	ID   string      `json:"id"`
	Kind SubjectKind `json:"kind"`
}

Subject is a principal that can hold roles in a permission group.

func RemoteApplicationSubject

func RemoteApplicationSubject(id string) Subject

func UserSubject

func UserSubject(id string) Subject

type SubjectKind

type SubjectKind string

SubjectKind discriminates who holds a role in a permission group.

const (
	SubjectKindUser              SubjectKind = "user"
	SubjectKindRemoteApplication SubjectKind = "remote_application"
)

type Token

type Token struct {
	Value     string    `json:"value"`
	ExpiresAt time.Time `json:"expires_at"`
}

Token is a signed token and the moment it expires.

type TokenSet

type TokenSet struct {
	AccessToken  string  `json:"access_token"`
	TokenType    string  `json:"token_type"`
	ExpiresIn    int64   `json:"expires_in"`
	RefreshToken *string `json:"refresh_token"`
}

TokenSet is the one session-token envelope. A session-establishing route returns it as the whole body, or under "token_set" when the response says more (registration, step-up, device keys, SIWS/passwordless extras). RefreshToken is nil when none is issued, or when the mount's refresh cookie carries it.

type TwoFactorMethod

type TwoFactorMethod string

TwoFactorMethod is one second-factor channel a host enables.

const (
	TwoFactorEmail TwoFactorMethod = "email"
	TwoFactorSMS   TwoFactorMethod = "sms"
	TwoFactorTOTP  TwoFactorMethod = "totp"
)

type TwoFactorMode

type TwoFactorMode string

TwoFactorMode is the host's account-wide 2FA enrollment policy.

const (
	// TwoFactorDisabled turns 2FA off entirely: no user enrollment/challenge/
	// verify routes are usable.
	TwoFactorDisabled TwoFactorMode = "disabled"
	// TwoFactorOptional lets users enroll a second factor if they choose; an
	// un-enrolled user is not blocked from normal session use.
	TwoFactorOptional TwoFactorMode = "optional"
	// TwoFactorRequired forces every user to enroll a second factor before normal
	// session use. Existing un-enrolled users are challenged on their next
	// authenticated request (the session, not just signup, is gated).
	TwoFactorRequired TwoFactorMode = "required"
)

type User

type User struct {
	ID                string     `json:"id"`
	Email             *string    `json:"email"`
	Phone             *string    `json:"phone_number"`
	Username          string     `json:"username"`
	EmailVerified     bool       `json:"email_verified"`
	PhoneVerified     bool       `json:"phone_verified"`
	PreferredLanguage *string    `json:"preferred_language"`
	AvatarURL         *string    `json:"avatar_url"`
	CreatedAt         time.Time  `json:"created_at"`
	UpdatedAt         time.Time  `json:"updated_at"`
	LastLogin         *time.Time `json:"last_login"`
	DeletedAt         *time.Time `json:"deleted_at"`
	// Ban is nil when no ban is in force.
	Ban *BanState `json:"ban"`
}

User is an account. It is the privileged view: it carries contact details, so render other people with PublicUser. A nil field is unset.

type UserDeletion

type UserDeletion struct {
	ID        string
	UserID    string
	DeletedAt time.Time
	PurgeAt   time.Time
}

UserDeletion identifies one recoverable account-deletion generation. Hooks are delivered at least once. Use ID together with the hook name as an idempotency key; deleting again after restoration creates a new ID.

type UserEntry

type UserEntry struct {
	User
	RootRole     *Role    `json:"root_role"`
	Entitlements []string `json:"entitlements"`
}

UserEntry is one row of the user directory: the account and its root role (nil when it holds none), and, when the query asks, its entitlements.

type UserKey

type UserKey string

UserKey names how a UserRef addresses an account.

const (
	UserKeyID       UserKey = "id"
	UserKeyEmail    UserKey = "email"
	UserKeyPhone    UserKey = "phone"
	UserKeyUsername UserKey = "username"
)

type UserQuery

type UserQuery struct {
	Search           string
	Status           UserStatus
	RootRole         Role
	Entitlement      string
	Sort             UserSort
	Desc             bool
	Total            bool
	WithEntitlements bool
	Page             PageRequest
}

UserQuery lists accounts. Search matches text within a username, email or phone (at its start, below three characters), an account id, or exactly a linked sign-in's subject (a wallet address, a provider's user id), provider email or provider username. RootRole filters on a role in the root group; Entitlement needs an entitlements provider that can list subjects. Total counts every match into ListPage.Total; WithEntitlements fills UserEntry.Entitlements from the entitlements provider.

type UserRef

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

UserRef addresses one account by exactly one key. Build it with UserByID, UserByEmail, UserByPhone or UserByUsername; the zero UserRef finds nobody.

func UserByEmail

func UserByEmail(email string) UserRef

func UserByID

func UserByID(id string) UserRef

func UserByPhone

func UserByPhone(phone string) UserRef

func UserByUsername

func UserByUsername(username string) UserRef

func (UserRef) IsZero

func (r UserRef) IsZero() bool

func (UserRef) Key

func (r UserRef) Key() UserKey

Key and Value are the addressing mode and its value.

func (UserRef) String

func (r UserRef) String() string

func (UserRef) Value

func (r UserRef) Value() string

type UserSort

type UserSort string

UserSort orders a user list; ties break on id.

const (
	UserSortCreatedAt UserSort = "created_at" // default
	UserSortLastLogin UserSort = "last_login"
	UserSortUsername  UserSort = "username"
	UserSortEmail     UserSort = "email"
)

type UserStatus

type UserStatus string

UserStatus filters a user list.

const (
	UserStatusLive    UserStatus = ""        // not deleted (default)
	UserStatusActive  UserStatus = "active"  // not deleted, not banned
	UserStatusBanned  UserStatus = "banned"  // not deleted, banned
	UserStatusDeleted UserStatus = "deleted" // soft-deleted
	UserStatusAny     UserStatus = "any"
)

type UserUpdate

type UserUpdate struct {
	Email, Phone, Username, AvatarURL, PreferredLanguage, Password *string
	EmailVerified, PhoneVerified                                   *bool
	PasswordHash                                                   *PasswordHash
}

UserUpdate changes an account; nil fields stay unchanged, and "" clears AvatarURL and PreferredLanguage. A new Email or Phone starts unverified unless the same update sets its verified flag.

type VerificationPurpose

type VerificationPurpose string

VerificationPurpose says what a MessageVerification proves the contact for.

const (
	PurposeSignup              VerificationPurpose = "signup"
	PurposeContactVerify       VerificationPurpose = "contact_verify"
	PurposeContactChange       VerificationPurpose = "contact_change"
	PurposePasswordlessLogin   VerificationPurpose = "passwordless_login"
	PurposeTwoFactorSetup      VerificationPurpose = "2fa_setup"
	PurposeDeviceKeyEnrollment VerificationPurpose = "device_key_enrollment"
)

Jump to

Keyboard shortcuts

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