Documentation
¶
Overview ¶
Package credbound provides transport-independent authentication, workspace authorization and instance-administration primitives for Go SaaS services: local accounts, self-service signup, TOTP and passkey factors, magic links, email OTP, password reset, PATs, server-side sessions, SSO linking with verified workspace domains and JIT provisioning, workspace RBAC, invitations, SCIM provisioning, an OAuth 2.1/OIDC authorization server for MCP resources, and a hash-chained audit log.
Credbound starts no HTTP server and issues no cookies or JWTs. The host service owns TLS, sessions, CSRF, throttling and UI; the optional oauthhttp and scimhttp packages provide mountable protocol handlers. The full contract lives in specs/API.md and specs/PRD.md in the module source.
Construction ¶
A Manager is built once from a validated Config:
auth, err := credbound.New(credbound.Config{
Store: store, // required persistence port
Passwords: passwords, // required Argon2id-style hasher
SecretKey: key, // exactly 32 bytes
PATPepper: patPepper, // at least 32 bytes
RecoveryPepper: recPepper, // at least 32 bytes
})
New rejects every invalid configuration invariant; cryptographic values have no weak fallback. Zero durations and limits fall back to safe defaults (10 minute step-up window, 12 character minimum password, 10 failed logins before a 15 minute lockout, and so on).
API map ¶
The Manager exposes every operation as a flat method set; this map groups the entry points by domain so the one you need is a search away:
- Sign-in: AuthenticatePassword, VerifyTOTP, Begin/FinishPasskeyAuthentication, Begin/FinishDiscoverablePasskeyAuthentication (usernameless), Begin/CompleteEmailAuthentication (magic link), Begin/CompleteEmailOTP, Begin/FinishSSO, AuthenticatePAT, SignUp.
- Passwords: ChangePassword, Begin/CompletePasswordReset.
- Second factor: Begin/ConfirmTOTPEnrollment, DisableTOTP, RegenerateRecoveryCodes, TOTPStatus; Begin/FinishPasskeyRegistration, DeletePasskey, Passkeys; AdminResetSecondFactor for total loss.
- Email addresses: BeginEmailAddition, ConfirmEmail, ResendEmailVerification, SetPrimaryEmail, RemoveEmail, Emails.
- Server-side sessions: CreateSession, AuthenticateSession, SignOut, Sessions, RevokeSession, RevokeUserSessions.
- SSO linking and domains: BeginSSOLink, BeginSSOStepUp, UnlinkSSO, SSOIdentities; CreateWorkspaceDomain, ConfirmWorkspaceDomain, UpdateWorkspaceDomainPolicy, RemoveWorkspaceDomain, WorkspaceDomains.
- PATs and revocation: CreatePAT, RevokePAT, PATs, WorkspacePATs, RevokeWorkspacePAT; RevokeUserCredentials.
- Tenant authorization: AuthorizePermission (canonical), Authorize, GrantRole, RequireStepUp.
- Lifecycle: Bootstrap, CreateUser, UpdateUser, Disable/EnableUser, CreateWorkspace, UpdateWorkspace, Disable/EnableWorkspace, AddMembership, SetMembershipStatus, RemoveMembership, the User, Workspace, Membership getters, and the Users, Workspaces, UserWorkspaces, Memberships listings.
- Invitations: InviteToWorkspace, AcceptInvitation, RegisterFromInvitation, RevokeInvitation, WorkspaceInvitations.
- Privacy (data-subject requests): ExportUserData, AnonymizeUser.
- Instance administration: AuthorizeAdmin, RequireAdminMutation, SetInstanceRole, RemoveInstanceRole, and the Admin* variants of user, workspace and profile mutations.
- SCIM provisioning: CreateSCIMConfiguration and the SCIM* resource operations (see scim.go).
- OAuth 2.1/OIDC server: the OAuth* operations (see oauth.go and the mountable oauthhttp package).
- Audit and extension: RecordAudit, AuditEvents, InstanceAuditEvents, VerifyAuditChain, VerifyAuditChainFrom, AddTransactionHook, AddEventListener.
Naming ¶
Multi-step flows follow one convention: Begin.../Finish... frame a ceremony whose opaque state round-trips through the caller (WebAuthn, SSO); Begin.../Complete... frame a flow finished by presenting a token or code (reset, magic link, email OTP, OAuth authorization); Confirm... proves possession to activate a pending resource (email addition, TOTP enrollment, workspace domains).
Operations exist at up to three privilege scopes, and the signature tells them apart: self-service methods take only the actor; workspace-scoped mutations authorize through workspace RBAC (AuthorizePermission); and instance-scoped administrative mutations — the Admin* methods plus Disable/EnableUser, SetInstanceRole, RemoveInstanceRole, RevokeUserSessions and RevokeUserCredentials on another account — take a TrustedRequest and demand an admin mutation (a fresh AAL2 step-up, or a TrustedRequest verified as loopback by the server adapter).
Authentication and sessions ¶
Authentication is a server-side capability describing who authenticated, how (Method), at what assurance level (AAL1 or AAL2) and when. It is returned by AuthenticatePassword, VerifyTOTP, FinishPasskeyAuthentication, FinishSSO, CompleteEmailAuthentication, CompleteEmailOTP and AuthenticatePAT. The host stores it in its own session and passes it back as the actor of later calls; it must never be rebuilt from fields supplied by a client, because every authorization decision trusts it.
A password or email login yields AAL1 and reports through SecondFactorRequired whether an active TOTP factor still has to be verified; VerifyTOTP upgrades the context to AAL2. A pending context is a first factor only: until VerifyTOTP completes it, every operation that requires a recent interactive authentication — registering a passkey, linking an SSO identity, re-enrolling TOTP, changing the password, adding an email address, and the other self-service operations — refuses it with ErrStepUpRequired, so a stolen password alone can never enroll a replacement second factor. Passkey authentication produces AAL2 directly. SSO yields AAL2 only when the provider carries a Config.SSOAssurance policy the asserted context satisfies (or that trusts the provider unverified); otherwise it is AAL1, because SSO never mints AAL2 on the IdP's unverified word. Sensitive operations demand a fresh interactive AAL2 context (RequireStepUp) and fail with ErrStepUpRequired otherwise. Config.StepUp relaxes that demand per family of operations — PAT, tenant mutations, account credentials, instance administration — for deployments whose users enroll no second factor; RequireStepUpFor answers what a given family demands of a given actor, so a host can prompt before the refusal. See StepUpPolicies. Administrative mutations use RequireAdminMutation, which may waive the step-up only for a TrustedRequest built from an actually observed loopback peer (see TrustedRequestFromAddr) — never from client-supplied headers.
Pagination ¶
List operations return iter.Seq2[PageEvent[T], error] with opaque cursors and a default limit of 50. Each page streams item events followed by a final page_end event, matching the NDJSON transport contract:
{"type":"item","data":{"id":"..."}}
{"type":"page_end","next_cursor":"opaque","has_more":true}
CollectPage drains one page into ([]T, PageEnd, error) for callers that want a slice and a cursor; streaming callers range over the sequence and forward each PageEvent.
Errors ¶
Failures map to sentinel errors compared with errors.Is: ErrInvalidCredentials, ErrUnauthorized, ErrForbidden, ErrStepUpRequired, ErrConflict, ErrNotFound, ErrNotSupported, ErrInvalidInput, ErrExpired, ErrLocked, ErrSSORequired, ErrDomainVerification, ErrAuditUnavailable, ErrAuditCompromised and ErrTransactionRejected. Two more are contracts between a security provider and the manager rather than manager-to-host results: ErrNoPasskey (a passkey provider reporting the user has none) and ErrPasskeyCloneDetected (a passkey provider rejecting a cloned authenticator; the caller still sees ErrInvalidCredentials). User-input validation failures additionally carry a *ValidationError{Field, Rule, Message} retrievable with errors.As; every ValidationError also satisfies errors.Is(err, ErrInvalidInput). HTTPStatus maps every sentinel to its canonical HTTP status code, so an HTTP adapter shares one table instead of maintaining its own errors.Is ladder. Public errors never contain secrets, and enumeration-sensitive flows (AuthenticatePassword, BeginPasswordReset, BeginEmailAuthentication, BeginEmailOTP) answer identically whether or not the account exists.
Extension points ¶
TransactionHook lets the host append its own writes to a Credbound mutation: hooks run inside the store transaction, after the mutation and before the audit write, so a hook error aborts the whole commit (ErrTransactionRejected). EventListener observes committed facts; listener errors are recorded for observability and never propagate. Both are registered in Config or later with AddTransactionHook and AddEventListener, and implementations embed UnimplementedTransactionHook or UnimplementedEventListener to stay compatible. A listener that also implements AnyEventListener additionally receives every event through the single OnAnyEvent method — the natural shape for an analytics feed or a webhook dispatcher. PasswordPolicy vets candidate passwords beyond the built-in length rules (for example against a breached-password corpus), and SSOProvider injects the network adapter for each registered identity provider.
Optional capabilities ¶
Config.TOTP and Config.Passkeys are optional providers; a Manager built without one reports ErrNotSupported from the corresponding enrollment, verification and ceremony operations. Store capabilities are detected by type assertion: SCIM provisioning requires SCIMStore, the OAuth server requires Config.OAuth plus OAuthStore, self-service signup requires Config.SignUp plus SignupStore, server-side sessions (CreateSession, AuthenticateSession, SignOut, device listing and the revocation cascade) require SessionStore, and verified workspace domains with JIT provisioning and domain-enforced SSO require DomainStore. Absent capabilities report ErrNotSupported without affecting anything else; the bundled memory, The PostgreSQL store implements all of them.
Audit ¶
Sensitive mutations and their audit events are committed atomically by the Store contract and fail closed when the audit cannot be persisted (ErrAuditUnavailable). Every audit event that identifies an actor is hash-chained to its predecessor with ComputeAuditHash; VerifyAuditChain recomputes the chain and reports tampering as ErrAuditCompromised. The chain write takes one instance-wide lock, so the rejection of a credential matching no record is deliberately left unchained (Commit.Unchained) and Config.PATTouchInterval coarsens the write of the machine-authentication path; both keep a hot path from serializing every tenant's mutations. WithRequestMetadata attaches the sanitized client IP address and user agent that audit events record.
Index ¶
- Constants
- Variables
- func ComputeAuditHash(previous []byte, event AuditEvent) []byte
- func CredentialFingerprint(hash string) []byte
- func HTTPRequestMetadata(next http.Handler, trustedProxies ...netip.Prefix) http.Handler
- func HTTPStatus(err error) int
- func WithRequestMetadata(ctx context.Context, metadata RequestMetadata) context.Context
- type ActorKind
- type AnyEventListener
- type AssuranceLevel
- type AuditChainCheckpoint
- type AuditChainReport
- type AuditEvent
- type AuditInput
- type AuditOutcome
- type AuditStore
- type AuditUnavailableEvent
- type AuthMethod
- type Authentication
- type AuthenticationEvent
- type AuthenticationFailureEvent
- type AuthorizationDeniedEvent
- type BeginOAuthAuthorizationInput
- type BootstrapCompletedEvent
- type BootstrapInput
- type CeremonyConsumption
- type ClientAuditRecord
- type ClientAuditRecordedEvent
- type Commit
- type Config
- type CreateOAuthInitialAccessTokenInput
- type CreateOAuthIssuerInput
- type CreateOAuthProtectedResourceInput
- type CreatePATInput
- type CreateSCIMConfigurationInput
- type CreateSessionInput
- type CreateUserInput
- type CreateWorkspaceInput
- type DiscoverablePasskeyProvider
- type DomainStore
- type DomainVerifier
- type EmailAddedEvent
- type EmailAddition
- type EmailAddress
- type EmailAuthenticationCredential
- type EmailAuthenticationRequestedEvent
- type EmailAuthenticationStore
- type EmailConfirmation
- type EmailConfirmedEvent
- type EmailRemoval
- type EmailRemovedEvent
- type EmailStore
- type EmailThrottleStore
- type EmailVerificationCredential
- type EmailVerificationResentEvent
- type EventListener
- type EventMeta
- type EventName
- type ExchangeOAuthAuthorizationCodeInput
- type IdentityStore
- type InstanceAdministrator
- type InstanceRole
- type InstanceRoleChange
- type InstanceRoleChangedEvent
- type InstanceRoleRemoval
- type InstanceRoleRemovedEvent
- type InvitationStore
- type InviteToWorkspaceInput
- type IssuedEmailAuthentication
- type IssuedEmailOTP
- type IssuedEmailVerification
- type IssuedOAuthClient
- type IssuedOAuthInitialAccessToken
- type IssuedPAT
- type IssuedPasswordReset
- type IssuedSCIMCredential
- type IssuedSession
- type IssuedWorkspaceDomain
- type IssuedWorkspaceInvitation
- type LoginThrottle
- type Manager
- func (m *Manager) AcceptInvitation(ctx context.Context, actor Authentication, raw string) (_ Membership, err error)
- func (m *Manager) AddEventListener(listener EventListener) Subscription
- func (m *Manager) AddMembership(ctx context.Context, actor Authentication, workspaceID, userID UUID, role Role) (Membership, error)
- func (m *Manager) AddTransactionHook(hook TransactionHook) Subscription
- func (m *Manager) AdminDisableWorkspace(ctx context.Context, actor Authentication, request TrustedRequest, ...) error
- func (m *Manager) AdminEnableWorkspace(ctx context.Context, actor Authentication, request TrustedRequest, ...) error
- func (m *Manager) AdminResetSecondFactor(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID) (err error)
- func (m *Manager) AdminUpdateUser(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID, ...) (user User, err error)
- func (m *Manager) AdminUpdateWorkspace(ctx context.Context, actor Authentication, request TrustedRequest, ...) (_ Workspace, err error)
- func (m *Manager) AdoptSCIMUser(ctx context.Context, actor Authentication, configurationID, userID UUID, ...) (_ SCIMUser, err error)
- func (m *Manager) AnonymizeUser(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID) (err error)
- func (m *Manager) AuditEvents(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[AuditEvent], error]
- func (m *Manager) AuthenticateOAuthAccessToken(ctx context.Context, resourceURI, raw string) (_ OAuthAuthentication, err error)
- func (m *Manager) AuthenticatePAT(ctx context.Context, raw string) (_ Authentication, err error)
- func (m *Manager) AuthenticatePassword(ctx context.Context, email, password string) (_ Authentication, err error)
- func (m *Manager) AuthenticateSCIM(ctx context.Context, raw string) (_ SCIMAuthentication, err error)
- func (m *Manager) AuthenticateSession(ctx context.Context, raw string) (_ Authentication, _ Session, err error)
- func (m *Manager) Authorize(ctx context.Context, authn Authentication, workspaceID UUID, minimumRole Role) error
- func (m *Manager) AuthorizeAdmin(ctx context.Context, actor Authentication, permission Permission) error
- func (m *Manager) AuthorizePermission(ctx context.Context, authn Authentication, workspaceID UUID, ...) error
- func (m *Manager) BeginDiscoverablePasskeyAuthentication(ctx context.Context) (_ PasskeyChallenge, err error)
- func (m *Manager) BeginEmailAddition(ctx context.Context, actor Authentication, address string) (_ IssuedEmailVerification, err error)
- func (m *Manager) BeginEmailAuthentication(ctx context.Context, email string) (_ IssuedEmailAuthentication, err error)
- func (m *Manager) BeginEmailOTP(ctx context.Context, email string) (_ IssuedEmailOTP, err error)
- func (m *Manager) BeginOAuthAuthorization(ctx context.Context, actor Authentication, input BeginOAuthAuthorizationInput) (_ OAuthConsent, err error)
- func (m *Manager) BeginPasskeyAuthentication(ctx context.Context, email string) (_ PasskeyChallenge, err error)
- func (m *Manager) BeginPasskeyRegistration(ctx context.Context, actor Authentication, name string) (_ PasskeyChallenge, err error)
- func (m *Manager) BeginPasswordReset(ctx context.Context, email string) (_ IssuedPasswordReset, err error)
- func (m *Manager) BeginSSO(ctx context.Context, providerConfigurationID UUID) (SSOChallenge, error)
- func (m *Manager) BeginSSOLink(ctx context.Context, actor Authentication, providerConfigurationID UUID) (SSOChallenge, error)
- func (m *Manager) BeginSSOStepUp(ctx context.Context, actor Authentication, providerConfigurationID UUID) (SSOChallenge, error)
- func (m *Manager) BeginTOTPEnrollment(ctx context.Context, actor Authentication) (_ TOTPEnrollment, err error)
- func (m *Manager) Bootstrap(ctx context.Context, input BootstrapInput) (_ Authentication, _ Workspace, err error)
- func (m *Manager) ChangePassword(ctx context.Context, actor Authentication, currentPassword, newPassword string) (err error)
- func (m *Manager) CompleteEmailAuthentication(ctx context.Context, raw string) (_ Authentication, err error)
- func (m *Manager) CompleteEmailOTP(ctx context.Context, continuation, code string) (_ Authentication, err error)
- func (m *Manager) CompleteOAuthAuthorization(ctx context.Context, actor Authentication, rawContinuation string, ...) (_ OAuthAuthorizationResult, err error)
- func (m *Manager) CompletePasswordReset(ctx context.Context, raw, newPassword string) (_ User, err error)
- func (m *Manager) ConfirmEmail(ctx context.Context, raw string) (_ EmailAddress, err error)
- func (m *Manager) ConfirmTOTPEnrollment(ctx context.Context, actor Authentication, code string) (_ []string, err error)
- func (m *Manager) ConfirmWorkspaceDomain(ctx context.Context, actor Authentication, domainID UUID) (err error)
- func (m *Manager) CreateOAuthInitialAccessToken(ctx context.Context, actor Authentication, request TrustedRequest, ...) (_ IssuedOAuthInitialAccessToken, err error)
- func (m *Manager) CreateOAuthIssuer(ctx context.Context, actor Authentication, request TrustedRequest, ...) (_ OAuthIssuer, err error)
- func (m *Manager) CreateOAuthProtectedResource(ctx context.Context, actor Authentication, workspaceID UUID, ...) (_ OAuthProtectedResource, err error)
- func (m *Manager) CreatePAT(ctx context.Context, actor Authentication, input CreatePATInput) (_ IssuedPAT, err error)
- func (m *Manager) CreateSCIMConfiguration(ctx context.Context, actor Authentication, workspaceID UUID, ...) (_ IssuedSCIMCredential, err error)
- func (m *Manager) CreateSession(ctx context.Context, actor Authentication, _ CreateSessionInput) (_ IssuedSession, err error)
- func (m *Manager) CreateUser(ctx context.Context, actor Authentication, workspaceID UUID, ...) (_ User, err error)
- func (m *Manager) CreateWorkspace(ctx context.Context, actor Authentication, input CreateWorkspaceInput) (_ Workspace, err error)
- func (m *Manager) CreateWorkspaceDomain(ctx context.Context, actor Authentication, workspaceID UUID, domain string) (_ IssuedWorkspaceDomain, err error)
- func (m *Manager) DeletePasskey(ctx context.Context, actor Authentication, passkeyID UUID) (err error)
- func (m *Manager) DeleteSCIMGroup(ctx context.Context, principal SCIMAuthentication, id UUID) (err error)
- func (m *Manager) DeprovisionSCIMUser(ctx context.Context, principal SCIMAuthentication, id UUID) (err error)
- func (m *Manager) DisableOAuthClient(ctx context.Context, actor Authentication, request TrustedRequest, ...) error
- func (m *Manager) DisableOAuthIssuer(ctx context.Context, actor Authentication, request TrustedRequest, ...) error
- func (m *Manager) DisableOAuthProtectedResource(ctx context.Context, actor Authentication, workspaceID UUID, resourceID UUID) error
- func (m *Manager) DisableSCIMConfiguration(ctx context.Context, actor Authentication, configurationID UUID) (err error)
- func (m *Manager) DisableTOTP(ctx context.Context, actor Authentication, code string) (err error)
- func (m *Manager) DisableUser(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID) error
- func (m *Manager) DisableWorkspace(ctx context.Context, actor Authentication, workspaceID UUID) error
- func (m *Manager) Emails(ctx context.Context, actor Authentication, userID UUID, page PageRequest) iter.Seq2[PageEvent[EmailAddress], error]
- func (m *Manager) EnableOAuthClient(ctx context.Context, actor Authentication, request TrustedRequest, ...) error
- func (m *Manager) EnableOAuthIssuer(ctx context.Context, actor Authentication, request TrustedRequest, ...) error
- func (m *Manager) EnableOAuthProtectedResource(ctx context.Context, actor Authentication, workspaceID UUID, resourceID UUID) error
- func (m *Manager) EnableUser(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID) error
- func (m *Manager) EnableWorkspace(ctx context.Context, actor Authentication, workspaceID UUID) error
- func (m *Manager) ExchangeOAuthAuthorizationCode(ctx context.Context, input ExchangeOAuthAuthorizationCodeInput) (_ OAuthTokenResponse, err error)
- func (m *Manager) ExportUserData(ctx context.Context, actor Authentication, userID UUID) (_ UserDataExport, err error)
- func (m *Manager) FinishDiscoverablePasskeyAuthentication(ctx context.Context, continuation string, response []byte) (_ Authentication, err error)
- func (m *Manager) FinishPasskeyAuthentication(ctx context.Context, continuation string, response []byte) (_ Authentication, err error)
- func (m *Manager) FinishPasskeyRegistration(ctx context.Context, actor Authentication, continuation string, ...) (_ Passkey, err error)
- func (m *Manager) FinishSSO(ctx context.Context, continuation string, response []byte) (_ Authentication, err error)
- func (m *Manager) GrantRole(ctx context.Context, actor Authentication, workspaceID, userID UUID, role Role) (err error)
- func (m *Manager) InstanceAdministrator(ctx context.Context, actor Authentication, userID UUID) (InstanceAdministrator, error)
- func (m *Manager) InstanceAdministrators(ctx context.Context, actor Authentication) iter.Seq2[InstanceAdministrator, error]
- func (m *Manager) InstanceAuditEvents(ctx context.Context, actor Authentication, page PageRequest) iter.Seq2[PageEvent[AuditEvent], error]
- func (m *Manager) InviteToWorkspace(ctx context.Context, actor Authentication, workspaceID UUID, ...) (_ IssuedWorkspaceInvitation, err error)
- func (m *Manager) IssueOAuthClientCredentials(ctx context.Context, input OAuthClientCredentialsInput) (_ OAuthTokenResponse, err error)
- func (m *Manager) Membership(ctx context.Context, actor Authentication, workspaceID, userID UUID) (Membership, error)
- func (m *Manager) Memberships(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[Membership], error]
- func (m *Manager) OAuthAuthorizationServerMetadata(ctx context.Context, issuerURL string) (OAuthAuthorizationServerMetadata, error)
- func (m *Manager) OAuthClients(ctx context.Context, actor Authentication, issuerID UUID, page PageRequest) iter.Seq2[PageEvent[OAuthClient], error]
- func (m *Manager) OAuthGrants(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[OAuthGrant], error]
- func (m *Manager) OAuthInitialAccessTokens(ctx context.Context, actor Authentication, issuerID UUID) iter.Seq2[OAuthInitialAccessToken, error]
- func (m *Manager) OAuthIssuers(ctx context.Context, actor Authentication, page PageRequest) iter.Seq2[PageEvent[OAuthIssuer], error]
- func (m *Manager) OAuthJWKS(ctx context.Context, issuerURL string) ([]byte, error)
- func (m *Manager) OAuthProtectedResourceMetadata(ctx context.Context, resourceURI string) (OAuthProtectedResourceMetadata, error)
- func (m *Manager) OAuthProtectedResources(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[OAuthProtectedResource], error]
- func (m *Manager) OAuthUserInfo(ctx context.Context, issuerURL, rawAccessToken string) (OIDCUserInfo, error)
- func (m *Manager) PATs(ctx context.Context, actor Authentication, userID UUID, page PageRequest) iter.Seq2[PageEvent[PAT], error]
- func (m *Manager) Passkeys(ctx context.Context, actor Authentication, userID UUID) iter.Seq2[Passkey, error]
- func (m *Manager) PreRegisterOAuthClient(ctx context.Context, actor Authentication, request TrustedRequest, ...) (_ IssuedOAuthClient, err error)
- func (m *Manager) ProvisionSCIMUser(ctx context.Context, principal SCIMAuthentication, input SCIMUserInput) (_ SCIMUser, err error)
- func (m *Manager) RecordAudit(ctx context.Context, actor Authentication, input AuditInput) (err error)
- func (m *Manager) RefreshOAuthToken(ctx context.Context, input RefreshOAuthTokenInput) (_ OAuthTokenResponse, err error)
- func (m *Manager) RegenerateRecoveryCodes(ctx context.Context, actor Authentication) (_ []string, err error)
- func (m *Manager) RegisterFromInvitation(ctx context.Context, raw string, input RegisterFromInvitationInput) (_ Authentication, _ User, err error)
- func (m *Manager) RegisterOAuthClient(ctx context.Context, issuerURL, initialAccessToken string, ...) (_ IssuedOAuthClient, err error)
- func (m *Manager) RemoveEmail(ctx context.Context, actor Authentication, emailID UUID) (err error)
- func (m *Manager) RemoveInstanceRole(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID) (err error)
- func (m *Manager) RemoveMembership(ctx context.Context, actor Authentication, workspaceID, userID UUID) (err error)
- func (m *Manager) RemoveWorkspaceDomain(ctx context.Context, actor Authentication, domainID UUID) (err error)
- func (m *Manager) ReplaceOAuthClientJWKS(ctx context.Context, actor Authentication, request TrustedRequest, ...) (err error)
- func (m *Manager) ReplaceSCIMUser(ctx context.Context, principal SCIMAuthentication, id UUID, ...) (_ SCIMUser, err error)
- func (m *Manager) RequireAdminMutation(actor Authentication, request TrustedRequest) error
- func (m *Manager) RequireStepUp(authn Authentication) error
- func (m *Manager) RequireStepUpFor(ctx context.Context, authn Authentication, scope StepUpScope) error
- func (m *Manager) ResendEmailVerification(ctx context.Context, address string) (_ IssuedEmailVerification, err error)
- func (m *Manager) RevokeInvitation(ctx context.Context, actor Authentication, workspaceID, invitationID UUID) (err error)
- func (m *Manager) RevokeOAuthGrant(ctx context.Context, actor Authentication, grantID UUID) (err error)
- func (m *Manager) RevokeOAuthInitialAccessToken(ctx context.Context, actor Authentication, request TrustedRequest, ...) (err error)
- func (m *Manager) RevokeOAuthToken(ctx context.Context, input RevokeOAuthTokenInput) (err error)
- func (m *Manager) RevokePAT(ctx context.Context, actor Authentication, patID UUID) (err error)
- func (m *Manager) RevokeSCIMCredential(ctx context.Context, actor Authentication, configurationID UUID, ...) (err error)
- func (m *Manager) RevokeSession(ctx context.Context, actor Authentication, sessionID UUID) (err error)
- func (m *Manager) RevokeUserCredentials(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID) (err error)
- func (m *Manager) RevokeUserSessions(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID) (err error)
- func (m *Manager) RevokeWorkspacePAT(ctx context.Context, actor Authentication, workspaceID, patID UUID) (err error)
- func (m *Manager) RotateOAuthClientSecret(ctx context.Context, actor Authentication, request TrustedRequest, ...) (_ IssuedOAuthClient, err error)
- func (m *Manager) RotateSCIMCredential(ctx context.Context, actor Authentication, configurationID UUID, ...) (_ IssuedSCIMCredential, err error)
- func (m *Manager) SCIMConfigurations(ctx context.Context, actor Authentication, workspaceID UUID) iter.Seq2[SCIMConfiguration, error]
- func (m *Manager) SCIMCredentials(ctx context.Context, actor Authentication, configurationID UUID) iter.Seq2[SCIMCredential, error]
- func (m *Manager) SCIMGroup(ctx context.Context, principal SCIMAuthentication, id UUID) (SCIMGroup, error)
- func (m *Manager) SCIMGroups(ctx context.Context, principal SCIMAuthentication, filter SCIMFilter, ...) iter.Seq2[PageEvent[SCIMGroup], error]
- func (m *Manager) SCIMUser(ctx context.Context, principal SCIMAuthentication, id UUID) (SCIMUser, error)
- func (m *Manager) SCIMUsers(ctx context.Context, principal SCIMAuthentication, filter SCIMFilter, ...) iter.Seq2[PageEvent[SCIMUser], error]
- func (m *Manager) SSOIdentities(ctx context.Context, actor Authentication, userID UUID, page PageRequest) iter.Seq2[PageEvent[SSOIdentity], error]
- func (m *Manager) Sessions(ctx context.Context, actor Authentication, userID UUID, page PageRequest) iter.Seq2[PageEvent[Session], error]
- func (m *Manager) SetInstanceRole(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID, ...) (err error)
- func (m *Manager) SetMembershipStatus(ctx context.Context, actor Authentication, workspaceID, userID UUID, ...) (Membership, error)
- func (m *Manager) SetPrimaryEmail(ctx context.Context, actor Authentication, emailID UUID) (err error)
- func (m *Manager) SignOut(ctx context.Context, raw string) (err error)
- func (m *Manager) SignUp(ctx context.Context, input SignUpInput) (_ SignUpResult, err error)
- func (m *Manager) TOTPStatus(ctx context.Context, actor Authentication, userID UUID) (_ TOTPStatus, err error)
- func (m *Manager) UnlinkSSO(ctx context.Context, actor Authentication, identityID UUID) (err error)
- func (m *Manager) UpdateOAuthIssuer(ctx context.Context, actor Authentication, request TrustedRequest, ...) (_ OAuthIssuer, err error)
- func (m *Manager) UpdateSCIMConfiguration(ctx context.Context, actor Authentication, configurationID UUID, ...) (_ SCIMConfiguration, err error)
- func (m *Manager) UpdateUser(ctx context.Context, actor Authentication, input UpdateUserInput) (user User, err error)
- func (m *Manager) UpdateWorkspace(ctx context.Context, actor Authentication, workspaceID UUID, ...) (_ Workspace, err error)
- func (m *Manager) UpdateWorkspaceDomainPolicy(ctx context.Context, actor Authentication, domainID UUID, ...) (err error)
- func (m *Manager) UpsertSCIMGroup(ctx context.Context, principal SCIMAuthentication, id UUID, ...) (_ SCIMGroup, err error)
- func (m *Manager) User(ctx context.Context, actor Authentication, userID UUID) (User, error)
- func (m *Manager) UserWorkspaces(ctx context.Context, actor Authentication, page PageRequest) iter.Seq2[PageEvent[Workspace], error]
- func (m *Manager) Users(ctx context.Context, actor Authentication, page PageRequest) iter.Seq2[PageEvent[User], error]
- func (m *Manager) ValidateOAuthAuthorizationRedirect(ctx context.Context, issuerURL, clientID, redirectURI string) error
- func (m *Manager) VerifyAuditChain(ctx context.Context, actor Authentication) (_ AuditChainReport, err error)
- func (m *Manager) VerifyAuditChainFrom(ctx context.Context, actor Authentication, checkpoint AuditChainCheckpoint) (_ AuditChainReport, err error)
- func (m *Manager) VerifyTOTP(ctx context.Context, actor Authentication, code string) (_ Authentication, err error)
- func (m *Manager) Workspace(ctx context.Context, actor Authentication, workspaceID UUID) (Workspace, error)
- func (m *Manager) WorkspaceDomains(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[WorkspaceDomain], error]
- func (m *Manager) WorkspaceInvitations(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[WorkspaceInvitation], error]
- func (m *Manager) WorkspaceMembers(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[WorkspaceMember], error]
- func (m *Manager) WorkspacePATs(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[WorkspacePAT], error]
- func (m *Manager) Workspaces(ctx context.Context, actor Authentication, page PageRequest) iter.Seq2[PageEvent[Workspace], error]
- type Membership
- type MembershipChange
- type MembershipChangedEvent
- type MembershipStatus
- type OAuthAccessToken
- type OAuthApplicationType
- type OAuthAuthentication
- type OAuthAuthorizationCode
- type OAuthAuthorizationResult
- type OAuthAuthorizationServerMetadata
- type OAuthCIMDMode
- type OAuthChange
- type OAuthClient
- type OAuthClientAccessToken
- type OAuthClientAssertionVerifier
- type OAuthClientCredentialsInput
- type OAuthClientMetadataDocument
- type OAuthClientMetadataFetcher
- type OAuthClientRegistrationInput
- type OAuthClientSource
- type OAuthConfig
- type OAuthConsent
- type OAuthConsentScope
- type OAuthDCRMode
- type OAuthEvent
- type OAuthGrant
- type OAuthInitialAccessToken
- type OAuthIssuer
- type OAuthProtectedResource
- type OAuthProtectedResourceMetadata
- type OAuthRefreshToken
- type OAuthScopeDefinition
- type OAuthStore
- type OAuthTokenEndpointAuthMethod
- type OAuthTokenKind
- type OAuthTokenResponse
- type OIDCClaims
- type OIDCSigner
- type OIDCUserInfo
- type Observer
- type Operation
- type PAT
- type PATAuthenticatedEvent
- type PATCreatedEvent
- type PATCreation
- type PATRejectedEvent
- type PATRevocation
- type PATRevokedEvent
- type PATStore
- type PageEnd
- type PageEvent
- type PageRequest
- type Passkey
- type PasskeyAuthenticatedEvent
- type PasskeyChallenge
- type PasskeyCredentialStore
- type PasskeyDeletedEvent
- type PasskeyDeletion
- type PasskeyProvider
- type PasskeyRegisteredEvent
- type PasskeyRegistration
- type PasskeyStore
- type PasskeyUser
- type PasskeyUserLookup
- type PasswordChange
- type PasswordChangedEvent
- type PasswordCredential
- type PasswordHasher
- type PasswordPolicy
- type PasswordRehashedEvent
- type PasswordResetCompletedEvent
- type PasswordResetCredential
- type PasswordResetRequestedEvent
- type PasswordResetStore
- type Permission
- type PrimaryEmailChange
- type PrimaryEmailChangedEvent
- type PrivacyStore
- type RecoveryCode
- type RecoveryCodeConsumedEvent
- type RecoveryCodeRegeneration
- type RecoveryCodesRegeneratedEvent
- type RefreshOAuthTokenInput
- type RegisterFromInvitationInput
- type RequestMetadata
- type RevocationStore
- type RevokeOAuthTokenInput
- type Role
- type RoleDefinition
- type RoleGrant
- type RoleGrantedEvent
- type SCIMAuthentication
- type SCIMConfiguration
- type SCIMConfigurationChange
- type SCIMConfigurationCreatedEvent
- type SCIMCredential
- type SCIMEmail
- type SCIMFilter
- type SCIMGroup
- type SCIMGroupChange
- type SCIMGroupEvent
- type SCIMGroupInput
- type SCIMGroupRoleMapping
- type SCIMStore
- type SCIMUser
- type SCIMUserChange
- type SCIMUserEvent
- type SCIMUserInput
- type SSOAssurancePolicy
- type SSOAuthenticatedEvent
- type SSOChallenge
- type SSOChallengeIssuedEvent
- type SSOClaims
- type SSOIdentity
- type SSOJITProvisionedEvent
- type SSOLink
- type SSOLinkStore
- type SSOLinkedEvent
- type SSOProvider
- type SSOProviderChallenge
- type SSOProviderKind
- type SSORequest
- type SSOUnlink
- type SSOUnlinkedEvent
- type SecondFactorReset
- type SecondFactorResetEvent
- type Session
- type SessionCreatedEvent
- type SessionCreation
- type SessionRevocation
- type SessionRevokedEvent
- type SessionStore
- type SignUpCompletedEvent
- type SignUpConfig
- type SignUpInput
- type SignUpResult
- type SignupStore
- type StepUpDeniedEvent
- type StepUpPolicies
- type StepUpPolicy
- type StepUpScope
- type Store
- type StoreKind
- type Subscription
- type TOTPActivatedEvent
- type TOTPActivation
- type TOTPDisable
- type TOTPDisabledEvent
- type TOTPEnrollment
- type TOTPEnrollmentChange
- type TOTPEnrollmentStartedEvent
- type TOTPFactor
- type TOTPProvider
- type TOTPReplayRejectedEvent
- type TOTPStatus
- type TOTPStore
- type TOTPVerifiedEvent
- type TransactionHook
- type TrustedRequest
- type Tx
- type UUID
- type UnimplementedEventListener
- func (UnimplementedEventListener) OnAuditUnavailable(context.Context, AuditUnavailableEvent) error
- func (UnimplementedEventListener) OnAuthenticationFailed(context.Context, AuthenticationFailureEvent) error
- func (UnimplementedEventListener) OnAuthenticationSucceeded(context.Context, AuthenticationEvent) error
- func (UnimplementedEventListener) OnAuthorizationDenied(context.Context, AuthorizationDeniedEvent) error
- func (UnimplementedEventListener) OnBootstrapCompleted(context.Context, BootstrapCompletedEvent) error
- func (UnimplementedEventListener) OnClientAuditRecorded(context.Context, ClientAuditRecordedEvent) error
- func (UnimplementedEventListener) OnEmailAdded(context.Context, EmailAddedEvent) error
- func (UnimplementedEventListener) OnEmailAuthenticationRequested(context.Context, EmailAuthenticationRequestedEvent) error
- func (UnimplementedEventListener) OnEmailConfirmed(context.Context, EmailConfirmedEvent) error
- func (UnimplementedEventListener) OnEmailRemoved(context.Context, EmailRemovedEvent) error
- func (UnimplementedEventListener) OnEmailVerificationResent(context.Context, EmailVerificationResentEvent) error
- func (UnimplementedEventListener) OnInstanceRoleChanged(context.Context, InstanceRoleChangedEvent) error
- func (UnimplementedEventListener) OnInstanceRoleRemoved(context.Context, InstanceRoleRemovedEvent) error
- func (UnimplementedEventListener) OnMembershipChanged(context.Context, MembershipChangedEvent) error
- func (UnimplementedEventListener) OnOAuthEvent(context.Context, OAuthEvent) error
- func (UnimplementedEventListener) OnPATAuthenticated(context.Context, PATAuthenticatedEvent) error
- func (UnimplementedEventListener) OnPATCreated(context.Context, PATCreatedEvent) error
- func (UnimplementedEventListener) OnPATRejected(context.Context, PATRejectedEvent) error
- func (UnimplementedEventListener) OnPATRevoked(context.Context, PATRevokedEvent) error
- func (UnimplementedEventListener) OnPasskeyAuthenticated(context.Context, PasskeyAuthenticatedEvent) error
- func (UnimplementedEventListener) OnPasskeyDeleted(context.Context, PasskeyDeletedEvent) error
- func (UnimplementedEventListener) OnPasskeyRegistered(context.Context, PasskeyRegisteredEvent) error
- func (UnimplementedEventListener) OnPasswordChanged(context.Context, PasswordChangedEvent) error
- func (UnimplementedEventListener) OnPasswordRehashed(context.Context, PasswordRehashedEvent) error
- func (UnimplementedEventListener) OnPasswordResetCompleted(context.Context, PasswordResetCompletedEvent) error
- func (UnimplementedEventListener) OnPasswordResetRequested(context.Context, PasswordResetRequestedEvent) error
- func (UnimplementedEventListener) OnPrimaryEmailChanged(context.Context, PrimaryEmailChangedEvent) error
- func (UnimplementedEventListener) OnRecoveryCodeConsumed(context.Context, RecoveryCodeConsumedEvent) error
- func (UnimplementedEventListener) OnRecoveryCodesRegenerated(context.Context, RecoveryCodesRegeneratedEvent) error
- func (UnimplementedEventListener) OnRoleGranted(context.Context, RoleGrantedEvent) error
- func (UnimplementedEventListener) OnSCIMConfigurationCreated(context.Context, SCIMConfigurationCreatedEvent) error
- func (UnimplementedEventListener) OnSCIMGroupCreated(context.Context, SCIMGroupEvent) error
- func (UnimplementedEventListener) OnSCIMGroupDeleted(context.Context, SCIMGroupEvent) error
- func (UnimplementedEventListener) OnSCIMGroupMembersChanged(context.Context, SCIMGroupEvent) error
- func (UnimplementedEventListener) OnSCIMGroupUpdated(context.Context, SCIMGroupEvent) error
- func (UnimplementedEventListener) OnSCIMUserActivated(context.Context, SCIMUserEvent) error
- func (UnimplementedEventListener) OnSCIMUserDeprovisioned(context.Context, SCIMUserEvent) error
- func (UnimplementedEventListener) OnSCIMUserProvisioned(context.Context, SCIMUserEvent) error
- func (UnimplementedEventListener) OnSCIMUserSuspended(context.Context, SCIMUserEvent) error
- func (UnimplementedEventListener) OnSCIMUserUpdated(context.Context, SCIMUserEvent) error
- func (UnimplementedEventListener) OnSSOAuthenticated(context.Context, SSOAuthenticatedEvent) error
- func (UnimplementedEventListener) OnSSOChallengeIssued(context.Context, SSOChallengeIssuedEvent) error
- func (UnimplementedEventListener) OnSSOJITProvisioned(context.Context, SSOJITProvisionedEvent) error
- func (UnimplementedEventListener) OnSSOLinked(context.Context, SSOLinkedEvent) error
- func (UnimplementedEventListener) OnSSOUnlinked(context.Context, SSOUnlinkedEvent) error
- func (UnimplementedEventListener) OnSecondFactorReset(context.Context, SecondFactorResetEvent) error
- func (UnimplementedEventListener) OnSessionCreated(context.Context, SessionCreatedEvent) error
- func (UnimplementedEventListener) OnSessionRevoked(context.Context, SessionRevokedEvent) error
- func (UnimplementedEventListener) OnSignUpCompleted(context.Context, SignUpCompletedEvent) error
- func (UnimplementedEventListener) OnStepUpDenied(context.Context, StepUpDeniedEvent) error
- func (UnimplementedEventListener) OnTOTPActivated(context.Context, TOTPActivatedEvent) error
- func (UnimplementedEventListener) OnTOTPDisabled(context.Context, TOTPDisabledEvent) error
- func (UnimplementedEventListener) OnTOTPEnrollmentStarted(context.Context, TOTPEnrollmentStartedEvent) error
- func (UnimplementedEventListener) OnTOTPReplayRejected(context.Context, TOTPReplayRejectedEvent) error
- func (UnimplementedEventListener) OnTOTPVerified(context.Context, TOTPVerifiedEvent) error
- func (UnimplementedEventListener) OnUserAnonymized(context.Context, UserAnonymizedEvent) error
- func (UnimplementedEventListener) OnUserCreated(context.Context, UserCreatedEvent) error
- func (UnimplementedEventListener) OnUserCredentialsRevoked(context.Context, UserCredentialsRevokedEvent) error
- func (UnimplementedEventListener) OnUserLocked(context.Context, UserLockedEvent) error
- func (UnimplementedEventListener) OnUserProfileUpdated(context.Context, UserProfileUpdatedEvent) error
- func (UnimplementedEventListener) OnUserSessionsRevoked(context.Context, UserSessionsRevokedEvent) error
- func (UnimplementedEventListener) OnUserStatusChanged(context.Context, UserStatusEvent) error
- func (UnimplementedEventListener) OnWorkspaceChanged(context.Context, WorkspaceChangedEvent) error
- func (UnimplementedEventListener) OnWorkspaceCreated(context.Context, WorkspaceCreatedEvent) error
- func (UnimplementedEventListener) OnWorkspaceDomainConfirmed(context.Context, WorkspaceDomainEvent) error
- func (UnimplementedEventListener) OnWorkspaceDomainCreated(context.Context, WorkspaceDomainEvent) error
- func (UnimplementedEventListener) OnWorkspaceDomainPolicyUpdated(context.Context, WorkspaceDomainEvent) error
- func (UnimplementedEventListener) OnWorkspaceDomainRemoved(context.Context, WorkspaceDomainEvent) error
- func (UnimplementedEventListener) OnWorkspaceInvitationAccepted(context.Context, WorkspaceInvitationEvent) error
- func (UnimplementedEventListener) OnWorkspaceInvitationCreated(context.Context, WorkspaceInvitationEvent) error
- func (UnimplementedEventListener) OnWorkspaceInvitationRevoked(context.Context, WorkspaceInvitationEvent) error
- type UnimplementedTransactionHook
- func (UnimplementedTransactionHook) ApplyClientAudit(context.Context, Tx, ClientAuditRecord) error
- func (UnimplementedTransactionHook) ApplyEmailAddition(context.Context, Tx, EmailAddition) error
- func (UnimplementedTransactionHook) ApplyEmailConfirmation(context.Context, Tx, EmailConfirmation) error
- func (UnimplementedTransactionHook) ApplyEmailRemoval(context.Context, Tx, EmailRemoval) error
- func (UnimplementedTransactionHook) ApplyInstanceRoleChange(context.Context, Tx, InstanceRoleChange) error
- func (UnimplementedTransactionHook) ApplyInstanceRoleRemoval(context.Context, Tx, InstanceRoleRemoval) error
- func (UnimplementedTransactionHook) ApplyMembershipChange(context.Context, Tx, MembershipChange) error
- func (UnimplementedTransactionHook) ApplyOAuthChange(context.Context, Tx, OAuthChange) error
- func (UnimplementedTransactionHook) ApplyPATCreation(context.Context, Tx, PATCreation) error
- func (UnimplementedTransactionHook) ApplyPATRevocation(context.Context, Tx, PATRevocation) error
- func (UnimplementedTransactionHook) ApplyPasskeyDeletion(context.Context, Tx, PasskeyDeletion) error
- func (UnimplementedTransactionHook) ApplyPasskeyRegistration(context.Context, Tx, PasskeyRegistration) error
- func (UnimplementedTransactionHook) ApplyPasswordChange(context.Context, Tx, PasswordChange) error
- func (UnimplementedTransactionHook) ApplyPrimaryEmailChange(context.Context, Tx, PrimaryEmailChange) error
- func (UnimplementedTransactionHook) ApplyRecoveryCodeRegeneration(context.Context, Tx, RecoveryCodeRegeneration) error
- func (UnimplementedTransactionHook) ApplyRoleGrant(context.Context, Tx, RoleGrant) error
- func (UnimplementedTransactionHook) ApplySCIMConfigurationCreate(context.Context, Tx, SCIMConfigurationChange) error
- func (UnimplementedTransactionHook) ApplySCIMGroupDelete(context.Context, Tx, SCIMGroupChange) error
- func (UnimplementedTransactionHook) ApplySCIMGroupUpsert(context.Context, Tx, SCIMGroupChange) error
- func (UnimplementedTransactionHook) ApplySCIMUserDeprovision(context.Context, Tx, SCIMUserChange) error
- func (UnimplementedTransactionHook) ApplySCIMUserProvision(context.Context, Tx, SCIMUserChange) error
- func (UnimplementedTransactionHook) ApplySCIMUserUpdate(context.Context, Tx, SCIMUserChange) error
- func (UnimplementedTransactionHook) ApplySSOLink(context.Context, Tx, SSOLink) error
- func (UnimplementedTransactionHook) ApplySSOUnlink(context.Context, Tx, SSOUnlink) error
- func (UnimplementedTransactionHook) ApplySecondFactorReset(context.Context, Tx, SecondFactorReset) error
- func (UnimplementedTransactionHook) ApplySessionCreation(context.Context, Tx, SessionCreation) error
- func (UnimplementedTransactionHook) ApplySessionRevocation(context.Context, Tx, SessionRevocation) error
- func (UnimplementedTransactionHook) ApplyTOTPActivation(context.Context, Tx, TOTPActivation) error
- func (UnimplementedTransactionHook) ApplyTOTPDisable(context.Context, Tx, TOTPDisable) error
- func (UnimplementedTransactionHook) ApplyTOTPEnrollment(context.Context, Tx, TOTPEnrollmentChange) error
- func (UnimplementedTransactionHook) ApplyUserAnonymization(context.Context, Tx, UserAnonymization) error
- func (UnimplementedTransactionHook) ApplyUserCreate(context.Context, Tx, UserCreateChange) error
- func (UnimplementedTransactionHook) ApplyUserCredentialRevocation(context.Context, Tx, UserCredentialRevocation) error
- func (UnimplementedTransactionHook) ApplyUserProfileChange(context.Context, Tx, UserProfileChange) error
- func (UnimplementedTransactionHook) ApplyUserSessionRevocation(context.Context, Tx, UserSessionRevocation) error
- func (UnimplementedTransactionHook) ApplyUserStatusChange(context.Context, Tx, UserStatusChange) error
- func (UnimplementedTransactionHook) ApplyWorkspaceChange(context.Context, Tx, WorkspaceChange) error
- func (UnimplementedTransactionHook) ApplyWorkspaceCreate(context.Context, Tx, WorkspaceCreateChange) error
- func (UnimplementedTransactionHook) ApplyWorkspaceDomainChange(context.Context, Tx, WorkspaceDomainChange) error
- func (UnimplementedTransactionHook) ApplyWorkspaceInvitationChange(context.Context, Tx, WorkspaceInvitationChange) error
- type UpdateOAuthIssuerInput
- type UpdateSCIMConfigurationInput
- type UpdateUserInput
- type UpdateWorkspaceInput
- type User
- type UserAnonymization
- type UserAnonymizedEvent
- type UserCreateChange
- type UserCreatedEvent
- type UserCredentialRevocation
- type UserCredentialsRevokedEvent
- type UserDataExport
- type UserLockedEvent
- type UserProfileChange
- type UserProfileUpdatedEvent
- type UserSessionRevocation
- type UserSessionsRevokedEvent
- type UserStatusChange
- type UserStatusEvent
- type ValidationError
- type Workspace
- type WorkspaceChange
- type WorkspaceChangedEvent
- type WorkspaceCreateChange
- type WorkspaceCreatedEvent
- type WorkspaceDomain
- type WorkspaceDomainChange
- type WorkspaceDomainEvent
- type WorkspaceDomainPolicyInput
- type WorkspaceInvitation
- type WorkspaceInvitationChange
- type WorkspaceInvitationEvent
- type WorkspaceMember
- type WorkspaceMembership
- type WorkspacePAT
- type WorkspacePermission
- type WorkspaceStore
Examples ¶
Constants ¶
const ProvisioningSourceLocal = "local"
ProvisioningSourceLocal marks a membership managed by local operations. A SCIM-managed membership instead carries the UUIDv7 of its configuration, and ordinary local mutations cannot overwrite it.
Variables ¶
var ( ErrInvalidCredentials = errors.New("credbound: invalid credentials") ErrForbidden = errors.New("credbound: access forbidden") ErrStepUpRequired = errors.New("credbound: recent interactive AAL2 authentication required") ErrConflict = errors.New("credbound: resource already exists") ErrNotFound = errors.New("credbound: resource not found") ErrInvalidInput = errors.New("credbound: invalid input") ErrExpired = errors.New("credbound: credential expired") ErrLocked = errors.New("credbound: account temporarily locked") ErrSSORequired = errors.New("credbound: single sign-on required by domain policy") ErrDomainVerification = errors.New("credbound: domain ownership not verified") ErrNoPasskey = errors.New("credbound: user has no passkey") ErrPasskeyCloneDetected = errors.New("credbound: passkey authenticator clone detected") ErrAuditCompromised = errors.New("credbound: audit chain verification failed") ErrTransactionRejected = errors.New("credbound: transaction rejected by hook") ErrNotSupported = errors.New("credbound: capability not enabled") )
Sentinel errors compared with errors.Is. An application HTTP adapter maps them to its transport error contract (for example RFC 9457 problem documents) — HTTPStatus provides the canonical status-code table so the adapter only owns the body; the messages themselves are not part of the API.
Functions ¶
func ComputeAuditHash ¶
func ComputeAuditHash(previous []byte, event AuditEvent) []byte
ComputeAuditHash returns the SHA-256 hash that chains an audit event to its predecessor. Every field is length-prefixed so no two distinct events share a canonical encoding. Stores call it inside the commit transaction; hosts and auditors can recompute it to verify exported audit logs.
func CredentialFingerprint ¶
CredentialFingerprint condenses a stored password hash into the opaque, unkeyed digest carried by Authentication.CredentialDigest. Stores implement the SessionStore currency guard by recomputing it from the credential row inside the session-creation transaction and comparing it byte-for-byte with the fingerprint the sign-in observed. It reveals nothing useful about the hash (itself already a one-way derivation), so an Authentication remains safe to hold in host session state.
func HTTPRequestMetadata ¶
HTTPRequestMetadata wraps next so that every request context carries the client's RequestMetadata — IP address and User-Agent — which audit events recorded while serving the request then pick up automatically.
Without trusted proxies the client address is the transport peer from RemoteAddr and no header is read, so a client can never influence what the audit chain records. When the peer belongs to one of the trusted prefixes, the rightmost X-Forwarded-For address outside every trusted prefix is used instead: entries appended by the host's own proxies are skipped, and entries a client forged beyond them are never reached. A malformed entry stops the walk and the peer address is recorded, failing toward an address the client cannot choose.
func HTTPStatus ¶
HTTPStatus maps an error returned by any Manager operation to the HTTP status code a host transport would conventionally answer with, so every integrator does not re-write the same errors.Is ladder. nil maps to 200 and an unrecognized error to 500, so infrastructure failures never leak detail.
The mapping is intentionally coarse — a body format such as an RFC 9457 problem document, localized copy, and headers like Retry-After or WWW-Authenticate remain the host's contract. Hosts needing a different convention (401 versus 403 for step-up, 423 versus 429 for lockout) keep writing their own switch; this helper is the documented default:
401 ErrInvalidCredentials, ErrUnauthorized, ErrExpired,
ErrPasskeyCloneDetected, ErrNoPasskey (folded with invalid
credentials so passkey presence never leaks)
403 ErrForbidden, ErrStepUpRequired, ErrSSORequired
404 ErrNotFound
409 ErrConflict
400 ErrInvalidInput (including every ValidationError)
422 ErrDomainVerification, ErrTransactionRejected
429 ErrLocked
501 ErrNotSupported
503 ErrAuditUnavailable
500 ErrAuditCompromised and anything unrecognized
func WithRequestMetadata ¶
func WithRequestMetadata(ctx context.Context, metadata RequestMetadata) context.Context
WithRequestMetadata returns a context that carries the client network context for every audit event recorded while serving the request. The host service is responsible for resolving the real client address from its trusted proxy configuration before attaching it.
Types ¶
type ActorKind ¶
type ActorKind string
ActorKind classifies the audit actor: an authenticated user, a service credential (SCIM, OAuth client), or the system itself.
type AnyEventListener ¶
type AnyEventListener interface {
OnAnyEvent(ctx context.Context, name EventName, event any) error
}
AnyEventListener is an optional extension an EventListener may also implement to receive every event through a single method — the natural shape for an analytics feed, a host-owned outbox relay, or a webhook dispatcher that would otherwise implement every typed method. For each emitted event the registry first invokes the typed method, then OnAnyEvent on the same listener, under the same post-commit, best-effort delivery: errors and panics are observed and never propagate. The event value is the same typed struct the typed method received (a PasskeyRegisteredEvent, an OAuthEvent, …), so a dispatcher can type-switch on it or marshal it directly, and every one embeds EventMeta for the idempotency key.
type AssuranceLevel ¶
type AssuranceLevel uint8
AssuranceLevel is the authenticator assurance level of an Authentication, after NIST 800-63B: AAL1 for a single factor, AAL2 once a second factor (TOTP, recovery code) or a strong single ceremony (passkey, SSO) has been verified.
const ( AAL1 AssuranceLevel = 1 AAL2 AssuranceLevel = 2 )
type AuditChainCheckpoint ¶
AuditChainCheckpoint is a previously verified chain position: the HeadSequence and HeadHash of an earlier report, kept by the caller in a trusted place. The zero value means the genesis.
type AuditChainReport ¶
AuditChainReport summarizes a successful audit chain verification. Its HeadSequence and HeadHash form the AuditChainCheckpoint a later VerifyAuditChainFrom resumes from.
type AuditEvent ¶
type AuditEvent struct {
ID UUID
OccurredAt time.Time
ActorKind ActorKind
ActorID UUID
Action string
ResourceType string
ResourceID string
WorkspaceID UUID
Outcome AuditOutcome
Reason string
IPAddress string
UserAgent string
// Sequence, PreviousHash and Hash are assigned by the store inside the
// commit transaction and chain every event to its predecessor. A zero
// Sequence marks an event recorded before the chain existed.
Sequence int64
PreviousHash []byte
Hash []byte
}
AuditEvent is one immutable entry of the append-only audit log. ID, ActorID and OccurredAt are always derived by Credbound so a consuming service cannot impersonate an actor or backdate an entry.
type AuditInput ¶
type AuditInput struct {
Action string
ResourceType string
ResourceID string
WorkspaceID UUID
Outcome AuditOutcome
Reason string
}
AuditInput is a host-supplied audit entry recorded through RecordAudit. Credbound derives the actor, identifier and timestamp itself.
type AuditOutcome ¶
type AuditOutcome string
AuditOutcome records whether the audited action succeeded or failed.
const ( AuditSucceeded AuditOutcome = "succeeded" AuditFailed AuditOutcome = "failed" )
type AuditStore ¶
type AuditStore interface {
AppendAudit(context.Context, Commit) error
AuditEvents(context.Context, UUID, PageRequest) iter.Seq2[PageEvent[AuditEvent], error]
InstanceAuditEvents(context.Context, PageRequest) iter.Seq2[PageEvent[AuditEvent], error]
// AuditChainHead returns the sequence and hash of the latest chained
// audit event; sequence 0 with a 32-zero-byte hash for an empty chain.
AuditChainHead(context.Context) (int64, []byte, error)
// ChainedAuditEvents streams every chained audit event with a sequence
// strictly greater than afterSequence, in ascending order, so the chain
// can be recomputed from the genesis (0) or from a checkpoint.
ChainedAuditEvents(ctx context.Context, afterSequence int64) iter.Seq2[AuditEvent, error]
}
AuditStore persists the append-only audit trail and its hash chain.
type AuditUnavailableEvent ¶
type AuditUnavailableEvent struct {
}
type AuthMethod ¶
type AuthMethod string
AuthMethod identifies how an Authentication was produced.
const ( MethodPassword AuthMethod = "password" MethodTOTP AuthMethod = "totp" MethodPasskey AuthMethod = "passkey" MethodPAT AuthMethod = "pat" MethodSSO AuthMethod = "sso" MethodEmail AuthMethod = "email" )
type Authentication ¶
type Authentication struct {
UserID UUID
// Method records the factor that produced this context (password, totp,
// passkey, sso, email, or pat). Non-interactive methods (PAT) are
// rejected by step-up checks regardless of age.
Method AuthMethod
// Level is the assurance reached so far. AAL1 contexts are denied
// sensitive operations until VerifyTOTP, a passkey or an SSO step-up
// promotes the session to AAL2.
Level AssuranceLevel
// AuthenticatedAt is when the factor was verified. RequireStepUp accepts
// only interactive AAL2 contexts whose AuthenticatedAt falls within
// Config.StepUpMaxAge, so the host must preserve this timestamp rather
// than refresh it.
AuthenticatedAt time.Time
// SecondFactorRequired reports that the account has an active TOTP
// factor which has not been verified yet. The host should defer creating
// the final session until VerifyTOTP upgrades the context; Authorize and
// AuthorizePermission reject a pending context with ErrStepUpRequired, so
// the first factor alone never authorizes workspace operations.
SecondFactorRequired bool
// WorkspaceID restricts the context to one workspace. It is set for
// workspace-bound PATs; authorization in any other workspace fails.
WorkspaceID UUID
// Scopes limits what the credential may do (PATs). Empty means the
// context carries no scope restriction of its own.
Scopes []string
// CredentialDigest fingerprints the password credential this context was
// verified against (CredentialFingerprint of the stored hash); it is empty
// for contexts that no password produced. CreateSession refuses, inside
// the session transaction, a context whose fingerprint no longer matches
// the stored credential, so an authentication that verified a password
// concurrently replaced by ChangePassword or CompletePasswordReset can
// never mint a session the replacement should have killed.
CredentialDigest []byte
}
Authentication is the server-side capability returned by every successful authentication. The host stores it in its own session and passes it back as the actor of later operations; because every authorization decision trusts its fields, it must never be reconstructed from data a client supplies. Credbound issues no cookies or JWTs — the session strategy belongs to the host.
func (Authentication) HasScope ¶
func (a Authentication) HasScope(required string) bool
HasScope reports whether the context carries the required scope. An empty requirement always passes and the literal "*" scope matches everything. AuthorizePermission consults it for every scoped authentication, so hosts only need it for checks outside the workspace RBAC model.
func (Authentication) Interactive ¶
func (a Authentication) Interactive() bool
Interactive reports whether the context was produced by a user-present ceremony rather than a stored credential such as a PAT. Only interactive contexts can satisfy step-up requirements.
type AuthenticationEvent ¶
type AuthenticationEvent struct {
EventMeta
Authentication Authentication
// Request carries the client network context supplied by the host through
// WithRequestMetadata, so listeners can throttle or alert by address
// without re-reading the audit log.
Request RequestMetadata
}
AuthenticationEvent reports every successful authentication, whatever the method.
type AuthenticationFailureEvent ¶
type AuthenticationFailureEvent struct {
EventMeta
Method AuthMethod
UserID UUID
Reason string
// Request carries the client network context supplied by the host through
// WithRequestMetadata, so listeners can throttle or alert by address
// without re-reading the audit log.
Request RequestMetadata
}
AuthenticationFailureEvent reports a failed authentication attempt. UserID is empty when the failure cannot be attributed to an existing account.
type AuthorizationDeniedEvent ¶
AuthorizationDeniedEvent is an advisory signal that a workspace authorization failed; it carries no audit of its own. RequiredRole is empty for permission-based checks.
type BeginOAuthAuthorizationInput ¶
type BeginOAuthAuthorizationInput struct {
Issuer string
ClientID string
RedirectURI string
Resource string
Scopes []string
State string
CodeChallenge string
CodeChallengeMethod string
Nonce string
// MaxAge is the OIDC max_age request parameter: the maximum age the
// end-user's authentication may have. When positive and the actor's
// authentication is older, the consent reports RequiresStepUp and
// CompleteOAuthAuthorization refuses with ErrStepUpRequired until the host
// re-authenticates the user. Zero leaves the decision to the per-scope
// server policy alone.
MaxAge time.Duration
}
BeginOAuthAuthorizationInput is a parsed authorization request. Resource and State are mandatory, and only PKCE S256 is accepted.
type BootstrapCompletedEvent ¶
Listener event payloads mirror the committed fact they announce: the embedded EventMeta identifies the event, the remaining fields are a scrubbed snapshot of the affected records. Like transaction payloads, they never carry passwords, hashes, raw tokens, secrets, or digests; fields that are not otherwise documented are exactly the persisted values.
type BootstrapInput ¶
BootstrapInput describes the first account and workspace of an empty instance.
type CeremonyConsumption ¶
CeremonyConsumption identifies a single-use ceremony continuation being consumed by a Commit. ID is the UUIDv7 minted when the ceremony began and ExpiresAt bounds how long the store must remember it.
type ClientAuditRecord ¶
type ClientAuditRecord struct {
EventMeta
Audit AuditEvent
}
ClientAuditRecord carries a host-supplied audit entry recorded through RecordAudit, with the derived actor and timestamp already enforced.
type ClientAuditRecordedEvent ¶
type ClientAuditRecordedEvent struct {
EventMeta
Audit AuditEvent
}
type Commit ¶
type Commit struct {
Audit AuditEvent
Transactional func(context.Context, Tx) error
// Ceremony, when set, marks the single-use ceremony that authorized
// this mutation as consumed in the same transaction: the store records
// the ceremony id and fails the whole commit with ErrConflict when it
// was already recorded, so a replayed ceremony can never commit twice.
// Records may be pruned once ExpiresAt has passed.
Ceremony *CeremonyConsumption
// Unchained appends the audit event without extending the hash chain:
// no sequence, no predecessor hash, and no chain head to advance. It is
// reserved for the high-frequency events that carry no identified actor
// and therefore nothing to protect from tampering — today, the rejection
// of a token that matches no record at all. Chaining those would make an
// unauthenticated caller take the instance-wide chain lock once per
// request, which is a throughput ceiling and an amplification vector;
// leaving them out of the chain costs nothing, because an event about
// nobody cannot be tampered with in anyone's favour. The event is still
// persisted, still listed, and still fails the operation when it cannot
// be written (AUDIT-002); it is simply invisible to VerifyAuditChain.
//
// A store that ignores the flag and chains the event anyway stays
// correct — it only keeps the contention.
Unchained bool
}
Commit couples the mandatory audit with an optional extension of the same store transaction. Store implementations must commit all three stages (mutation, Transactional callback and audit) or none of them.
type Config ¶
type Config struct {
// Store is the required persistence port. A store that additionally
// implements SCIMStore, OAuthStore, SignupStore, SessionStore, or
// DomainStore unlocks the corresponding optional capability.
Store Store
// Passwords derives and verifies password hashes (Argon2id is the
// intended algorithm). Required.
Passwords PasswordHasher
// TOTP is the optional TOTP provider; without it the TOTP enrollment and
// verification operations return ErrNotSupported.
TOTP TOTPProvider
// Passkeys is the optional WebAuthn provider; without it the passkey
// ceremonies return ErrNotSupported.
Passkeys PasskeyProvider
// DomainVerifier proves control of a workspace domain inside
// ConfirmWorkspaceDomain; see DomainVerifier. Without one,
// ConfirmWorkspaceDomain refuses with ErrNotSupported unless
// TrustActorDomainVerification opts into trusting the actor instead.
DomainVerifier DomainVerifier
// TrustActorDomainVerification lets ConfirmWorkspaceDomain succeed
// without a DomainVerifier, treating the call as the administrator's
// assertion that DNS verification completed out of band. Dangerous
// wherever ConfirmWorkspaceDomain is reachable from self-serve actors: a
// confirmed domain governs SSO enforcement and JIT provisioning for every
// address on it. Ignored when a DomainVerifier is registered.
TrustActorDomainVerification bool
// SecretKey is the 32-byte root key. Distinct AEAD and HMAC keys are
// derived from it with HKDF to seal ceremony continuations and TOTP
// secrets and to digest single-use tokens.
SecretKey []byte
// PATPepper keys the HMAC digests of PAT and SCIM credentials. At least
// 32 bytes.
PATPepper []byte
// RecoveryPepper keys the HMAC digests of TOTP recovery codes. At least
// 32 bytes.
RecoveryPepper []byte
// RetiredSecretKeys lists previously active SecretKeys that reads still
// accept: sealed TOTP secrets and passkey credentials keep decrypting
// and outstanding token digests (sessions included) keep matching after
// a rotation, while everything new uses SecretKey. Each retired key is
// exactly 32 bytes. Remove a retired key once nothing sealed or
// digested under it remains.
RetiredSecretKeys [][]byte
// RetiredPATPeppers lists previously active PATPeppers that reads
// still accept, so outstanding PATs and SCIM credentials survive a
// pepper rotation; new tokens always digest under PATPepper.
RetiredPATPeppers [][]byte
// RetiredRecoveryPeppers lists previously active RecoveryPeppers that
// reads still accept, so outstanding recovery codes survive a pepper
// rotation; new codes always digest under RecoveryPepper.
RetiredRecoveryPeppers [][]byte
// PATPrefix is the marker a PAT carries, ahead of its indexed prefix and
// secret: PATPrefix_<12 hex>_<43 characters>. Zero keeps "cbp". Give each
// deployment its own value when several of them issue PATs, so a token can
// be attributed — and matched by a secret scanner — from its text alone.
// One to sixteen lowercase letters or digits, no underscore: that is the
// separator the parser splits on.
//
// Choose it once, before the first PAT is issued: the digest covers the
// whole token, marker included, and only the digest is stored, so a later
// change makes every outstanding PAT fail to authenticate with no raw
// token left to re-digest. Peppers rotate (RetiredPATPeppers); this does
// not. It leaves SCIM credentials, sessions and OAuth tokens untouched,
// which keep their own markers.
PATPrefix string
// PATTouchInterval coarsens the write AuthenticatePAT performs: a
// successful authentication whose token was already touched within the
// interval skips the last-used refresh, the owner's last-seen refresh and
// the audit event, turning the per-request write transaction into at most
// one write per token per interval. It matters more than its session
// sibling, because a PAT is how machines authenticate: every write goes
// through the instance-wide audit-chain lock, so a per-request write makes
// one token's traffic contend with every mutation of every tenant.
// Revocation, expiry, disablement and workspace-access checks still run
// against the store on every call, so a revoked token is refused on the
// very next request; the explicit trade-offs are last-used granularity, an
// audit log that records a token's activity at most once per interval, and
// the authentication events (EventPATAuthenticated,
// EventAuthenticationSucceeded) firing at that same reduced rate. Zero
// keeps the per-request write.
PATTouchInterval time.Duration
// StepUpMaxAge bounds how old an interactive AAL2 authentication may be
// to satisfy RequireStepUp. Zero keeps the default of 10 minutes.
StepUpMaxAge time.Duration
// StepUp selects, per family of guarded operations, the authentication a
// step-up-gated call demands; see StepUpPolicies and StepUpScope. The
// zero value keeps the strict historical behaviour everywhere: only a
// fresh interactive AAL2 context may perform them. Relaxing a family
// trades assurance for reachability in a deployment whose users hold no
// second factor; no setting here ever lets a non-interactive credential
// (a PAT) through, and none of them touch AuthenticatePAT or the
// workspace RequireMFA policy.
StepUp StepUpPolicies
// CeremonyTTL bounds the validity of sealed ceremony continuations
// (WebAuthn, SSO, OAuth consent). Email OTP continuations follow
// EmailAuthenticationTTL plus a one-minute audit grace instead. Zero
// keeps the default of 5 minutes.
CeremonyTTL time.Duration
// MinPasswordLen is the minimum accepted password length in runes. Zero
// keeps the default of 12; values below 10 are rejected.
MinPasswordLen int
// PasswordPolicy optionally vets candidate passwords beyond the built-in
// length rules (for example against a breached-password corpus). Nil
// keeps only the built-in validation.
PasswordPolicy PasswordPolicy
// MaxFailedLogins locks an account after that many consecutive password
// or TOTP failures. Zero keeps the default of 10; a negative value
// disables the built-in lockout for hosts that throttle upstream.
MaxFailedLogins int
// LockoutDuration is how long a locked account rejects authentication.
// Zero keeps the default of 15 minutes.
LockoutDuration time.Duration
// Clock supplies the current time; nil uses time.Now. Injectable for
// tests only — persisted timestamps are always converted to UTC.
Clock func() time.Time
// Random supplies cryptographic randomness; nil uses crypto/rand.
// Injectable for tests only.
Random io.Reader
// Observer receives one Operation record per API call, transaction hook
// and event listener invocation, for metrics and tracing. Nil disables
// observation.
Observer Observer
// AdminPermissions restricts the default instance-role permission
// matrix. A role may only be narrowed: granting a permission outside its
// default set fails, and no role but root may hold instance-role write.
AdminPermissions map[InstanceRole][]Permission
// WorkspaceRoles registers additional workspace roles and permissions in
// the immutable RBAC catalog. Definitions are validated during New; see
// RoleDefinition.
WorkspaceRoles []RoleDefinition
// EmailVerificationTTL bounds the validity of an email addition token.
// Zero keeps the default of 24 hours.
EmailVerificationTTL time.Duration
// PasswordResetTTL bounds the validity of a password reset token. Zero
// keeps the default of 1 hour.
PasswordResetTTL time.Duration
// EmailAuthenticationTTL bounds the validity of magic-link tokens and
// email OTP codes. Zero keeps the default of 15 minutes.
EmailAuthenticationTTL time.Duration
// InvitationTTL bounds the validity of a workspace invitation token.
// Zero keeps the default of 7 days.
InvitationTTL time.Duration
// DomainClaimTTL bounds how long an unconfirmed workspace-domain claim
// reserves its globally unique name. Once the window passes without
// ConfirmWorkspaceDomain, a new CreateWorkspaceDomain for the same name —
// from any workspace — replaces the stale pending claim, so an
// unverified claim can never permanently deny the domain's real owner.
// Confirmed domains never expire. Zero keeps the default of 7 days.
DomainClaimTTL time.Duration
// SessionTTL bounds the absolute lifetime of a server-side session issued
// by CreateSession (ExpiresAt = CreatedAt + SessionTTL); activity never
// extends it. Zero keeps the default of 30 days. Sessions additionally
// require a SessionStore-capable store.
SessionTTL time.Duration
// SessionIdleTimeout expires a server-side session after this much
// inactivity: AuthenticateSession refuses a session whose last-seen
// timestamp is older than the timeout, in addition to the absolute
// SessionTTL. Zero disables the idle check (absolute expiry only). Every
// successful AuthenticateSession refreshes last-seen, so the window slides
// with activity.
SessionIdleTimeout time.Duration
// SessionTouchInterval coarsens the write AuthenticateSession performs:
// a successful validation whose session was already touched within the
// interval skips the last-seen refresh and its audit event, turning the
// per-request write transaction into at most one write per session per
// interval. Revocation, expiry, idle and disabled-user checks still run
// against the store on every call, so — unlike a host-side result cache —
// a revoked session is refused on the very next request; the explicit
// trade-offs are last-seen granularity, an audit log that records session
// activity at most once per interval per session, and a revocation racing
// one in-flight validation no longer being caught by the touch's
// conflict check. Zero keeps the per-request write. When
// SessionIdleTimeout is set, the interval must be shorter than it, or a
// continuously active session could idle-expire; New rejects the
// combination.
SessionTouchInterval time.Duration
// EmailIssuanceCooldown is the minimum interval between two token-issuing
// emails to the same address for the same purpose (password reset,
// magic-link, email OTP, verification resend). Within the window the flow
// answers with its usual enumeration-safe decoy and issues no token, so a
// user cannot be bombarded with mails. Zero disables the cooldown. It
// requires an EmailThrottleStore-capable store; New fails construction
// otherwise, so a configured protection can never be silently inert.
EmailIssuanceCooldown time.Duration
// SSOProviders registers the identity providers the host enables. Each
// must expose a unique UUIDv7 configuration ID and a known kind.
SSOProviders []SSOProvider
// SSOAssurance sets, per provider configuration ID, the authentication
// context a provider must assert before FinishSSO grants AAL2; see
// SSOAssurancePolicy. A provider with no entry here grants only AAL1 —
// SSO never mints AAL2 on the IdP's unverified word — so a provider whose
// sign-ins must satisfy a RequireMFA workspace or a step-up needs a
// policy (use TrustUnverified to trust an IdP that asserts no ACR or
// AMR). Every key must name a registered provider and every policy must
// require something.
SSOAssurance map[UUID]SSOAssurancePolicy
// TransactionHooks run inside every mutation's store transaction, after
// the mutation and before the audit write. A hook error aborts the
// commit. More hooks can be added later with AddTransactionHook.
TransactionHooks []TransactionHook
// EventListeners observe committed facts. Listener errors are recorded
// for observability and never propagate. More listeners can be added
// later with AddEventListener.
EventListeners []EventListener
// OAuth enables the OAuth/OIDC authorization server module when the
// store also implements OAuthStore. Nil leaves the module disabled.
OAuth *OAuthConfig
// SignUp enables self-service registration when the store also
// implements SignupStore. Nil leaves the operation disabled.
SignUp *SignUpConfig
}
Config assembles the ports, keys and policies of a Manager. Store and Passwords are required; New validates every invariant and applies safe defaults to zero durations and limits. Cryptographic values have no weak fallback.
type CreateOAuthInitialAccessTokenInput ¶
CreateOAuthInitialAccessTokenInput describes a protected-DCR bootstrap token: an expiry within 30 days and a registration limit between 1 and 100 (zero means 1).
type CreateOAuthIssuerInput ¶
type CreateOAuthIssuerInput struct {
Issuer string
OIDCEnabled bool
CIMDMode OAuthCIMDMode
CIMDAllowedOrigins []string
DCRMode OAuthDCRMode
DCRAllowClientSecrets bool
DCROpenRegistrationLimit int
CodeTTL time.Duration
AccessTokenTTL time.Duration
RefreshTokenTTL time.Duration
}
CreateOAuthIssuerInput describes a new issuer. Zero TTLs fall back to the defaults (5 minute codes, 15 minute access tokens, 30 day refresh tokens), each bounded by a hard maximum.
type CreateOAuthProtectedResourceInput ¶
type CreateOAuthProtectedResourceInput struct {
IssuerID UUID
Resource string
Scopes []OAuthScopeDefinition
}
CreateOAuthProtectedResourceInput describes a new MCP resource: its issuer, HTTPS resource URI and scope definitions.
type CreatePATInput ¶
CreatePATInput describes a new personal access token. An empty WorkspaceID leaves the token unbound; at least one scope is required and a nil ExpiresAt issues a non-expiring token.
type CreateSCIMConfigurationInput ¶
type CreateSCIMConfigurationInput struct {
DefaultRole Role
TrustDirectoryEmails bool
GroupRoleMappings []SCIMGroupRoleMapping
CredentialExpiresAt *time.Time
}
CreateSCIMConfigurationInput configures a new provisioning domain and its first credential. A zero DefaultRole means member; a nil CredentialExpiresAt issues a non-expiring credential.
type CreateSessionInput ¶
type CreateSessionInput struct{}
CreateSessionInput reserves room for future per-session options. Device metadata is not part of it: CreateSession reads the sanitized RequestMetadata attached to the context with WithRequestMetadata.
type CreateUserInput ¶
CreateUserInput describes an administratively created account. The address becomes the verified primary email and Role is the membership role in the target workspace.
type CreateWorkspaceInput ¶
CreateWorkspaceInput describes a new workspace. RequireMFA enables the workspace MFA policy from the start.
type DiscoverablePasskeyProvider ¶
type DiscoverablePasskeyProvider interface {
// BeginDiscoverableAuthentication starts an assertion ceremony bound to
// no account; the authenticator offers its resident credentials.
BeginDiscoverableAuthentication(context.Context) (json.RawMessage, []byte, error)
// FinishDiscoverableAuthentication validates the browser response,
// resolving the account through the lookup and verifying the asserted
// user handle belongs to it.
FinishDiscoverableAuthentication(ctx context.Context, session, response []byte, lookup PasskeyUserLookup) (credentialID, credentialJSON []byte, err error)
}
DiscoverablePasskeyProvider is an optional extension of PasskeyProvider enabling usernameless (discoverable-credential) authentication: the ceremony starts with an empty allowCredentials list, so no per-address challenge exists to enumerate — the full fix for the residual allowCredentials-count signal the per-address decoy cannot close.
type DomainStore ¶
type DomainStore interface {
CreateWorkspaceDomain(ctx context.Context, domain WorkspaceDomain, staleBefore time.Time, commit Commit) error
WorkspaceDomainByID(ctx context.Context, id UUID) (WorkspaceDomain, error)
// ConfirmedWorkspaceDomainByName is the hot lookup behind SSO enforcement
// and JIT provisioning: it resolves a normalized domain name to its
// confirmed record and returns ErrNotFound when the domain is absent or
// not yet confirmed.
ConfirmedWorkspaceDomainByName(ctx context.Context, domain string) (WorkspaceDomain, error)
ConfirmWorkspaceDomain(ctx context.Context, id UUID, at time.Time, commit Commit) error
UpdateWorkspaceDomainPolicy(ctx context.Context, id UUID, policy WorkspaceDomainPolicyInput, at time.Time, commit Commit) error
DeleteWorkspaceDomain(ctx context.Context, id UUID, commit Commit) error
WorkspaceDomains(ctx context.Context, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[WorkspaceDomain], error]
// JITProvisionSSOUser atomically creates a passwordless user, their
// verified primary email, the auto-join membership and the SSO identity
// link, or nothing at all. A concurrently claimed address or identity
// fails with ErrConflict.
JITProvisionSSOUser(ctx context.Context, user User, email EmailAddress, membership Membership, identity SSOIdentity, at time.Time, commit Commit) error
}
DomainStore is an optional persistence capability required by the workspace-domain operations (CreateWorkspaceDomain, ConfirmWorkspaceDomain, UpdateWorkspaceDomainPolicy, RemoveWorkspaceDomain, WorkspaceDomains), by domain-enforced SSO and by JIT provisioning; without it every domain operation returns ErrNotSupported and the authentication flows behave as if no domain existed.
A domain name is globally unique across workspaces: CreateWorkspaceDomain fails with ErrConflict for a taken name, except that it replaces — in the same transaction — a stale pending claim, one still unconfirmed and created before staleBefore, so an unverified claim can never permanently deny the domain's real owner (Config.DomainClaimTTL sets the window). Confirmed domains never expire. ConfirmWorkspaceDomain fails with ErrConflict when the domain was already confirmed, and UpdateWorkspaceDomainPolicy fails with ErrConflict on an unconfirmed domain, so the pending state never carries policy.
type DomainVerifier ¶
DomainVerifier optionally proves control of a workspace domain before ConfirmWorkspaceDomain marks it verified. Registered in Config.DomainVerifier, its VerifyDomain runs inside ConfirmWorkspaceDomain with the domain name and the challenge token minted at creation; any non-nil error refuses the confirmation with ErrDomainVerification. A typical implementation resolves the domain's TXT records and checks the challenge is published. Without a verifier, ConfirmWorkspaceDomain refuses with ErrNotSupported unless Config.TrustActorDomainVerification explicitly opts into trusting the actor's out-of-band check — a dangerous setting wherever the operation is reachable from self-serve actors, because a confirmed domain governs SSO enforcement and JIT provisioning for every address on it, instance-wide.
type EmailAddedEvent ¶
type EmailAddedEvent struct {
EventMeta
Email EmailAddress
}
type EmailAddition ¶
type EmailAddition struct {
EventMeta
Email EmailAddress
}
type EmailAddress ¶
type EmailAddress struct {
ID UUID
UserID UUID
Address string
Primary bool
VerifiedAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
EmailAddress is one of a user's globally unique, normalized addresses. Exactly one address per user is primary; an address becomes usable for sign-in only once VerifiedAt is set.
type EmailAuthenticationCredential ¶
type EmailAuthenticationCredential struct {
ID UUID
UserID UUID
EmailID UUID
Digest []byte
CreatedAt time.Time
ExpiresAt time.Time
UsedAt *time.Time
}
EmailAuthenticationCredential is the persisted single-use proof behind a magic link or email OTP. Only the HMAC digest of the token or code is stored.
type EmailAuthenticationStore ¶
type EmailAuthenticationStore interface {
CreateEmailAuthentication(context.Context, EmailAuthenticationCredential, Commit) error
EmailAuthenticationByID(context.Context, UUID) (EmailAuthenticationCredential, error)
// ConsumeEmailAuthentication atomically marks the single-use magic-link
// or email OTP token as used. When completesLogin is true it also
// updates last_seen_at and clears the login throttle; a consumption
// that leaves a second factor pending passes false so the completing
// factor clears them on success. It returns ErrConflict when the token
// was already consumed.
ConsumeEmailAuthentication(ctx context.Context, tokenID, userID UUID, at time.Time, completesLogin bool, commit Commit) error
}
EmailAuthenticationStore persists the single-use magic-link and email OTP credentials.
type EmailConfirmation ¶
type EmailConfirmation struct {
EventMeta
Email EmailAddress
}
type EmailConfirmedEvent ¶
type EmailConfirmedEvent struct {
EventMeta
Email EmailAddress
}
type EmailRemoval ¶
type EmailRemovedEvent ¶
type EmailStore ¶
type EmailStore interface {
SaveEmail(context.Context, EmailAddress, EmailVerificationCredential, Commit) error
EmailVerificationByID(context.Context, UUID) (EmailAddress, EmailVerificationCredential, error)
// EmailByAddress resolves an address to its record without the verification
// credential; ErrNotFound when no address matches. ResendEmailVerification
// uses it to find the pending address to re-issue.
EmailByAddress(context.Context, string) (EmailAddress, error)
// ReissueEmailVerification replaces the pending verification credential of
// an unverified address; an already-verified or missing address reports
// ErrConflict.
ReissueEmailVerification(context.Context, UUID, EmailVerificationCredential, Commit) error
VerifyEmail(context.Context, UUID, time.Time, Commit) error
SetPrimaryEmail(context.Context, UUID, UUID, Commit) error
RemoveEmail(context.Context, UUID, UUID, Commit) error
Emails(context.Context, UUID, PageRequest) iter.Seq2[PageEvent[EmailAddress], error]
}
EmailStore persists a user's email addresses and their verification credentials.
type EmailThrottleStore ¶
type EmailThrottleStore interface {
// ClaimEmailIssuance atomically records an issuance for (address, purpose)
// at time `at` and reports whether it was allowed: it claims only when no
// prior issuance for that pair is newer than notBefore. The address the
// manager passes is an opaque, fixed-size HMAC key derived from the
// normalized address — never the address itself — so rows stay bounded
// and the store learns nothing about the addresses tried. Entries with
// last_issued_at at or before notBefore no longer throttle anything, and
// implementations prune them on each claim so anonymous traffic cannot
// grow the bookkeeping beyond the current cooldown window. It is keyed
// regardless of account existence, so it opens no enumeration oracle,
// and it needs no audit commit — it is rate-limit bookkeeping.
ClaimEmailIssuance(ctx context.Context, address, purpose string, at, notBefore time.Time) (bool, error)
}
EmailThrottleStore is an optional persistence capability that backs the per-address cooldown on unauthenticated email-issuing flows (BeginPasswordReset, BeginEmailAuthentication, BeginEmailOTP, ResendEmailVerification). Without it, or with Config.EmailIssuanceCooldown left at zero, those flows are not throttled and the host is responsible for its own rate limiting.
type EmailVerificationCredential ¶
EmailVerificationCredential is the persisted proof for a pending email addition. Only the HMAC digest of the token is stored.
type EmailVerificationResentEvent ¶
type EmailVerificationResentEvent struct {
EventMeta
Email EmailAddress
}
type EventListener ¶
type EventListener interface {
OnBootstrapCompleted(context.Context, BootstrapCompletedEvent) error
OnSignUpCompleted(context.Context, SignUpCompletedEvent) error
OnUserCreated(context.Context, UserCreatedEvent) error
OnWorkspaceCreated(context.Context, WorkspaceCreatedEvent) error
OnUserStatusChanged(context.Context, UserStatusEvent) error
OnUserProfileUpdated(context.Context, UserProfileUpdatedEvent) error
OnWorkspaceChanged(context.Context, WorkspaceChangedEvent) error
OnMembershipChanged(context.Context, MembershipChangedEvent) error
OnWorkspaceInvitationCreated(context.Context, WorkspaceInvitationEvent) error
OnWorkspaceInvitationAccepted(context.Context, WorkspaceInvitationEvent) error
OnWorkspaceInvitationRevoked(context.Context, WorkspaceInvitationEvent) error
OnWorkspaceDomainCreated(context.Context, WorkspaceDomainEvent) error
OnWorkspaceDomainConfirmed(context.Context, WorkspaceDomainEvent) error
OnWorkspaceDomainPolicyUpdated(context.Context, WorkspaceDomainEvent) error
OnWorkspaceDomainRemoved(context.Context, WorkspaceDomainEvent) error
OnPasswordChanged(context.Context, PasswordChangedEvent) error
OnPasswordRehashed(context.Context, PasswordRehashedEvent) error
OnAuthenticationSucceeded(context.Context, AuthenticationEvent) error
OnAuthenticationFailed(context.Context, AuthenticationFailureEvent) error
OnStepUpDenied(context.Context, StepUpDeniedEvent) error
OnAuthorizationDenied(context.Context, AuthorizationDeniedEvent) error
OnEmailAdded(context.Context, EmailAddedEvent) error
OnEmailConfirmed(context.Context, EmailConfirmedEvent) error
OnEmailVerificationResent(context.Context, EmailVerificationResentEvent) error
OnPrimaryEmailChanged(context.Context, PrimaryEmailChangedEvent) error
OnEmailRemoved(context.Context, EmailRemovedEvent) error
OnTOTPEnrollmentStarted(context.Context, TOTPEnrollmentStartedEvent) error
OnTOTPActivated(context.Context, TOTPActivatedEvent) error
OnTOTPDisabled(context.Context, TOTPDisabledEvent) error
OnTOTPVerified(context.Context, TOTPVerifiedEvent) error
OnTOTPReplayRejected(context.Context, TOTPReplayRejectedEvent) error
OnRecoveryCodeConsumed(context.Context, RecoveryCodeConsumedEvent) error
OnPasskeyRegistered(context.Context, PasskeyRegisteredEvent) error
OnPasskeyDeleted(context.Context, PasskeyDeletedEvent) error
OnPasskeyAuthenticated(context.Context, PasskeyAuthenticatedEvent) error
OnPATCreated(context.Context, PATCreatedEvent) error
OnPATRevoked(context.Context, PATRevokedEvent) error
OnUserCredentialsRevoked(context.Context, UserCredentialsRevokedEvent) error
OnSecondFactorReset(context.Context, SecondFactorResetEvent) error
OnUserAnonymized(context.Context, UserAnonymizedEvent) error
OnRecoveryCodesRegenerated(context.Context, RecoveryCodesRegeneratedEvent) error
OnSessionCreated(context.Context, SessionCreatedEvent) error
OnSessionRevoked(context.Context, SessionRevokedEvent) error
OnUserSessionsRevoked(context.Context, UserSessionsRevokedEvent) error
OnUserLocked(context.Context, UserLockedEvent) error
OnPasswordResetRequested(context.Context, PasswordResetRequestedEvent) error
OnPasswordResetCompleted(context.Context, PasswordResetCompletedEvent) error
OnEmailAuthenticationRequested(context.Context, EmailAuthenticationRequestedEvent) error
OnPATAuthenticated(context.Context, PATAuthenticatedEvent) error
OnPATRejected(context.Context, PATRejectedEvent) error
OnSSOChallengeIssued(context.Context, SSOChallengeIssuedEvent) error
OnSSOLinked(context.Context, SSOLinkedEvent) error
OnSSOUnlinked(context.Context, SSOUnlinkedEvent) error
OnSSOAuthenticated(context.Context, SSOAuthenticatedEvent) error
OnSSOJITProvisioned(context.Context, SSOJITProvisionedEvent) error
OnRoleGranted(context.Context, RoleGrantedEvent) error
OnInstanceRoleChanged(context.Context, InstanceRoleChangedEvent) error
OnInstanceRoleRemoved(context.Context, InstanceRoleRemovedEvent) error
OnClientAuditRecorded(context.Context, ClientAuditRecordedEvent) error
OnSCIMConfigurationCreated(context.Context, SCIMConfigurationCreatedEvent) error
OnSCIMUserProvisioned(context.Context, SCIMUserEvent) error
OnSCIMUserUpdated(context.Context, SCIMUserEvent) error
OnSCIMUserActivated(context.Context, SCIMUserEvent) error
OnSCIMUserSuspended(context.Context, SCIMUserEvent) error
OnSCIMUserDeprovisioned(context.Context, SCIMUserEvent) error
OnSCIMGroupCreated(context.Context, SCIMGroupEvent) error
OnSCIMGroupUpdated(context.Context, SCIMGroupEvent) error
OnSCIMGroupDeleted(context.Context, SCIMGroupEvent) error
OnSCIMGroupMembersChanged(context.Context, SCIMGroupEvent) error
OnOAuthEvent(context.Context, OAuthEvent) error
// contains filtered or unexported methods
}
EventListener observes committed facts. Listeners run synchronously after the commit; their errors and panics are recorded through the Observer for observability only and never propagate or interrupt other listeners. Delivery is best effort — guaranteed delivery belongs in a host-owned outbox written from a TransactionHook, with EventMeta.ID as the idempotency key. Implementations embed UnimplementedEventListener to stay compatible as methods are added. Events never carry secrets.
type EventMeta ¶
type EventMeta struct {
ID UUID
Name EventName
Operation string
OccurredAt time.Time
ActorID UUID
WorkspaceID UUID
AuditID UUID
}
EventMeta is embedded by every hook payload and event. ID is a UUIDv7 suitable as an idempotency key or outbox messageId; AuditID references the audit event committed with the change (empty for advisory events that have no audit of their own).
type EventName ¶
type EventName string
EventName is the stable, unversioned name of an event, such as "user.created" or "workspace.created". Payload shapes follow the library version; names do not change.
const ( EventBootstrapCompleted EventName = "bootstrap.completed" EventSignUpCompleted EventName = "signup.completed" EventUserCreated EventName = "user.created" EventUserDisabled EventName = "user.disabled" EventUserEnabled EventName = "user.enabled" EventWorkspaceCreated EventName = "workspace.created" EventWorkspaceUpdated EventName = "workspace.updated" EventWorkspaceDisabled EventName = "workspace.disabled" EventWorkspaceEnabled EventName = "workspace.enabled" EventMembershipAdded EventName = "membership.added" EventWorkspaceInvitationCreated EventName = "workspace.invitation_created" EventWorkspaceInvitationAccepted EventName = "workspace.invitation_accepted" EventWorkspaceInvitationRevoked EventName = "workspace.invitation_revoked" EventWorkspaceDomainCreated EventName = "workspace.domain.created" EventWorkspaceDomainConfirmed EventName = "workspace.domain.confirmed" EventWorkspaceDomainPolicyUpdated EventName = "workspace.domain.policy_updated" EventWorkspaceDomainRemoved EventName = "workspace.domain.removed" EventMembershipStatusChanged EventName = "membership.status_changed" EventMembershipRemoved EventName = "membership.removed" EventPasswordChanged EventName = "password.changed" EventPasswordRehashed EventName = "password.rehashed" EventPasswordResetRequested EventName = "password.reset_requested" EventPasswordResetCompleted EventName = "password.reset_completed" EventEmailAuthenticationRequested EventName = "email_authentication.requested" EventAuthenticationSucceeded EventName = "authentication.succeeded" EventAuthenticationFailed EventName = "authentication.failed" EventStepUpDenied EventName = "step_up.denied" EventAuthorizationDenied EventName = "authorization.denied" EventEmailAdded EventName = "email.added" EventEmailConfirmed EventName = "email.confirmed" EventEmailVerificationResent EventName = "email.verification_resent" EventPrimaryEmailChanged EventName = "email.primary_changed" EventEmailRemoved EventName = "email.removed" EventTOTPEnrollmentStarted EventName = "totp.enrollment_started" EventTOTPActivated EventName = "totp.activated" EventTOTPDisabled EventName = "totp.disabled" EventTOTPVerified EventName = "totp.verified" EventTOTPReplayRejected EventName = "totp.replay_rejected" EventRecoveryCodeConsumed EventName = "recovery_code.consumed" EventRecoveryCodesRegenerated EventName = "recovery_codes.regenerated" EventPasskeyRegistered EventName = "passkey.registered" EventPasskeyDeleted EventName = "passkey.deleted" EventPasskeyAuthenticated EventName = "passkey.authenticated" EventUserCredentialsRevoked EventName = "user.credentials_revoked" EventSecondFactorReset EventName = "user.second_factor_reset" EventUserAnonymized EventName = "user.anonymized" EventUserLocked EventName = "user.locked" EventUserProfileUpdated EventName = "user.profile_updated" EventSessionCreated EventName = "session.created" EventSessionRevoked EventName = "session.revoked" EventUserSessionsRevoked EventName = "session.user_revoked" EventPATCreated EventName = "pat.created" EventPATRevoked EventName = "pat.revoked" EventPATAuthenticated EventName = "pat.authenticated" EventPATRejected EventName = "pat.rejected" EventSSOChallengeIssued EventName = "sso.challenge_issued" EventSSOLinked EventName = "sso.linked" EventSSOUnlinked EventName = "sso.unlinked" EventSSOAuthenticated EventName = "sso.authenticated" EventSSOJITProvisioned EventName = "sso.jit_provisioned" EventRoleGranted EventName = "role.granted" EventInstanceRoleChanged EventName = "instance_role.changed" EventInstanceRoleRemoved EventName = "instance_role.removed" EventClientAuditRecorded EventName = "client_audit.recorded" EventSCIMConfigurationCreated EventName = "scim.configuration.created" EventSCIMUserProvisioned EventName = "scim.user.provisioned" EventSCIMUserUpdated EventName = "scim.user.updated" EventSCIMUserActivated EventName = "scim.user.activated" EventSCIMUserSuspended EventName = "scim.user.suspended" EventSCIMUserDeprovisioned EventName = "scim.user.deprovisioned" EventSCIMGroupCreated EventName = "scim.group.created" EventSCIMGroupUpdated EventName = "scim.group.updated" EventSCIMGroupDeleted EventName = "scim.group.deleted" EventSCIMGroupMembersChanged EventName = "scim.group.members_changed" EventOAuthClientRegistered EventName = "oauth.client.registered" EventOAuthClientDisabled EventName = "oauth.client.disabled" EventOAuthClientEnabled EventName = "oauth.client.enabled" EventOAuthClientSecretRotated EventName = "oauth.client.secret_rotated" EventOAuthClientJWKSReplaced EventName = "oauth.client.jwks_replaced" EventOAuthIssuerDisabled EventName = "oauth.issuer.disabled" EventOAuthIssuerEnabled EventName = "oauth.issuer.enabled" EventOAuthResourceDisabled EventName = "oauth.resource.disabled" EventOAuthResourceEnabled EventName = "oauth.resource.enabled" EventOAuthCIMDResolved EventName = "oauth.cimd.resolved" EventOAuthCIMDRejected EventName = "oauth.cimd.rejected" EventOAuthCIMDChanged EventName = "oauth.cimd.changed" EventOAuthAuthorizationGranted EventName = "oauth.authorization.granted" EventOAuthAuthorizationDenied EventName = "oauth.authorization.denied" EventOAuthTokenIssued EventName = "oauth.token.issued" EventOAuthTokenRefreshed EventName = "oauth.token.refreshed" EventOAuthTokenRevoked EventName = "oauth.token.revoked" EventOAuthRefreshReuseDetected EventName = "oauth.refresh_token.reuse_detected" EventOAuthCodeReuseDetected EventName = "oauth.authorization_code.reuse_detected" EventOAuthConsentRevoked EventName = "oauth.consent.revoked" )
type ExchangeOAuthAuthorizationCodeInput ¶
type ExchangeOAuthAuthorizationCodeInput struct {
Issuer string
ClientID string
ClientSecret string
// ClientSecretInBody reports that ClientSecret arrived as a
// client_secret form field rather than an Authorization: Basic header.
// The registered client_secret_basic method accepts only the header
// (RFC 6749 section 2.3.1 discourages the form transport and the
// discovery document does not announce client_secret_post), so a
// body-borne secret fails authentication.
ClientSecretInBody bool
ClientAssertion string
ClientAssertionType string
Code string
RedirectURI string
CodeVerifier string
Resource string
}
ExchangeOAuthAuthorizationCodeInput is a parsed token-endpoint request for the authorization_code grant, including the client credentials or assertion and the PKCE verifier.
type IdentityStore ¶
type IdentityStore interface {
Bootstrap(context.Context, User, EmailAddress, PasswordCredential, Workspace, Membership, InstanceAdministrator, Commit) error
CreateUser(context.Context, User, EmailAddress, PasswordCredential, Membership, Commit) error
SetUserDisabled(context.Context, UUID, bool, time.Time, Commit) error
// UpdateUser persists the user's mutable profile fields. It returns
// ErrNotFound for an unknown identifier.
UpdateUser(context.Context, User, Commit) error
UserByEmail(context.Context, string) (User, error)
UserByID(context.Context, UUID) (User, error)
Users(context.Context, PageRequest) iter.Seq2[PageEvent[User], error]
PasswordByUserID(context.Context, UUID) (PasswordCredential, error)
// RehashPassword installs a stronger hash of the password that just
// verified — the transparent-rehash path — but only while previousHash,
// the stored hash the verification ran against, is still in place. When
// a concurrent change or reset already replaced the credential it
// returns ErrConflict and leaves the store untouched: an unconditional
// swap here would let an in-flight sign-in resurrect the very password
// the user just rotated away from.
RehashPassword(ctx context.Context, password PasswordCredential, previousHash string, commit Commit) error
// ChangePassword installs the user's new password for an authenticated
// password change. Unlike RehashPassword, a SessionStore-capable store
// must stamp RevokedAt on the user's active sessions in the same
// transaction, so a failure leaves both the password and the sessions
// untouched.
ChangePassword(ctx context.Context, password PasswordCredential, at time.Time, commit Commit) error
// RecordAuthentication updates last_seen_at and clears the user's login
// throttle in the same transaction as its audit event.
RecordAuthentication(context.Context, UUID, time.Time, Commit) error
// RecordPasswordAuthentication finalizes a password sign-in like
// RecordAuthentication, but only while currentHash is still the user's
// stored password credential; the comparison and the finalization happen
// in the same transaction. When a concurrent change or reset replaced the
// credential — or removed it — it returns ErrConflict and leaves the
// store untouched, so a sign-in that verified a password can never
// complete after that password stopped being current.
RecordPasswordAuthentication(ctx context.Context, userID UUID, currentHash string, at time.Time, commit Commit) error
LoginThrottleByUserID(context.Context, UUID) (LoginThrottle, error)
// RecordLoginFailure atomically increments the failure counter and, once
// the counter reaches the threshold, persists the lockout deadline. It
// returns the updated throttle.
RecordLoginFailure(ctx context.Context, userID UUID, at time.Time, threshold int64, lockedUntil time.Time, commit Commit) (LoginThrottle, error)
}
IdentityStore is the account backbone of the required Store: user records, the password credential with its currency guards, and the login throttle.
type InstanceAdministrator ¶
type InstanceAdministrator struct {
UserID UUID
Role InstanceRole
CreatedAt time.Time
UpdatedAt time.Time
}
InstanceAdministrator records the instance role held by a user. The first account created by Bootstrap atomically receives root.
type InstanceRole ¶
type InstanceRole string
InstanceRole is an instance-administration role, independent of workspace RBAC. The set is closed: only the five constants below exist, and only root may grant or remove instance roles.
const ( InstanceRoleRoot InstanceRole = "root" InstanceRoleDeveloper InstanceRole = "developer" InstanceRoleSupport InstanceRole = "support" InstanceRoleMarketing InstanceRole = "marketing" InstanceRoleSales InstanceRole = "sales" )
type InstanceRoleChange ¶
type InstanceRoleChange struct {
EventMeta
UserID UUID
Role InstanceRole
PreviousRole InstanceRole
}
type InstanceRoleChangedEvent ¶
type InstanceRoleChangedEvent struct {
EventMeta
UserID UUID
Role InstanceRole
PreviousRole InstanceRole
}
type InstanceRoleRemoval ¶
type InstanceRoleRemoval struct {
EventMeta
UserID UUID
PreviousRole InstanceRole
}
type InstanceRoleRemovedEvent ¶
type InstanceRoleRemovedEvent struct {
EventMeta
UserID UUID
PreviousRole InstanceRole
}
type InvitationStore ¶
type InvitationStore interface {
CreateWorkspaceInvitation(context.Context, WorkspaceInvitation, Commit) error
WorkspaceInvitationByID(context.Context, UUID) (WorkspaceInvitation, error)
PendingWorkspaceInvitation(ctx context.Context, workspaceID UUID, email string) (WorkspaceInvitation, error)
// AcceptWorkspaceInvitation atomically marks the pending invitation
// accepted by the user and upserts the membership. It returns
// ErrConflict when the invitation was already accepted or revoked.
AcceptWorkspaceInvitation(ctx context.Context, invitationID, userID UUID, at time.Time, membership Membership, commit Commit) error
// RegisterInvitedUser atomically creates the invited account (user,
// verified email, password, membership) and marks the invitation
// accepted. It returns ErrConflict when the invitation was already
// accepted or revoked.
RegisterInvitedUser(ctx context.Context, invitationID UUID, user User, email EmailAddress, password PasswordCredential, membership Membership, at time.Time, commit Commit) error
RevokeWorkspaceInvitation(ctx context.Context, workspaceID, invitationID UUID, at time.Time, commit Commit) error
WorkspaceInvitations(context.Context, UUID, PageRequest) iter.Seq2[PageEvent[WorkspaceInvitation], error]
}
InvitationStore persists workspace invitations and their atomic acceptance paths.
type InviteToWorkspaceInput ¶
InviteToWorkspaceInput describes a workspace invitation: the address to invite and the role the invitee will receive on acceptance.
type IssuedEmailAuthentication ¶
type IssuedEmailAuthentication struct {
UserID UUID
EmailID UUID
Token string
ExpiresAt time.Time
// Deliverable reports whether this is a real issuance to email. A zero
// value (false, empty Token) is the enumeration-resistant decoy answer
// for an ineligible address: send nothing, but answer the end user
// exactly as if a message had been sent.
Deliverable bool
}
IssuedEmailAuthentication carries the single-use magic-link token exactly once. The host service delivers it to the verified address and never stores it.
type IssuedEmailOTP ¶
type IssuedEmailOTP struct {
UserID UUID
EmailID UUID
Code string
Continuation string
ExpiresAt time.Time
// Deliverable reports whether this is a real issuance to email. A zero
// value (false, empty Code) is the enumeration-resistant decoy answer
// for an ineligible address: send nothing, but answer the end user
// exactly as if a message had been sent.
Deliverable bool
}
IssuedEmailOTP carries the single-use numeric code exactly once, with the sealed continuation the host must hand back to CompleteEmailOTP. Code is empty when the address was not eligible; the host then sends no email but answers the end user identically.
type IssuedEmailVerification ¶
type IssuedEmailVerification struct {
Email EmailAddress
Token string
// Deliverable reports whether this is a real issuance to email. A zero
// value (false, empty Token) is the enumeration-resistant decoy answer
// of ResendEmailVerification: send nothing, but answer the end user
// exactly as if a message had been sent.
Deliverable bool
}
IssuedEmailVerification carries the raw verification token exactly once, from BeginEmailAddition or ResendEmailVerification. The host delivers it to the new address and never stores it.
type IssuedOAuthClient ¶
type IssuedOAuthClient struct {
Client OAuthClient
ClientSecret string
}
IssuedOAuthClient carries the client secret exactly once, when a registration requested client_secret_basic; ClientSecret is empty otherwise. The public Client value has no secret digest.
type IssuedOAuthInitialAccessToken ¶
type IssuedOAuthInitialAccessToken struct {
Credential OAuthInitialAccessToken
Token string
}
IssuedOAuthInitialAccessToken carries the raw DCR bootstrap token exactly once, from CreateOAuthInitialAccessToken.
type IssuedPAT ¶
IssuedPAT carries the raw PAT exactly once, from CreatePAT. The host shows it to the user once and never stores it.
type IssuedPasswordReset ¶
type IssuedPasswordReset struct {
UserID UUID
Token string
ExpiresAt time.Time
// Deliverable reports whether this is a real issuance to email. A zero
// value (false, empty Token) is the enumeration-resistant decoy answer
// for an unknown, disabled, or throttled address: send nothing, but
// answer the end user exactly as if a message had been sent.
Deliverable bool
}
IssuedPasswordReset carries the single-use reset token exactly once. The host service delivers it to the address that requested the reset and never stores it.
type IssuedSCIMCredential ¶
type IssuedSCIMCredential struct {
Configuration SCIMConfiguration
Credential SCIMCredential
Token string
}
IssuedSCIMCredential carries the raw SCIM bearer token exactly once, from CreateSCIMConfiguration or RotateSCIMCredential. The public Credential value has an empty Digest.
type IssuedSession ¶
IssuedSession carries the raw session token exactly once, from CreateSession. The host transports it (typically in a cookie) and never stores it; only the digest is persisted.
type IssuedWorkspaceDomain ¶
type IssuedWorkspaceDomain struct {
Domain WorkspaceDomain
Challenge string
}
IssuedWorkspaceDomain is the result of CreateWorkspaceDomain: the pending record and its DNS challenge value. Unlike single-use tokens the challenge is not secret (it is published in DNS) and also stays on the record.
type IssuedWorkspaceInvitation ¶
type IssuedWorkspaceInvitation struct {
Invitation WorkspaceInvitation
Token string
}
IssuedWorkspaceInvitation carries the raw invitation token exactly once, from InviteToWorkspace. The host delivers it to the invited address and never stores it.
type LoginThrottle ¶
type LoginThrottle struct {
UserID UUID
FailedAttempts int64
LockedUntil *time.Time
UpdatedAt time.Time
}
LoginThrottle tracks consecutive authentication failures for one user. It backs the built-in account lockout and is reset by any successful authentication.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager is the façade over every Credbound capability. It is safe for concurrent use and is built once per process with New.
func New ¶
New validates the configuration and builds a Manager. It rejects missing required ports, undersized keys and invalid role or provider definitions with errors matching ErrInvalidInput, and fills zero durations and limits with the documented defaults.
Example ¶
ExampleNew shows the smallest working configuration: a store, the Argon2id password hasher, and the three secrets. TOTP and passkey providers are optional — without them the related flows return ErrNotSupported while everything else works. Production replaces memory.New() with a SQL store whose migrations have been applied, and loads the secrets from a secret manager instead of embedding them.
package main
import (
"bytes"
"fmt"
"log"
"github.com/deepteams/credbound"
"github.com/deepteams/credbound/memory"
"github.com/deepteams/credbound/password"
)
func main() {
passwords, err := password.New(password.DefaultParams())
if err != nil {
log.Fatal(err)
}
manager, err := credbound.New(credbound.Config{
Store: memory.New(),
Passwords: passwords,
SecretKey: bytes.Repeat([]byte{0x11}, 32), // exactly 32 bytes
PATPepper: bytes.Repeat([]byte{0x22}, 32), // at least 32 bytes
RecoveryPepper: bytes.Repeat([]byte{0x33}, 32), // at least 32 bytes
})
fmt.Println(manager != nil, err)
}
Output: true <nil>
func (*Manager) AcceptInvitation ¶
func (m *Manager) AcceptInvitation(ctx context.Context, actor Authentication, raw string) (_ Membership, err error)
AcceptInvitation lets an authenticated user who owns the invited address as a verified email join the workspace with the invited role.
func (*Manager) AddEventListener ¶
func (m *Manager) AddEventListener(listener EventListener) Subscription
AddEventListener registers a listener after construction, in addition to Config.EventListeners. It returns the Subscription that removes it; a nil listener is ignored and yields a no-op Subscription.
func (*Manager) AddMembership ¶
func (m *Manager) AddMembership(ctx context.Context, actor Authentication, workspaceID, userID UUID, role Role) (Membership, error)
AddMembership adds an existing user to the workspace with the given role, atomically with the audit event. The actor needs a fresh AAL2 step-up plus workspace users write and RBAC write. An existing membership — local or SCIM-managed — fails with ErrConflict.
func (*Manager) AddTransactionHook ¶
func (m *Manager) AddTransactionHook(hook TransactionHook) Subscription
AddTransactionHook registers a hook after construction, in addition to Config.TransactionHooks. It returns the Subscription that removes it; a nil hook is ignored and yields a no-op Subscription.
func (*Manager) AdminDisableWorkspace ¶
func (m *Manager) AdminDisableWorkspace(ctx context.Context, actor Authentication, request TrustedRequest, workspaceID UUID) error
AdminDisableWorkspace is the instance-administration variant of DisableWorkspace: it requires admin workspaces write and an admin mutation (fresh AAL2, or a trusted local request) instead of a membership.
func (*Manager) AdminEnableWorkspace ¶
func (m *Manager) AdminEnableWorkspace(ctx context.Context, actor Authentication, request TrustedRequest, workspaceID UUID) error
AdminEnableWorkspace is the instance-administration variant of EnableWorkspace: it requires admin workspaces write and an admin mutation (fresh AAL2, or a trusted local request) instead of a membership.
func (*Manager) AdminResetSecondFactor ¶
func (m *Manager) AdminResetSecondFactor(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID) (err error)
AdminResetSecondFactor is the total-loss recovery path: when a user has lost every second factor (TOTP device, recovery codes, passkeys), an instance administrator removes them all and revokes the user's server-side sessions in one atomic operation, so the account falls back to its first factor and the user re-enrolls from a fresh sign-in. The actor needs admin users write and an admin mutation (fresh AAL2, or a trusted local request); the target must exist and cannot be the actor — an administrator's own factors are removed through DisableTOTP and DeletePasskey, which re-prove possession. Hosts should notify the affected user out of band through SecondFactorResetEvent.
func (*Manager) AdminUpdateUser ¶
func (m *Manager) AdminUpdateUser(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID, input UpdateUserInput) (user User, err error)
AdminUpdateUser changes any account's display name. The actor needs admin users write and an admin mutation (fresh AAL2, or a trusted local request), like every other administrative user lifecycle operation. It returns the updated user.
func (*Manager) AdminUpdateWorkspace ¶
func (m *Manager) AdminUpdateWorkspace(ctx context.Context, actor Authentication, request TrustedRequest, workspaceID UUID, input UpdateWorkspaceInput) (_ Workspace, err error)
AdminUpdateWorkspace is the instance-administration variant of UpdateWorkspace: it requires admin workspaces write and an admin mutation (fresh AAL2, or a trusted local request) instead of a membership, and is audited like every administrative access.
func (*Manager) AdoptSCIMUser ¶
func (m *Manager) AdoptSCIMUser(ctx context.Context, actor Authentication, configurationID, userID UUID, input SCIMUserInput) (_ SCIMUser, err error)
AdoptSCIMUser explicitly places an existing local membership under directory management, creating the SCIM link atomically with the audit event. Unlike the provisioning operations it is run by a workspace administrator: the actor needs a fresh AAL2 step-up and workspace RBAC write. A membership already managed by SCIM fails with ErrConflict.
func (*Manager) AnonymizeUser ¶
func (m *Manager) AnonymizeUser(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID) (err error)
AnonymizeUser is the right-to-erasure primitive: an instance administrator pseudonymizes a user by scrubbing their mutable personal data — display name, email addresses (replaced with unique tombstones), SSO and PAT names, session IP/User-Agent, the personal attributes of linked SCIM profiles (which are also marked deprovisioned) and the address on workspace invitations the user accepted — while disabling the account, revoking its PATs, sessions and OAuth grants, and removing its second factors, all in one transaction. The append-only, hash-chained audit log is deliberately preserved: it retains a pseudonymous user id and the request IP/User-Agent under the host's security-log retention basis, since scrubbing it would break VerifyAuditChain. The actor needs admin users write and an admin mutation (fresh AAL2, or a trusted local request); anonymizing the last enabled root or the sole admin of a workspace fails with ErrConflict, as disabling them would. Hosts erase their own application-owned data — and any business records referencing the user — separately, per ExportUserData and their retention policy. It is irreversible.
func (*Manager) AuditEvents ¶
func (m *Manager) AuditEvents(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[AuditEvent], error]
AuditEvents streams the audit log of one workspace. The actor needs a fresh AAL2 step-up and workspace audit read in that workspace.
func (*Manager) AuthenticateOAuthAccessToken ¶
func (m *Manager) AuthenticateOAuthAccessToken(ctx context.Context, resourceURI, raw string) (_ OAuthAuthentication, err error)
AuthenticateOAuthAccessToken validates a bearer access token for one resource URI — the MCP middleware check run on every request. It verifies the token digest, expiry and revocation, then re-validates the grant, client, issuer, resource binding, user, workspace and the workspace permissions behind every scope, so a suspended membership or revoked consent takes effect immediately. Returns the OAuthAuthentication capability, or ErrInvalidCredentials (ErrForbidden when only a scope permission is missing).
func (*Manager) AuthenticatePAT ¶
AuthenticatePAT validates a raw PAT, whose marker is Config.PATPrefix, in constant time against its stored digest and returns a non-interactive AAL1 authentication carrying the PAT's workspace binding and scopes; last_used_at is updated atomically with the audit event. Malformed, unknown, expired and revoked tokens, as well as tokens of disabled users or workspaces, all fail with ErrInvalidCredentials. The result never satisfies step-up checks.
func (*Manager) AuthenticatePassword ¶
func (m *Manager) AuthenticatePassword(ctx context.Context, email, password string) (_ Authentication, err error)
AuthenticatePassword verifies an email and password and returns an AAL1 interactive authentication whose SecondFactorRequired flag reports an active TOTP factor. An unknown address, a wrong password, a disabled user, an account without a password credential and a locked account all perform the same hash derivation and fail identically with ErrInvalidCredentials, so the caller learns nothing about account existence — ErrLocked would be an existence oracle here and is never returned to this unauthenticated entry point. The lockout is still audited (reason "locked") and hosts observe it through UserLockedEvent and AuthenticationFailureEvent; flows that follow a proof of possession (VerifyTOTP, CompleteEmailOTP) keep reporting ErrLocked. Consecutive failures on an existing enabled account count toward the lockout.
Example ¶
ExampleManager_AuthenticatePassword signs a user in with email and password. The deterministic clock, random source, and fast hasher come from the credboundtest package and are test-only.
package main
import (
"bytes"
"context"
"fmt"
"log"
"github.com/deepteams/credbound"
"github.com/deepteams/credbound/credboundtest"
"github.com/deepteams/credbound/memory"
)
func main() {
clock := credboundtest.NewClock(credboundtest.DefaultStartTime)
manager, err := credbound.New(credbound.Config{
Store: memory.New(),
Passwords: credboundtest.Passwords{},
SecretKey: bytes.Repeat([]byte{0x11}, 32),
PATPepper: bytes.Repeat([]byte{0x22}, 32),
RecoveryPepper: bytes.Repeat([]byte{0x33}, 32),
Clock: clock.Now,
Random: credboundtest.NewDeterministicRandom(),
})
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
if _, _, err := manager.Bootstrap(ctx, credbound.BootstrapInput{
Email: "root@example.com", DisplayName: "Root", Password: "correct horse battery staple", WorkspaceName: "Main",
}); err != nil {
log.Fatal(err)
}
// The returned Authentication is a security capability: the host stores
// it server-side and reuses it verbatim on later requests. Level 1 is
// AAL1; only VerifyTOTP, a passkey, or SSO reauthentication produce AAL2.
authn, err := manager.AuthenticatePassword(ctx, "root@example.com", "correct horse battery staple")
if err != nil {
log.Fatal(err)
}
fmt.Println(authn.Method, authn.Level, authn.SecondFactorRequired)
}
Output: password 1 false
func (*Manager) AuthenticateSCIM ¶
func (m *Manager) AuthenticateSCIM(ctx context.Context, raw string) (_ SCIMAuthentication, err error)
AuthenticateSCIM validates a raw cbs_ bearer token in constant time and returns the SCIMAuthentication service capability scoped to its configuration and workspace; the credential's last use is recorded atomically with the audit event. Malformed, unknown, expired and revoked tokens, disabled configurations and disabled workspaces all fail with ErrInvalidCredentials; ErrNotSupported without the SCIM capability.
func (*Manager) AuthenticateSession ¶
func (m *Manager) AuthenticateSession(ctx context.Context, raw string) (_ Authentication, _ Session, err error)
AuthenticateSession validates a raw cbs_ session token in constant time against its stored digest, enforces expiry (ErrExpired) and revocation, re-checks that the user is still enabled, touches the session's last-seen timestamp atomically with the audit event, and returns the immutable Authentication snapshot together with the Session record (Digest scrubbed). Malformed and unknown tokens, revoked sessions and sessions of disabled users all fail with ErrInvalidCredentials.
The returned Authentication reproduces the snapshot verbatim — including AuthenticatedAt — so step-up freshness keeps measuring the original factor verification. Validation happens on every request, so it deliberately emits no authentication.succeeded (or any other) event; the audit log is the record of session activity.
Cost note: by default every successful validation performs one write transaction (the last-seen touch committed with its audit event). High-traffic hosts set Config.SessionTouchInterval to coarsen that to at most one write per session per interval — revocation, expiry, idle and disabled-user checks still run against the store on every call, so a revoked session is refused on the very next request, unlike with a host-side result cache. Within the interval the session's LastSeenAt is returned as last persisted.
func (*Manager) Authorize ¶
func (m *Manager) Authorize(ctx context.Context, authn Authentication, workspaceID UUID, minimumRole Role) error
Authorize checks that the authentication belongs to an enabled user with an active membership whose role includes the minimum role in that workspace. Missing or insufficient memberships, disabled users or workspaces, and workspace-bound credentials used elsewhere fail with ErrForbidden; a workspace requiring MFA rejects interactive AAL1 contexts with ErrStepUpRequired, and a TOTP-pending context (SecondFactorRequired) is rejected the same way in every workspace — the first factor alone never authorizes anything. A scoped credential (a PAT) passes this coarse role check only with the "*" wildcard scope — a narrowed token has no role-shaped privilege, so route it through AuthorizePermission, the canonical, finer check.
func (*Manager) AuthorizeAdmin ¶
func (m *Manager) AuthorizeAdmin(ctx context.Context, actor Authentication, permission Permission) error
AuthorizeAdmin checks that the actor is an enabled user holding an instance role that maps to the permission. Every check — allowed or denied — appends an audit event and fails with ErrAuditUnavailable when that audit cannot be persisted; a missing role or permission fails with ErrForbidden. Instance roles never grant workspace data access by themselves, and a scope-narrowed or workspace-bound credential (a PAT or OAuth token) is refused outright — instance administration is not a delegable, workspace-scoped capability.
Deliberate exception: a workspace-unbound PAT holding the "*" scope is an unrestricted credential of its owner and passes this check, so when the owner is an instance administrator such a PAT can perform administration reads — listing every user and workspace, reading the instance audit log. Mutations stay out of reach: RequireAdminMutation additionally demands an interactive fresh AAL2 authentication (or a trusted local request), which no PAT satisfies. Mint "*" PATs for automation deliberately.
func (*Manager) AuthorizePermission ¶
func (m *Manager) AuthorizePermission(ctx context.Context, authn Authentication, workspaceID UUID, permission WorkspacePermission) error
AuthorizePermission is the canonical tenant authorization: it checks that the authentication belongs to an enabled user with an active membership whose role carries the workspace permission. A scoped credential (a PAT) must additionally carry the permission itself — or the "*" wildcard — as a scope: scopes are the least privilege the owner chose at creation, and without this check the role lookup would silently widen a narrow token back to the member's full permission set. Failures behave exactly like Authorize — ErrForbidden fails closed, a workspace requiring MFA rejects interactive AAL1 contexts with ErrStepUpRequired while non-interactive credentials such as PATs are unaffected, and a TOTP-pending context (SecondFactorRequired) is rejected with ErrStepUpRequired in every workspace.
func (*Manager) BeginDiscoverablePasskeyAuthentication ¶
func (m *Manager) BeginDiscoverablePasskeyAuthentication(ctx context.Context) (_ PasskeyChallenge, err error)
BeginDiscoverablePasskeyAuthentication starts a usernameless WebAuthn ceremony: no address is asked and the challenge carries an empty allowCredentials list, so the authenticator offers its discoverable credentials. Because the challenge is bound to no account, there is no per-address answer left to probe — this closes the residual enumeration signal of the per-address decoy, whose fabricated allowCredentials list holds one entry while a real account may show several. It requires a provider implementing DiscoverablePasskeyProvider and a PasskeyCredentialStore-capable store; otherwise it returns ErrNotSupported.
func (*Manager) BeginEmailAddition ¶
func (m *Manager) BeginEmailAddition(ctx context.Context, actor Authentication, address string) (_ IssuedEmailVerification, err error)
BeginEmailAddition attaches a new, unverified address to the actor's account and returns the raw verification token exactly once for the host to deliver to that address; only its HMAC is persisted. It requires a recent interactive authentication, and a globally taken address fails with ErrConflict. The address becomes usable for sign-in only after ConfirmEmail.
func (*Manager) BeginEmailAuthentication ¶
func (m *Manager) BeginEmailAuthentication(ctx context.Context, email string) (_ IssuedEmailAuthentication, err error)
BeginEmailAuthentication issues a single-use, short-lived magic-link token for the account owning the verified address. The host delivers the token to that address and answers the end user identically whether or not the account exists. When the address does not belong to a verified email of an enabled account, the call succeeds with a zero IssuedEmailAuthentication so the host's error path never becomes an enumeration oracle: send the email only when Deliverable is true.
func (*Manager) BeginEmailOTP ¶
BeginEmailOTP issues a single-use, short-lived numeric code for the account owning the verified address, together with a sealed continuation that the host passes back to CompleteEmailOTP with the code the user typed. The continuation is AEAD-sealed and tamper-proof, so it may safely round-trip through the client (a cookie or hidden form field); what matters is that it come back with the code, not where it was kept. Binding the code to the continuation keeps its short length safe: a code is only ever compared against the single credential it was issued for, and failed attempts count toward the account lockout.
The call answers identically whether or not the address is eligible: for an unknown, disabled, or unverified address it still returns a well-formed continuation with an empty Code and Deliverable false, so the host sends no email but responds to the end user exactly as in the success case, and the later completion fails like any wrong code. Send the email only when Deliverable is true.
func (*Manager) BeginOAuthAuthorization ¶
func (m *Manager) BeginOAuthAuthorization(ctx context.Context, actor Authentication, input BeginOAuthAuthorizationInput) (_ OAuthConsent, err error)
BeginOAuthAuthorization validates an authorization request — client, exact redirect URI, PKCE S256, state, resource, scopes and the actor's membership and per-scope permissions — and returns a sealed OAuthConsent for the host's consent UI. Nothing is persisted yet: only CompleteOAuthAuthorization can turn the continuation into a grant and code. The actor must be interactive; RequiresStepUp on the result signals that a scope demands a stronger or fresher authentication.
func (*Manager) BeginPasskeyAuthentication ¶
func (m *Manager) BeginPasskeyAuthentication(ctx context.Context, email string) (_ PasskeyChallenge, err error)
BeginPasskeyAuthentication starts a WebAuthn authentication ceremony for the account owning the address. No actor is required. An address that cannot authenticate — unknown, disabled, or with no passkey — is answered with a decoy challenge indistinguishable from a real one, so the response never reveals whether the account exists or holds a passkey; the decoy fails at FinishPasskeyAuthentication like any wrong credential. Returns ErrNotSupported without Config.Passkeys.
func (*Manager) BeginPasskeyRegistration ¶
func (m *Manager) BeginPasskeyRegistration(ctx context.Context, actor Authentication, name string) (_ PasskeyChallenge, err error)
BeginPasskeyRegistration starts a WebAuthn registration ceremony for the actor and returns the browser options with a sealed continuation bound to the user, operation and expiry. It requires a recent interactive authentication and returns ErrNotSupported without Config.Passkeys.
func (*Manager) BeginPasswordReset ¶
func (m *Manager) BeginPasswordReset(ctx context.Context, email string) (_ IssuedPasswordReset, err error)
BeginPasswordReset issues a single-use, expiring reset token for the account owning the address. The host delivers the token to that address and answers the end user identically whether or not the account exists. When the address does not belong to an enabled account, the call succeeds with a zero IssuedPasswordReset so the host's error path never becomes an enumeration oracle: send the email only when Deliverable is true. The library performs the same cryptographic work and a comparable store write in both cases so timing does not reveal the difference either.
func (*Manager) BeginSSO ¶
BeginSSO starts a sign-in ceremony with a registered provider and returns the redirect URL with a sealed continuation for FinishSSO. No actor is required; an unregistered configuration fails with ErrNotFound. Sign-in only succeeds for an identity previously linked with BeginSSOLink — Credbound never matches accounts by IdP email.
func (*Manager) BeginSSOLink ¶
func (m *Manager) BeginSSOLink(ctx context.Context, actor Authentication, providerConfigurationID UUID) (SSOChallenge, error)
BeginSSOLink starts a ceremony that links the provider identity to the actor's existing account when finished. It requires a recent interactive authentication, per the explicit-linking policy.
func (*Manager) BeginSSOStepUp ¶
func (m *Manager) BeginSSOStepUp(ctx context.Context, actor Authentication, providerConfigurationID UUID) (SSOChallenge, error)
BeginSSOStepUp starts a step-up ceremony for the actor: the provider is asked to force reauthentication and its own MFA, and the finished ceremony must resolve to an identity already linked to this actor. It requires a recent interactive authentication.
func (*Manager) BeginTOTPEnrollment ¶
func (m *Manager) BeginTOTPEnrollment(ctx context.Context, actor Authentication) (_ TOTPEnrollment, err error)
BeginTOTPEnrollment creates or replaces the actor's inactive TOTP factor and returns the otpauth URI once for the host to render; the secret is persisted sealed. It requires a recent interactive authentication and returns ErrNotSupported without Config.TOTP. The factor gates nothing until ConfirmTOTPEnrollment activates it.
func (*Manager) Bootstrap ¶
func (m *Manager) Bootstrap(ctx context.Context, input BootstrapInput) (_ Authentication, _ Workspace, err error)
Bootstrap creates the first account of an empty instance: the user, its verified primary email, the initial workspace, an admin membership and the instance-level root role, all in one transaction with the audit event. It requires no actor, returns an AAL1 password authentication, and every call after the first fails with ErrConflict.
func (*Manager) ChangePassword ¶
func (m *Manager) ChangePassword(ctx context.Context, actor Authentication, currentPassword, newPassword string) (err error)
ChangePassword replaces the actor's password after re-verifying the current one. A wrong current password counts toward the account lockout exactly like a failed sign-in, and a locked account is refused with ErrLocked before any verification. It requires a recent interactive authentication (any AAL, within the step-up window) and validates the new password against the built-in rules and Config.PasswordPolicy. A wrong current password is audited and returns ErrInvalidCredentials.
A change revokes the user's server-side sessions (when the store is SessionStore-capable) in the same transaction that installs the new password, so a leaked session token cannot outlive it and a failure leaves both untouched; this includes the actor's current session, so the host re-establishes one afterwards by re-authenticating with the new password — a pre-change password Authentication can no longer mint sessions (see CreateSession). PATs and OAuth grants are deliberately preserved: they are integration credentials, not interactive sessions, and a routine change should not break machine-to-machine access. A host managing its own sessions must terminate them itself, and one treating the change as a full compromise response can still call RevokeUserCredentials alongside it.
func (*Manager) CompleteEmailAuthentication ¶
func (m *Manager) CompleteEmailAuthentication(ctx context.Context, raw string) (_ Authentication, err error)
CompleteEmailAuthentication consumes a magic-link token and returns an AAL1 interactive authentication. Like a password login, it reports whether an active TOTP factor still has to be verified before AAL2 operations.
func (*Manager) CompleteEmailOTP ¶
func (m *Manager) CompleteEmailOTP(ctx context.Context, continuation, code string) (_ Authentication, err error)
CompleteEmailOTP consumes an email OTP and returns an AAL1 interactive authentication. Like a password login, it reports whether an active TOTP factor still has to be verified before AAL2 operations. A wrong code counts toward the account lockout exactly like a wrong password.
func (*Manager) CompleteOAuthAuthorization ¶
func (m *Manager) CompleteOAuthAuthorization(ctx context.Context, actor Authentication, rawContinuation string, approved bool) (_ OAuthAuthorizationResult, err error)
CompleteOAuthAuthorization resolves a consent continuation: approval re-validates everything, creates the grant and the single-use authorization code atomically with the audit event, and returns the code exactly once; denial audits the refusal and returns an access_denied result for the redirect. The continuation must belong to the same interactive actor as BeginOAuthAuthorization, and scopes demanding a stronger or fresher authentication fail with ErrStepUpRequired.
func (*Manager) CompletePasswordReset ¶
func (m *Manager) CompletePasswordReset(ctx context.Context, raw, newPassword string) (_ User, err error)
CompletePasswordReset consumes a reset token and installs the new password. For a passwordless member provisioned by SSO JIT or SCIM this installs their first password: the verified email proof is the same authority every reset rests on. An address under a confirmed EnforceSSO domain is refused by BeginPasswordReset up front, and the account's primary address is re-checked here so an in-flight token does not outlive an EnforceSSO confirmation — consuming it would install an unusable password yet still revoke the account's sessions, PATs and grants. As required by the recovery policy, it atomically revokes every PAT and OAuth grant of the account and clears its login throttle. When the store supports sessions (SessionStore) the user's server-side sessions are revoked in the same transaction; hosts managing their own sessions must still terminate those themselves. The user then signs in again with the new password.
func (*Manager) ConfirmEmail ¶
ConfirmEmail marks the pending address verified by proving possession of the verification token. Possession of the token is the authorization — no actor is required. An unknown or mismatched token fails with ErrInvalidCredentials, a stale one with ErrExpired, and an already verified address with ErrConflict.
func (*Manager) ConfirmTOTPEnrollment ¶
func (m *Manager) ConfirmTOTPEnrollment(ctx context.Context, actor Authentication, code string) (_ []string, err error)
ConfirmTOTPEnrollment activates the pending factor after proving a valid code and returns the single-use recovery codes exactly once; only their peppered digests are persisted. It requires a recent interactive authentication and returns ErrNotSupported without Config.TOTP, ErrNotFound without a pending enrollment, and ErrInvalidCredentials for a wrong code.
func (*Manager) ConfirmWorkspaceDomain ¶
func (m *Manager) ConfirmWorkspaceDomain(ctx context.Context, actor Authentication, domainID UUID) (err error)
ConfirmWorkspaceDomain marks the pending domain verified. When a Config.DomainVerifier is registered it proves control here — resolving the challenge against the domain's DNS — and fails with ErrDomainVerification when the challenge is not published. Without one it refuses with ErrNotSupported unless Config.TrustActorDomainVerification explicitly opts into treating the call as the actor's assertion that DNS verification completed; Credbound never queries DNS itself. It requires a fresh AAL2 step-up and workspace settings write in the owning workspace, and fails with ErrConflict when the domain was already confirmed. Only from this point on does the domain's policy apply.
func (*Manager) CreateOAuthInitialAccessToken ¶
func (m *Manager) CreateOAuthInitialAccessToken(ctx context.Context, actor Authentication, request TrustedRequest, issuerID UUID, input CreateOAuthInitialAccessTokenInput) (_ IssuedOAuthInitialAccessToken, err error)
CreateOAuthInitialAccessToken issues the expiring, registration-limited bootstrap credential for protected DCR and returns the raw token exactly once; only its HMAC is persisted, atomically with the audit event. The actor needs admin settings write and an admin mutation (fresh AAL2, or a trusted local request); an issuer whose DCR mode is not protected fails with ErrNotSupported. The token grants no authority over any resource.
func (*Manager) CreateOAuthIssuer ¶
func (m *Manager) CreateOAuthIssuer(ctx context.Context, actor Authentication, request TrustedRequest, input CreateOAuthIssuerInput) (_ OAuthIssuer, err error)
CreateOAuthIssuer registers an authorization-server issuer with its CIMD, DCR, OIDC and token-lifetime policy, atomically with the audit event. The actor needs admin settings write and an admin mutation (fresh AAL2, or a trusted local request). Returns ErrNotSupported unless both Config.OAuth and the OAuthStore capability exist.
func (*Manager) CreateOAuthProtectedResource ¶
func (m *Manager) CreateOAuthProtectedResource(ctx context.Context, actor Authentication, workspaceID UUID, input CreateOAuthProtectedResourceInput) (_ OAuthProtectedResource, err error)
CreateOAuthProtectedResource registers an MCP resource of the workspace under an issuer, with scope definitions that map onto registered workspace permissions, atomically with the audit event. The actor needs a fresh AAL2 step-up and the oauth.resource.manage workspace permission; access tokens are later bound to this resource URI and workspace. Returns ErrNotSupported without the OAuth capability.
func (*Manager) CreatePAT ¶
func (m *Manager) CreatePAT(ctx context.Context, actor Authentication, input CreatePATInput) (_ IssuedPAT, err error)
CreatePAT issues a personal access token with at least 256 bits of entropy and returns the raw token exactly once; only its HMAC digest is persisted, atomically with the audit event. The authentication it demands follows Config.StepUp's PAT scope — a fresh AAL2 step-up by default — and binding the token to a workspace additionally requires access to that workspace. Each scope is either the "*" wildcard or a workspace permission string: AuthorizePermission denies a scoped authentication any permission outside its scopes, and the coarse role-based Authorize requires the wildcard, so the scopes chosen here are the ceiling of what the token can ever do.
Example ¶
ExampleManager_CreatePAT issues a personal access token. Creation requires a fresh interactive AAL2 authentication (a step-up); the raw token is returned exactly once and only its digest is persisted.
package main
import (
"bytes"
"context"
"fmt"
"log"
"github.com/deepteams/credbound"
"github.com/deepteams/credbound/credboundtest"
"github.com/deepteams/credbound/memory"
)
func main() {
clock := credboundtest.NewClock(credboundtest.DefaultStartTime)
manager, err := credbound.New(credbound.Config{
Store: memory.New(),
Passwords: credboundtest.Passwords{},
SecretKey: bytes.Repeat([]byte{0x11}, 32),
PATPepper: bytes.Repeat([]byte{0x22}, 32),
RecoveryPepper: bytes.Repeat([]byte{0x33}, 32),
Clock: clock.Now,
Random: credboundtest.NewDeterministicRandom(),
})
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
authn, workspace, err := manager.Bootstrap(ctx, credbound.BootstrapInput{
Email: "root@example.com", DisplayName: "Root", Password: "correct horse battery staple", WorkspaceName: "Main",
})
if err != nil {
log.Fatal(err)
}
stepUp := credboundtest.AAL2(authn.UserID, clock.Now()) // test-only step-up
issued, err := manager.CreatePAT(ctx, stepUp, credbound.CreatePATInput{
Name: "ci", WorkspaceID: workspace.ID, Scopes: []string{"read"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(issued.PAT.Name, issued.PAT.Scopes, issued.Token != "", issued.PAT.Digest == nil)
}
Output: ci [read] true true
func (*Manager) CreateSCIMConfiguration ¶
func (m *Manager) CreateSCIMConfiguration(ctx context.Context, actor Authentication, workspaceID UUID, input CreateSCIMConfigurationInput) (_ IssuedSCIMCredential, err error)
CreateSCIMConfiguration creates the provisioning domain of a workspace with its first bearer credential and returns the raw token exactly once; only its HMAC is persisted, atomically with the audit event. The actor needs a fresh AAL2 step-up and workspace RBAC write. Returns ErrNotSupported when the store lacks the SCIM capability.
func (*Manager) CreateSession ¶
func (m *Manager) CreateSession(ctx context.Context, actor Authentication, _ CreateSessionInput) (_ IssuedSession, err error)
CreateSession persists a server-side session for the actor: an immutable snapshot of the Authentication (method, level, authenticated-at, pending second factor) plus the device metadata attached to the context with WithRequestMetadata, behind an opaque cbs_ token returned exactly once. Only the HMAC digest of the token is stored, atomically with the audit event. It requires a SessionStore-capable store (ErrNotSupported otherwise), an interactive actor — a PAT-backed Authentication is non-interactive and fails with ErrForbidden — and an enabled user.
Sessions never change assurance level in place: after VerifyTOTP or any other AAL transition the host calls CreateSession again with the promoted Authentication and revokes the previous session, which doubles as fixation protection. Expiry is absolute (CreatedAt plus Config.SessionTTL) and is never extended by activity.
A password-derived Authentication carries a fingerprint of the credential it verified, and the store re-checks it inside the session transaction: an Authentication whose password was replaced in the meantime — by ChangePassword or CompletePasswordReset — fails with ErrInvalidCredentials instead of minting a session the replacement's revocation sweep can no longer reach. After a password change the host therefore re-authenticates with the new password before creating the follow-up session.
func (*Manager) CreateUser ¶
func (m *Manager) CreateUser(ctx context.Context, actor Authentication, workspaceID UUID, input CreateUserInput) (_ User, err error)
CreateUser administratively creates an account with a verified primary email and an active membership in the workspace, atomically with its audit. The actor needs a fresh AAL2 step-up and workspace users write in that workspace; a taken address fails with ErrConflict.
func (*Manager) CreateWorkspace ¶
func (m *Manager) CreateWorkspace(ctx context.Context, actor Authentication, input CreateWorkspaceInput) (_ Workspace, err error)
CreateWorkspace creates a workspace and makes the actor its admin member, atomically with the audit event. It requires a fresh AAL2 step-up from an enabled user.
func (*Manager) CreateWorkspaceDomain ¶
func (m *Manager) CreateWorkspaceDomain(ctx context.Context, actor Authentication, workspaceID UUID, domain string) (_ IssuedWorkspaceDomain, err error)
CreateWorkspaceDomain registers an email domain for the workspace in a pending state and returns the DNS challenge value the host publishes as a TXT record. Credbound performs no network I/O: the host proves control of the domain out of band and then calls ConfirmWorkspaceDomain. The domain name is normalized to lowercase and must be a registrable DNS name, unique across all workspaces (ErrConflict) — except that a stale pending claim, one left unconfirmed past Config.DomainClaimTTL, is replaced rather than defended, so an unverified claim can never permanently deny the domain's real owner. It requires a DomainStore-capable store (ErrNotSupported otherwise), a fresh AAL2 step-up and workspace settings write, exactly like UpdateWorkspace. An unconfirmed domain has no effect on any flow.
func (*Manager) DeletePasskey ¶
func (m *Manager) DeletePasskey(ctx context.Context, actor Authentication, passkeyID UUID) (err error)
DeletePasskey removes one of the actor's passkeys, atomically with the audit event. It requires a fresh AAL2 step-up and works even without Config.Passkeys, so stale credentials remain removable.
func (*Manager) DeleteSCIMGroup ¶
func (m *Manager) DeleteSCIMGroup(ctx context.Context, principal SCIMAuthentication, id UUID) (err error)
DeleteSCIMGroup logically deletes a directory group and recomputes the roles of its former members, atomically with the transactional hook and audit. Deleting an unknown group is a no-op.
func (*Manager) DeprovisionSCIMUser ¶
func (m *Manager) DeprovisionSCIMUser(ctx context.Context, principal SCIMAuthentication, id UUID) (err error)
DeprovisionSCIMUser logically deprovisions a managed user: it suspends the membership and revokes the user's workspace-scoped PATs while keeping the global account and the SCIM link for auditing and restoration, atomically with the transactional hook and audit. Deprovisioning an unknown or already deprovisioned user is a no-op.
Server-side sessions are global, not workspace-scoped, so they survive a SCIM deprovisioning: the suspended membership already denies every tenant-scoped authorization on the next check. A host that wants IdP offboarding to also end sessions everywhere calls DisableUser or RevokeUserSessions from its own directory-event handling.
func (*Manager) DisableOAuthClient ¶
func (m *Manager) DisableOAuthClient(ctx context.Context, actor Authentication, request TrustedRequest, clientRecordID UUID) error
DisableOAuthClient disables a client record so authorization, token, and bearer-validation operations refuse it — already-issued access tokens stop authenticating immediately — atomically with the audit event. The actor needs admin settings write and an admin mutation (fresh AAL2, or a trusted local request); disabling an already disabled client is a no-op.
func (*Manager) DisableOAuthIssuer ¶
func (m *Manager) DisableOAuthIssuer(ctx context.Context, actor Authentication, request TrustedRequest, issuerID UUID) error
DisableOAuthIssuer disables an issuer so all its discovery, authorization and token operations are refused, atomically with the audit event. The actor needs admin settings write and an admin mutation (fresh AAL2, or a trusted local request); disabling an already disabled issuer is a no-op. Returns ErrNotSupported without the OAuth capability.
func (*Manager) DisableOAuthProtectedResource ¶
func (m *Manager) DisableOAuthProtectedResource(ctx context.Context, actor Authentication, workspaceID UUID, resourceID UUID) error
DisableOAuthProtectedResource disables an MCP resource of the workspace so its bearer validation and metadata are refused, atomically with the audit event. The actor needs a fresh AAL2 step-up and the oauth.resource.manage permission in that workspace; a resource of another workspace fails with ErrForbidden.
func (*Manager) DisableSCIMConfiguration ¶
func (m *Manager) DisableSCIMConfiguration(ctx context.Context, actor Authentication, configurationID UUID) (err error)
DisableSCIMConfiguration turns provisioning off for the workspace and revokes all the configuration's active credentials, atomically with the audit event. The actor needs a fresh AAL2 step-up and workspace RBAC write; ErrNotSupported without the SCIM capability.
func (*Manager) DisableTOTP ¶
DisableTOTP removes the actor's active TOTP factor and its recovery codes after re-proving possession of a valid code, atomically with the audit event. The verification itself yields the fresh AAL2 step-up the removal requires. Returns ErrNotSupported without Config.TOTP.
func (*Manager) DisableUser ¶
func (m *Manager) DisableUser(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID) error
DisableUser disables a global account so it can no longer authenticate or authorize anywhere, atomically with the audit event. The actor needs admin users write and an admin mutation (fresh AAL2, or a trusted local request); the store protects the last enabled root administrator. The store cascade revokes the user's credentials and, when the store supports sessions (SessionStore), their server-side sessions in the same transaction; re-enabling never restores them. Disabling an already disabled user is a no-op.
func (*Manager) DisableWorkspace ¶
func (m *Manager) DisableWorkspace(ctx context.Context, actor Authentication, workspaceID UUID) error
DisableWorkspace disables the workspace so every tenant-scoped capability is denied until it is re-enabled; the store cascade also revokes the workspace-bound PAT and OAuth credentials. The actor needs a fresh AAL2 step-up and workspace settings write. Disabling an already disabled workspace is a no-op.
func (*Manager) Emails ¶
func (m *Manager) Emails(ctx context.Context, actor Authentication, userID UUID, page PageRequest) iter.Seq2[PageEvent[EmailAddress], error]
Emails streams a user's email addresses. An empty userID means the actor, which requires a recent interactive authentication; reading another user requires admin users read.
func (*Manager) EnableOAuthClient ¶
func (m *Manager) EnableOAuthClient(ctx context.Context, actor Authentication, request TrustedRequest, clientRecordID UUID) error
EnableOAuthClient re-enables a disabled client under the same authorization as DisableOAuthClient.
func (*Manager) EnableOAuthIssuer ¶
func (m *Manager) EnableOAuthIssuer(ctx context.Context, actor Authentication, request TrustedRequest, issuerID UUID) error
EnableOAuthIssuer re-enables a disabled issuer under the same authorization as DisableOAuthIssuer.
func (*Manager) EnableOAuthProtectedResource ¶
func (m *Manager) EnableOAuthProtectedResource(ctx context.Context, actor Authentication, workspaceID UUID, resourceID UUID) error
EnableOAuthProtectedResource re-enables a disabled resource under the same authorization as DisableOAuthProtectedResource.
func (*Manager) EnableUser ¶
func (m *Manager) EnableUser(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID) error
EnableUser re-enables a disabled global account under the same authorization as DisableUser.
func (*Manager) EnableWorkspace ¶
func (m *Manager) EnableWorkspace(ctx context.Context, actor Authentication, workspaceID UUID) error
EnableWorkspace restores a disabled workspace. The actor must still be an active member holding workspace settings write with a fresh AAL2 step-up — the only tenant mutation a disabled workspace accepts.
func (*Manager) ExchangeOAuthAuthorizationCode ¶
func (m *Manager) ExchangeOAuthAuthorizationCode(ctx context.Context, input ExchangeOAuthAuthorizationCodeInput) (_ OAuthTokenResponse, err error)
ExchangeOAuthAuthorizationCode implements the token endpoint's authorization_code grant: it authenticates the client, verifies the single-use code, exact redirect URI and PKCE verifier, and consumes the code atomically with the issued tokens and audit. Opaque tokens are returned exactly once; a refresh token is added only for offline_access grants of refresh-capable clients and an ID Token only for openid grants of OIDC issuers. Every mismatch fails with ErrInvalidCredentials.
func (*Manager) ExportUserData ¶
func (m *Manager) ExportUserData(ctx context.Context, actor Authentication, userID UUID) (_ UserDataExport, err error)
ExportUserData gathers every record Credbound holds about a user into one document for a data-subject access request. An empty userID exports the actor's own data and needs only a recent interactive authentication; exporting another user requires a fresh AAL2 step-up and admin users read. Sessions are included only on a SessionStore-capable store, SCIM profiles and accepted workspace invitations only on a PrivacyStore-capable one, and OAuth grants only with the OAuth capability. Token digests, sealed passkey credentials and the audit log are never included; the audit log stays available through AuditEvents under its own retention policy.
func (*Manager) FinishDiscoverablePasskeyAuthentication ¶
func (m *Manager) FinishDiscoverablePasskeyAuthentication(ctx context.Context, continuation string, response []byte) (_ Authentication, err error)
FinishDiscoverablePasskeyAuthentication validates the browser response of a discoverable ceremony, resolves the account from the asserted credential, and returns an AAL2 interactive authentication with the same single-use ceremony consumption as FinishPasskeyAuthentication. A disabled account and a confirmed EnforceSSO domain are refused — the domain policy, checked by address at Begin in the email-first flow, is enforced here against the resolved account's primary address.
func (*Manager) FinishPasskeyAuthentication ¶
func (m *Manager) FinishPasskeyAuthentication(ctx context.Context, continuation string, response []byte) (_ Authentication, err error)
FinishPasskeyAuthentication validates the browser response against the sealed continuation and returns an AAL2 interactive authentication — a user-verified passkey ceremony needs no second factor. The passkey's last-use timestamp is updated atomically with the audit event, and the same commit consumes the single-use ceremony, so a captured response can never be replayed — WebAuthn signature counters alone cannot guarantee that, since many authenticators legitimately report a constant zero. A failed or replayed ceremony is audited and returns ErrInvalidCredentials.
func (*Manager) FinishPasskeyRegistration ¶
func (m *Manager) FinishPasskeyRegistration(ctx context.Context, actor Authentication, continuation string, response []byte) (_ Passkey, err error)
FinishPasskeyRegistration validates the browser response against the sealed continuation and persists the new passkey, atomically with the audit event. The continuation must belong to the same actor, who still needs a recent interactive authentication; a failed ceremony is audited and returns ErrInvalidCredentials. The returned Passkey carries no credential material.
func (*Manager) FinishSSO ¶
func (m *Manager) FinishSSO(ctx context.Context, continuation string, response []byte) (_ Authentication, err error)
FinishSSO completes any SSO ceremony (sign-in, link or step-up) by validating the provider response against the sealed continuation. The authentication is AAL2 only when the provider carries a Config.SSOAssurance policy the asserted context satisfies (or that trusts the provider unverified); otherwise it is AAL1, because SSO never mints AAL2 on the IdP's unverified word. Link ceremonies persist the new identity atomically with the audit event; sign-in and step-up resolve the stable issuer/subject pair and update its last use. Failed or mismatched ceremonies return ErrInvalidCredentials, stale continuations ErrExpired.
On a DomainStore-capable store, a sign-in whose identity is unknown may JIT-provision an account: when the IdP-verified email belongs to a confirmed auto-join workspace domain that trusts this provider configuration and no existing account owns the address, one transaction creates a passwordless user, its verified primary email, the configured membership, and the identity link. An address owned by an existing account is never auto-linked and the sign-in fails as an unknown identity.
func (*Manager) GrantRole ¶
func (m *Manager) GrantRole(ctx context.Context, actor Authentication, workspaceID, userID UUID, role Role) (err error)
GrantRole sets a user's workspace role, creating the local membership when none exists, atomically with the audit event. The actor needs a fresh AAL2 step-up and workspace RBAC write; SCIM-managed memberships fail with ErrConflict and unknown roles are rejected. The store protects the last active workspace administrator from demotion.
func (*Manager) InstanceAdministrator ¶
func (m *Manager) InstanceAdministrator(ctx context.Context, actor Authentication, userID UUID) (InstanceAdministrator, error)
InstanceAdministrator returns one user's instance-administration role. It requires admin instance-roles read; an unknown or role-less user reports ErrNotFound.
func (*Manager) InstanceAdministrators ¶
func (m *Manager) InstanceAdministrators(ctx context.Context, actor Authentication) iter.Seq2[InstanceAdministrator, error]
InstanceAdministrators streams every instance role assignment, oldest first, so an administration interface can render the governance roster. It requires admin instance-roles read.
func (*Manager) InstanceAuditEvents ¶
func (m *Manager) InstanceAuditEvents(ctx context.Context, actor Authentication, page PageRequest) iter.Seq2[PageEvent[AuditEvent], error]
InstanceAuditEvents streams the audit log of the whole instance. It requires admin audit read.
func (*Manager) InviteToWorkspace ¶
func (m *Manager) InviteToWorkspace(ctx context.Context, actor Authentication, workspaceID UUID, input InviteToWorkspaceInput) (_ IssuedWorkspaceInvitation, err error)
InviteToWorkspace invites an email address into a workspace with a pre-assigned role and returns the single-use token once. The invitee either accepts it from an existing authenticated account owning that address, or registers a new account with it.
func (*Manager) IssueOAuthClientCredentials ¶
func (m *Manager) IssueOAuthClientCredentials(ctx context.Context, input OAuthClientCredentialsInput) (_ OAuthTokenResponse, err error)
IssueOAuthClientCredentials authenticates a confidential client and issues a machine-to-machine access token bound to a protected resource, with no user subject and no refresh token (RFC 6749 §4.4). The client must authenticate (client_secret or private_key_jwt, never a public client) and be registered for the client_credentials grant; the requested scopes must be non-reserved scopes the resource defines and the client is allowed. Revocation is implicit when the client, resource or issuer is disabled.
func (*Manager) Membership ¶
func (m *Manager) Membership(ctx context.Context, actor Authentication, workspaceID, userID UUID) (Membership, error)
Membership returns one membership by workspace and user. An empty userID means the actor's own membership, which needs workspace access; reading another member requires workspace users read in that workspace.
func (*Manager) Memberships ¶
func (m *Manager) Memberships(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[Membership], error]
Memberships streams the memberships of a workspace. The actor needs workspace users read in that workspace.
func (*Manager) OAuthAuthorizationServerMetadata ¶
func (m *Manager) OAuthAuthorizationServerMetadata(ctx context.Context, issuerURL string) (OAuthAuthorizationServerMetadata, error)
OAuthAuthorizationServerMetadata returns the RFC 8414 discovery document of an enabled issuer, reflecting its actual DCR, CIMD and OIDC policy. No authentication is required; unknown or disabled issuers fail with ErrNotFound.
func (*Manager) OAuthClients ¶
func (m *Manager) OAuthClients(ctx context.Context, actor Authentication, issuerID UUID, page PageRequest) iter.Seq2[PageEvent[OAuthClient], error]
OAuthClients streams the client records of an issuer. It requires admin settings read.
func (*Manager) OAuthGrants ¶
func (m *Manager) OAuthGrants(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[OAuthGrant], error]
OAuthGrants streams delegations. With a workspaceID it lists the workspace's grants and requires the oauth.resource.manage permission there; with an empty workspaceID it lists the actor's own grants and requires a recent interactive authentication.
func (*Manager) OAuthInitialAccessTokens ¶
func (m *Manager) OAuthInitialAccessTokens(ctx context.Context, actor Authentication, issuerID UUID) iter.Seq2[OAuthInitialAccessToken, error]
OAuthInitialAccessTokens streams the issuer's DCR bootstrap credentials, oldest first, revoked ones included and digests omitted, so an administration interface can inventory and revoke them. The actor needs admin settings read; ErrNotSupported without the OAuth capability.
func (*Manager) OAuthIssuers ¶
func (m *Manager) OAuthIssuers(ctx context.Context, actor Authentication, page PageRequest) iter.Seq2[PageEvent[OAuthIssuer], error]
OAuthIssuers streams every registered issuer. It requires admin settings read; ErrNotSupported without the OAuth capability.
func (*Manager) OAuthJWKS ¶
OAuthJWKS returns the JSON Web Key Set of an OIDC-enabled issuer, as published by the configured OIDCSigner. No authentication is required; issuers without OIDC or a signer fail with ErrNotSupported.
func (*Manager) OAuthProtectedResourceMetadata ¶
func (m *Manager) OAuthProtectedResourceMetadata(ctx context.Context, resourceURI string) (OAuthProtectedResourceMetadata, error)
OAuthProtectedResourceMetadata returns the RFC 9728 metadata of an enabled protected resource. No authentication is required; unknown or disabled resources and issuers fail with ErrNotFound.
func (*Manager) OAuthProtectedResources ¶
func (m *Manager) OAuthProtectedResources(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[OAuthProtectedResource], error]
OAuthProtectedResources streams the MCP resources of a workspace. The actor needs the oauth.resource.manage permission in that workspace.
func (*Manager) OAuthUserInfo ¶
func (m *Manager) OAuthUserInfo(ctx context.Context, issuerURL, rawAccessToken string) (OIDCUserInfo, error)
OAuthUserInfo implements the OIDC UserInfo endpoint for an issuer. The access token must carry the openid scope; the subject is pairwise and never exposes the user's global UUID, and email claims require the email scope. Returns ErrNotSupported when the issuer has OIDC disabled.
func (*Manager) PATs ¶
func (m *Manager) PATs(ctx context.Context, actor Authentication, userID UUID, page PageRequest) iter.Seq2[PageEvent[PAT], error]
PATs streams a user's tokens — metadata, prefix and timestamps, never the secret. An empty userID means the actor, which requires an interactive authentication and nothing more: what it returns are names, prefixes and timestamps, not credentials, and a token-management screen that had to re-prompt for a password every StepUpMaxAge would train users to re-authenticate on sight. Issuing and revoking stay gated by Config.StepUp's PAT scope. Reading another user requires admin users read — the same scoping as Sessions, Emails and Passkeys.
func (*Manager) Passkeys ¶
func (m *Manager) Passkeys(ctx context.Context, actor Authentication, userID UUID) iter.Seq2[Passkey, error]
Passkeys streams the metadata of a user's registered passkeys so the host can render a credential-management page. The sealed credential material is never exposed. Reading another user requires admin users read permission.
func (*Manager) PreRegisterOAuthClient ¶
func (m *Manager) PreRegisterOAuthClient(ctx context.Context, actor Authentication, request TrustedRequest, issuerID UUID, input OAuthClientRegistrationInput) (_ IssuedOAuthClient, err error)
PreRegisterOAuthClient administratively registers a client under an issuer, atomically with the audit event, and returns the generated client secret exactly once when client_secret_basic is requested. The actor needs admin settings write and an admin mutation (fresh AAL2, or a trusted local request); only pre-registered clients may be marked Trusted.
func (*Manager) ProvisionSCIMUser ¶
func (m *Manager) ProvisionSCIMUser(ctx context.Context, principal SCIMAuthentication, input SCIMUserInput) (_ SCIMUser, err error)
ProvisionSCIMUser creates a passwordless global account, its primary email, a directory-owned membership with the configuration's default role and the SCIM link, all atomically with the transactional hook and audit. The principal is a SCIMAuthentication from AuthenticateSCIM; the primary address is marked verified only under TrustDirectoryEmails. A taken address or userName fails with ErrConflict.
func (*Manager) RecordAudit ¶
func (m *Manager) RecordAudit(ctx context.Context, actor Authentication, input AuditInput) (err error)
RecordAudit appends a host-supplied event to the audit log. Credbound derives the actor, UUIDv7 and timestamp itself so a consuming service can neither impersonate an actor nor backdate an entry. A workspace-scoped event requires workspace access in that workspace; a global event requires admin access. The entry commits atomically with the ApplyClientAudit hook and fails closed with ErrAuditUnavailable.
func (*Manager) RefreshOAuthToken ¶
func (m *Manager) RefreshOAuthToken(ctx context.Context, input RefreshOAuthTokenInput) (_ OAuthTokenResponse, err error)
RefreshOAuthToken rotates a refresh token: it authenticates the client, re-validates the grant, and atomically retires the presented token while issuing a new access/refresh pair, optionally narrowed to a subset of the granted scopes. Reuse of an already rotated or revoked refresh token revokes its whole family and fails with ErrInvalidCredentials; an expired token fails with ErrExpired.
func (*Manager) RegenerateRecoveryCodes ¶
func (m *Manager) RegenerateRecoveryCodes(ctx context.Context, actor Authentication) (_ []string, err error)
RegenerateRecoveryCodes replaces the actor's recovery codes with a fresh set returned exactly once; the previous codes stop working in the same transaction, so a user who suspects their codes leaked — or has consumed most of them — rotates the set without re-enrolling TOTP. It requires an active TOTP factor and a fresh interactive AAL2 authentication. Returns ErrNotSupported without Config.TOTP and ErrNotFound without an active factor.
func (*Manager) RegisterFromInvitation ¶
func (m *Manager) RegisterFromInvitation(ctx context.Context, raw string, input RegisterFromInvitationInput) (_ Authentication, _ User, err error)
RegisterFromInvitation creates the invited account in one atomic operation: the invitee chooses their own password, the invited address is the verified primary email (delivery of the token proved control of the mailbox), and the invited role becomes their membership.
func (*Manager) RegisterOAuthClient ¶
func (m *Manager) RegisterOAuthClient(ctx context.Context, issuerURL, initialAccessToken string, input OAuthClientRegistrationInput) (_ IssuedOAuthClient, err error)
RegisterOAuthClient performs dynamic client registration against an issuer. In protected DCR mode it consumes one registration of a valid initial access token; in open mode no credential is accepted. Registered clients are never trusted, and a client secret is returned exactly once when the issuer allows it. Returns ErrNotSupported when the issuer's DCR mode is disabled and ErrInvalidCredentials for an unusable initial access token.
func (*Manager) RemoveEmail ¶
RemoveEmail deletes one of the actor's addresses, atomically with the audit event. It requires a fresh AAL2 step-up; the primary address and the last verified address cannot be removed.
func (*Manager) RemoveInstanceRole ¶
func (m *Manager) RemoveInstanceRole(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID) (err error)
RemoveInstanceRole withdraws a user's instance-administration role, atomically with the audit event, under the same authorization as SetInstanceRole. An administrator cannot remove its own role, and the store protects the last root.
func (*Manager) RemoveMembership ¶
func (m *Manager) RemoveMembership(ctx context.Context, actor Authentication, workspaceID, userID UUID) (err error)
RemoveMembership removes a local membership, atomically with the audit event; the store cascade revokes the member's workspace-bound credentials. The actor needs a fresh AAL2 step-up and workspace users write. SCIM-managed memberships fail with ErrConflict, and the store protects the last active workspace administrator.
func (*Manager) RemoveWorkspaceDomain ¶
func (m *Manager) RemoveWorkspaceDomain(ctx context.Context, actor Authentication, domainID UUID) (err error)
RemoveWorkspaceDomain deletes the domain and its policy, atomically with the audit event; addresses under it immediately authenticate like any other. It requires a fresh AAL2 step-up and workspace settings write in the owning workspace.
func (*Manager) ReplaceOAuthClientJWKS ¶
func (m *Manager) ReplaceOAuthClientJWKS(ctx context.Context, actor Authentication, request TrustedRequest, clientRecordID UUID, jwks []byte) (err error)
ReplaceOAuthClientJWKS atomically replaces the inline JWKS of a pre-registered or DCR private_key_jwt client, so a compromised signing key rotates without re-registering the client. A client publishing a jwks_uri rotates by republishing its own document instead and fails here with ErrConflict, like a CIMD client, whose keys follow its published metadata. Same authorization and capability requirements as RotateOAuthClientSecret.
func (*Manager) ReplaceSCIMUser ¶
func (m *Manager) ReplaceSCIMUser(ctx context.Context, principal SCIMAuthentication, id UUID, input SCIMUserInput) (_ SCIMUser, err error)
ReplaceSCIMUser replaces the SCIM representation of a managed user and synchronizes the membership status from Active — false suspends, true reactivates — atomically with the transactional hook and audit. The membership must be managed by the principal's configuration (ErrConflict otherwise); the global account is never disabled.
func (*Manager) RequireAdminMutation ¶
func (m *Manager) RequireAdminMutation(actor Authentication, request TrustedRequest) error
RequireAdminMutation gates administrative writes: the actor must be interactive, and either the request was verified as loopback by the server adapter (TrustedRequest.Local) or RequireStepUp must pass. It complements AuthorizeAdmin, which checks the permission itself.
func (*Manager) RequireStepUp ¶
func (m *Manager) RequireStepUp(authn Authentication) error
RequireStepUp accepts only an interactive AAL2 authentication whose AuthenticatedAt falls within Config.StepUpMaxAge. Anything else — a PAT regardless of age, an AAL1 context, or a stale AAL2 context — fails with ErrStepUpRequired (ErrUnauthorized when there is no actor at all), and the host should prompt for the second factor.
func (*Manager) RequireStepUpFor ¶ added in v0.0.2
func (m *Manager) RequireStepUpFor(ctx context.Context, authn Authentication, scope StepUpScope) error
RequireStepUpFor answers what the operations of one scope demand under Config.StepUp, for an actor and as of now: nil when authn may perform them, ErrStepUpRequired when the host should prompt for a second factor, ErrUnauthorized without an actor, ErrForbidden when the account is disabled or unreadable. It is the check the Manager runs itself before every gated call, so a host can raise the prompt ahead of the refusal — an admin screen that greys out its mutations, for instance — instead of discovering it from the error. Unlike RequireStepUp it needs a context, because deciding StepUpPolicyUserCapable means reading what the user has enrolled.
func (*Manager) ResendEmailVerification ¶
func (m *Manager) ResendEmailVerification(ctx context.Context, address string) (_ IssuedEmailVerification, err error)
ResendEmailVerification re-issues a verification token for an unverified address without requiring authentication, so a user whose signup token expired or was lost is not locked out of their own account. The host answers the end user identically whether or not the address exists or is already verified: the call succeeds with a zero IssuedEmailVerification in those cases, so the error path is not an enumeration oracle — send the email only when Deliverable is true. It performs the same cryptographic work and a comparable store write in every case so timing does not reveal the difference. Re-issuing a token invalidates the previous one (the stored digest is replaced). An address under a confirmed EnforceSSO domain is refused before any lookup, exactly like signup.
func (*Manager) RevokeInvitation ¶
func (m *Manager) RevokeInvitation(ctx context.Context, actor Authentication, workspaceID, invitationID UUID) (err error)
RevokeInvitation withdraws a pending invitation so its token can no longer be accepted.
func (*Manager) RevokeOAuthGrant ¶
func (m *Manager) RevokeOAuthGrant(ctx context.Context, actor Authentication, grantID UUID) (err error)
RevokeOAuthGrant revokes a delegation and its tokens, atomically with the audit event. The grant's own user needs a fresh AAL2 step-up; revoking another user's grant requires the oauth.resource.manage permission in the grant's workspace with a fresh AAL2 step-up.
func (*Manager) RevokeOAuthInitialAccessToken ¶
func (m *Manager) RevokeOAuthInitialAccessToken(ctx context.Context, actor Authentication, request TrustedRequest, tokenID UUID) (err error)
RevokeOAuthInitialAccessToken withdraws a DCR bootstrap credential, atomically with the audit event, under the same authorization as CreateOAuthInitialAccessToken.
func (*Manager) RevokeOAuthToken ¶
func (m *Manager) RevokeOAuthToken(ctx context.Context, input RevokeOAuthTokenInput) (err error)
RevokeOAuthToken implements RFC 7009 revocation for the authenticated client's own tokens: an access token is revoked individually, a refresh token revokes its whole family. As the RFC requires, unknown or foreign tokens are silently ignored; only a failed client authentication returns an error.
func (*Manager) RevokePAT ¶
RevokePAT revokes one of the actor's own tokens, atomically with the audit event. It demands the same authentication as CreatePAT (the PAT scope of Config.StepUp, a fresh AAL2 step-up by default), so a token can never become harder to revoke than it was to issue; a token belonging to another user is reported as ErrNotFound by the store.
func (*Manager) RevokeSCIMCredential ¶
func (m *Manager) RevokeSCIMCredential(ctx context.Context, actor Authentication, configurationID UUID, credentialID UUID) (err error)
RevokeSCIMCredential revokes one bearer credential of the configuration, atomically with the audit event. The actor needs a fresh AAL2 step-up and workspace RBAC write; ErrNotSupported without the SCIM capability.
func (*Manager) RevokeSession ¶
func (m *Manager) RevokeSession(ctx context.Context, actor Authentication, sessionID UUID) (err error)
RevokeSession revokes one of the actor's own sessions, atomically with the audit event. Like PAT revocation it requires a fresh AAL2 step-up, and a session belonging to another user is reported as ErrNotFound. Bulk and administrative revocation go through RevokeUserSessions instead.
func (*Manager) RevokeUserCredentials ¶
func (m *Manager) RevokeUserCredentials(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID) (err error)
RevokeUserCredentials revokes every active PAT of a user and, when the store has the OAuth capability, every OAuth grant with its tokens, in one atomic operation. When the store supports sessions (SessionStore) the user's server-side sessions are revoked in the same transaction. A user runs it on their own account after a suspected compromise; revoking another account requires an instance administrator. Sessions the host service manages itself remain host-owned and must be invalidated by the host alongside this call.
func (*Manager) RevokeUserSessions ¶
func (m *Manager) RevokeUserSessions(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID) (err error)
RevokeUserSessions revokes every active session of a user in one atomic operation ("log out everywhere"). Its authorization mirrors RevokeUserCredentials: a user runs it on their own account with a fresh AAL2 step-up; revoking another user's sessions requires an instance administrator with admin users write and an admin mutation (fresh AAL2, or a trusted local request).
func (*Manager) RevokeWorkspacePAT ¶ added in v0.0.4
func (m *Manager) RevokeWorkspacePAT(ctx context.Context, actor Authentication, workspaceID, patID UUID) (err error)
RevokeWorkspacePAT revokes a token bound to the workspace, whoever owns it, atomically with the audit event. It is how a tenant disables the key of a departed member without instance administration: the actor needs workspace credentials manage and the step-up its workspace scope demands (Config.StepUp). A token that is not bound to this workspace — another tenant's, or an unbound instance-wide one — reports ErrNotFound, so the permission can never reach beyond the tenant that granted it.
func (*Manager) RotateOAuthClientSecret ¶
func (m *Manager) RotateOAuthClientSecret(ctx context.Context, actor Authentication, request TrustedRequest, clientRecordID UUID) (_ IssuedOAuthClient, err error)
RotateOAuthClientSecret replaces the secret of a pre-registered or DCR client that authenticates with client_secret_basic and returns the new secret exactly once; the previous secret stops authenticating immediately. The client keeps its client_id, so deployed configurations only change the secret, and rotation works on a disabled client, so the compromise runbook — disable, rotate, re-enable — has no window where the old secret is live. The actor needs admin settings write and an admin mutation (fresh AAL2, or a trusted local request). A CIMD client fails with ErrConflict (its credentials follow its published metadata) and a client without client_secret_basic with ErrInvalidInput. Returns ErrNotSupported without the OAuth capability.
func (*Manager) RotateSCIMCredential ¶
func (m *Manager) RotateSCIMCredential(ctx context.Context, actor Authentication, configurationID UUID, expiresAt *time.Time) (_ IssuedSCIMCredential, err error)
RotateSCIMCredential issues an additional bearer credential for the configuration and returns the raw token exactly once; existing credentials stay valid until individually revoked. The actor needs a fresh AAL2 step-up and workspace RBAC write; ErrNotSupported without the SCIM capability.
func (*Manager) SCIMConfigurations ¶
func (m *Manager) SCIMConfigurations(ctx context.Context, actor Authentication, workspaceID UUID) iter.Seq2[SCIMConfiguration, error]
SCIMConfigurations streams the workspace's provisioning domains, oldest first, so an administration interface can inventory them. The actor needs workspace RBAC write in that workspace — the permission governing every SCIM administration operation — but no step-up, since nothing mutates. ErrNotSupported without the SCIM capability.
func (*Manager) SCIMCredentials ¶
func (m *Manager) SCIMCredentials(ctx context.Context, actor Authentication, configurationID UUID) iter.Seq2[SCIMCredential, error]
SCIMCredentials streams the configuration's bearer credentials, oldest first, with digests omitted, so an administration interface can inventory and rotate them. The actor needs workspace RBAC write in the configuration's workspace but no step-up, since nothing mutates. ErrNotSupported without the SCIM capability.
func (*Manager) SCIMGroup ¶
func (m *Manager) SCIMGroup(ctx context.Context, principal SCIMAuthentication, id UUID) (SCIMGroup, error)
SCIMGroup reads one directory group of the principal's configuration.
func (*Manager) SCIMGroups ¶
func (m *Manager) SCIMGroups(ctx context.Context, principal SCIMAuthentication, filter SCIMFilter, page PageRequest) iter.Seq2[PageEvent[SCIMGroup], error]
SCIMGroups streams the directory groups of the principal's configuration, optionally narrowed by a supported equality filter (id, externalId, displayName); unsupported filters fail with ErrInvalidInput.
func (*Manager) SCIMUser ¶
func (m *Manager) SCIMUser(ctx context.Context, principal SCIMAuthentication, id UUID) (SCIMUser, error)
SCIMUser reads one managed user of the principal's configuration.
func (*Manager) SCIMUsers ¶
func (m *Manager) SCIMUsers(ctx context.Context, principal SCIMAuthentication, filter SCIMFilter, page PageRequest) iter.Seq2[PageEvent[SCIMUser], error]
SCIMUsers streams the managed users of the principal's configuration, optionally narrowed by a supported equality filter (id, externalId, userName, emails.value, active); unsupported filters fail with ErrInvalidInput.
func (*Manager) SSOIdentities ¶
func (m *Manager) SSOIdentities(ctx context.Context, actor Authentication, userID UUID, page PageRequest) iter.Seq2[PageEvent[SSOIdentity], error]
SSOIdentities streams a user's linked external identities with their latest uses. An empty userID means the actor, which requires a recent interactive authentication; reading another user requires admin users read — the same scoping as Sessions, Emails and Passkeys.
func (*Manager) Sessions ¶
func (m *Manager) Sessions(ctx context.Context, actor Authentication, userID UUID, page PageRequest) iter.Seq2[PageEvent[Session], error]
Sessions streams a user's sessions — snapshot, device metadata and timestamps, never the token digest. The actor lists their own sessions (userID empty or equal to the actor) with a recent interactive authentication; listing another user's sessions additionally requires a fresh AAL2 step-up and admin users read.
func (*Manager) SetInstanceRole ¶
func (m *Manager) SetInstanceRole(ctx context.Context, actor Authentication, request TrustedRequest, userID UUID, role InstanceRole) (err error)
SetInstanceRole grants or changes a user's instance-administration role, atomically with the audit event. The actor needs admin instance-roles write (root only by default) and an admin mutation (fresh AAL2, or a trusted local request). A root cannot downgrade itself, and only the five built-in roles are accepted.
func (*Manager) SetMembershipStatus ¶
func (m *Manager) SetMembershipStatus(ctx context.Context, actor Authentication, workspaceID, userID UUID, status MembershipStatus) (Membership, error)
SetMembershipStatus suspends or reactivates a local membership, atomically with the audit event; the store cascade revokes workspace-bound credentials on suspension. The actor needs a fresh AAL2 step-up and workspace users write. SCIM-managed memberships fail with ErrConflict, and the store protects the last active workspace administrator.
func (*Manager) SetPrimaryEmail ¶
func (m *Manager) SetPrimaryEmail(ctx context.Context, actor Authentication, emailID UUID) (err error)
SetPrimaryEmail makes one of the actor's verified addresses the primary address, atomically with the audit event. It requires a fresh AAL2 step-up; an unverified address is rejected by the store.
func (*Manager) SignOut ¶
SignOut revokes the session identified by possession of its raw token — the ordinary logout. Unlike RevokeSession it needs no step-up and no actor: holding the single-display token proves ownership exactly as it does for AuthenticateSession, so even a password-only (AAL1) deployment can sign out immediately. Signing out an already-revoked session succeeds silently (logout is idempotent); an expired session is still revoked so its record reads as closed. Malformed, unknown, and forged tokens fail with ErrInvalidCredentials.
func (*Manager) SignUp ¶
func (m *Manager) SignUp(ctx context.Context, input SignUpInput) (_ SignUpResult, err error)
SignUp registers an anonymous visitor: one store transaction creates the user, their primary email address, their password credential, their workspace and their admin membership, with no instance role. It requires Config.SignUp and a SignupStore-capable store; otherwise it returns ErrNotSupported. The primary address starts unverified and the result carries the IssuedEmailVerification token the host delivers — the account cannot authenticate by email address until ConfirmEmail proves it. With Config.SignUp.AutoVerifyEmail the address is verified immediately and the result instead carries an AAL1 password Authentication.
When the address already belongs to an account the call performs the same hashing and identifier generation, audits the collision, and returns SignUpResult{ExistingAccount: true} with no error, so the host answers the end user identically and may deliver an "already registered" notice to the address instead of a verification token.
func (*Manager) TOTPStatus ¶
func (m *Manager) TOTPStatus(ctx context.Context, actor Authentication, userID UUID) (_ TOTPStatus, err error)
TOTPStatus reports whether a user has a TOTP factor, whether it is active, and how many recovery codes remain unused. It never exposes the secret. Reading another user requires admin users read permission.
func (*Manager) UnlinkSSO ¶
UnlinkSSO removes one of the actor's linked external identities, atomically with the audit event. It requires a fresh AAL2 step-up, and fails with ErrConflict when the identity is the actor's last remaining authentication method (no password, no passkey, no other SSO identity), so a JIT-provisioned passwordless member cannot lock themselves out.
func (*Manager) UpdateOAuthIssuer ¶
func (m *Manager) UpdateOAuthIssuer(ctx context.Context, actor Authentication, request TrustedRequest, issuerID UUID, input UpdateOAuthIssuerInput) (_ OAuthIssuer, err error)
UpdateOAuthIssuer replaces the policy of an issuer (its URL is immutable), atomically with the audit event, under the same authorization as CreateOAuthIssuer.
func (*Manager) UpdateSCIMConfiguration ¶
func (m *Manager) UpdateSCIMConfiguration(ctx context.Context, actor Authentication, configurationID UUID, input UpdateSCIMConfigurationInput) (_ SCIMConfiguration, err error)
UpdateSCIMConfiguration replaces the role policy of the configuration and immediately recomputes the roles of every membership it manages; the configuration change, the recomputed memberships and the audit record commit atomically. The actor needs a fresh AAL2 step-up and workspace RBAC write in the configuration's workspace. An ambiguous group mapping fails with ErrConflict; ErrNotSupported without the SCIM capability.
func (*Manager) UpdateUser ¶
func (m *Manager) UpdateUser(ctx context.Context, actor Authentication, input UpdateUserInput) (user User, err error)
UpdateUser changes the actor's own display name. It requires a recent interactive authentication (any assurance level within the step-up window), mirroring ChangePassword. Updating another account is an administrative mutation, see AdminUpdateUser. It returns the updated user.
func (*Manager) UpdateWorkspace ¶
func (m *Manager) UpdateWorkspace(ctx context.Context, actor Authentication, workspaceID UUID, input UpdateWorkspaceInput) (_ Workspace, err error)
UpdateWorkspace renames the workspace and optionally toggles its MFA policy, atomically with the audit event. The actor needs a fresh AAL2 step-up and workspace settings write as an active member.
func (*Manager) UpdateWorkspaceDomainPolicy ¶
func (m *Manager) UpdateWorkspaceDomainPolicy(ctx context.Context, actor Authentication, domainID UUID, input WorkspaceDomainPolicyInput) (err error)
UpdateWorkspaceDomainPolicy replaces the policy of a confirmed domain: the auto-join flag with its target role, the SSO provider configuration the domain trusts, and the SSO enforcement flag. An unconfirmed domain fails with ErrConflict. A zero AutoJoinRole means member and the role must exist in the workspace role catalog; when AutoJoin or EnforceSSO is set the provider configuration must be registered with the Manager. It requires a fresh AAL2 step-up and workspace settings write in the owning workspace.
func (*Manager) UpsertSCIMGroup ¶
func (m *Manager) UpsertSCIMGroup(ctx context.Context, principal SCIMAuthentication, id UUID, input SCIMGroupInput) (_ SCIMGroup, err error)
UpsertSCIMGroup creates (empty id) or replaces a directory group and recomputes the roles of every membership the change affects through the configured group-role mappings, atomically with the transactional hook and audit. An unknown or deprovisioned member fails with ErrInvalidInput and an ambiguous mapping fails closed with ErrConflict.
func (*Manager) User ¶
User returns one account by ID. An empty userID means the actor, which requires a recent interactive authentication; reading another user requires admin users read — the same scoping as Emails and Passkeys.
func (*Manager) UserWorkspaces ¶
func (m *Manager) UserWorkspaces(ctx context.Context, actor Authentication, page PageRequest) iter.Seq2[PageEvent[Workspace], error]
UserWorkspaces streams the workspaces the actor belongs to. It only requires an authenticated, enabled actor.
func (*Manager) Users ¶
func (m *Manager) Users(ctx context.Context, actor Authentication, page PageRequest) iter.Seq2[PageEvent[User], error]
Users streams every global account. It requires admin users read; the authorization itself is audited like every administrative access.
func (*Manager) ValidateOAuthAuthorizationRedirect ¶
func (m *Manager) ValidateOAuthAuthorizationRedirect(ctx context.Context, issuerURL, clientID, redirectURI string) error
ValidateOAuthAuthorizationRedirect resolves the client and validates an exact redirect URI. HTTP adapters use it before deciding whether an OAuth authorization error may safely be redirected to the client.
func (*Manager) VerifyAuditChain ¶
func (m *Manager) VerifyAuditChain(ctx context.Context, actor Authentication) (_ AuditChainReport, err error)
VerifyAuditChain recomputes the whole audit hash chain from the genesis and compares it with the persisted chain head. Any edited, deleted or reordered chained event yields ErrAuditCompromised. It requires admin audit read; VerifyAuditChainFrom verifies only the delta after a trusted checkpoint when the full scan grows too expensive.
func (*Manager) VerifyAuditChainFrom ¶
func (m *Manager) VerifyAuditChainFrom(ctx context.Context, actor Authentication, checkpoint AuditChainCheckpoint) (_ AuditChainReport, err error)
VerifyAuditChainFrom recomputes the chain from a previously verified checkpoint — the HeadSequence and HeadHash of an earlier report — to the current head, so periodic verification costs the delta instead of a full scan. The zero checkpoint verifies from the genesis. The checkpoint must come from the caller's own trusted record (the previous run's report, ideally anchored outside the database as OPERATIONS.md recommends): a checkpoint read back from compromised storage would vouch for a rewritten prefix, since events at or below its sequence are not re-read.
func (*Manager) VerifyTOTP ¶
func (m *Manager) VerifyTOTP(ctx context.Context, actor Authentication, code string) (_ Authentication, err error)
VerifyTOTP validates a TOTP or single-use recovery code for an interactive actor and returns a fresh AAL2 authentication — the second step of the password-then-TOTP flow the host stores in place of the AAL1 context. Replays of an already accepted time step and wrong codes fail with ErrInvalidCredentials; wrong codes count toward the account lockout (ErrLocked while it lasts). Returns ErrNotSupported without Config.TOTP.
func (*Manager) Workspace ¶
func (m *Manager) Workspace(ctx context.Context, actor Authentication, workspaceID UUID) (Workspace, error)
Workspace returns one workspace by ID. The actor needs workspace access in it; admin workspaces read reaches any workspace of the instance.
func (*Manager) WorkspaceDomains ¶
func (m *Manager) WorkspaceDomains(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[WorkspaceDomain], error]
WorkspaceDomains streams the workspace's domains with their confirmation state and policy. The listing requires an active membership holding workspace access, like the other tenant read operations; the challenge is included because it is published in public DNS and is not a secret.
func (*Manager) WorkspaceInvitations ¶
func (m *Manager) WorkspaceInvitations(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[WorkspaceInvitation], error]
WorkspaceInvitations streams the workspace's invitations without their digests.
func (*Manager) WorkspaceMembers ¶
func (m *Manager) WorkspaceMembers(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[WorkspaceMember], error]
WorkspaceMembers streams the memberships of a workspace joined with each member's account profile (display name, primary email), so a workspace administrator can render a member list without the instance-wide admin users read permission. Like Memberships, it requires workspace users read in that workspace; a membership whose account vanished mid-stream is skipped rather than failing the page.
func (*Manager) WorkspacePATs ¶ added in v0.0.4
func (m *Manager) WorkspacePATs(ctx context.Context, actor Authentication, workspaceID UUID, page PageRequest) iter.Seq2[PageEvent[WorkspacePAT], error]
WorkspacePATs streams the tokens bound to one workspace joined with each owner's account profile, so a workspace administrator can render the keys page of their tenant — including the key of a colleague who has left — without the instance-wide admin users read permission. It requires workspace credentials manage in that workspace. Digests are never exposed, and a token whose owner vanished mid-stream is skipped rather than failing the page, exactly like WorkspaceMembers.
Only workspace-bound tokens appear: a token created without a WorkspaceID belongs to its user across the whole instance, not to a tenant, so no workspace administrator can see or revoke it.
func (*Manager) Workspaces ¶
func (m *Manager) Workspaces(ctx context.Context, actor Authentication, page PageRequest) iter.Seq2[PageEvent[Workspace], error]
Workspaces streams every workspace of the instance. It requires admin workspaces read.
type Membership ¶
type Membership struct {
WorkspaceID UUID
UserID UUID
Role Role
Status MembershipStatus
// ProvisioningSource identifies who owns the membership: the literal
// "local", or the UUIDv7 of the SCIM configuration that manages it.
// SCIM-managed memberships reject ordinary local mutations.
ProvisioningSource string
CreatedAt time.Time
UpdatedAt time.Time
}
Membership binds a user to a workspace with a role and lifecycle status.
type MembershipChange ¶
type MembershipChange struct {
EventMeta
Membership Membership
Previous *Membership
Removed bool
}
MembershipChange covers membership addition, status change and removal. Previous is nil for an addition, and Removed marks a removal whose Membership field holds the final state.
type MembershipChangedEvent ¶
type MembershipChangedEvent struct {
EventMeta
Membership Membership
Previous *Membership
Removed bool
}
MembershipChangedEvent reports a membership addition, status change or removal. Previous is nil for an addition and Removed marks a removal.
type MembershipStatus ¶
type MembershipStatus string
MembershipStatus is the lifecycle state of a workspace membership. A suspended membership fails every authorization but retains its role.
const ( MembershipActive MembershipStatus = "active" MembershipSuspended MembershipStatus = "suspended" )
type OAuthAccessToken ¶
type OAuthAccessToken struct {
ID UUID
Prefix string
Digest []byte
GrantID UUID
ClientRecordID UUID
UserID UUID
WorkspaceID UUID
ResourceID UUID
Scopes []string
CreatedAt time.Time
ExpiresAt time.Time
RevokedAt *time.Time
}
OAuthAccessToken is the persisted metadata of an opaque access token, bound to one grant, resource and workspace. Only the HMAC Digest of the raw token is stored.
type OAuthApplicationType ¶
type OAuthApplicationType string
OAuthApplicationType distinguishes web clients (HTTPS redirect URIs only) from native clients (HTTPS, or plain HTTP on a loopback host).
const ( OAuthApplicationWeb OAuthApplicationType = "web" OAuthApplicationNative OAuthApplicationType = "native" )
type OAuthAuthentication ¶
type OAuthAuthentication struct {
TokenID UUID
GrantID UUID
ClientRecordID UUID
ClientID string
UserID UUID
WorkspaceID UUID
Resource string
Scopes []string
AuthenticatedAt time.Time
}
OAuthAuthentication is the validated bearer capability returned by AuthenticateOAuthAccessToken: the token, grant, client, user, workspace, resource and scopes a request may act with. Like Authentication, it must never be rebuilt from client-supplied data.
func (OAuthAuthentication) HasScope ¶
func (a OAuthAuthentication) HasScope(required string) bool
HasScope reports whether the token carries the required scope; an empty requirement always passes.
type OAuthAuthorizationCode ¶
type OAuthAuthorizationCode struct {
ID UUID
Prefix string
Digest []byte
GrantID UUID
ClientRecordID UUID
RedirectURI string
ResourceID UUID
Scopes []string
CodeChallenge string
Nonce string
CreatedAt time.Time
ExpiresAt time.Time
UsedAt *time.Time
}
OAuthAuthorizationCode is the persisted single-use code of an approved authorization, stored as an HMAC digest with its PKCE challenge and exact redirect URI.
type OAuthAuthorizationResult ¶
type OAuthAuthorizationResult struct {
RedirectURI string
Code string
State string
Issuer string
Error string
}
OAuthAuthorizationResult is the outcome of a completed authorization: the redirect target with either the single-use Code (returned only once) or an OAuth error code such as access_denied.
type OAuthAuthorizationServerMetadata ¶
type OAuthAuthorizationServerMetadata struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
RevocationEndpoint string `json:"revocation_endpoint"`
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
JWKSURI string `json:"jwks_uri,omitempty"`
UserInfoEndpoint string `json:"userinfo_endpoint,omitempty"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
ResponseTypesSupported []string `json:"response_types_supported"`
GrantTypesSupported []string `json:"grant_types_supported"`
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
AuthorizationResponseIssuerParameterSupport bool `json:"authorization_response_iss_parameter_supported"`
ClientIDMetadataDocumentSupported bool `json:"client_id_metadata_document_supported"`
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
}
OAuthAuthorizationServerMetadata is the RFC 8414 discovery document of an issuer.
type OAuthCIMDMode ¶
type OAuthCIMDMode string
OAuthCIMDMode is an issuer's policy for Client Identifier Metadata Document clients: disabled, restricted to an origin allowlist, or open to any HTTPS Client Identifier URL on the public web.
const ( OAuthCIMDDisabled OAuthCIMDMode = "disabled" OAuthCIMDAllowlist OAuthCIMDMode = "allowlist" OAuthCIMDPublicWeb OAuthCIMDMode = "public_web" )
type OAuthChange ¶
type OAuthChange struct {
EventMeta
IssuerID UUID
// ClientRecordID is Credbound's client record, like the other identifiers
// here. The protocol client_id is a different value — text, and a Client
// Identifier URL for CIMD clients — and is not carried by this payload.
ClientRecordID UUID
ClientSource OAuthClientSource
GrantID UUID
TokenID UUID
ResourceID UUID
Scopes []string
}
OAuthChange is the shared payload of every OAuth hook call and event. Only the identifiers that apply to the specific change are set; raw codes, tokens and secrets never appear.
type OAuthClient ¶
type OAuthClient struct {
ID UUID
IssuerID UUID
ClientID string
Source OAuthClientSource
Name string
ApplicationType OAuthApplicationType
RedirectURIs []string
// SectorIdentifier is the single redirect-URI host used to derive
// pairwise OIDC subjects.
SectorIdentifier string
GrantTypes []string
ResponseTypes []string
Scopes []string
// ClientCredentialsResources is the explicit allowlist of protected
// resource URIs the client may target with the client_credentials grant.
// Resolving a client never authorizes it: a machine-to-machine token is
// only minted for a resource named here, and only for a pre-registered
// client with a non-empty registered scope list.
ClientCredentialsResources []string
TokenEndpointAuthMethod OAuthTokenEndpointAuthMethod
JWKSURI string
JWKS json.RawMessage
// Trusted may only be set on pre-registered clients; CIMD and DCR
// clients are never trusted.
Trusted bool
SecretDigest []byte
// MetadataHash fingerprints the client metadata. Grants pin it, so a
// changed CIMD document invalidates the delegations approved under the
// previous metadata.
MetadataHash []byte
// MetadataExpiresAt bounds a cached CIMD document; a stale record is
// re-fetched on next resolution.
MetadataExpiresAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
DisabledAt *time.Time
}
OAuthClient is a persisted application identity under an issuer. ID is Credbound's record identifier while ClientID is the protocol client_id (equal to ID for pre-registered and DCR clients, the Client Identifier URL for CIMD clients).
type OAuthClientAccessToken ¶
type OAuthClientAccessToken struct {
ID UUID
Prefix string
Digest []byte
ClientRecordID UUID
IssuerID UUID
ResourceID UUID
WorkspaceID UUID
Scopes []string
CreatedAt time.Time
ExpiresAt time.Time
RevokedAt *time.Time
}
OAuthClientAccessToken is the persisted metadata of a client-credentials access token: a machine-to-machine bearer with no user subject, bound to a client, resource and workspace. It has no refresh token (RFC 6749 §4.4.3); the owning client revokes it individually through RevokeOAuthToken, and disabling the client, resource or issuer retires it implicitly.
type OAuthClientAssertionVerifier ¶
type OAuthClientAssertionVerifier interface {
Verify(context.Context, OAuthClient, string, string, time.Time) error
}
OAuthClientAssertionVerifier validates a private_key_jwt client assertion against the client's registered keys for the given token-endpoint audience at the given time. Implementations must provide atomic jti replay protection; oauthclientadapter.NewJWTAssertionVerifier is the bundled implementation.
type OAuthClientCredentialsInput ¶
type OAuthClientCredentialsInput struct {
Issuer string
ClientID string
ClientSecret string
// ClientSecretInBody reports that ClientSecret arrived as a
// client_secret form field rather than an Authorization: Basic header.
// The registered client_secret_basic method accepts only the header
// (RFC 6749 section 2.3.1 discourages the form transport and the
// discovery document does not announce client_secret_post), so a
// body-borne secret fails authentication.
ClientSecretInBody bool
ClientAssertion string
ClientAssertionType string
Resource string
Scopes []string
}
OAuthClientCredentialsInput authenticates a confidential client and requests a machine-to-machine access token bound to a protected resource. The resource must appear in the client's ClientCredentialsResources allowlist. Scopes may be empty to receive every non-reserved scope the client is registered for that the resource defines.
type OAuthClientMetadataDocument ¶
type OAuthClientMetadataDocument struct {
ClientID string
ClientName string
ApplicationType OAuthApplicationType
RedirectURIs []string
GrantTypes []string
ResponseTypes []string
Scope string
TokenEndpointAuthMethod OAuthTokenEndpointAuthMethod
JWKSURI string
JWKS json.RawMessage
FetchedAt time.Time
ExpiresAt time.Time
}
OAuthClientMetadataDocument is a validated CIMD document as returned by an OAuthClientMetadataFetcher, with the fetch time and cache expiry that bound how long the resolved client may be reused.
type OAuthClientMetadataFetcher ¶
type OAuthClientMetadataFetcher interface {
Fetch(context.Context, string) (OAuthClientMetadataDocument, error)
}
OAuthClientMetadataFetcher retrieves the CIMD document behind a Client Identifier URL. Implementations must defend against SSRF, DNS rebinding, redirects and oversized responses; oauthhttp.NewMetadataFetcher provides a hardened implementation.
type OAuthClientRegistrationInput ¶
type OAuthClientRegistrationInput struct {
Name string
ApplicationType OAuthApplicationType
RedirectURIs []string
GrantTypes []string
ResponseTypes []string
Scopes []string
// ClientCredentialsResources allowlists the protected resource URIs a
// client_credentials client may target. The grant is pre-registration
// only and requires both this list and Scopes to be non-empty; the field
// is rejected without the grant.
ClientCredentialsResources []string
TokenEndpointAuthMethod OAuthTokenEndpointAuthMethod
JWKSURI string
JWKS json.RawMessage
Trusted bool
}
OAuthClientRegistrationInput is the client metadata accepted by pre-registration and DCR. Empty grant and response types default to authorization_code with code; Trusted is honored only for pre-registration.
type OAuthClientSource ¶
type OAuthClientSource string
OAuthClientSource records how a client record came to exist: administrative pre-registration, CIMD resolution, or DCR.
const ( OAuthClientPreRegistered OAuthClientSource = "pre_registered" OAuthClientCIMD OAuthClientSource = "cimd" OAuthClientDCR OAuthClientSource = "dcr" )
type OAuthConfig ¶
type OAuthConfig struct {
// Pepper keys the HMAC digests of OAuth codes, tokens and client
// secrets, and the pairwise OIDC subjects. At least 32 bytes.
Pepper []byte
// MetadataFetcher resolves Client Identifier Metadata Documents; it is
// required for CIMD client policies. oauthhttp.NewMetadataFetcher
// provides a hardened implementation.
MetadataFetcher OAuthClientMetadataFetcher
// ClientAssertions verifies private_key_jwt client assertions; required
// for that authentication method.
ClientAssertions OAuthClientAssertionVerifier
// OIDCSigner signs ID Tokens and publishes the JWKS; required for any
// issuer with OIDC enabled.
OIDCSigner OIDCSigner
}
OAuthConfig configures the optional OAuth/OIDC module.
type OAuthConsent ¶
type OAuthConsent struct {
Continuation string
ClientID string
ClientName string
ClientHost string
RedirectURI string
RedirectHost string
Resource string
WorkspaceID UUID
Scopes []OAuthConsentScope
// RequiresStepUp signals that a requested scope demands a stronger or
// fresher authentication than the current session; the host should
// re-authenticate before completing.
RequiresStepUp bool
// LocalhostRedirect flags a localhost redirect target so the UI can warn
// that the code will be delivered to a program on the user's machine.
LocalhostRedirect bool
}
OAuthConsent is a validated, sealed authorization awaiting the user's decision in the host UI. Only CompleteOAuthAuthorization can turn the Continuation into an approval or denial; the display fields let the host render an honest consent page.
type OAuthConsentScope ¶
OAuthConsentScope is one scope of a pending consent with its human-readable description for the consent page.
type OAuthDCRMode ¶
type OAuthDCRMode string
OAuthDCRMode is an issuer's dynamic client registration policy: disabled, protected by an initial access token, or open with a registration limit. It is independent of the CIMD policy.
const ( OAuthDCRDisabled OAuthDCRMode = "disabled" OAuthDCRProtected OAuthDCRMode = "protected" OAuthDCROpen OAuthDCRMode = "open" )
type OAuthEvent ¶
type OAuthEvent struct {
OAuthChange
}
OAuthEvent is the shared payload of every OAuth event, distinguished by the EventMeta name of its OAuthChange.
type OAuthGrant ¶
type OAuthGrant struct {
ID UUID
IssuerID UUID
ClientRecordID UUID
UserID UUID
WorkspaceID UUID
ResourceID UUID
Scopes []string
MetadataHash []byte
AuthTime time.Time
AuthMethod AuthMethod
AAL AssuranceLevel
CreatedAt time.Time
UpdatedAt time.Time
RevokedAt *time.Time
}
OAuthGrant is a persisted delegation from a user to a client for one resource and scope set. AuthTime, AuthMethod and AAL snapshot the authorizing session for OIDC claims, and MetadataHash pins the client metadata that was consented to. Revoking the grant kills its tokens.
type OAuthInitialAccessToken ¶
type OAuthInitialAccessToken struct {
ID UUID
IssuerID UUID
Prefix string
Digest []byte
MaxRegistrations int
RegistrationCount int
CreatedAt time.Time
ExpiresAt time.Time
RevokedAt *time.Time
}
OAuthInitialAccessToken is the persisted metadata of a protected-DCR bootstrap credential: expiring, revocable, limited to MaxRegistrations registrations, and granting no authority over any resource. Only the HMAC Digest of the raw token is stored.
type OAuthIssuer ¶
type OAuthIssuer struct {
ID UUID
Issuer string
OIDCEnabled bool
CIMDMode OAuthCIMDMode
CIMDAllowedOrigins []string
DCRMode OAuthDCRMode
DCRAllowClientSecrets bool
DCROpenRegistrationLimit int
CodeTTL time.Duration
AccessTokenTTL time.Duration
RefreshTokenTTL time.Duration
CreatedAt time.Time
UpdatedAt time.Time
DisabledAt *time.Time
}
OAuthIssuer is one authorization server: its HTTPS issuer URL, CIMD, DCR and OIDC policy, and token lifetimes. A disabled issuer refuses every discovery, authorization and token operation.
type OAuthProtectedResource ¶
type OAuthProtectedResource struct {
ID UUID
IssuerID UUID
WorkspaceID UUID
Resource string
Scopes []OAuthScopeDefinition
CreatedAt time.Time
UpdatedAt time.Time
DisabledAt *time.Time
}
OAuthProtectedResource is a tenant-scoped MCP resource under an issuer. Access tokens are bound to its Resource URI and workspace and cannot be replayed elsewhere.
type OAuthProtectedResourceMetadata ¶
type OAuthProtectedResourceMetadata struct {
Resource string `json:"resource"`
AuthorizationServers []string `json:"authorization_servers"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
BearerMethods []string `json:"bearer_methods_supported"`
}
OAuthProtectedResourceMetadata is the RFC 9728 metadata document of a protected resource.
type OAuthRefreshToken ¶
type OAuthRefreshToken struct {
ID UUID
FamilyID UUID
Prefix string
Digest []byte
GrantID UUID
ClientRecordID UUID
UserID UUID
WorkspaceID UUID
ResourceID UUID
Scopes []string
CreatedAt time.Time
ExpiresAt time.Time
UsedAt *time.Time
ReplacedByID UUID
RevokedAt *time.Time
}
OAuthRefreshToken is the persisted metadata of a rotating refresh token. Tokens descending from the same initial issuance share a FamilyID; reuse of a rotated token revokes the whole family.
type OAuthScopeDefinition ¶
type OAuthScopeDefinition struct {
Name string
Description string
// Permissions are the registered workspace permissions the grant's user
// must hold for this scope. At least one is required.
Permissions []WorkspacePermission
// MinimumAAL is the assurance the authorizing session must have reached;
// zero means AAL1.
MinimumAAL AssuranceLevel
// MaxAuthAge bounds how old the authorizing authentication may be; zero
// disables the freshness requirement.
MaxAuthAge time.Duration
}
OAuthScopeDefinition declares one scope of a protected resource and its assurance policy. The workspace permissions behind the scope are re-evaluated on every bearer validation, not only at consent time.
type OAuthStore ¶
type OAuthStore interface {
CreateOAuthIssuer(context.Context, OAuthIssuer, Commit) error
UpdateOAuthIssuer(context.Context, OAuthIssuer, Commit) error
SetOAuthIssuerDisabled(context.Context, UUID, bool, time.Time, Commit) error
OAuthIssuerByID(context.Context, UUID) (OAuthIssuer, error)
OAuthIssuerByURL(context.Context, string) (OAuthIssuer, error)
OAuthIssuers(context.Context, PageRequest) iter.Seq2[PageEvent[OAuthIssuer], error]
CreateOAuthProtectedResource(context.Context, OAuthProtectedResource, Commit) error
SetOAuthProtectedResourceDisabled(context.Context, UUID, bool, time.Time, Commit) error
OAuthProtectedResourceByID(context.Context, UUID) (OAuthProtectedResource, error)
OAuthProtectedResourceByURI(context.Context, string) (OAuthProtectedResource, error)
OAuthProtectedResources(context.Context, UUID, PageRequest) iter.Seq2[PageEvent[OAuthProtectedResource], error]
CreateOAuthClient(context.Context, OAuthClient, UUID, time.Time, Commit) error
UpsertOAuthCIMDClient(context.Context, OAuthClient, Commit) error
SetOAuthClientDisabled(context.Context, UUID, bool, time.Time, Commit) error
// RotateOAuthClientCredentials atomically replaces the client's secret
// digest and/or inline JWKS (with its recomputed metadata hash) after an
// administrative credential rotation; a nil secretDigest keeps the
// current secret and a nil jwks keeps the current key set.
RotateOAuthClientCredentials(ctx context.Context, id UUID, secretDigest, jwks, metadataHash []byte, at time.Time, commit Commit) error
OAuthClientByID(context.Context, UUID) (OAuthClient, error)
OAuthClientByClientID(context.Context, UUID, string) (OAuthClient, error)
OAuthClients(context.Context, UUID, PageRequest) iter.Seq2[PageEvent[OAuthClient], error]
CreateOAuthInitialAccessToken(context.Context, OAuthInitialAccessToken, Commit) error
OAuthInitialAccessTokenByPrefix(context.Context, string) (OAuthInitialAccessToken, error)
// OAuthInitialAccessTokens streams the issuer's DCR bootstrap
// credentials, oldest first, revoked ones included and digests omitted.
OAuthInitialAccessTokens(context.Context, UUID) iter.Seq2[OAuthInitialAccessToken, error]
RevokeOAuthInitialAccessToken(context.Context, UUID, time.Time, Commit) error
CreateOAuthGrantAndCode(context.Context, OAuthGrant, OAuthAuthorizationCode, Commit) error
OAuthGrant(context.Context, UUID) (OAuthGrant, error)
RevokeOAuthGrant(context.Context, UUID, time.Time, Commit) error
OAuthGrants(context.Context, UUID, UUID, PageRequest) iter.Seq2[PageEvent[OAuthGrant], error]
OAuthAuthorizationCodeByPrefix(context.Context, string) (OAuthAuthorizationCode, error)
ConsumeOAuthAuthorizationCode(context.Context, UUID, time.Time, OAuthAccessToken, *OAuthRefreshToken, Commit) error
OAuthAccessTokenByPrefix(context.Context, string) (OAuthAccessToken, error)
// CreateOAuthClientAccessToken persists a client-credentials access token
// (machine-to-machine, no user subject); OAuthClientAccessTokenByPrefix
// resolves one for AuthenticateOAuthAccessToken, and
// RevokeOAuthClientAccessToken stamps one revoked for RevokeOAuthToken.
CreateOAuthClientAccessToken(context.Context, OAuthClientAccessToken, Commit) error
OAuthClientAccessTokenByPrefix(context.Context, string) (OAuthClientAccessToken, error)
RevokeOAuthClientAccessToken(context.Context, UUID, time.Time, Commit) error
OAuthRefreshTokenByPrefix(context.Context, string) (OAuthRefreshToken, error)
RotateOAuthRefreshToken(context.Context, UUID, time.Time, OAuthAccessToken, OAuthRefreshToken, Commit) error
RevokeOAuthAccessToken(context.Context, UUID, time.Time, Commit) error
// RevokeOAuthRefreshFamily stamps RevokedAt on every token of the
// refresh-token family and on the access tokens of the grants the
// family descends from, so a detected reuse (or an RFC 7009 refresh
// revocation) leaves no derived bearer credential alive.
RevokeOAuthRefreshFamily(context.Context, UUID, time.Time, Commit) error
}
OAuthStore is an optional persistence capability. OAuth operations return ErrNotSupported unless both Config.OAuth and this store capability exist.
type OAuthTokenEndpointAuthMethod ¶
type OAuthTokenEndpointAuthMethod string
OAuthTokenEndpointAuthMethod is how a client authenticates at the token endpoint. private_key_jwt requires Config.OAuth.ClientAssertions; client_secret_basic is only granted where the issuer policy allows it.
const ( OAuthAuthNone OAuthTokenEndpointAuthMethod = "none" OAuthAuthPrivateKeyJWT OAuthTokenEndpointAuthMethod = "private_key_jwt" OAuthAuthClientSecretBasic OAuthTokenEndpointAuthMethod = "client_secret_basic" )
type OAuthTokenKind ¶
type OAuthTokenKind string
OAuthTokenKind distinguishes the two opaque bearer token families.
const ( OAuthAccessTokenKind OAuthTokenKind = "access_token" OAuthRefreshTokenKind OAuthTokenKind = "refresh_token" )
type OAuthTokenResponse ¶
type OAuthTokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int64 `json:"expires_in"`
RefreshToken string `json:"refresh_token,omitempty"`
Scope string `json:"scope"`
IDToken string `json:"id_token,omitempty"`
}
OAuthTokenResponse is the token-endpoint success payload. The tokens are opaque, returned only once, and never recoverable from persisted models.
type OIDCClaims ¶
type OIDCClaims struct {
Issuer string `json:"iss"`
Subject string `json:"sub"`
Audience string `json:"aud"`
ExpiresAt int64 `json:"exp"`
IssuedAt int64 `json:"iat"`
AuthTime int64 `json:"auth_time,omitempty"`
Nonce string `json:"nonce,omitempty"`
ACR string `json:"acr,omitempty"`
AMR []string `json:"amr,omitempty"`
Email string `json:"email,omitempty"`
EmailVerified *bool `json:"email_verified,omitempty"`
}
OIDCClaims is the minimal ID Token claim set Credbound asks an OIDCSigner to sign. Subject is pairwise and never the user's global UUID.
type OIDCSigner ¶
type OIDCSigner interface {
SignIDToken(context.Context, OIDCClaims) (string, error)
JWKS(context.Context) (json.RawMessage, error)
Algorithms() []string
}
OIDCSigner signs ID Tokens and publishes the verification keys of an OIDC issuer. Algorithms advertises the actual signing algorithms for discovery; "none" is rejected. The bundled ES256 signer keeps one active signing key plus verification-only retiring keys.
type OIDCUserInfo ¶
type OIDCUserInfo struct {
Subject string `json:"sub"`
Email string `json:"email,omitempty"`
EmailVerified *bool `json:"email_verified,omitempty"`
}
OIDCUserInfo is the minimal UserInfo response: the pairwise subject, and email claims only when the token carries the email scope.
type Observer ¶
Observer receives Operation records for metrics, logs and traces (OTEL is the intended sink). Implementations must be safe for concurrent use.
type Operation ¶
Operation is one observed unit of work: an API call, a transaction hook or an event listener invocation. Outcome is "success", "error" or "panic". Names are low-cardinality and values never contain secrets.
type PAT ¶
type PAT struct {
ID UUID
UserID UUID
Name string
Prefix string
Digest []byte
WorkspaceID UUID
Scopes []string
CreatedAt time.Time
ExpiresAt *time.Time
LastUsedAt *time.Time
RevokedAt *time.Time
}
PAT is the persisted metadata of a personal access token. The raw token has the form <marker>_<prefix>_<secret>, where the marker is Config.PATPrefix ("cbp" by default); Prefix enables an indexed lookup and only the HMAC Digest of the full token is stored. A PAT bound to a WorkspaceID authenticates only within that workspace.
type PATAuthenticatedEvent ¶
type PATCreatedEvent ¶
type PATCreation ¶
type PATRejectedEvent ¶
PATRejectedEvent reports a rejected PAT authentication. The token owner, when identifiable, is only in the associated audit — malformed tokens have no attributable user.
type PATRevocation ¶
type PATRevokedEvent ¶
type PATStore ¶
type PATStore interface {
CreatePAT(context.Context, PAT, Commit) error
PATByPrefix(context.Context, string) (PAT, error)
// PATByID returns one token by identifier, reporting ErrNotFound for an
// unknown one. It backs the workspace-scoped administration, which
// resolves a token's owner and binding before acting on it.
PATByID(context.Context, UUID) (PAT, error)
TouchPAT(context.Context, UUID, time.Time, Commit) error
RevokePAT(context.Context, UUID, UUID, time.Time, Commit) error
PATs(context.Context, UUID, PageRequest) iter.Seq2[PageEvent[PAT], error]
// WorkspacePATs streams the tokens bound to one workspace, newest first,
// across every owner. Tokens that are not bound to a workspace belong to
// no tenant and never appear here.
WorkspacePATs(context.Context, UUID, PageRequest) iter.Seq2[PageEvent[PAT], error]
}
PATStore persists personal access tokens.
type PageEnd ¶
type PageEnd struct {
NextCursor string `json:"next_cursor,omitempty"`
HasMore bool `json:"has_more"`
}
PageEnd terminates a page: the opaque cursor of the next page and whether one exists.
func CollectPage ¶
CollectPage drains a paginated sequence into its items and final PageEnd — the common case when a caller wants one page and a cursor rather than a stream:
pats, page, err := credbound.CollectPage(manager.PATs(ctx, authn, "", credbound.PageRequest{Limit: 50}))
Streaming callers range over the sequence directly and forward each PageEvent (for example as NDJSON) instead.
Example ¶
ExampleCollectPage drains a paginated listing into one page of items plus the cursor for the next call. Streaming callers range over the sequence directly instead.
package main
import (
"bytes"
"context"
"fmt"
"log"
"time"
"github.com/deepteams/credbound"
"github.com/deepteams/credbound/credboundtest"
"github.com/deepteams/credbound/memory"
)
func main() {
clock := credboundtest.NewClock(credboundtest.DefaultStartTime)
manager, err := credbound.New(credbound.Config{
Store: memory.New(),
Passwords: credboundtest.Passwords{},
SecretKey: bytes.Repeat([]byte{0x11}, 32),
PATPepper: bytes.Repeat([]byte{0x22}, 32),
RecoveryPepper: bytes.Repeat([]byte{0x33}, 32),
Clock: clock.Now,
Random: credboundtest.NewDeterministicRandom(),
})
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
authn, workspace, err := manager.Bootstrap(ctx, credbound.BootstrapInput{
Email: "root@example.com", DisplayName: "Root", Password: "correct horse battery staple", WorkspaceName: "Main",
})
if err != nil {
log.Fatal(err)
}
stepUp := credboundtest.AAL2(authn.UserID, clock.Now()) // test-only step-up
for _, name := range []string{"ci", "deploy", "backup"} {
if _, err := manager.CreatePAT(ctx, stepUp, credbound.CreatePATInput{
Name: name, WorkspaceID: workspace.ID, Scopes: []string{"read"},
}); err != nil {
log.Fatal(err)
}
clock.Advance(time.Second) // distinct creation instants keep the page order stable
}
pats, page, err := credbound.CollectPage(manager.PATs(ctx, authn, credbound.UUID{}, credbound.PageRequest{Limit: 2}))
if err != nil {
log.Fatal(err)
}
fmt.Println(len(pats), page.HasMore)
rest, page, err := credbound.CollectPage(manager.PATs(ctx, authn, credbound.UUID{}, credbound.PageRequest{Limit: 2, Cursor: page.NextCursor}))
if err != nil {
log.Fatal(err)
}
fmt.Println(len(rest), page.HasMore)
}
Output: 2 true 1 false
type PageEvent ¶
type PageEvent[T any] struct { Type string `json:"type"` Data *T `json:"data,omitempty"` End *PageEnd `json:"page_end,omitempty"` }
PageEvent is one element of a paginated stream: either an item (Data set) or the final page_end (End set). Its JSON encoding is the NDJSON transport contract documented in the package overview.
type PageRequest ¶
PageRequest selects one page of a list. Cursor is an opaque value from a previous PageEnd (empty for the first page); Limit defaults to 50 and is capped at 100.
type Passkey ¶
type Passkey struct {
ID UUID
UserID UUID
Name string
CredentialID []byte
CredentialJSON []byte
CreatedAt time.Time
LastUsedAt *time.Time
}
Passkey is a registered WebAuthn credential. CredentialJSON holds the provider's sealed credential state and is scrubbed from every value the Manager returns to callers.
type PasskeyChallenge ¶
type PasskeyChallenge struct {
Options json.RawMessage `json:"options"`
Continuation string `json:"continuation"`
}
PasskeyChallenge is a started WebAuthn ceremony: the provider options the host forwards to the browser and the sealed continuation it passes back to the matching Finish call.
type PasskeyCredentialStore ¶
type PasskeyCredentialStore interface {
// PasskeyByCredentialID returns the passkey owning the credential ID,
// or ErrNotFound.
PasskeyByCredentialID(ctx context.Context, credentialID []byte) (Passkey, error)
}
PasskeyCredentialStore is an optional persistence capability required by discoverable passkey authentication: a global credential-ID lookup, which the per-user Passkeys stream cannot provide.
type PasskeyDeletedEvent ¶
type PasskeyDeletion ¶
type PasskeyProvider ¶
type PasskeyProvider interface {
BeginRegistration(context.Context, PasskeyUser) (json.RawMessage, []byte, error)
FinishRegistration(context.Context, PasskeyUser, []byte, []byte) (credentialID, credentialJSON []byte, err error)
// BeginAuthentication starts an assertion ceremony over the user's stored
// passkeys. It returns ErrNoPasskey when the user has none, which the
// manager answers with a decoy so the response never reveals whether an
// account has a passkey.
BeginAuthentication(context.Context, PasskeyUser) (json.RawMessage, []byte, error)
// BeginDecoyAuthentication produces an assertion challenge for an address
// with no passkey (or no account), structurally indistinguishable from
// BeginAuthentication. The seed makes the fabricated credential descriptors
// stable for a given address, so repeated probes cannot tell a decoy from a
// real challenge by its variation.
BeginDecoyAuthentication(ctx context.Context, seed []byte) (json.RawMessage, []byte, error)
FinishAuthentication(context.Context, PasskeyUser, []byte, []byte) (credentialID, credentialJSON []byte, err error)
}
PasskeyProvider is the optional WebAuthn ceremony port. Each Begin method returns the browser options and an opaque session that Credbound seals into the continuation; each Finish method validates the browser response against that session. Implementations must require user verification.
type PasskeyRegisteredEvent ¶
type PasskeyRegistration ¶
type PasskeyStore ¶
type PasskeyStore interface {
Passkeys(context.Context, UUID) iter.Seq2[Passkey, error]
SavePasskey(context.Context, Passkey, Commit) error
// TouchPasskey persists the credential's updated JSON and last-used time
// after a successful assertion, updates last_seen_at and — the sign-in
// completed (AUTH-009) — clears the login throttle.
TouchPasskey(context.Context, UUID, []byte, []byte, time.Time, Commit) error
DeletePasskey(context.Context, UUID, UUID, Commit) error
}
PasskeyStore persists WebAuthn credentials. The optional PasskeyCredentialStore capability extends it for the usernameless flow.
type PasskeyUser ¶
PasskeyUser is the view of a user handed to a PasskeyProvider: the account and a lazy sequence of its registered credentials with the credential state decrypted.
type PasskeyUserLookup ¶
type PasskeyUserLookup func(ctx context.Context, credentialID []byte) (PasskeyUser, error)
PasskeyUserLookup resolves the account owning a credential ID during a discoverable ceremony. It reports ErrNotFound for an unknown credential.
type PasswordChange ¶
type PasswordChangedEvent ¶
type PasswordCredential ¶
PasswordCredential is the persisted password hash of a user. Hash is an encoded derivation (Argon2id by default) and never the password itself.
type PasswordHasher ¶
type PasswordHasher interface {
Hash(string) (string, error)
Verify(string, string) (match bool, rehash bool, err error)
}
PasswordHasher derives and verifies password hashes. Verify additionally reports rehash when the stored hash uses outdated parameters, so a successful authentication can transparently renew it. Argon2id with versioned parameters is the intended implementation.
type PasswordPolicy ¶
PasswordPolicy lets the host reject candidate passwords beyond the built-in length rules — typically against a breached-password corpus such as Have I Been Pwned via k-anonymity, per NIST 800-63B. Return an error wrapping ErrInvalidInput to reject the password; any other error is treated as an infrastructure failure and aborts the operation. The policy runs on every password acceptance path (bootstrap, user creation, change, reset, and invitation registration) and always after the built-in length validation. The candidate password must never be logged or persisted by implementations.
type PasswordRehashedEvent ¶
PasswordRehashedEvent reports the transparent hash renewal performed after a successful authentication when the hashing parameters changed.
type PasswordResetCredential ¶
type PasswordResetCredential struct {
ID UUID
UserID UUID
Digest []byte
CreatedAt time.Time
ExpiresAt time.Time
UsedAt *time.Time
}
PasswordResetCredential is the persisted single-use reset proof. Only the HMAC digest of the token is stored.
type PasswordResetStore ¶
type PasswordResetStore interface {
CreatePasswordReset(context.Context, PasswordResetCredential, Commit) error
PasswordResetByID(context.Context, UUID) (PasswordResetCredential, error)
// CompletePasswordReset atomically consumes the single-use reset,
// installs the password — replacing the previous one, or creating the
// account's first for a passwordless member provisioned by SSO JIT or
// SCIM — deletes the user's other pending resets, revokes the user's
// PATs and OAuth grants (and, for SessionStore-capable stores, their
// sessions), and clears the login throttle. It returns ErrConflict when
// the reset was already consumed.
CompletePasswordReset(ctx context.Context, resetID UUID, password PasswordCredential, at time.Time, commit Commit) error
}
PasswordResetStore persists the single-use password-reset credentials and the atomic reset completion with its revocation sweep.
type Permission ¶
type Permission string
Permission names an instance-administration capability checked by AuthorizeAdmin. Each InstanceRole maps to an explicit permission set; services authorize by permission, never by comparing role names.
const ( PermissionAdminAccess Permission = "admin.access" PermissionAuditRead Permission = "admin.audit.read" PermissionSettingsRead Permission = "admin.settings.read" PermissionSettingsWrite Permission = "admin.settings.write" PermissionUsersRead Permission = "admin.users.read" PermissionUsersWrite Permission = "admin.users.write" PermissionWorkspacesRead Permission = "admin.workspaces.read" PermissionWorkspacesWrite Permission = "admin.workspaces.write" PermissionRBACRead Permission = "admin.rbac.read" PermissionRBACWrite Permission = "admin.rbac.write" PermissionInstanceRolesRead Permission = "admin.instance_roles.read" PermissionInstanceRolesWrite Permission = "admin.instance_roles.write" )
type PrimaryEmailChange ¶
type PrivacyStore ¶
type PrivacyStore interface {
// SCIMUsersByUser streams every tenant-scoped SCIM profile linked to
// the user, across configurations, oldest first.
SCIMUsersByUser(context.Context, UUID) iter.Seq2[SCIMUser, error]
// AcceptedWorkspaceInvitations streams every workspace invitation the
// user accepted, oldest first, with digests included; readers exporting
// them must scrub the Digest.
AcceptedWorkspaceInvitations(context.Context, UUID) iter.Seq2[WorkspaceInvitation, error]
}
PrivacyStore is an optional persistence capability that extends the data-subject primitives beyond the core account records. ExportUserData includes SCIM profiles and accepted workspace invitations only on a PrivacyStore-capable store; the first-party stores all implement it. Custom stores that skip it keep every other feature.
type RecoveryCode ¶
RecoveryCode is one single-use TOTP fallback code, persisted as a peppered HMAC digest only.
type RecoveryCodeRegeneration ¶
RecoveryCodeRegeneration reports the replacement of a user's recovery codes; payloads carry only the count, never code material.
type RecoveryCodesRegeneratedEvent ¶
RecoveryCodesRegeneratedEvent reports that the user replaced their recovery codes; the previous set stopped working in the same transaction.
type RefreshOAuthTokenInput ¶
type RefreshOAuthTokenInput struct {
Issuer string
ClientID string
ClientSecret string
// ClientSecretInBody reports that ClientSecret arrived as a
// client_secret form field rather than an Authorization: Basic header.
// The registered client_secret_basic method accepts only the header
// (RFC 6749 section 2.3.1 discourages the form transport and the
// discovery document does not announce client_secret_post), so a
// body-borne secret fails authentication.
ClientSecretInBody bool
ClientAssertion string
ClientAssertionType string
RefreshToken string
Resource string
Scopes []string
}
RefreshOAuthTokenInput is a parsed token-endpoint request for the refresh_token grant. Scopes optionally narrows the issue to a subset of the granted scopes.
type RegisterFromInvitationInput ¶
RegisterFromInvitationInput carries the profile the invitee chooses when registering a new account from an invitation token. The invited address becomes the verified primary email.
type RequestMetadata ¶
RequestMetadata carries the client network context of the request being served. The host service extracts it from its trusted proxy headers and attaches it with WithRequestMetadata; Credbound never reads transport headers itself. Both fields are sanitized and bounded before being audited.
type RevocationStore ¶
type RevocationStore interface {
// RevokeUserCredentials atomically revokes every active PAT of the user
// and, when the store has the OAuth capability, every OAuth grant and its
// tokens. A SessionStore-capable store also revokes the user's sessions
// in the same transaction; sessions the host manages itself remain
// host-owned and unaffected.
RevokeUserCredentials(context.Context, UUID, time.Time, Commit) error
// AnonymizeUser pseudonymizes a user in one transaction: it scrubs the
// mutable personal data (display name, email addresses, SSO and PAT names,
// session IP/User-Agent), disables the account, revokes its PATs, sessions
// and (with the OAuth capability) grants, and removes its second factors.
// A SCIMStore-capable store also scrubs the personal attributes of every
// SCIM profile linked to the user (user name, display name, emails,
// directory attributes) and marks it deprovisioned, and the email on
// workspace invitations the user accepted is replaced with a tombstone.
// The append-only audit chain is deliberately left intact. It reports
// ErrConflict when the target is the last enabled root administrator or the
// sole admin of a workspace, mirroring SetUserDisabled, and ErrNotFound for
// an unknown user.
AnonymizeUser(ctx context.Context, userID UUID, at time.Time, commit Commit) error
}
RevocationStore holds the cross-credential compromise-response and privacy operations.
type RevokeOAuthTokenInput ¶
type RevokeOAuthTokenInput struct {
Issuer string
ClientID string
ClientSecret string
// ClientSecretInBody reports that ClientSecret arrived as a
// client_secret form field rather than an Authorization: Basic header.
// The registered client_secret_basic method accepts only the header
// (RFC 6749 section 2.3.1 discourages the form transport and the
// discovery document does not announce client_secret_post), so a
// body-borne secret fails authentication.
ClientSecretInBody bool
ClientAssertion string
ClientAssertionType string
Token string
}
RevokeOAuthTokenInput is a parsed RFC 7009 revocation request; Token may be an access or refresh token.
type Role ¶
type Role string
Role names a workspace role. The built-in member and admin roles always exist; Config.WorkspaceRoles may register additional roles that implicitly inherit from member. An unknown role fails closed everywhere.
type RoleDefinition ¶
type RoleDefinition struct {
Role Role
Permissions []WorkspacePermission
// Inherits lists roles whose permissions this role also receives.
Inherits []Role
}
RoleDefinition registers a workspace role in the immutable RBAC catalog validated by New. A definition named member or admin adds permissions to the built-in role without removing its guarantees; any other role implicitly inherits from member. Inheritance must be acyclic.
type RoleGrantedEvent ¶
type SCIMAuthentication ¶
type SCIMAuthentication struct {
ConfigurationID UUID
WorkspaceID UUID
CredentialID UUID
AuthenticatedAt time.Time
}
SCIMAuthentication is the service capability obtained through AuthenticateSCIM. It scopes every provisioning operation to one configuration and its workspace and must never be constructed from fields freely supplied by a client.
type SCIMConfiguration ¶
type SCIMConfiguration struct {
ID UUID
WorkspaceID UUID
Enabled bool
DefaultRole Role
// TrustDirectoryEmails marks the primary address of provisioned users as
// verified, making it usable for sign-in. Without it even the primary
// SCIM address stays unverified.
TrustDirectoryEmails bool
GroupRoleMappings []SCIMGroupRoleMapping
CreatedAt time.Time
UpdatedAt time.Time
}
SCIMConfiguration is the provisioning domain of one workspace: the default role for provisioned users, the group-to-role mappings, and whether directory-asserted primary emails are trusted as verified.
type SCIMConfigurationChange ¶
type SCIMConfigurationChange struct {
EventMeta
Configuration SCIMConfiguration
}
type SCIMConfigurationCreatedEvent ¶
type SCIMConfigurationCreatedEvent struct {
EventMeta
Configuration SCIMConfiguration
}
type SCIMCredential ¶
type SCIMCredential struct {
ID UUID
ConfigurationID UUID
Prefix string
Digest []byte
CreatedAt time.Time
ExpiresAt *time.Time
LastUsedAt *time.Time
RevokedAt *time.Time
}
SCIMCredential is the persisted metadata of a SCIM bearer credential. The raw token has the form cbs_<prefix>_<secret> and only its HMAC Digest is stored. A credential is a service identity and never represents a user.
type SCIMEmail ¶
type SCIMEmail struct {
Value string `json:"value"`
Type string `json:"type,omitempty"`
Primary bool `json:"primary,omitempty"`
}
SCIMEmail is one email attribute of a SCIM user profile. Only the primary address created with a new user joins the global identity model; the others remain tenant-scoped profile data.
type SCIMFilter ¶
SCIMFilter is a single equality filter over a supported SCIM attribute. A zero value matches everything.
type SCIMGroup ¶
type SCIMGroup struct {
ID UUID
ConfigurationID UUID
ExternalID string
DisplayName string
MemberIDs []UUID
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
SCIMGroup is a persisted directory group. MemberIDs reference SCIMUser link identifiers, not global user IDs.
type SCIMGroupChange ¶
type SCIMGroupEvent ¶
SCIMGroupEvent is the shared payload of every SCIM group lifecycle event (created, updated, deleted, members changed); the EventMeta name tells them apart.
type SCIMGroupInput ¶
SCIMGroupInput is the SCIM group representation accepted by UpsertSCIMGroup. MemberIDs reference SCIMUser link identifiers.
type SCIMGroupRoleMapping ¶
SCIMGroupRoleMapping maps a directory group (by its external identifier) to a workspace role from the immutable catalog. When a user belongs to several mapped groups the highest Priority wins; two mappings of equal priority resolving to different roles fail closed with ErrConflict.
type SCIMStore ¶
type SCIMStore interface {
CreateSCIMConfiguration(context.Context, SCIMConfiguration, SCIMCredential, Commit) error
SCIMConfiguration(context.Context, UUID) (SCIMConfiguration, error)
// SCIMConfigurations streams the workspace's provisioning domains,
// oldest first. A workspace holds few configurations, so the stream is
// not paginated.
SCIMConfigurations(context.Context, UUID) iter.Seq2[SCIMConfiguration, error]
UpdateSCIMConfiguration(context.Context, SCIMConfiguration, []Membership, Commit) error
SCIMConfigurationByCredentialPrefix(context.Context, string) (SCIMConfiguration, SCIMCredential, error)
// SCIMCredentials streams the configuration's bearer credentials, oldest
// first, with digests omitted.
SCIMCredentials(context.Context, UUID) iter.Seq2[SCIMCredential, error]
SaveSCIMCredential(context.Context, SCIMCredential, Commit) error
RevokeSCIMCredential(context.Context, UUID, UUID, time.Time, Commit) error
TouchSCIMCredential(context.Context, UUID, time.Time, Commit) error
DisableSCIMConfiguration(context.Context, UUID, time.Time, Commit) error
CreateSCIMUser(context.Context, User, EmailAddress, Membership, SCIMUser, Commit) error
AdoptSCIMUser(context.Context, Membership, SCIMUser, Commit) error
SCIMUser(context.Context, UUID, UUID) (SCIMUser, error)
SCIMUserByExternalID(context.Context, UUID, string) (SCIMUser, error)
SCIMUserByUserName(context.Context, UUID, string) (SCIMUser, error)
UpdateSCIMUser(context.Context, SCIMUser, Membership, bool, Commit) error
SCIMUsers(context.Context, UUID, SCIMFilter, PageRequest) iter.Seq2[PageEvent[SCIMUser], error]
UpsertSCIMGroup(context.Context, SCIMGroup, []Membership, Commit) error
SCIMGroup(context.Context, UUID, UUID) (SCIMGroup, error)
SCIMGroupByExternalID(context.Context, UUID, string) (SCIMGroup, error)
DeleteSCIMGroup(context.Context, SCIMGroup, []Membership, Commit) error
SCIMGroups(context.Context, UUID, SCIMFilter, PageRequest) iter.Seq2[PageEvent[SCIMGroup], error]
}
SCIMStore is an optional persistence capability. Custom stores that do not implement it can continue to use every non-SCIM feature of Credbound.
type SCIMUser ¶
type SCIMUser struct {
ID UUID
ConfigurationID UUID
UserID UUID
Schemas []string
ExternalID string
UserName string
DisplayName string
Emails []SCIMEmail
Attributes map[string]json.RawMessage
Active bool
CreatedAt time.Time
UpdatedAt time.Time
DeprovisionedAt *time.Time
}
SCIMUser is the tenant-scoped link between a SCIM configuration and a global account. Its ID is the SCIM resource identifier and is distinct from UserID; unknown directory attributes are retained in Attributes. Deprovisioning sets DeprovisionedAt without disabling the global account.
type SCIMUserChange ¶
type SCIMUserEvent ¶
SCIMUserEvent is the shared payload of every SCIM user lifecycle event (provisioned, updated, activated, suspended, deprovisioned); the EventMeta name tells them apart.
type SCIMUserInput ¶
type SCIMUserInput struct {
Schemas []string
ExternalID string
UserName string
DisplayName string
Emails []SCIMEmail
Attributes map[string]json.RawMessage
Active bool
}
SCIMUserInput is the normalized SCIM user representation accepted by the provisioning operations. Active drives the membership status; false suspends the membership without touching the global account.
type SSOAssurancePolicy ¶
SSOAssurancePolicy makes the AAL2 an SSO sign-in produces verifiable instead of declared: FinishSSO completes only when the provider's asserted authentication context satisfies the policy, and fails with ErrStepUpRequired otherwise, so the host can send the user back to the IdP for its second factor. A policy with AcceptedACR requires the asserted ACR to be one of the listed values; RequiredAMR requires every listed method among the asserted AMR values; both together must both hold. Registered per provider configuration in Config.SSOAssurance.
A provider without a registered policy grants only AAL1: SSO cannot mint AAL2 on the IdP's unverified word. TrustUnverified is the explicit, auditable opt-out for a provider whose IdP asserts no ACR or AMR yet the host still consciously trusts to have authenticated at AAL2 — it grants AAL2 without inspecting the asserted context, and is the only way a policy may leave both AcceptedACR and RequiredAMR empty.
type SSOAuthenticatedEvent ¶
type SSOAuthenticatedEvent struct {
EventMeta
IdentityID UUID
Authentication Authentication
}
type SSOChallenge ¶
SSOChallenge is a started SSO ceremony: the provider redirect URL for the browser and the sealed continuation the host passes back to FinishSSO.
type SSOChallengeIssuedEvent ¶
type SSOChallengeIssuedEvent struct {
EventMeta
ProviderConfigurationID UUID
ProviderKind SSOProviderKind
Purpose string
}
type SSOClaims ¶
type SSOClaims struct {
Issuer string
Subject string
Email string
EmailVerified bool
// ACR is the authentication context class the provider asserted — the
// OIDC acr claim or the SAML AuthnContextClassRef — or empty when the
// provider asserted none. Config.SSOAssurance can require it before
// the authentication is granted AAL2.
ACR string
// AMR lists the OIDC authentication method references (amr) the
// provider asserted, such as "mfa", "otp" or "hwk".
AMR []string
}
SSOClaims is the validated identity a provider returns from a finished ceremony. Issuer and Subject form the stable link key; Email is informational only and never triggers an automatic account match.
type SSOIdentity ¶
type SSOIdentity struct {
ID UUID
UserID UUID
ProviderConfigurationID UUID
ProviderKind SSOProviderKind
Issuer string
Subject string
Email string
CreatedAt time.Time
LastUsedAt *time.Time
}
SSOIdentity is a persisted link between a user and an external identity. The (ProviderConfigurationID, Issuer, Subject) triplet is the stable key; Email is informational only.
type SSOJITProvisionedEvent ¶
type SSOJITProvisionedEvent struct {
EventMeta
User User
Email EmailAddress
Membership Membership
Identity SSOIdentity
// DomainID references the confirmed workspace domain whose auto-join
// policy produced the account.
DomainID UUID
}
SSOJITProvisionedEvent reports a just-in-time provisioned account: the passwordless user created inside FinishSSO from a verified IdP email under a confirmed auto-join domain, its verified primary email, the configured membership and the linked identity. It is emitted alongside user.created, sso.linked and authentication.succeeded for the same commit.
type SSOLink ¶
type SSOLink struct {
EventMeta
Identity SSOIdentity
}
type SSOLinkStore ¶
type SSOLinkStore interface {
SSOIdentity(context.Context, UUID, string, string) (SSOIdentity, error)
// LinkSSO stores a new SSO identity link; an identity already linked to
// any user reports ErrConflict. A link carrying LastUsedAt records a
// completed sign-in, so it also updates last_seen_at and clears the
// login throttle (AUTH-009).
LinkSSO(context.Context, SSOIdentity, Commit) error
// TouchSSO updates the identity's last-used time after a successful SSO
// login, updates last_seen_at and — the sign-in completed (AUTH-009) —
// clears the login throttle.
TouchSSO(context.Context, UUID, UUID, time.Time, Commit) error
// UnlinkSSO removes one linked identity, refusing with ErrConflict when
// it is the user's last remaining authentication method (no password
// credential, no passkey, no other SSO identity) — a JIT-provisioned,
// passwordless member must not be able to lock themselves out.
UnlinkSSO(context.Context, UUID, UUID, Commit) error
SSOIdentities(context.Context, UUID, PageRequest) iter.Seq2[PageEvent[SSOIdentity], error]
}
SSOLinkStore persists the links between accounts and external SSO identities.
type SSOLinkedEvent ¶
type SSOLinkedEvent struct {
EventMeta
Identity SSOIdentity
}
type SSOProvider ¶
type SSOProvider interface {
ConfigurationID() UUID
Kind() SSOProviderKind
Begin(context.Context, SSORequest) (SSOProviderChallenge, error)
Finish(context.Context, []byte, []byte) (SSOClaims, error)
}
SSOProvider is the network adapter for one registered identity provider. ConfigurationID returns the stable UUIDv7 the host addresses it by, Begin starts a ceremony (honoring SSORequest.ForceReauthentication for step-up), and Finish validates the provider response against the sealed session and returns the asserted claims.
type SSOProviderChallenge ¶
SSOProviderChallenge is the provider's half of a started SSO ceremony: the URL to send the browser to and the opaque session state Credbound seals into the continuation.
type SSOProviderKind ¶
type SSOProviderKind string
SSOProviderKind is the protocol family of a registered SSO provider.
const ( SSOProviderGoogle SSOProviderKind = "google" SSOProviderGitHub SSOProviderKind = "github" SSOProviderMicrosoft SSOProviderKind = "microsoft" SSOProviderOIDC SSOProviderKind = "oidc" SSOProviderSAML SSOProviderKind = "saml" )
type SSORequest ¶
type SSORequest struct {
ForceReauthentication bool
}
SSORequest carries Credbound's requirements to an SSOProvider when a ceremony begins. ForceReauthentication is set for step-up flows so the provider re-verifies the user and its own MFA instead of reusing an existing IdP session.
type SSOUnlinkedEvent ¶
type SecondFactorReset ¶
SecondFactorReset reports the administrative removal of every second factor of a user: the TOTP factor with its recovery codes and all passkeys, with the user's sessions revoked in the same transaction.
type SecondFactorResetEvent ¶
SecondFactorResetEvent reports that an instance administrator removed every second factor of the user (TOTP, recovery codes, passkeys) and revoked their sessions — the total-loss recovery path. Hosts should notify the affected user out of band.
type Session ¶
type Session struct {
ID UUID
UserID UUID
// Method and Level snapshot the creating Authentication verbatim; they
// never change for the lifetime of the session.
Method AuthMethod
Level AssuranceLevel
// AuthenticatedAt is copied from the creating Authentication so step-up
// freshness keeps measuring the factor verification, not session reuse.
AuthenticatedAt time.Time
SecondFactorRequired bool
// UserAgent and IPAddress are the sanitized RequestMetadata observed when
// the session was created, for device listings.
UserAgent string
IPAddress string
// Digest is the HMAC of the raw token under the derived key (domain
// "session:"). It is scrubbed from listings and from every value the
// Manager returns.
Digest []byte
CreatedAt time.Time
// LastSeenAt is telemetry only: expiry is absolute (CreatedAt plus
// Config.SessionTTL) and is never extended by activity.
LastSeenAt time.Time
ExpiresAt time.Time
RevokedAt *time.Time
}
Session is a persisted server-side session: an immutable snapshot of the Authentication that created it, plus the device metadata observed at creation. The raw cbs_ token is returned exactly once by CreateSession and only its HMAC digest is stored. A session never upgrades its assurance level in place — after VerifyTOTP or any other AAL transition the host mints a new session and revokes the previous one, which doubles as fixation protection.
type SessionCreatedEvent ¶
type SessionCreatedEvent struct {
EventMeta
Session Session
// Request carries the client network context supplied by the host through
// WithRequestMetadata, so listeners can alert on new devices without
// re-reading the audit log.
Request RequestMetadata
}
SessionCreatedEvent reports a new server-side session. The Session carries a nil Digest and the raw token is never part of any event. AuthenticateSession emits no per-validation event — it runs on every request and would flood listeners; only the audit log records validations.
type SessionCreation ¶
type SessionCreation struct {
EventMeta
Session Session
// Request is the client network context observed at creation.
Request RequestMetadata
}
SessionCreation carries the created session record; its Digest is always nil so hook payloads never see token material.
type SessionRevocation ¶
type SessionRevokedEvent ¶
type SessionStore ¶
type SessionStore interface {
// CreateSession stores a server-side session. A non-empty
// credentialDigest is the currency guard: inside the same transaction the
// store recomputes CredentialFingerprint over the user's current password
// credential hash and returns ErrConflict when it no longer matches (or
// the credential vanished), so an Authentication whose password was
// concurrently replaced cannot mint a session that would survive the
// replacement's revocation sweep. An empty digest skips the guard — the
// context was not produced by a password.
CreateSession(ctx context.Context, session Session, credentialDigest []byte, commit Commit) error
SessionByID(ctx context.Context, id UUID) (Session, error)
// TouchSession refuses a session already revoked with ErrConflict, so an
// authentication racing a revocation can neither record activity on nor
// extend the idle window of a dead session; otherwise it
// updates the session's last-seen timestamp (and the user's
// last_seen_at) in the same transaction as its audit event.
TouchSession(ctx context.Context, id UUID, at time.Time, commit Commit) error
RevokeSession(ctx context.Context, id UUID, at time.Time, commit Commit) error
// RevokeUserSessions stamps RevokedAt on every active session of the user.
RevokeUserSessions(ctx context.Context, userID UUID, at time.Time, commit Commit) error
Sessions(ctx context.Context, userID UUID, page PageRequest) iter.Seq2[PageEvent[Session], error]
}
SessionStore is an optional persistence capability required by the session operations (CreateSession, AuthenticateSession, Sessions, RevokeSession, RevokeUserSessions); without it every one of them returns ErrNotSupported.
Cascade contract: a store implementing SessionStore must extend its CompletePasswordReset, ChangePassword, SetUserDisabled (when disabling, not when re-enabling) and RevokeUserCredentials implementations to also stamp RevokedAt on the user's active sessions inside the same transaction, so a recovery or lockdown revokes interactive sessions atomically with the rest of the account's credentials. Stores without the capability keep their existing contracts; hosts then terminate their own sessions.
Sessions never returns the token digest: listed Session values carry a nil Digest. SessionByID returns the digest for constant-time validation by the Manager, which scrubs it before results leave the library.
type SignUpCompletedEvent ¶
SignUpCompletedEvent reports a successful self-service registration: the created account and the workspace it administers. Collisions with an existing address emit no event so listeners cannot become an enumeration side channel.
type SignUpConfig ¶
type SignUpConfig struct {
// AutoVerifyEmail marks the primary address verified at creation instead
// of issuing an email-verification token, and makes SignUp additionally
// return an AAL1 password Authentication. Hosts enabling it accept that
// mailbox control was never proven.
AutoVerifyEmail bool
}
SignUpConfig configures the optional self-service signup operation.
type SignUpInput ¶
SignUpInput describes a self-service registration: the visitor's address, profile, chosen password and the name of the workspace created for them.
type SignUpResult ¶
type SignUpResult struct {
User User
Workspace Workspace
Authentication Authentication
EmailVerification IssuedEmailVerification
ExistingAccount bool
}
SignUpResult is the outcome of a SignUp call. When ExistingAccount is true the address already belonged to an account: every other field is zero, the collision was reported only to the audit log, and the host must answer the end user exactly as if the registration had succeeded. Otherwise User and Workspace carry the created records; EmailVerification carries the single-use token proving the primary address (delivered by the host, never stored) unless Config.SignUp.AutoVerifyEmail is set, in which case Authentication instead carries an AAL1 password authentication.
type SignupStore ¶
type SignupStore interface {
CreateSignup(ctx context.Context, user User, email EmailAddress, verification *EmailVerificationCredential, password PasswordCredential, workspace Workspace, membership Membership, commit Commit) error
}
SignupStore is an optional persistence capability required by SignUp. CreateSignup atomically creates the user, their primary email address, the password credential, the workspace and the admin membership, or nothing at all. The verification credential is nil when the host auto-verifies the address (the email then carries VerifiedAt); otherwise the email starts unverified and the credential is persisted exactly as SaveEmail would, keyed by the email identifier, so ConfirmEmail completes the proof. A globally taken address fails with ErrConflict. Custom stores that do not implement it can continue to use every other feature of Credbound.
type StepUpDeniedEvent ¶
StepUpDeniedEvent is an advisory signal that an operation was refused for lack of a fresh AAL2 authentication; it carries no audit of its own.
type StepUpPolicies ¶ added in v0.0.2
type StepUpPolicies struct {
// Default applies to every scope that names no policy of its own. Zero
// keeps StepUpPolicyRequired.
Default StepUpPolicy
// PAT overrides Default for StepUpScopePAT. Zero inherits Default.
PAT StepUpPolicy
// Workspace overrides Default for StepUpScopeWorkspace. Zero inherits
// Default.
Workspace StepUpPolicy
// Account overrides Default for StepUpScopeAccount. Zero inherits
// Default.
Account StepUpPolicy
// Admin overrides Default for StepUpScopeAdmin. Zero inherits Default —
// so a relaxed Default reaches instance administration too, which is the
// one place worth naming explicitly when that is not what you want.
Admin StepUpPolicy
}
StepUpPolicies assigns a StepUpPolicy to each StepUpScope. Default governs every family, and the per-family fields override it where one deserves a different answer; a zero field inherits Default, and a zero Default is StepUpPolicyRequired. The zero StepUpPolicies therefore means "every step-up-gated operation demands a fresh interactive AAL2 context", which is what Credbound did before the policies existed.
StepUpPolicyUserCapable deserves a special mention here: under it, a user who enrolls a second factor is immediately held to the full step-up on every family, including the operations that could remove that factor (DeletePasskey, DisableTOTP). Relaxing a deployment for the users who have no factor therefore does not weaken the users who do.
type StepUpPolicy ¶
type StepUpPolicy string
StepUpPolicy selects how strictly an operation demands a fresh interactive AAL2 authentication. A step-up is only ever as available as the second factors a deployment actually enrolls: without a policy, a product whose users have no TOTP factor and no passkey is simply locked out of every operation the step-up guards. Config.StepUp assigns one policy per family of guarded operations; see StepUpPolicies.
const ( // StepUpPolicyRequired is the default and the strict mode: the operation // accepts exactly what RequireStepUp accepts, so a user with no enrolled // second factor can never perform it. StepUpPolicyRequired StepUpPolicy = "required" // StepUpPolicyUserCapable demands the full step-up from every user who // could satisfy one — an active TOTP factor, or a passkey with a passkey // provider configured — and accepts a plain interactive authentication // from users who have neither. Enrolling a factor therefore tightens the // operation for that user with no configuration change, and nobody is // locked out meanwhile. StepUpPolicyUserCapable StepUpPolicy = "user_capable" // StepUpPolicyNone asks for no step-up: any interactive authentication of // an active user is accepted, at any assurance level and any age. The // non-interactive floor still holds — a PAT can never mint or revoke a // PAT — and a context whose second factor is still pending is refused. StepUpPolicyNone StepUpPolicy = "none" )
type StepUpScope ¶ added in v0.0.2
type StepUpScope string
StepUpScope names one family of step-up-gated operations. The families are what Config.StepUp configures and what RequireStepUpFor evaluates, so a host can prompt for a second factor before an operation instead of after its refusal.
const ( // StepUpScopePAT covers CreatePAT and RevokePAT. StepUpScopePAT StepUpScope = "pat" // StepUpScopeWorkspace covers the tenant mutations: creating, updating, // disabling and enabling a workspace, memberships and their roles, // invitations, workspace domains, SCIM configurations, workspace OAuth // resources, administrative user creation, and reading a workspace audit // log. StepUpScopeWorkspace StepUpScope = "workspace" // StepUpScopeAccount covers a user's own credentials and data: deleting a // passkey, disabling TOTP, regenerating recovery codes, selecting or // removing an email address, unlinking an SSO identity, listing and // revoking sessions, revoking a personal OAuth grant, the credential // revocation cascade, and the personal-data export. StepUpScopeAccount StepUpScope = "account" // StepUpScopeAdmin covers the instance-administration mutations behind // RequireAdminMutation — disabling a user, instance roles, the instance // audit log — whose loopback exception (TrustedRequest) is unaffected. StepUpScopeAdmin StepUpScope = "admin" )
type Store ¶
type Store interface {
IdentityStore
PasswordResetStore
EmailAuthenticationStore
EmailStore
TOTPStore
PasskeyStore
PATStore
RevocationStore
InvitationStore
WorkspaceStore
SSOLinkStore
AuditStore
}
Store persists every mutation together with its transaction hooks and audit. Implementations must commit all three stages or none of them.
Store is the required persistence port. It is composed of the capability groups below purely for navigability — the method set is unchanged and a Store implements all of them; the optional capabilities (SignupStore, SessionStore, EmailThrottleStore, DomainStore, SCIMStore, PrivacyStore, OAuthStore, PasskeyCredentialStore) remain separate interfaces.
Compatibility contract: the required method set may grow in minor releases as features ship, so implementing Store from scratch means tracking every release. External stores that want to stay compile-compatible across upgrades should embed one of the shipped implementations (sqlstore/postgresql, memory) in a struct and override only the methods they need.
type StoreKind ¶
type StoreKind string
StoreKind identifies the engine behind a Tx so a TransactionHook can recover the engine-specific handle (for example sqlstore's TxFrom).
type Subscription ¶
type Subscription interface {
Remove()
}
Subscription undoes an AddTransactionHook or AddEventListener registration. Remove is idempotent.
type TOTPActivatedEvent ¶
type TOTPActivation ¶
type TOTPDisable ¶
type TOTPDisabledEvent ¶
type TOTPEnrollment ¶
type TOTPEnrollment struct {
URI string
}
TOTPEnrollment is a started TOTP enrollment. URI is the otpauth:// URI the host renders as a QR code; it contains the secret and must not be persisted or logged.
type TOTPEnrollmentChange ¶
type TOTPFactor ¶
type TOTPFactor struct {
UserID UUID
EncryptedSecret []byte
// Active becomes true only after ConfirmTOTPEnrollment proved a valid
// code; an inactive enrollment never gates authentication.
Active bool
LastUsedStep int64
CreatedAt time.Time
UpdatedAt time.Time
}
TOTPFactor is a user's persisted TOTP enrollment. The secret is sealed with the Manager's AEAD key, and LastUsedStep prevents replay of an already accepted time step.
type TOTPProvider ¶
type TOTPProvider interface {
Generate(accountName string) (secret string, uri string, err error)
Validate(code, secret string, at time.Time) (step int64, valid bool)
}
TOTPProvider is the optional time-based OTP algorithm port. Generate returns the shared secret and its otpauth:// URI; Validate reports the accepted time step so the store can reject replays of the same step.
type TOTPReplayRejectedEvent ¶
type TOTPStatus ¶
type TOTPStatus struct {
Enrolled bool
Active bool
UnusedRecoveryCodes int
CreatedAt time.Time
UpdatedAt time.Time
}
TOTPStatus is the read-only state of a user's TOTP factor. It never contains the secret, the otpauth URI or any recovery code material.
type TOTPStore ¶
type TOTPStore interface {
TOTPByUserID(context.Context, UUID) (TOTPFactor, error)
SaveTOTPEnrollment(context.Context, TOTPFactor, Commit) error
ActivateTOTP(context.Context, TOTPFactor, []RecoveryCode, Commit) error
UseTOTP(context.Context, UUID, int64, Commit) (bool, error)
ConsumeRecoveryCode(context.Context, UUID, []byte, time.Time, Commit) (bool, error)
CountUnusedRecoveryCodes(context.Context, UUID) (int64, error)
DisableTOTP(context.Context, UUID, Commit) error
// ReplaceRecoveryCodes atomically deletes the user's recovery codes and
// inserts the replacement set. It returns ErrNotFound without an active
// TOTP factor.
ReplaceRecoveryCodes(ctx context.Context, userID UUID, codes []RecoveryCode, commit Commit) error
// ResetSecondFactor atomically removes the user's TOTP factor with its
// recovery codes and every passkey and, for SessionStore-capable
// stores, revokes the user's sessions in the same transaction. It
// succeeds even when the user has no second factor; an unknown user
// reports ErrNotFound.
ResetSecondFactor(ctx context.Context, userID UUID, at time.Time, commit Commit) error
}
TOTPStore persists the TOTP factor, its recovery codes, and the atomic second-factor reset.
type TOTPVerifiedEvent ¶
type TransactionHook ¶
type TransactionHook interface {
ApplyUserCreate(context.Context, Tx, UserCreateChange) error
ApplyWorkspaceCreate(context.Context, Tx, WorkspaceCreateChange) error
ApplyUserStatusChange(context.Context, Tx, UserStatusChange) error
ApplyUserProfileChange(context.Context, Tx, UserProfileChange) error
ApplyWorkspaceChange(context.Context, Tx, WorkspaceChange) error
ApplyMembershipChange(context.Context, Tx, MembershipChange) error
ApplyWorkspaceInvitationChange(context.Context, Tx, WorkspaceInvitationChange) error
ApplyWorkspaceDomainChange(context.Context, Tx, WorkspaceDomainChange) error
ApplyPasswordChange(context.Context, Tx, PasswordChange) error
ApplyEmailAddition(context.Context, Tx, EmailAddition) error
ApplyEmailConfirmation(context.Context, Tx, EmailConfirmation) error
ApplyPrimaryEmailChange(context.Context, Tx, PrimaryEmailChange) error
ApplyEmailRemoval(context.Context, Tx, EmailRemoval) error
ApplyTOTPEnrollment(context.Context, Tx, TOTPEnrollmentChange) error
ApplyTOTPActivation(context.Context, Tx, TOTPActivation) error
ApplyTOTPDisable(context.Context, Tx, TOTPDisable) error
ApplyPasskeyRegistration(context.Context, Tx, PasskeyRegistration) error
ApplyPasskeyDeletion(context.Context, Tx, PasskeyDeletion) error
ApplyPATCreation(context.Context, Tx, PATCreation) error
ApplyPATRevocation(context.Context, Tx, PATRevocation) error
ApplyUserCredentialRevocation(context.Context, Tx, UserCredentialRevocation) error
ApplySecondFactorReset(context.Context, Tx, SecondFactorReset) error
ApplyUserAnonymization(context.Context, Tx, UserAnonymization) error
ApplyRecoveryCodeRegeneration(context.Context, Tx, RecoveryCodeRegeneration) error
ApplySessionCreation(context.Context, Tx, SessionCreation) error
ApplySessionRevocation(context.Context, Tx, SessionRevocation) error
ApplyUserSessionRevocation(context.Context, Tx, UserSessionRevocation) error
ApplySSOLink(context.Context, Tx, SSOLink) error
ApplySSOUnlink(context.Context, Tx, SSOUnlink) error
ApplyRoleGrant(context.Context, Tx, RoleGrant) error
ApplyInstanceRoleChange(context.Context, Tx, InstanceRoleChange) error
ApplyInstanceRoleRemoval(context.Context, Tx, InstanceRoleRemoval) error
ApplyClientAudit(context.Context, Tx, ClientAuditRecord) error
ApplySCIMConfigurationCreate(context.Context, Tx, SCIMConfigurationChange) error
ApplySCIMUserProvision(context.Context, Tx, SCIMUserChange) error
ApplySCIMUserUpdate(context.Context, Tx, SCIMUserChange) error
ApplySCIMUserDeprovision(context.Context, Tx, SCIMUserChange) error
ApplySCIMGroupUpsert(context.Context, Tx, SCIMGroupChange) error
ApplySCIMGroupDelete(context.Context, Tx, SCIMGroupChange) error
ApplyOAuthChange(context.Context, Tx, OAuthChange) error
// contains filtered or unexported methods
}
TransactionHook lets the host add its own writes to a Credbound mutation. Hooks run sequentially inside the store transaction, after the mutation and before the audit write, so returning an error aborts the whole commit; errors that are not sentinel errors surface as ErrTransactionRejected. Hooks must not perform external I/O, invoke another Manager mutation, retain the Tx, or use it from another goroutine. Implementations embed UnimplementedTransactionHook to stay compatible as methods are added.
type TrustedRequest ¶
type TrustedRequest struct {
// Local reports that the transport peer is a loopback address. When set,
// RequireAdminMutation waives the AAL2 step-up for administrative
// mutations, so it must never be copied from a request parameter,
// header or body.
Local bool
}
TrustedRequest is constructed by a trusted server adapter, never from client-controlled URL, Host, Origin or forwarding headers. TrustedRequestFromAddr derives it correctly from the observed network peer.
func TrustedRequestFromAddr ¶
func TrustedRequestFromAddr(remoteAddr string) TrustedRequest
TrustedRequestFromAddr derives a TrustedRequest from the transport peer address (typically http.Request.RemoteAddr): Local is set only when the peer is a loopback address. Always call it with the actual network peer — never with a value copied from a request parameter, header, or body, which a client controls. Requests arriving through a reverse proxy have the proxy as their peer and are correctly reported as non-local.
type Tx ¶
type Tx interface {
Kind() StoreKind
Audit() AuditEvent
}
Tx represents the store transaction that is open during a TransactionHook. It is only valid for the duration of the callback and must not be retained or used by another goroutine.
type UUID ¶
UUID identifies every Credbound record: the 16 raw bytes PostgreSQL stores in a uuid column. It is comparable, so it works with == and as a map key, and it costs 16 bytes rather than the 36 of the canonical text.
Every identifier the library mints is a UUIDv7 (RFC 9562), so identifiers sort by creation time — which is what cursor pagination and the audit ordering rely on.
The zero value means "no identifier": it is what an absent optional reference reads as, and it maps to SQL NULL in both directions. A zero UUID carries neither version 7 nor the RFC variant, so it never passes the checks at the API boundary nor the CHECK constraints in the schema.
This is an alias, not a new type, so it stays interchangeable with the package it comes from. That package is the one accepted for the Go standard library (golang/go#62026), vendored verbatim under internal/uuid until it ships; when it does, this alias is repointed at the standard library and the vendored copy is deleted, with no other change. Nothing depends on methods the standard library does not provide: the store binds pgtype.UUID for its rows and converts at the boundary.
func MustParseUUID ¶
MustParseUUID is ParseUUID for constants and tests; it panics on a malformed value and must never be handed anything a caller supplied.
func ParseUUID ¶
ParseUUID reads an identifier in its canonical 8-4-4-4-12 form. A malformed value reports ErrInvalidInput, so a caller can classify it like any other rejected input.
Only the canonical form is accepted. The underlying parser also takes the compact, braced and urn:uuid: spellings, but Credbound does not: those would give one record several spellings that String never renders back, so a client comparing or caching what it received would see identifiers that differ from the ones the library returns. Hexadecimal case is the one exception, since String renders it back canonically; a token parser, whose input the library itself issued, is stricter still and takes the rendered form alone.
type UnimplementedEventListener ¶
type UnimplementedEventListener struct{}
func (UnimplementedEventListener) OnAuditUnavailable ¶
func (UnimplementedEventListener) OnAuditUnavailable(context.Context, AuditUnavailableEvent) error
func (UnimplementedEventListener) OnAuthenticationFailed ¶
func (UnimplementedEventListener) OnAuthenticationFailed(context.Context, AuthenticationFailureEvent) error
func (UnimplementedEventListener) OnAuthenticationSucceeded ¶
func (UnimplementedEventListener) OnAuthenticationSucceeded(context.Context, AuthenticationEvent) error
func (UnimplementedEventListener) OnAuthorizationDenied ¶
func (UnimplementedEventListener) OnAuthorizationDenied(context.Context, AuthorizationDeniedEvent) error
func (UnimplementedEventListener) OnBootstrapCompleted ¶
func (UnimplementedEventListener) OnBootstrapCompleted(context.Context, BootstrapCompletedEvent) error
func (UnimplementedEventListener) OnClientAuditRecorded ¶
func (UnimplementedEventListener) OnClientAuditRecorded(context.Context, ClientAuditRecordedEvent) error
func (UnimplementedEventListener) OnEmailAdded ¶
func (UnimplementedEventListener) OnEmailAdded(context.Context, EmailAddedEvent) error
func (UnimplementedEventListener) OnEmailAuthenticationRequested ¶
func (UnimplementedEventListener) OnEmailAuthenticationRequested(context.Context, EmailAuthenticationRequestedEvent) error
func (UnimplementedEventListener) OnEmailConfirmed ¶
func (UnimplementedEventListener) OnEmailConfirmed(context.Context, EmailConfirmedEvent) error
func (UnimplementedEventListener) OnEmailRemoved ¶
func (UnimplementedEventListener) OnEmailRemoved(context.Context, EmailRemovedEvent) error
func (UnimplementedEventListener) OnEmailVerificationResent ¶
func (UnimplementedEventListener) OnEmailVerificationResent(context.Context, EmailVerificationResentEvent) error
func (UnimplementedEventListener) OnInstanceRoleChanged ¶
func (UnimplementedEventListener) OnInstanceRoleChanged(context.Context, InstanceRoleChangedEvent) error
func (UnimplementedEventListener) OnInstanceRoleRemoved ¶
func (UnimplementedEventListener) OnInstanceRoleRemoved(context.Context, InstanceRoleRemovedEvent) error
func (UnimplementedEventListener) OnMembershipChanged ¶
func (UnimplementedEventListener) OnMembershipChanged(context.Context, MembershipChangedEvent) error
func (UnimplementedEventListener) OnOAuthEvent ¶
func (UnimplementedEventListener) OnOAuthEvent(context.Context, OAuthEvent) error
func (UnimplementedEventListener) OnPATAuthenticated ¶
func (UnimplementedEventListener) OnPATAuthenticated(context.Context, PATAuthenticatedEvent) error
func (UnimplementedEventListener) OnPATCreated ¶
func (UnimplementedEventListener) OnPATCreated(context.Context, PATCreatedEvent) error
func (UnimplementedEventListener) OnPATRejected ¶
func (UnimplementedEventListener) OnPATRejected(context.Context, PATRejectedEvent) error
func (UnimplementedEventListener) OnPATRevoked ¶
func (UnimplementedEventListener) OnPATRevoked(context.Context, PATRevokedEvent) error
func (UnimplementedEventListener) OnPasskeyAuthenticated ¶
func (UnimplementedEventListener) OnPasskeyAuthenticated(context.Context, PasskeyAuthenticatedEvent) error
func (UnimplementedEventListener) OnPasskeyDeleted ¶
func (UnimplementedEventListener) OnPasskeyDeleted(context.Context, PasskeyDeletedEvent) error
func (UnimplementedEventListener) OnPasskeyRegistered ¶
func (UnimplementedEventListener) OnPasskeyRegistered(context.Context, PasskeyRegisteredEvent) error
func (UnimplementedEventListener) OnPasswordChanged ¶
func (UnimplementedEventListener) OnPasswordChanged(context.Context, PasswordChangedEvent) error
func (UnimplementedEventListener) OnPasswordRehashed ¶
func (UnimplementedEventListener) OnPasswordRehashed(context.Context, PasswordRehashedEvent) error
func (UnimplementedEventListener) OnPasswordResetCompleted ¶
func (UnimplementedEventListener) OnPasswordResetCompleted(context.Context, PasswordResetCompletedEvent) error
func (UnimplementedEventListener) OnPasswordResetRequested ¶
func (UnimplementedEventListener) OnPasswordResetRequested(context.Context, PasswordResetRequestedEvent) error
func (UnimplementedEventListener) OnPrimaryEmailChanged ¶
func (UnimplementedEventListener) OnPrimaryEmailChanged(context.Context, PrimaryEmailChangedEvent) error
func (UnimplementedEventListener) OnRecoveryCodeConsumed ¶
func (UnimplementedEventListener) OnRecoveryCodeConsumed(context.Context, RecoveryCodeConsumedEvent) error
func (UnimplementedEventListener) OnRecoveryCodesRegenerated ¶
func (UnimplementedEventListener) OnRecoveryCodesRegenerated(context.Context, RecoveryCodesRegeneratedEvent) error
func (UnimplementedEventListener) OnRoleGranted ¶
func (UnimplementedEventListener) OnRoleGranted(context.Context, RoleGrantedEvent) error
func (UnimplementedEventListener) OnSCIMConfigurationCreated ¶
func (UnimplementedEventListener) OnSCIMConfigurationCreated(context.Context, SCIMConfigurationCreatedEvent) error
func (UnimplementedEventListener) OnSCIMGroupCreated ¶
func (UnimplementedEventListener) OnSCIMGroupCreated(context.Context, SCIMGroupEvent) error
func (UnimplementedEventListener) OnSCIMGroupDeleted ¶
func (UnimplementedEventListener) OnSCIMGroupDeleted(context.Context, SCIMGroupEvent) error
func (UnimplementedEventListener) OnSCIMGroupMembersChanged ¶
func (UnimplementedEventListener) OnSCIMGroupMembersChanged(context.Context, SCIMGroupEvent) error
func (UnimplementedEventListener) OnSCIMGroupUpdated ¶
func (UnimplementedEventListener) OnSCIMGroupUpdated(context.Context, SCIMGroupEvent) error
func (UnimplementedEventListener) OnSCIMUserActivated ¶
func (UnimplementedEventListener) OnSCIMUserActivated(context.Context, SCIMUserEvent) error
func (UnimplementedEventListener) OnSCIMUserDeprovisioned ¶
func (UnimplementedEventListener) OnSCIMUserDeprovisioned(context.Context, SCIMUserEvent) error
func (UnimplementedEventListener) OnSCIMUserProvisioned ¶
func (UnimplementedEventListener) OnSCIMUserProvisioned(context.Context, SCIMUserEvent) error
func (UnimplementedEventListener) OnSCIMUserSuspended ¶
func (UnimplementedEventListener) OnSCIMUserSuspended(context.Context, SCIMUserEvent) error
func (UnimplementedEventListener) OnSCIMUserUpdated ¶
func (UnimplementedEventListener) OnSCIMUserUpdated(context.Context, SCIMUserEvent) error
func (UnimplementedEventListener) OnSSOAuthenticated ¶
func (UnimplementedEventListener) OnSSOAuthenticated(context.Context, SSOAuthenticatedEvent) error
func (UnimplementedEventListener) OnSSOChallengeIssued ¶
func (UnimplementedEventListener) OnSSOChallengeIssued(context.Context, SSOChallengeIssuedEvent) error
func (UnimplementedEventListener) OnSSOJITProvisioned ¶
func (UnimplementedEventListener) OnSSOJITProvisioned(context.Context, SSOJITProvisionedEvent) error
func (UnimplementedEventListener) OnSSOLinked ¶
func (UnimplementedEventListener) OnSSOLinked(context.Context, SSOLinkedEvent) error
func (UnimplementedEventListener) OnSSOUnlinked ¶
func (UnimplementedEventListener) OnSSOUnlinked(context.Context, SSOUnlinkedEvent) error
func (UnimplementedEventListener) OnSecondFactorReset ¶
func (UnimplementedEventListener) OnSecondFactorReset(context.Context, SecondFactorResetEvent) error
func (UnimplementedEventListener) OnSessionCreated ¶
func (UnimplementedEventListener) OnSessionCreated(context.Context, SessionCreatedEvent) error
func (UnimplementedEventListener) OnSessionRevoked ¶
func (UnimplementedEventListener) OnSessionRevoked(context.Context, SessionRevokedEvent) error
func (UnimplementedEventListener) OnSignUpCompleted ¶
func (UnimplementedEventListener) OnSignUpCompleted(context.Context, SignUpCompletedEvent) error
func (UnimplementedEventListener) OnStepUpDenied ¶
func (UnimplementedEventListener) OnStepUpDenied(context.Context, StepUpDeniedEvent) error
func (UnimplementedEventListener) OnTOTPActivated ¶
func (UnimplementedEventListener) OnTOTPActivated(context.Context, TOTPActivatedEvent) error
func (UnimplementedEventListener) OnTOTPDisabled ¶
func (UnimplementedEventListener) OnTOTPDisabled(context.Context, TOTPDisabledEvent) error
func (UnimplementedEventListener) OnTOTPEnrollmentStarted ¶
func (UnimplementedEventListener) OnTOTPEnrollmentStarted(context.Context, TOTPEnrollmentStartedEvent) error
func (UnimplementedEventListener) OnTOTPReplayRejected ¶
func (UnimplementedEventListener) OnTOTPReplayRejected(context.Context, TOTPReplayRejectedEvent) error
func (UnimplementedEventListener) OnTOTPVerified ¶
func (UnimplementedEventListener) OnTOTPVerified(context.Context, TOTPVerifiedEvent) error
func (UnimplementedEventListener) OnUserAnonymized ¶
func (UnimplementedEventListener) OnUserAnonymized(context.Context, UserAnonymizedEvent) error
func (UnimplementedEventListener) OnUserCreated ¶
func (UnimplementedEventListener) OnUserCreated(context.Context, UserCreatedEvent) error
func (UnimplementedEventListener) OnUserCredentialsRevoked ¶
func (UnimplementedEventListener) OnUserCredentialsRevoked(context.Context, UserCredentialsRevokedEvent) error
func (UnimplementedEventListener) OnUserLocked ¶
func (UnimplementedEventListener) OnUserLocked(context.Context, UserLockedEvent) error
func (UnimplementedEventListener) OnUserProfileUpdated ¶
func (UnimplementedEventListener) OnUserProfileUpdated(context.Context, UserProfileUpdatedEvent) error
func (UnimplementedEventListener) OnUserSessionsRevoked ¶
func (UnimplementedEventListener) OnUserSessionsRevoked(context.Context, UserSessionsRevokedEvent) error
func (UnimplementedEventListener) OnUserStatusChanged ¶
func (UnimplementedEventListener) OnUserStatusChanged(context.Context, UserStatusEvent) error
func (UnimplementedEventListener) OnWorkspaceChanged ¶
func (UnimplementedEventListener) OnWorkspaceChanged(context.Context, WorkspaceChangedEvent) error
func (UnimplementedEventListener) OnWorkspaceCreated ¶
func (UnimplementedEventListener) OnWorkspaceCreated(context.Context, WorkspaceCreatedEvent) error
func (UnimplementedEventListener) OnWorkspaceDomainConfirmed ¶
func (UnimplementedEventListener) OnWorkspaceDomainConfirmed(context.Context, WorkspaceDomainEvent) error
func (UnimplementedEventListener) OnWorkspaceDomainCreated ¶
func (UnimplementedEventListener) OnWorkspaceDomainCreated(context.Context, WorkspaceDomainEvent) error
func (UnimplementedEventListener) OnWorkspaceDomainPolicyUpdated ¶
func (UnimplementedEventListener) OnWorkspaceDomainPolicyUpdated(context.Context, WorkspaceDomainEvent) error
func (UnimplementedEventListener) OnWorkspaceDomainRemoved ¶
func (UnimplementedEventListener) OnWorkspaceDomainRemoved(context.Context, WorkspaceDomainEvent) error
func (UnimplementedEventListener) OnWorkspaceInvitationAccepted ¶
func (UnimplementedEventListener) OnWorkspaceInvitationAccepted(context.Context, WorkspaceInvitationEvent) error
func (UnimplementedEventListener) OnWorkspaceInvitationCreated ¶
func (UnimplementedEventListener) OnWorkspaceInvitationCreated(context.Context, WorkspaceInvitationEvent) error
func (UnimplementedEventListener) OnWorkspaceInvitationRevoked ¶
func (UnimplementedEventListener) OnWorkspaceInvitationRevoked(context.Context, WorkspaceInvitationEvent) error
type UnimplementedTransactionHook ¶
type UnimplementedTransactionHook struct{}
func (UnimplementedTransactionHook) ApplyClientAudit ¶
func (UnimplementedTransactionHook) ApplyClientAudit(context.Context, Tx, ClientAuditRecord) error
func (UnimplementedTransactionHook) ApplyEmailAddition ¶
func (UnimplementedTransactionHook) ApplyEmailAddition(context.Context, Tx, EmailAddition) error
func (UnimplementedTransactionHook) ApplyEmailConfirmation ¶
func (UnimplementedTransactionHook) ApplyEmailConfirmation(context.Context, Tx, EmailConfirmation) error
func (UnimplementedTransactionHook) ApplyEmailRemoval ¶
func (UnimplementedTransactionHook) ApplyEmailRemoval(context.Context, Tx, EmailRemoval) error
func (UnimplementedTransactionHook) ApplyInstanceRoleChange ¶
func (UnimplementedTransactionHook) ApplyInstanceRoleChange(context.Context, Tx, InstanceRoleChange) error
func (UnimplementedTransactionHook) ApplyInstanceRoleRemoval ¶
func (UnimplementedTransactionHook) ApplyInstanceRoleRemoval(context.Context, Tx, InstanceRoleRemoval) error
func (UnimplementedTransactionHook) ApplyMembershipChange ¶
func (UnimplementedTransactionHook) ApplyMembershipChange(context.Context, Tx, MembershipChange) error
func (UnimplementedTransactionHook) ApplyOAuthChange ¶
func (UnimplementedTransactionHook) ApplyOAuthChange(context.Context, Tx, OAuthChange) error
func (UnimplementedTransactionHook) ApplyPATCreation ¶
func (UnimplementedTransactionHook) ApplyPATCreation(context.Context, Tx, PATCreation) error
func (UnimplementedTransactionHook) ApplyPATRevocation ¶
func (UnimplementedTransactionHook) ApplyPATRevocation(context.Context, Tx, PATRevocation) error
func (UnimplementedTransactionHook) ApplyPasskeyDeletion ¶
func (UnimplementedTransactionHook) ApplyPasskeyDeletion(context.Context, Tx, PasskeyDeletion) error
func (UnimplementedTransactionHook) ApplyPasskeyRegistration ¶
func (UnimplementedTransactionHook) ApplyPasskeyRegistration(context.Context, Tx, PasskeyRegistration) error
func (UnimplementedTransactionHook) ApplyPasswordChange ¶
func (UnimplementedTransactionHook) ApplyPasswordChange(context.Context, Tx, PasswordChange) error
func (UnimplementedTransactionHook) ApplyPrimaryEmailChange ¶
func (UnimplementedTransactionHook) ApplyPrimaryEmailChange(context.Context, Tx, PrimaryEmailChange) error
func (UnimplementedTransactionHook) ApplyRecoveryCodeRegeneration ¶
func (UnimplementedTransactionHook) ApplyRecoveryCodeRegeneration(context.Context, Tx, RecoveryCodeRegeneration) error
func (UnimplementedTransactionHook) ApplyRoleGrant ¶
func (UnimplementedTransactionHook) ApplySCIMConfigurationCreate ¶
func (UnimplementedTransactionHook) ApplySCIMConfigurationCreate(context.Context, Tx, SCIMConfigurationChange) error
func (UnimplementedTransactionHook) ApplySCIMGroupDelete ¶
func (UnimplementedTransactionHook) ApplySCIMGroupDelete(context.Context, Tx, SCIMGroupChange) error
func (UnimplementedTransactionHook) ApplySCIMGroupUpsert ¶
func (UnimplementedTransactionHook) ApplySCIMGroupUpsert(context.Context, Tx, SCIMGroupChange) error
func (UnimplementedTransactionHook) ApplySCIMUserDeprovision ¶
func (UnimplementedTransactionHook) ApplySCIMUserDeprovision(context.Context, Tx, SCIMUserChange) error
func (UnimplementedTransactionHook) ApplySCIMUserProvision ¶
func (UnimplementedTransactionHook) ApplySCIMUserProvision(context.Context, Tx, SCIMUserChange) error
func (UnimplementedTransactionHook) ApplySCIMUserUpdate ¶
func (UnimplementedTransactionHook) ApplySCIMUserUpdate(context.Context, Tx, SCIMUserChange) error
func (UnimplementedTransactionHook) ApplySSOLink ¶
func (UnimplementedTransactionHook) ApplySSOUnlink ¶
func (UnimplementedTransactionHook) ApplySecondFactorReset ¶
func (UnimplementedTransactionHook) ApplySecondFactorReset(context.Context, Tx, SecondFactorReset) error
func (UnimplementedTransactionHook) ApplySessionCreation ¶
func (UnimplementedTransactionHook) ApplySessionCreation(context.Context, Tx, SessionCreation) error
func (UnimplementedTransactionHook) ApplySessionRevocation ¶
func (UnimplementedTransactionHook) ApplySessionRevocation(context.Context, Tx, SessionRevocation) error
func (UnimplementedTransactionHook) ApplyTOTPActivation ¶
func (UnimplementedTransactionHook) ApplyTOTPActivation(context.Context, Tx, TOTPActivation) error
func (UnimplementedTransactionHook) ApplyTOTPDisable ¶
func (UnimplementedTransactionHook) ApplyTOTPDisable(context.Context, Tx, TOTPDisable) error
func (UnimplementedTransactionHook) ApplyTOTPEnrollment ¶
func (UnimplementedTransactionHook) ApplyTOTPEnrollment(context.Context, Tx, TOTPEnrollmentChange) error
func (UnimplementedTransactionHook) ApplyUserAnonymization ¶
func (UnimplementedTransactionHook) ApplyUserAnonymization(context.Context, Tx, UserAnonymization) error
func (UnimplementedTransactionHook) ApplyUserCreate ¶
func (UnimplementedTransactionHook) ApplyUserCreate(context.Context, Tx, UserCreateChange) error
func (UnimplementedTransactionHook) ApplyUserCredentialRevocation ¶
func (UnimplementedTransactionHook) ApplyUserCredentialRevocation(context.Context, Tx, UserCredentialRevocation) error
func (UnimplementedTransactionHook) ApplyUserProfileChange ¶
func (UnimplementedTransactionHook) ApplyUserProfileChange(context.Context, Tx, UserProfileChange) error
func (UnimplementedTransactionHook) ApplyUserSessionRevocation ¶
func (UnimplementedTransactionHook) ApplyUserSessionRevocation(context.Context, Tx, UserSessionRevocation) error
func (UnimplementedTransactionHook) ApplyUserStatusChange ¶
func (UnimplementedTransactionHook) ApplyUserStatusChange(context.Context, Tx, UserStatusChange) error
func (UnimplementedTransactionHook) ApplyWorkspaceChange ¶
func (UnimplementedTransactionHook) ApplyWorkspaceChange(context.Context, Tx, WorkspaceChange) error
func (UnimplementedTransactionHook) ApplyWorkspaceCreate ¶
func (UnimplementedTransactionHook) ApplyWorkspaceCreate(context.Context, Tx, WorkspaceCreateChange) error
func (UnimplementedTransactionHook) ApplyWorkspaceDomainChange ¶
func (UnimplementedTransactionHook) ApplyWorkspaceDomainChange(context.Context, Tx, WorkspaceDomainChange) error
func (UnimplementedTransactionHook) ApplyWorkspaceInvitationChange ¶
func (UnimplementedTransactionHook) ApplyWorkspaceInvitationChange(context.Context, Tx, WorkspaceInvitationChange) error
type UpdateOAuthIssuerInput ¶
type UpdateOAuthIssuerInput struct {
OIDCEnabled bool
CIMDMode OAuthCIMDMode
CIMDAllowedOrigins []string
DCRMode OAuthDCRMode
DCRAllowClientSecrets bool
DCROpenRegistrationLimit int
CodeTTL time.Duration
AccessTokenTTL time.Duration
RefreshTokenTTL time.Duration
}
UpdateOAuthIssuerInput replaces an issuer's policy; the issuer URL itself is immutable. Zero TTLs fall back to the defaults.
type UpdateSCIMConfigurationInput ¶
type UpdateSCIMConfigurationInput struct {
DefaultRole Role
TrustDirectoryEmails bool
GroupRoleMappings []SCIMGroupRoleMapping
}
UpdateSCIMConfigurationInput replaces the role policy of a provisioning domain. Applying it recomputes the roles of every managed membership.
type UpdateUserInput ¶
type UpdateUserInput struct {
// DisplayName is the new 1-200 character profile name, trimmed.
DisplayName string
}
UpdateUserInput describes a user profile update. Emails are managed by the dedicated add/primary/remove operations and the disabled flag by the administrative lifecycle, so the only mutable profile field is the display name.
type UpdateWorkspaceInput ¶
type UpdateWorkspaceInput struct {
Name string
// RequireMFA toggles the workspace MFA policy; nil leaves it unchanged.
RequireMFA *bool
}
UpdateWorkspaceInput carries a workspace update. Name is always applied.
type User ¶
type User struct {
ID UUID
Email string
DisplayName string
Disabled bool
LastSeenAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
User is a global account. Email mirrors the primary EmailAddress; LastSeenAt reflects the latest successful authentication across all factors and is updated atomically with the authentication audit.
type UserAnonymization ¶
UserAnonymization is the transactional-hook change for AnonymizeUser: the user's mutable personal data is scrubbed and their credentials revoked in the same transaction, while the append-only audit chain is preserved.
type UserAnonymizedEvent ¶
UserAnonymizedEvent reports that an instance administrator pseudonymized a user: their mutable personal data (display name, email addresses, SSO and credential names) was scrubbed and their credentials revoked, while the append-only audit chain was preserved. It is the library's answer to a right-to-erasure request; hosts erase their own application-owned data separately.
type UserCreateChange ¶
type UserCreateChange struct {
EventMeta
User User
Email EmailAddress
Membership Membership
}
UserCreateChange carries a created account with its primary email and initial membership.
type UserCreatedEvent ¶
type UserCreatedEvent struct {
EventMeta
User User
Email EmailAddress
Membership Membership
}
type UserDataExport ¶
type UserDataExport struct {
User User
Emails []EmailAddress
Workspaces []WorkspaceMembership
SSOIdentities []SSOIdentity
Passkeys []Passkey
PATs []PAT
Sessions []Session
// SCIMUsers are the tenant-scoped directory profiles provisioned for the
// user, with the directory's own attributes included.
SCIMUsers []SCIMUser
// Invitations are the workspace invitations the user accepted; they carry
// the address the user was invited under, which may predate a rename.
Invitations []WorkspaceInvitation
// OAuthGrants are the user's OAuth consents, revoked ones included.
OAuthGrants []OAuthGrant
}
UserDataExport is the structured copy of everything Credbound holds about one user, assembled for a data-subject access request (GDPR Article 15/20). It deliberately omits secrets — token digests and sealed passkey material are scrubbed — and the append-only audit log, which is retained under the host's security-log policy and read separately through AuditEvents. Sessions need a SessionStore-capable store, SCIMUsers and Invitations a PrivacyStore-capable one, and OAuthGrants the OAuth capability; the sections are empty otherwise.
type UserLockedEvent ¶
type UserLockedEvent struct {
EventMeta
UserID UUID
LockedUntil time.Time
// Request carries the client network context supplied by the host through
// WithRequestMetadata, so listeners can throttle or alert by address
// without re-reading the audit log.
Request RequestMetadata
}
UserLockedEvent is emitted once when consecutive failures reach the lockout threshold, so listeners can alert without counting failures themselves.
type UserProfileChange ¶
UserProfileChange carries a profile display-name update and the value it replaced.
type UserProfileUpdatedEvent ¶
type UserProfileUpdatedEvent struct {
EventMeta
UserID UUID
DisplayName string
PreviousProfile string
}
UserProfileUpdatedEvent reports a profile display-name update.
type UserSessionRevocation ¶
UserSessionRevocation reports a bulk "log out everywhere" for one user.
type UserSessionsRevokedEvent ¶
UserSessionsRevokedEvent reports that every active session of the user was revoked in one operation ("log out everywhere").
type UserStatusChange ¶
UserStatusChange covers both directions of the user lifecycle; Disabled tells them apart, as does the EventMeta name.
type UserStatusEvent ¶
UserStatusEvent reports a user being disabled or re-enabled; Disabled tells the directions apart.
type ValidationError ¶
type ValidationError struct {
// Field names the offending input in lower_snake_case, e.g. "email",
// "password", "display_name", "workspace_name", "role".
Field string
// Rule is a stable machine-readable identifier of the violated rule,
// e.g. "required", "format", "too_short", "too_long", "unknown".
Rule string
// Message is a human-readable English description for logs; hosts
// localize their own user-facing copy from Field and Rule.
Message string
}
ValidationError reports which input field failed validation and why, so a host can answer with structured, per-field feedback instead of parsing the error text. It always matches errors.Is(err, ErrInvalidInput); retrieve it with errors.As. User-input validation failures (addresses, passwords, names, roles) carry one; protocol-level rejections may still return a plain ErrInvalidInput.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
func (*ValidationError) Unwrap ¶
func (e *ValidationError) Unwrap() error
Unwrap makes every ValidationError satisfy errors.Is(err, ErrInvalidInput).
type Workspace ¶
type Workspace struct {
ID UUID
Name string
// RequireMFA rejects interactive access below AAL2 for every member of
// the workspace. Non-interactive credentials such as PATs, whose
// creation already required a step-up, are not affected.
RequireMFA bool
CreatedAt time.Time
UpdatedAt time.Time
DisabledAt *time.Time
}
Workspace is a tenant. A workspace with DisabledAt set denies every tenant-scoped capability until it is re-enabled.
type WorkspaceChange ¶
WorkspaceChange carries a workspace update, disable or enable together with the state it replaced.
type WorkspaceChangedEvent ¶
WorkspaceChangedEvent reports a workspace update, disable or enable together with the state it replaced.
type WorkspaceCreateChange ¶
type WorkspaceCreateChange struct {
EventMeta
Workspace Workspace
Owner Membership
}
type WorkspaceCreatedEvent ¶
type WorkspaceCreatedEvent struct {
EventMeta
Workspace Workspace
Owner Membership
}
type WorkspaceDomain ¶
type WorkspaceDomain struct {
ID UUID
WorkspaceID UUID
// Domain is the normalized, lowercase registrable DNS name
// ("corp.example.com").
Domain string
// Challenge is the DNS TXT value proving control of the domain. It is
// deliberately not a secret credential — the host publishes it in public
// DNS — so it is stored in plaintext and remains visible on the record so
// the host can re-display it until the domain is confirmed.
Challenge string
// ConfirmedAt is set once the host asserted that DNS verification
// completed. An unconfirmed domain carries no policy effect.
ConfirmedAt *time.Time
// AutoJoin enables JIT provisioning: an unknown SSO identity whose
// verified email is under this domain and arrives through the trusted
// provider configuration is provisioned as a passwordless member.
AutoJoin bool
// AutoJoinRole is the workspace role granted to JIT-provisioned users.
AutoJoinRole Role
// SSOProviderConfigurationID names the registered SSO provider
// configuration this domain trusts for JIT provisioning.
SSOProviderConfigurationID UUID
// EnforceSSO rejects password, magic-link and email-OTP authentication
// for addresses under the domain with ErrSSORequired.
EnforceSSO bool
CreatedAt time.Time
UpdatedAt time.Time
}
WorkspaceDomain is a workspace-owned email domain. It is created pending with a DNS challenge value; the host proves control of the domain (by convention a TXT record carrying Challenge) out of band and confirms it with ConfirmWorkspaceDomain. Only a confirmed domain carries policy: auto-join (JIT provisioning through the trusted SSO provider configuration) and SSO enforcement. A domain name is globally unique across workspaces.
type WorkspaceDomainChange ¶
type WorkspaceDomainChange struct {
EventMeta
Domain WorkspaceDomain
Removed bool
}
WorkspaceDomainChange covers workspace-domain creation, confirmation, policy update and removal; the EventMeta name tells them apart and Removed marks a removal whose Domain field holds the final state. The Challenge is deliberately included: it is published in public DNS and is not a secret.
type WorkspaceDomainEvent ¶
type WorkspaceDomainEvent struct {
EventMeta
Domain WorkspaceDomain
}
WorkspaceDomainEvent is the shared payload of the workspace-domain lifecycle events (created, confirmed, policy updated, removed); the EventMeta name tells them apart. The Challenge is deliberately included: it is published in public DNS and is not a secret.
type WorkspaceDomainPolicyInput ¶
type WorkspaceDomainPolicyInput struct {
AutoJoin bool
AutoJoinRole Role
SSOProviderConfigurationID UUID
EnforceSSO bool
}
WorkspaceDomainPolicyInput replaces the policy of a confirmed workspace domain: the auto-join flag with its target role, the SSO provider configuration the domain trusts, and the SSO enforcement flag. A zero AutoJoinRole means member. When AutoJoin or EnforceSSO is set the provider configuration must be registered with the Manager.
type WorkspaceInvitation ¶
type WorkspaceInvitation struct {
ID UUID
WorkspaceID UUID
Email string
Role Role
InvitedBy UUID
Digest []byte
CreatedAt time.Time
ExpiresAt time.Time
AcceptedAt *time.Time
AcceptedUserID UUID
RevokedAt *time.Time
}
WorkspaceInvitation invites an email address into a workspace with a pre-assigned role. The digest is stored server-side only; the single-use token is returned once at creation for the host to deliver.
type WorkspaceInvitationChange ¶
type WorkspaceInvitationChange struct {
EventMeta
Invitation WorkspaceInvitation
}
WorkspaceInvitationChange never carries the invitation digest.
type WorkspaceInvitationEvent ¶
type WorkspaceInvitationEvent struct {
EventMeta
Invitation WorkspaceInvitation
}
WorkspaceInvitationEvent never carries the invitation digest.
type WorkspaceMember ¶
type WorkspaceMember struct {
User User
Membership Membership
}
WorkspaceMember pairs a workspace membership with the member's account profile. It is the row a workspace administrator's member list renders.
type WorkspaceMembership ¶
type WorkspaceMembership struct {
Workspace Workspace
Membership Membership
}
WorkspaceMembership pairs a workspace with the exported user's membership in it. It is the workspace-affiliation entry of a UserDataExport.
type WorkspacePAT ¶ added in v0.0.4
WorkspacePAT is one workspace-bound token joined with its owner's account profile. It is the row a workspace administrator's key list renders: the token never carries its digest, and the owner is resolved so the page can name whose key it is without the instance-wide admin users read permission.
type WorkspacePermission ¶
type WorkspacePermission string
WorkspacePermission names a tenant-scoped capability checked by AuthorizePermission. The admin role always holds every registered workspace permission, including host-defined ones.
const ( PermissionWorkspaceAccess WorkspacePermission = "workspace.access" PermissionWorkspaceUsersRead WorkspacePermission = "workspace.users.read" PermissionWorkspaceUsersWrite WorkspacePermission = "workspace.users.write" PermissionWorkspaceSettingsWrite WorkspacePermission = "workspace.settings.write" PermissionWorkspaceRBACWrite WorkspacePermission = "workspace.rbac.write" PermissionWorkspaceAuditRead WorkspacePermission = "workspace.audit.read" // PermissionWorkspaceCredentialsManage administers the workspace-bound // credentials of every member — listing and revoking the PATs bound to // the workspace — without any power over the members themselves. It is // deliberately separate from workspace users write: the page that // disables a departed colleague's key is not the page that manages // memberships, and a machine-operations role may need the first without // the second. PermissionWorkspaceCredentialsManage WorkspacePermission = "workspace.credentials.manage" PermissionOAuthResourceManage WorkspacePermission = "oauth.resource.manage" )
type WorkspaceStore ¶
type WorkspaceStore interface {
CreateWorkspace(context.Context, Workspace, Membership, Commit) error
WorkspaceByID(context.Context, UUID) (Workspace, error)
UpdateWorkspace(context.Context, Workspace, Commit) error
SetWorkspaceDisabled(context.Context, UUID, bool, time.Time, Commit) error
Workspaces(context.Context, PageRequest) iter.Seq2[PageEvent[Workspace], error]
UserWorkspaces(context.Context, UUID, PageRequest) iter.Seq2[PageEvent[Workspace], error]
Membership(context.Context, UUID, UUID) (Membership, error)
UpsertMembership(context.Context, Membership, Commit) error
RemoveMembership(context.Context, UUID, UUID, time.Time, Commit) error
Memberships(context.Context, UUID, PageRequest) iter.Seq2[PageEvent[Membership], error]
InstanceAdministrator(context.Context, UUID) (InstanceAdministrator, error)
// InstanceAdministrators streams every instance role assignment, oldest
// first. The set is bounded by governance, not by end users, so it is
// not paginated.
InstanceAdministrators(context.Context) iter.Seq2[InstanceAdministrator, error]
SetInstanceRole(context.Context, InstanceAdministrator, Commit) error
RemoveInstanceRole(context.Context, UUID, Commit) error
}
WorkspaceStore persists workspaces, memberships and instance role assignments.
Source Files
¶
- admin.go
- audit.go
- auth.go
- client_audit.go
- config.go
- doc.go
- domain.go
- email.go
- emailotp.go
- errors.go
- errors_http.go
- events.go
- events_generated.go
- export.go
- internal.go
- invitation.go
- lifecycle.go
- magiclink.go
- oauth.go
- oauth_admin.go
- oauth_events.go
- oauth_tokens.go
- oauth_types.go
- passkey.go
- pat.go
- ports.go
- rbac.go
- request.go
- requesthttp.go
- reset.go
- revocation.go
- scim.go
- session.go
- signup.go
- sso.go
- timeutil.go
- totp.go
- types.go
- uuid.go
- workspace_roles.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package credboundtest provides deterministic test doubles and constructors for host services that integrate github.com/deepteams/credbound.
|
Package credboundtest provides deterministic test doubles and constructors for host services that integrate github.com/deepteams/credbound. |
|
examples
|
|
|
minimal
command
Command minimal is a runnable, end-to-end Credbound integration: PostgreSQL persistence through pgx, the embedded migrations, Argon2id password hashing, and a net/http layer backed by Credbound's server-side session module (CreateSession, AuthenticateSession, SignOut), following the "Sessions and the Authentication capability" contract from the README.
|
Command minimal is a runnable, end-to-end Credbound integration: PostgreSQL persistence through pgx, the embedded migrations, Argon2id password hashing, and a net/http layer backed by Credbound's server-side session module (CreateSession, AuthenticateSession, SignOut), following the "Sessions and the Authentication capability" contract from the README. |
|
Package githubadapter provides a hardened GitHub implementation of the credbound.SSOProvider port.
|
Package githubadapter provides a hardened GitHub implementation of the credbound.SSOProvider port. |
|
internal
|
|
|
cmd/genevents
command
Command genevents generates the forward-compatible no-op implementations for Credbound's typed hook and event interfaces.
|
Command genevents generates the forward-compatible no-op implementations for Credbound's typed hook and event interfaces. |
|
dbtype
Package dbtype names the types the generated queries bind to.
|
Package dbtype names the types the generated queries bind to. |
|
ssrf
Package ssrf holds the outbound-fetch address policy shared by the adapters that dereference configured URLs on behalf of a host: OAuth Client Identifier Metadata Documents (oauthhttp), private_key_jwt JWKS documents (oauthclientadapter), and SAML IdP metadata (samladapter).
|
Package ssrf holds the outbound-fetch address policy shared by the adapters that dereference configured URLs on behalf of a host: OAuth Client Identifier Metadata Documents (oauthhttp), private_key_jwt JWKS documents (oauthclientadapter), and SAML IdP metadata (samladapter). |
|
storetest
Package storetest runs the store-independent behavioral conformance suite of Credbound: the same Manager-level flows executed against every store implementation, so a divergence between the in-memory and PostgreSQL backends fails a test instead of reaching production.
|
Package storetest runs the store-independent behavioral conformance suite of Credbound: the same Manager-level flows executed against every store implementation, so a divergence between the in-memory and PostgreSQL backends fails a test instead of reaching production. |
|
uuid
Package uuid provides support for generating and manipulating UUIDs.
|
Package uuid provides support for generating and manipulating UUIDs. |
|
Package memory provides an in-memory implementation of every Credbound persistence port — Store plus the optional SessionStore, SignupStore, DomainStore, SCIMStore, EmailThrottleStore and OAuthStore capabilities — including the hash-chained audit log and its atomic commit semantics.
|
Package memory provides an in-memory implementation of every Credbound persistence port — Store plus the optional SessionStore, SignupStore, DomainStore, SCIMStore, EmailThrottleStore and OAuthStore capabilities — including the hash-chained audit log and its atomic commit semantics. |
|
Package migrations embeds Credbound's Goose-compatible PostgreSQL migrations.
|
Package migrations embeds Credbound's Goose-compatible PostgreSQL migrations. |
|
Package oauthclientadapter provides hardened OAuth client authentication adapters without coupling Credbound to an HTTP server: JWTAssertionVerifier validates private_key_jwt client assertions (RFC 7523) against a client's registered JWKS with single-use JWT ID enforcement, and MemoryReplayStore supplies the replay protection for single-process hosts.
|
Package oauthclientadapter provides hardened OAuth client authentication adapters without coupling Credbound to an HTTP server: JWTAssertionVerifier validates private_key_jwt client assertions (RFC 7523) against a client's registered JWKS with single-use JWT ID enforcement, and MemoryReplayStore supplies the replay protection for single-process hosts. |
|
Package oauthhttp provides the optional, mountable HTTP adapters for Credbound's OAuth 2.1/OIDC authorization server: Handler serves the protocol endpoints (discovery, JWKS, authorize, token, revoke, register, userinfo), Protect wraps an MCP or API resource with bearer-token authentication, and MetadataFetcher resolves Client Identifier Metadata Documents over SSRF-hardened HTTPS.
|
Package oauthhttp provides the optional, mountable HTTP adapters for Credbound's OAuth 2.1/OIDC authorization server: Handler serves the protocol endpoints (discovery, JWKS, authorize, token, revoke, register, userinfo), Protect wraps an MCP or API resource with bearer-token authentication, and MetadataFetcher resolves Client Identifier Metadata Documents over SSRF-hardened HTTPS. |
|
Package oidcadapter provides standard-library OIDC signing adapters.
|
Package oidcadapter provides standard-library OIDC signing adapters. |
|
Package otelobserver implements the credbound.Observer port with OpenTelemetry: every observed operation becomes a span, a "credbound.operations" counter increment, a "credbound.operation.duration" histogram sample and a log record, all attributed with the operation name and outcome.
|
Package otelobserver implements the credbound.Observer port with OpenTelemetry: every observed operation becomes a span, a "credbound.operations" counter increment, a "credbound.operation.duration" histogram sample and a log record, all attributed with the operation name and outcome. |
|
Package password implements the credbound.PasswordHasher port with Argon2id (RFC 9106).
|
Package password implements the credbound.PasswordHasher port with Argon2id (RFC 9106). |
|
Package samladapter provides a hardened SAML 2.0 service-provider implementation of the credbound.SSOProvider port, so hosts never hand-roll XML signature validation — historically the most dangerous part of SAML.
|
Package samladapter provides a hardened SAML 2.0 service-provider implementation of the credbound.SSOProvider port, so hosts never hand-roll XML signature validation — historically the most dangerous part of SAML. |
|
Package scimhttp exposes Credbound's optional SCIM 2.0 provisioning adapter (RFC 7643/7644): Users, Groups, /.search, PATCH, discovery endpoints and SCIM-shaped errors, all delegated to a Manager whose store implements credbound.SCIMStore.
|
Package scimhttp exposes Credbound's optional SCIM 2.0 provisioning adapter (RFC 7643/7644): Users, Groups, /.search, PATCH, discovery endpoints and SCIM-shaped errors, all delegated to a Manager whose store implements credbound.SCIMStore. |
|
sqlstore
|
|
|
postgresql
Package postgresql implements every Credbound persistence port — Store plus the optional SessionStore, SignupStore, DomainStore, SCIMStore, EmailThrottleStore and OAuthStore capabilities — on PostgreSQL, pairing sqlc-generated database/sql queries for transactional mutations with pgx streaming for paginated reads, and committing each mutation's hash-chained audit event atomically with the change.
|
Package postgresql implements every Credbound persistence port — Store plus the optional SessionStore, SignupStore, DomainStore, SCIMStore, EmailThrottleStore and OAuthStore capabilities — on PostgreSQL, pairing sqlc-generated database/sql queries for transactional mutations with pgx streaming for paginated reads, and committing each mutation's hash-chained audit event atomically with the change. |
|
Package ssoadapter provides a hardened, generic OpenID Connect implementation of the credbound.SSOProvider port, so hosts do not have to hand-roll the network side of SSO.
|
Package ssoadapter provides a hardened, generic OpenID Connect implementation of the credbound.SSOProvider port, so hosts do not have to hand-roll the network side of SSO. |
|
Package totpadapter implements the credbound.TOTPProvider port with RFC 6238 six-digit SHA-1 codes (the interoperable authenticator-app profile) on top of github.com/pquerna/otp.
|
Package totpadapter implements the credbound.TOTPProvider port with RFC 6238 six-digit SHA-1 codes (the interoperable authenticator-app profile) on top of github.com/pquerna/otp. |
|
Package webauthnadapter implements the credbound.PasskeyProvider port on top of github.com/go-webauthn/webauthn.
|
Package webauthnadapter implements the credbound.PasskeyProvider port on top of github.com/go-webauthn/webauthn. |