Documentation
¶
Overview ¶
Package theauth provides session-based authentication for Go applications.
TheAuth ships magic-link email auth, opaque session tokens with revocation, and chi-friendly middleware. Storage backends include in-memory and Postgres (pgx + sqlc). OAuth providers, TOTP, WebAuthn, and MCP OAuth 2.1 land in future versions — see the README roadmap.
Index ¶
- Constants
- Variables
- func DefaultRedactor(metadata map[string]any) map[string]any
- func HashEmailForAudit(email string) string
- func SeededSecretKeys() []string
- func WithAuditMetadata(ctx context.Context, md AuditMetadata) context.Context
- type AdminConfig
- type AuditConfig
- type AuditEvent
- type AuditMetadata
- type AuditQuery
- type Config
- type EnrollTOTPResult
- type Group
- type MagicLink
- type OAuthAccount
- type Organization
- type OrganizationMember
- type OrganizationsConfig
- type PasswordResetToken
- type Permission
- type Provider
- type ProviderToken
- type ProviderUser
- type RBACConfig
- type RecoveryCode
- type Role
- type RoleSeed
- type SAMLAttributeMap
- type SAMLConfig
- type SAMLConnection
- type SAMLConnectionInput
- type SAMLIdentity
- type SCIMConfig
- type SCIMGroupFilter
- type SCIMToken
- type SCIMUserFilter
- type Session
- type SigninStep
- type Stats
- type Storage
- type TOTPConfig
- type TOTPSecret
- type TargetRef
- type TheAuth
- func (a *TheAuth) AddOrganizationMember(ctx context.Context, orgID, userID ULID, role string) error
- func (a *TheAuth) AuthenticateSCIMToken(ctx context.Context, presented string) (ULID, error)
- func (a *TheAuth) Authn() func(http.Handler) http.Handler
- func (a *TheAuth) BeginPasskeyLogin(_ context.Context) (*protocol.CredentialAssertion, string, error)
- func (a *TheAuth) BeginPasskeyRegistration(ctx context.Context, userID ULID) (*protocol.CredentialCreation, string, error)
- func (a *TheAuth) BeginSAMLLogin(ctx context.Context, connectionID ULID, relayState string) (string, error)
- func (a *TheAuth) BeginTOTPEnrollment(ctx context.Context, userID ULID, accountName string) (EnrollTOTPResult, error)
- func (a *TheAuth) Close()
- func (a *TheAuth) ConsumeRecoveryCode(ctx context.Context, pendingSessionToken, code string) (string, Session, error)
- func (a *TheAuth) CreateOrganization(ctx context.Context, name, slug string, ownerUserID ULID) (Organization, error)
- func (a *TheAuth) CreateRole(ctx context.Context, orgID ULID, name, description string, perms []string) (Role, error)
- func (a *TheAuth) CreateSAMLConnection(ctx context.Context, in SAMLConnectionInput) (SAMLConnection, error)
- func (a *TheAuth) CreateSCIMToken(ctx context.Context, orgID ULID, name string) (string, SCIMToken, error)
- func (a *TheAuth) DeleteRole(ctx context.Context, roleID ULID) error
- func (a *TheAuth) DeleteSAMLConnection(ctx context.Context, id ULID) error
- func (a *TheAuth) EmitAudit(ctx context.Context, action string, target TargetRef, metadata map[string]any)
- func (a *TheAuth) FinishPasskeyLogin(ctx context.Context, challengeToken string, body io.Reader, ua, ip string) (string, Session, error)
- func (a *TheAuth) FinishPasskeyRegistration(ctx context.Context, userID ULID, challengeToken, name string, body io.Reader) (WebAuthnCredential, error)
- func (a *TheAuth) FinishSAMLLogin(ctx context.Context, connectionID ULID, samlResponseB64 string, ua, ip string) (string, Session, error)
- func (a *TheAuth) FinishTOTPEnrollment(ctx context.Context, userID ULID, enrollmentID, code string) ([]string, error)
- func (a *TheAuth) GrantRole(ctx context.Context, actor, target, roleID ULID) error
- func (a *TheAuth) HasPermission(ctx context.Context, userID ULID, orgID *ULID, perm string) (bool, error)
- func (a *TheAuth) IssuePending2FA(ctx context.Context, userID ULID, ua, ip string) (string, Session, error)
- func (a *TheAuth) ListOrganizationMembers(ctx context.Context, orgID ULID) ([]OrganizationMember, error)
- func (a *TheAuth) ListSAMLConnections(ctx context.Context, orgID ULID) ([]SAMLConnection, error)
- func (a *TheAuth) ListSCIMTokens(ctx context.Context, orgID ULID) ([]SCIMToken, error)
- func (a *TheAuth) ListUserOrganizations(ctx context.Context, userID ULID) ([]Organization, error)
- func (a *TheAuth) Mount(r chi.Router)
- func (a *TheAuth) OrganizationByID(ctx context.Context, id ULID) (*Organization, error)
- func (a *TheAuth) OrganizationBySlug(ctx context.Context, slug string) (*Organization, error)
- func (a *TheAuth) PermissionsForUser(ctx context.Context, userID ULID, orgID *ULID) ([]string, error)
- func (a *TheAuth) QueryAudit(ctx context.Context, q AuditQuery) ([]AuditEvent, string, error)
- func (a *TheAuth) RateLimitByEmail(perMinute int) func(http.Handler) http.Handler
- func (a *TheAuth) RateLimitByIP(perMinute int) func(http.Handler) http.Handler
- func (a *TheAuth) RemoveOrganizationMember(ctx context.Context, orgID, userID ULID) error
- func (a *TheAuth) RequireAuth() func(http.Handler) http.Handler
- func (a *TheAuth) RequirePendingOrFull() func(http.Handler) http.Handler
- func (a *TheAuth) RequirePermission(perms ...string) func(http.Handler) http.Handler
- func (a *TheAuth) RevokeRole(ctx context.Context, actor, target, roleID ULID) error
- func (a *TheAuth) RevokeSCIMToken(ctx context.Context, id ULID) error
- func (a *TheAuth) SAMLConnectionByID(ctx context.Context, id ULID) (*SAMLConnection, error)
- func (a *TheAuth) SAMLMetadataXML(ctx context.Context, connectionID ULID) ([]byte, error)
- func (a *TheAuth) SeedOrganizationRoles(ctx context.Context, orgID ULID) error
- func (a *TheAuth) SeedPermissions(ctx context.Context) ([]Permission, error)
- func (a *TheAuth) SetActiveOrganization(ctx context.Context, sessionID ULID, orgID *ULID) error
- func (a *TheAuth) Start() error
- func (a *TheAuth) Stats() Stats
- func (a *TheAuth) UpdateRole(ctx context.Context, roleID ULID, name, description string, perms []string) (Role, error)
- func (a *TheAuth) UpdateSAMLConnection(ctx context.Context, id ULID, in SAMLConnectionInput) (SAMLConnection, error)
- func (a *TheAuth) VerifyTOTP(ctx context.Context, pendingSessionToken, code string) (string, Session, error)
- type TheAuthError
- type ULID
- type User
- type UserRole
- type WebAuthnConfig
- type WebAuthnCredential
Examples ¶
Constants ¶
const ( CodeWeakPassword = "weak_password" CodeEmailTaken = "email_taken" CodeInvalidCredentials = "invalid_credentials" CodeRateLimited = "rate_limited" CodePasswordResetExpired = "password_reset_expired" CodePasswordResetInvalid = "password_reset_invalid" // v0.5 codes. CodeTOTPRequired = "totp_required" CodeInvalidTOTP = "invalid_totp" CodeAlreadyEnrolled = "already_enrolled" CodeWebAuthn = "webauthn_error" )
Stable error codes that callers can switch on. New endpoints return TheAuthError; old endpoints keep returning the sentinels above.
const ( AuthLevelFull = "full" AuthLevelPending2FA = "pending_2fa" )
Session auth levels (v0.5). AuthLevelFull is the post-MFA, full-access state. AuthLevelPending2FA is the short-lived state after a successful password verify on an account that also has TOTP enrolled: the user has proven the first factor and may only call /auth/totp/verify or /auth/totp/recovery. RequireAuth rejects pending sessions everywhere else.
const ( OrgRoleOwner = "owner" OrgRoleAdmin = "admin" OrgRoleMember = "member" )
Organization role constants. Scoped to one organization each.
const ( PermissionBillingRead = "billing:read" PermissionBillingWrite = "billing:write" PermissionBillingAdmin = "billing:admin" PermissionUsersRead = "users:read" PermissionUsersInvite = "users:invite" PermissionUsersAdmin = "users:admin" PermissionRolesRead = "roles:read" PermissionRolesAdmin = "roles:admin" PermissionAuditRead = "audit:read" PermissionSAMLAdmin = "saml:admin" PermissionSCIMAdmin = "scim:admin" PermissionSessionsRevoke = "sessions:revoke" )
Seeded permission catalog. Every consumer's permission set extends (never shrinks) this list; New returns an error if a custom Permission name duplicates a seeded one with a different description (defensive against silent overrides).
The list is intentionally finite and small. Wildcards and ABAC are deferred to v1.x per the v1.0 design document.
const MinPasswordLength = 12
MinPasswordLength is enforced at the library level (NIST 2024 baseline). No composition rules — NIST recommends against them. Consumers wanting stricter policies should layer them on top.
const PasswordResetTTL = time.Hour
PasswordResetTTL is how long a reset token stays valid after issuance. Reset tokens are single-use; this is also enforced atomically in storage.
const SystemRoleSuperAdmin = "super_admin"
SystemRoleSuperAdmin is the global role whose presence on a user bypasses every permission check. It is created at seed time with a NULL organization_id and is granted via direct DB insert or a CLI; the admin API never exposes a path to grant it.
Variables ¶
var ( ErrInvalidToken = errors.New("theauth: invalid token") ErrSessionExpired = errors.New("theauth: session expired") ErrUserNotFound = errors.New("theauth: user not found") ErrMagicLinkExpired = errors.New("theauth: magic link expired") ErrMagicLinkUsed = errors.New("theauth: magic link already used") ErrEmailNotVerified = errors.New("theauth: email not verified") // ErrStorageNotFound is the canonical "row missing" sentinel that storage // adapters return on lookup misses. Lives in the root package so service // code can errors.Is-check without importing the storage package // (which would create an import cycle). ErrStorageNotFound = errors.New("theauth: storage row not found") // ErrReplayDetected (v0.5) is returned by storage on a WebAuthn sign // count update where the new count is not strictly greater than the // stored value. The library treats this as a clone-attempt and refuses // the login (with the standard 0-stays-0 carve-out for authenticators // that do not implement counters; that case is handled by the caller). ErrReplayDetected = errors.New("theauth: webauthn sign count replay detected") // ErrAlreadyEnrolled (v0.5) is returned when /auth/totp/enroll/finish is // called against a user who already has a confirmed TOTP secret. Callers // must DELETE /auth/totp first to re-enroll. ErrAlreadyEnrolled = errors.New("theauth: totp already enrolled") // ErrSCIMRequiresOrganizations is returned by New when Config.SCIM is // non-nil but Config.Organizations is nil. SCIM is meaningless without // multi-tenancy because every token is bound to one organization. ErrSCIMRequiresOrganizations = errors.New("theauth: SCIM requires Organizations to be enabled") // ErrSAMLRequiresOrganizations is returned by New when Config.SAML is // non-nil but Config.Organizations is nil. Single-tenant SAML is // meaningless; per-connection routing keys off organization ownership. ErrSAMLRequiresOrganizations = errors.New("theauth: SAML requires Organizations to be enabled") // ErrSAMLUnsignedAssertion is returned by FinishSAMLLogin when the // inbound SAML Response parses but its assertion is not signed. ErrSAMLUnsignedAssertion = errors.New("theauth: saml assertion not signed") // ErrSAMLMissingEmail is returned by FinishSAMLLogin when the mapped // email attribute is empty (the find-or-create email fallback path // cannot proceed). ErrSAMLMissingEmail = errors.New("theauth: saml assertion missing email attribute") // ErrSAMLInvalidAssertion wraps the underlying crewjam/saml validation // error for failed signature / conditions / replay checks. ErrSAMLInvalidAssertion = errors.New("theauth: saml assertion invalid") // ErrLastOwner is returned when an org member removal would leave the // organization with zero owners. ErrLastOwner = errors.New("theauth: cannot remove the last owner") // ErrUnsupportedFilter is returned by the SCIM filter parser on any // filter shape outside the documented eq-only whitelist. ErrUnsupportedFilter = errors.New("theauth: scim filter not supported") // ErrSlugTaken is returned when the supplied organization slug already // exists. ErrSlugTaken = errors.New("theauth: organization slug already taken") // ErrAdminRequiresRBAC is returned by New when Config.Admin is non-nil // but Config.RBAC is nil. The admin endpoints are permission-gated and // meaningless without RBAC. ErrAdminRequiresRBAC = errors.New("theauth: Admin requires RBAC to be enabled") // ErrForbidden indicates the caller lacks a required permission for // the requested operation. Handlers map this to 403 with code // "rbac.forbidden" in the RFC 7807 problem body. ErrForbidden = errors.New("theauth: forbidden") // ErrUnknownPermission indicates a permission name not present in the // seeded or extended catalog. Mapped to 400 "rbac.unknown_permission". ErrUnknownPermission = errors.New("theauth: unknown permission") // ErrRoleInUse indicates a DELETE role would leave the organization // without any user holding the "users:admin" permission. Mapped to // 409 "rbac.role_in_use". ErrRoleInUse = errors.New("theauth: role in use") // ErrNoActiveOrg indicates the session has no active_organization_id // set. Mapped to 403 "rbac.no_active_org". ErrNoActiveOrg = errors.New("theauth: session has no active organization") // ErrOrgMismatch indicates the session's active_organization_id does // not match the resource organization. Mapped to 403 "rbac.org_mismatch". ErrOrgMismatch = errors.New("theauth: session active organization does not match resource") // ErrRBACDisabled is returned by RBAC service methods invoked when // Config.RBAC is nil. The RequirePermission middleware short-circuits // to 500 rather than returning this error directly. ErrRBACDisabled = errors.New("theauth: RBAC disabled (Config.RBAC is nil)") )
Sentinel errors — retained for backward compatibility with v0.1 callers that errors.Is-check against them. New code should prefer TheAuthError + the Code* constants below.
Functions ¶
func DefaultRedactor ¶ added in v1.0.0
DefaultRedactor masks values for keys named password, secret, token, code, refresh_token, access_token (case-insensitive) at any nesting depth. Nested maps and []any are descended; primitive values are kept as-is. Returns the same map (mutated in place) for chaining.
Applied once at emit time. A custom Config.Audit.Redactor receives the raw metadata and may strip/rename additional fields.
func HashEmailForAudit ¶ added in v1.0.0
HashEmailForAudit returns sha256(lowercase(email)) hex-encoded. Used by emit sites that want to correlate audit rows by email without storing the raw plaintext.
func SeededSecretKeys ¶ added in v1.0.0
func SeededSecretKeys() []string
SeededSecretKeys returns a copy of the case-insensitive key blocklist applied by the default redactor. Callers extending the redactor should merge this slice with their own additions.
func WithAuditMetadata ¶ added in v1.0.0
func WithAuditMetadata(ctx context.Context, md AuditMetadata) context.Context
WithAuditMetadata attaches an AuditMetadata to ctx; EmitAudit reads it. Overrides values derived from UserFromContext / SessionFromContext when the corresponding field is non-zero.
Types ¶
type AdminConfig ¶ added in v1.0.0
type AdminConfig struct {
// PathPrefix where admin routes mount. Defaults to "/admin/v1".
PathPrefix string
}
AdminConfig mounts /admin/v1/* when non-nil on the same chi router passed to Mount.
type AuditConfig ¶ added in v1.0.0
type AuditConfig struct {
// BufferSize is the channel buffer between EmitAudit and the writer
// goroutine. Defaults to 4096. When full, EmitAudit drops the event
// and increments Stats.AuditDropped.
BufferSize int
// BatchSize is the maximum events per INSERT. Defaults to 100.
BatchSize int
// FlushInterval is the maximum delay before a partial batch flushes.
// Defaults to 1 second.
FlushInterval time.Duration
// Redactor optionally transforms metadata before storage. The default
// redactor strips the keys "password", "secret", "token", "code",
// "refresh_token", "access_token" (case-insensitive) at any nesting
// depth and replaces their values with the string "[REDACTED]".
Redactor func(metadata map[string]any) map[string]any
// DrainTimeout caps how long Close waits for the writer goroutine to
// drain remaining events. Defaults to 5 seconds.
DrainTimeout time.Duration
}
AuditConfig configures the async audit writer. All fields have safe defaults; the zero value AuditConfig{} is valid.
type AuditEvent ¶ added in v0.7.0
type AuditEvent struct {
ID ULID `json:"id"`
OrganizationID *ULID `json:"organizationId,omitempty"`
ActorUserID *ULID `json:"actorUserId,omitempty"`
ActorSessionID *ULID `json:"actorSessionId,omitempty"`
Action string `json:"action"`
TargetType string `json:"targetType,omitempty"`
TargetID string `json:"targetId,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
IP string `json:"ip,omitempty"`
UserAgent string `json:"userAgent,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
AuditEvent is one append-only row in audit_events. Metadata is arbitrary jsonb; the default redactor masks secret-flavored keys at any depth before the value reaches storage.
type AuditMetadata ¶ added in v1.0.0
AuditMetadata is the optional bundle a caller may attach to a context to override the auto-derived actor / org / IP / UA. Useful in tests and in background jobs that do not pass through the HTTP middleware.
type AuditQuery ¶ added in v1.0.0
type AuditQuery struct {
OrganizationID *ULID
ActorUserID *ULID
Action string
TargetType string
TargetID string
Since *time.Time
Until *time.Time
Limit int
After string
}
AuditQuery filters and paginates audit_events reads. Zero-valued fields are ignored. Limit is capped at 200 and defaults to 50; After is the opaque cursor returned by the previous page.
type Config ¶
type Config struct {
Storage Storage
EmailSender email.Sender
BaseURL string
SigningKey ed25519.PrivateKey
SessionTTL time.Duration
MagicLinkTTL time.Duration
CookieName string
SecureCookie bool
// RateLimitPerIP is the per-IP per-minute budget applied to credential
// endpoints (signup/signin/forgot/reset). Defaults to 5 when zero.
RateLimitPerIP int
// RateLimitPerEmail is the per-email per-minute budget applied to signin
// + forgot. Defaults to 3 when zero.
RateLimitPerEmail int
// Providers is the list of OAuth providers exposed under
// /auth/providers/{name}/start and /callback. Leave nil to disable
// OAuth entirely (v0.1 / v0.2 behavior). Each provider's Name() must
// be unique within the slice.
Providers []Provider
// EncryptionKey is the 32-byte AES-256 key used to encrypt provider
// access/refresh tokens before they hit storage. Required when
// len(Providers) > 0; New returns an error otherwise. Source this from
// a secrets manager; never commit it.
EncryptionKey []byte
// PostLoginRedirect is where the OAuth callback handler 302s to after
// a successful sign-in. Defaults to "/" when empty. Set to a path on
// your own origin; cross-origin redirects are not validated here.
PostLoginRedirect string
// WebAuthn enables passkey registration + discoverable login when non-nil.
// RPID and RPOrigins are mandatory per spec. Leave nil to keep v0.4 behavior.
WebAuthn *WebAuthnConfig
// TOTP enables time-based second-factor enrollment + verification when non-nil.
// Requires Config.EncryptionKey (already required by v0.3 OAuth) so the stored
// secret is encrypted at rest. New returns an error if TOTP is set without a key.
TOTP *TOTPConfig
// Organizations (v0.7) enables multi-tenancy when non-nil. Single-tenant
// deployments leave this nil; organization-scoped routes (SAML connection
// CRUD, SCIM token CRUD, /auth/orgs/*) are not mounted.
Organizations *OrganizationsConfig
// SAML (v0.7) enables the per-connection Service Provider routes when
// non-nil. The SP keypair lives on the config so multi-tenant deployments
// can rotate it centrally; every connection signs AuthnRequests with this
// single keypair. Requires Organizations to be non-nil.
SAML *SAMLConfig
// SCIM (v0.7) enables the /scim/v2 endpoints when non-nil. Requires
// Organizations to be non-nil.
SCIM *SCIMConfig
// RBAC (v1.0) enables organization-scoped role and permission
// management when non-nil. The zero value RBACConfig{} accepts the
// seeded permissions and default org roles documented in
// service_rbac.go; consumers extend (never shrink) the seeded lists.
// New returns an error if Admin is non-nil and RBAC is nil because the
// admin endpoints are permission-gated and meaningless without RBAC.
RBAC *RBACConfig
// Audit (v1.0) enables the async batched audit writer when non-nil.
// When nil, EmitAudit is a silent no-op and no writer goroutine starts;
// the v0.7 stub call sites keep working as no-ops, so deployments that
// do not configure audit continue to behave exactly as before.
Audit *AuditConfig
// Admin (v1.0) mounts /admin/v1/* when non-nil. Requires RBAC to be
// non-nil. The PathPrefix can be moved (e.g. "/api/admin/v1") but the
// trailing version segment is always v1.
Admin *AdminConfig
}
Config holds the wiring for a TheAuth instance.
Storage and BaseURL are required. Everything else has sensible defaults applied by New: SessionTTL=24h, MagicLinkTTL=15m, CookieName="theauth_session", EmailSender=email.Noop{}. SigningKey is reserved for future JWT signing (v0.2+); v0.1 uses opaque tokens and leaves the field nil.
type EnrollTOTPResult ¶ added in v0.5.0
type EnrollTOTPResult struct {
Secret string `json:"secret"`
OTPAuthURL string `json:"otpAuthUrl"`
EnrollmentID string `json:"enrollmentId"`
}
EnrollTOTPResult is returned by BeginTOTPEnrollment for the caller to render: Secret is the base32 string the user can type manually, OTPAuthURL is the otpauth:// URI the consumer renders as a QR. EnrollmentID is the opaque token /enroll/finish must echo back to bind the secret to the session.
type Group ¶ added in v0.7.0
type Group struct {
ID ULID `json:"id"`
OrganizationID ULID `json:"organizationId"`
DisplayName string `json:"displayName"`
ExternalID string `json:"externalId,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
Group is a SCIM-first concept. v0.7 stores them flat (no nesting), scoped to one organization. Application semantics (mapping a group to a role) land in v0.8 RBAC.
type OAuthAccount ¶ added in v0.3.0
type OAuthAccount struct {
ID ULID `json:"id"`
UserID ULID `json:"userId"`
Provider string `json:"provider"`
ProviderUserID string `json:"providerUserId"`
AccessTokenEnc []byte `json:"-"`
RefreshTokenEnc []byte `json:"-"`
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
Scope string `json:"scope"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
OAuthAccount records the linkage between one of our Users and a remote OAuth provider identity (e.g. user X authenticates via GitHub). The (provider, provider_user_id) pair is unique; re-running the OAuth flow for the same provider account upserts this row rather than creating a duplicate. Tokens are encrypted at rest via crypto.Encrypt and are never serialized over JSON.
type Organization ¶ added in v0.7.0
type Organization struct {
ID ULID `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
Organization is the top-level multi-tenant container. Slug is a citext unique URL-safe handle (lowercased on write).
type OrganizationMember ¶ added in v0.7.0
type OrganizationMember struct {
OrganizationID ULID `json:"organizationId"`
UserID ULID `json:"userId"`
Role string `json:"role"`
JoinedAt time.Time `json:"joinedAt"`
}
OrganizationMember binds a user to an organization with a single role. Role values: "owner", "admin", "member". owner can manage SAML and SCIM, admin can manage SCIM only, member is read-only against org metadata.
type OrganizationsConfig ¶ added in v0.7.0
type OrganizationsConfig struct{}
OrganizationsConfig is currently empty: there are no tunables for v0.7. The presence of a non-nil value is the signal that multi-tenancy is on. Future fields (default member role, invitation TTL, etc.) land here without breaking the existing zero-value wiring.
type PasswordResetToken ¶ added in v0.2.0
type PasswordResetToken struct {
ID ULID `json:"id"`
UserID ULID `json:"userId"`
TokenHash []byte `json:"-"`
ExpiresAt time.Time `json:"expiresAt"`
UsedAt *time.Time `json:"usedAt,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
PasswordResetToken backs the /auth/email-password/forgot+reset flow. Shape mirrors MagicLink but binds to a known user_id (resets always operate on an existing account), and lives in its own table to keep flows isolated.
type Permission ¶ added in v1.0.0
type Permission struct {
ID ULID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
CreatedAt time.Time `json:"createdAt"`
}
Permission is one entry in the closed permission catalog. Names are program identifiers (ASCII, no whitespace) of the shape "domain:verb" or "domain:verb:scope". Seeded permissions live in service_rbac.go.
func SeededPermissions ¶ added in v1.0.0
func SeededPermissions() []Permission
SeededPermissions returns the v1.0 canonical permission catalog. The slice is returned by value (callers may not mutate the library state).
type Provider ¶ added in v0.3.0
type Provider interface {
// Name returns the stable registry key, e.g. "github". Routes mount as
// /auth/providers/{name}/start and /auth/providers/{name}/callback, and
// it lands in oauth_accounts.provider so it must be URL-safe and lower
// case.
Name() string
// AuthURL builds the absolute authorization URL the user agent will be
// redirected to. state and codeChallenge are generated by the caller;
// the implementation only assembles the query string.
AuthURL(state, codeChallenge, redirectURI string, scopes []string) string
// ExchangeCode trades an authorization code (and PKCE verifier) for a
// ProviderToken. Implementations should set a request timeout via ctx;
// the caller will not.
ExchangeCode(ctx context.Context, code, codeVerifier, redirectURI string) (*ProviderToken, error)
// UserInfo returns the canonical user profile for the supplied token.
// Implementations are responsible for picking the best email (verified +
// primary if the provider distinguishes them) and reflecting that in
// ProviderUser.EmailVerified.
UserInfo(ctx context.Context, token *ProviderToken) (*ProviderUser, error)
}
Provider is the contract every OAuth 2.0 / OIDC provider implements. Each concrete provider lives in its own sub-package under provider/<name>/ so consumers can pick what to import (avoids dragging in HTTP clients for providers they will never use).
All methods are called from the OAuth start/callback service flow:
- AuthURL builds the redirect target for /auth/providers/{name}/start.
- ExchangeCode swaps the authorization code (plus the PKCE verifier stored at /start time) for a *ProviderToken.
- UserInfo turns that token into a normalized *ProviderUser used by the find-or-create logic.
Implementations should be safe to share across goroutines; the service only holds one instance per provider name for the process lifetime.
type ProviderToken ¶ added in v0.3.0
type ProviderToken struct {
AccessToken string
RefreshToken string
ExpiresAt time.Time
Scope string
TokenType string
}
ProviderToken is the normalized shape of an OAuth token exchange response. Providers vary in which fields they populate (e.g. GitHub typically omits RefreshToken and ExpiresAt for "no-expiry" tokens). Storage encrypts the access/refresh tokens at rest via crypto.Encrypt.
type ProviderUser ¶ added in v0.3.0
ProviderUser is the normalized shape of a provider's userinfo response. ID is the provider-stable user identifier (e.g. GitHub numeric id as a string) and is what oauth_accounts.provider_user_id stores. Email may be empty when the user denied the email scope or has no public email on the provider; EmailVerified is true only when the provider attests to it.
type RBACConfig ¶ added in v1.0.0
type RBACConfig struct {
// Permissions extends the seeded catalog. Names are deduped by case-
// sensitive equality with the seeded list. New returns an error if any
// supplied name contains whitespace or non-ASCII (permission names are
// program identifiers, not user input).
Permissions []Permission
// DefaultRoles seeds every new organization. When empty the three
// default roles ("owner", "admin", "member") are used. Reserved names
// ("owner", "admin", "member") must remain present when consumers
// override; New returns an error otherwise.
DefaultRoles []RoleSeed
}
RBACConfig configures organization-scoped roles and permissions. The zero value is valid and accepts the seeded permission list plus the seeded "owner", "admin", "member" roles.
type RecoveryCode ¶ added in v0.5.0
type RecoveryCode struct {
ID ULID `json:"id"`
UserID ULID `json:"userId"`
CodeHash []byte `json:"-"`
UsedAt *time.Time `json:"usedAt,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
RecoveryCode is one of N single-use codes generated at TOTP enrollment. CodeHash carries the 16-byte salt prefix followed by sha256(salt || code), produced by crypto.HashRecoveryCode. UsedAt is set the first time a user consumes the code; the same hash cannot be replayed.
type Role ¶ added in v1.0.0
type Role struct {
ID ULID `json:"id"`
OrganizationID *ULID `json:"organizationId,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
Permissions []string `json:"permissions"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
Role binds a permission set to an organization (or to the global namespace when OrganizationID is nil, i.e. system roles such as super_admin). Permissions is the hydrated permission-name slice and is not persisted on the Role row itself; storage adapters fill it on read via PermissionsByRole.
type RoleSeed ¶ added in v1.0.0
RoleSeed describes one organization-scoped role created at SeedOrganizationRoles time. Permissions are permission names; unknown names cause New to return a validation error so typos surface at startup instead of on the first permission check.
func DefaultRoleSeeds ¶ added in v1.0.0
func DefaultRoleSeeds() []RoleSeed
DefaultRoleSeeds returns the three default organization roles seeded into every new organization. Consumers may extend with additional roles via Config.RBAC.DefaultRoles; the three reserved names ("owner", "admin", "member") must always remain present.
type SAMLAttributeMap ¶ added in v0.7.0
type SAMLAttributeMap struct {
Email string `json:"email"`
Name string `json:"name,omitempty"`
GivenName string `json:"givenName,omitempty"`
FamilyName string `json:"familyName,omitempty"`
Groups string `json:"groups,omitempty"`
Extra map[string]string `json:"extra,omitempty"`
}
SAMLAttributeMap projects SAML attribute names to canonical user fields. Stored as jsonb in saml_connections.attribute_map.
func DefaultSAMLAttributeMap ¶ added in v0.7.0
func DefaultSAMLAttributeMap() SAMLAttributeMap
DefaultSAMLAttributeMap returns the WS-Federation claim URIs that Microsoft, Okta, and OneLogin emit by default. A per-connection map can override any of these by writing a non-empty string for the field.
type SAMLConfig ¶ added in v0.7.0
type SAMLConfig struct {
// SPCertificatePEM and SPPrivateKeyPEM are PEM-encoded; they sign every
// outbound AuthnRequest and identify the SP in metadata XML. New returns
// an error if either is missing or unparseable.
SPCertificatePEM []byte
SPPrivateKeyPEM []byte
// AuthnRequestTTL caps how long an outstanding SP-initiated AuthnRequest
// ID is tracked for replay protection. Defaults to 10 minutes.
AuthnRequestTTL time.Duration
// ClockSkew accepted on Conditions.NotBefore / NotOnOrAfter. Defaults to
// 30 seconds (matches crewjam default). Some IdPs (Okta on slow clocks)
// need 60s.
ClockSkew time.Duration
}
SAMLConfig wires the Service Provider keypair and base behavior. Per-IdP configuration lives in saml_connections rows, not here.
type SAMLConnection ¶ added in v0.7.0
type SAMLConnection struct {
ID ULID `json:"id"`
OrganizationID ULID `json:"organizationId"`
IdPEntityID string `json:"idpEntityId"`
IdPSSOURL string `json:"idpSsoUrl"`
IdPX509Cert string `json:"idpX509Cert"`
SPEntityID string `json:"spEntityId"`
SPACSURL string `json:"spAcsUrl"`
AttributeMap SAMLAttributeMap `json:"attributeMap"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
SAMLConnection is one IdP binding for one organization. An organization can hold multiple connections (e.g. two distinct Okta tenants for subsidiaries), each routed by id in the URL.
type SAMLConnectionInput ¶ added in v0.7.0
type SAMLConnectionInput struct {
OrganizationID ULID
IdPEntityID string
IdPSSOURL string
IdPX509Cert string
SPEntityID string
SPACSURL string
AttributeMap SAMLAttributeMap
}
SAMLConnectionInput is the consumer-facing payload for create / update.
type SAMLIdentity ¶ added in v0.7.0
type SAMLIdentity struct {
ID ULID `json:"id"`
ConnectionID ULID `json:"connectionId"`
UserID ULID `json:"userId"`
NameID string `json:"nameId"`
NameIDFormat string `json:"nameIdFormat"`
LastLoginAt *time.Time `json:"lastLoginAt,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
SAMLIdentity links a successful SAML login to a user. Lookup key is (connection_id, name_id); name_id is whatever Subject.NameID.Value the IdP emitted, opaque and stable across sessions for most IdPs.
type SCIMConfig ¶ added in v0.7.0
type SCIMConfig struct {
// RequireHTTPS rejects requests whose r.TLS is nil and whose
// X-Forwarded-Proto header is not "https". Default true. Set false only
// when SCIM is fronted by a TLS-terminating proxy that strips the header
// (in which case the proxy is responsible for TLS).
RequireHTTPS bool
// MaxPageSize caps the count parameter on list endpoints. Defaults to
// 200. RFC 7644 section 3.4.2 lets the server enforce a maximum.
MaxPageSize int
}
SCIMConfig wires the SCIM 2.0 endpoint behavior.
type SCIMGroupFilter ¶ added in v0.7.0
SCIMGroupFilter is the equality-only filter accepted on /scim/v2/Groups.
type SCIMToken ¶ added in v0.7.0
type SCIMToken struct {
ID ULID `json:"id"`
OrganizationID ULID `json:"organizationId"`
Name string `json:"name"`
TokenHash []byte `json:"-"`
CreatedAt time.Time `json:"createdAt"`
LastUsedAt *time.Time `json:"lastUsedAt,omitempty"`
RevokedAt *time.Time `json:"revokedAt,omitempty"`
}
SCIMToken is a hashed bearer token bound to one organization. Plaintext is only ever returned from CreateSCIMToken; subsequent reads only ever see the hash. Hash is sha256(token); rationale documented in the v0.7 spec.
type SCIMUserFilter ¶ added in v0.7.0
SCIMUserFilter is the equality-only filter accepted on /scim/v2/Users. Anything outside this whitelist returns 400 invalidFilter.
type Session ¶
type Session struct {
ID ULID `json:"id"`
UserID ULID `json:"userId"`
TokenHash []byte `json:"-"` // never serialize raw hash
UserAgent string `json:"userAgent"`
IP string `json:"ip"`
CreatedAt time.Time `json:"createdAt"`
ExpiresAt time.Time `json:"expiresAt"`
RevokedAt *time.Time `json:"revokedAt,omitempty"`
// AuthLevel is "full" or "pending_2fa" (v0.5). Sessions issued by code
// paths predating v0.5 default to "full" so existing rows keep working.
AuthLevel string `json:"authLevel,omitempty"`
// ActiveOrganizationID (v0.7) scopes a session to one organization for
// the duration of the session. Nil in single-tenant deployments and on
// any session that has not picked an org yet.
ActiveOrganizationID *ULID `json:"activeOrganizationId,omitempty"`
}
func SessionFromContext ¶
SessionFromContext returns the Session attached by Authn middleware, if any. Returns false when the request is anonymous.
type SigninStep ¶ added in v0.5.0
type SigninStep string
SigninStep is the v0.5 indicator returned alongside the session token by signinWithPassword: "full" when the cookie is immediately usable, "totp_required" when the cookie is a pending_2fa session that can only hit /auth/totp/verify and /auth/totp/recovery.
const ( SigninStepFull SigninStep = "full" SigninStepTOTPRequired SigninStep = "totp_required" )
type Stats ¶ added in v1.0.0
type Stats struct {
AuditEmitted uint64 `json:"auditEmitted"`
AuditWritten uint64 `json:"auditWritten"`
AuditDropped uint64 `json:"auditDropped"`
AuditFailed uint64 `json:"auditFailed"`
}
Stats holds runtime counters useful for ops dashboards and tests. Counters are monotonically non-decreasing; reads are atomic snapshots. AuditFailed counts batches whose INSERT returned an error; the writer goroutine logs the error and does not retry (the v1.0 tradeoff documented in docs/2026-06-20-theauth-go-v1.0-design.md section 4.4).
type Storage ¶
type Storage interface {
// Users
CreateUser(ctx context.Context, u User) (User, error)
UserByEmail(ctx context.Context, email string) (*User, error)
UserByID(ctx context.Context, id ULID) (*User, error)
MarkEmailVerified(ctx context.Context, userID ULID) error
// Sessions
CreateSession(ctx context.Context, s Session) (Session, error)
SessionByTokenHash(ctx context.Context, hash []byte) (*Session, error)
RevokeSession(ctx context.Context, id ULID) error
RevokeUserSessions(ctx context.Context, userID ULID) error
// Magic links
CreateMagicLink(ctx context.Context, ml MagicLink) error
ConsumeMagicLink(ctx context.Context, tokenHash []byte) (*MagicLink, error)
// Email + password (v0.2)
SetUserPassword(ctx context.Context, userID ULID, passwordHash string) error
// UserByEmailWithPassword fetches a user along with their stored PHC hash.
// passwordHash is "" if the account exists but has never set a password
// (e.g. magic-link-only signup). Callers should treat empty hash as
// "no password credential available" and surface invalid_credentials.
UserByEmailWithPassword(ctx context.Context, email string) (user *User, passwordHash string, err error)
CreatePasswordResetToken(ctx context.Context, t PasswordResetToken) error
ConsumePasswordResetToken(ctx context.Context, tokenHash []byte) (*PasswordResetToken, error)
// OAuth accounts (v0.3)
// UpsertOAuthAccount inserts or updates the row keyed by
// (provider, provider_user_id). Returns the resulting row so callers
// can use the assigned ID and timestamps. Implementations must encrypt
// any token bytes before they reach storage; this layer only persists
// what it is given.
UpsertOAuthAccount(ctx context.Context, a OAuthAccount) (OAuthAccount, error)
// OAuthAccountByProviderUserID looks up the row for a provider/user
// pair. Returns ErrStorageNotFound when no row exists.
OAuthAccountByProviderUserID(ctx context.Context, provider, providerUserID string) (*OAuthAccount, error)
// Sessions: v0.5 step-up additions
// CreateSessionWithAuthLevel mints a session whose AuthLevel column is
// set to the supplied value (typically AuthLevelPending2FA). Mirrors
// CreateSession otherwise. CreateSession itself continues to default
// to AuthLevelFull at the DDL layer so older callers see no change.
CreateSessionWithAuthLevel(ctx context.Context, s Session) (Session, error)
// UpdateSessionAuthLevel rewrites a single session's AuthLevel column.
// Used by /auth/totp/verify to promote a pending session to full.
UpdateSessionAuthLevel(ctx context.Context, id ULID, level string) error
// WebAuthn (v0.5)
InsertWebAuthnCredential(ctx context.Context, c WebAuthnCredential) (WebAuthnCredential, error)
WebAuthnCredentialsByUserID(ctx context.Context, userID ULID) ([]WebAuthnCredential, error)
// WebAuthnCredentialByCredentialID returns the row keyed by the raw
// authenticator credential ID, or ErrStorageNotFound when missing.
WebAuthnCredentialByCredentialID(ctx context.Context, credentialID []byte) (*WebAuthnCredential, error)
// UpdateWebAuthnSignCount atomically writes a strictly greater sign
// count and bumps last_used_at. Returns ErrReplayDetected when the
// new count is not strictly greater than the stored value (the
// canonical replay signal per WebAuthn L2 / L3). Returns
// ErrStorageNotFound when the credential does not exist.
UpdateWebAuthnSignCount(ctx context.Context, credentialID []byte, newCount uint32, usedAt time.Time) error
// DeleteWebAuthnCredential removes a credential by ID, scoped to the
// owning user. Returns ErrStorageNotFound when the row does not exist
// or does not belong to the caller (no leak on cross-user lookup).
DeleteWebAuthnCredential(ctx context.Context, id ULID, userID ULID) error
// TOTP (v0.5)
// UpsertPendingTOTPSecret writes an encrypted secret with
// confirmed_at = NULL. Replaces any prior unconfirmed secret for the
// same user; preserves a confirmed one untouched (re-enrollment
// requires DeleteTOTPSecret first).
UpsertPendingTOTPSecret(ctx context.Context, s TOTPSecret) error
// ConfirmTOTPSecret sets confirmed_at on the user's pending secret.
// Returns ErrStorageNotFound when no pending row exists.
ConfirmTOTPSecret(ctx context.Context, userID ULID, at time.Time) error
TOTPSecretByUserID(ctx context.Context, userID ULID) (*TOTPSecret, error)
DeleteTOTPSecret(ctx context.Context, userID ULID) error
InsertRecoveryCodes(ctx context.Context, codes []RecoveryCode) error
// ConsumeRecoveryCode walks the user's unused codes, locates the one
// whose hash matches via crypto.VerifyRecoveryCode, and marks it used
// atomically. Returns ErrStorageNotFound when no matching unused code
// exists (covers wrong code, reused code, and cross-user mismatch).
ConsumeRecoveryCode(ctx context.Context, userID ULID, code string, at time.Time) error
// Organizations + membership
InsertOrganization(ctx context.Context, o Organization) (Organization, error)
OrganizationByID(ctx context.Context, id ULID) (*Organization, error)
OrganizationBySlug(ctx context.Context, slug string) (*Organization, error)
UpdateOrganization(ctx context.Context, o Organization) error
DeleteOrganization(ctx context.Context, id ULID) error
UpsertOrganizationMember(ctx context.Context, m OrganizationMember) error
DeleteOrganizationMember(ctx context.Context, orgID, userID ULID) error
OrganizationMembersByOrg(ctx context.Context, orgID ULID) ([]OrganizationMember, error)
OrganizationsByUser(ctx context.Context, userID ULID) ([]Organization, error)
OrganizationMemberRole(ctx context.Context, orgID, userID ULID) (string, error)
SetSessionActiveOrganization(ctx context.Context, sessionID ULID, orgID *ULID) error
// SAML connections and identities
InsertSAMLConnection(ctx context.Context, c SAMLConnection) (SAMLConnection, error)
UpdateSAMLConnectionRow(ctx context.Context, c SAMLConnection) error
DeleteSAMLConnection(ctx context.Context, id ULID) error
SAMLConnectionByID(ctx context.Context, id ULID) (*SAMLConnection, error)
SAMLConnectionsByOrg(ctx context.Context, orgID ULID) ([]SAMLConnection, error)
UpsertSAMLIdentity(ctx context.Context, i SAMLIdentity) (SAMLIdentity, error)
SAMLIdentityByConnectionAndNameID(ctx context.Context, connectionID ULID, nameID string) (*SAMLIdentity, error)
TouchSAMLIdentityLastLogin(ctx context.Context, id ULID, at time.Time) error
// SCIM tokens
InsertSCIMToken(ctx context.Context, t SCIMToken) (SCIMToken, error)
SCIMTokenByHash(ctx context.Context, hash []byte) (*SCIMToken, error)
SCIMTokensByOrg(ctx context.Context, orgID ULID) ([]SCIMToken, error)
RevokeSCIMTokenByID(ctx context.Context, id ULID, at time.Time) error
TouchSCIMTokenLastUsed(ctx context.Context, id ULID, at time.Time) error
// SCIM user + group lookups scoped to a single organization
ListUsersByOrganization(ctx context.Context, orgID ULID, offset, limit int, filter SCIMUserFilter) (users []User, total int, err error)
ListGroupsByOrganization(ctx context.Context, orgID ULID, offset, limit int, filter SCIMGroupFilter) (groups []Group, total int, err error)
UserByExternalIDInOrg(ctx context.Context, orgID ULID, externalID string) (*User, error)
UpdateUserSCIM(ctx context.Context, u User) error
// Groups (SCIM)
InsertGroup(ctx context.Context, g Group) (Group, error)
GroupByID(ctx context.Context, id ULID) (*Group, error)
GroupByExternalIDInOrg(ctx context.Context, orgID ULID, externalID string) (*Group, error)
UpdateGroup(ctx context.Context, g Group) error
DeleteGroup(ctx context.Context, id ULID) error
SetGroupMembers(ctx context.Context, groupID ULID, userIDs []ULID) error
AddGroupMembers(ctx context.Context, groupID ULID, userIDs []ULID) error
RemoveGroupMembers(ctx context.Context, groupID ULID, userIDs []ULID) error
GroupMembers(ctx context.Context, groupID ULID) ([]ULID, error)
// ---------- v1.0 RBAC ----------
// Permissions form a global catalog (no org scope). Insert is idempotent
// on the name unique index; duplicate names return the existing row
// rather than an error so seed runs at app start are safe.
InsertPermission(ctx context.Context, p Permission) (Permission, error)
PermissionByName(ctx context.Context, name string) (*Permission, error)
ListPermissions(ctx context.Context) ([]Permission, error)
InsertRole(ctx context.Context, r Role) (Role, error)
UpdateRoleRow(ctx context.Context, r Role) (Role, error)
DeleteRole(ctx context.Context, id ULID) error
RoleByID(ctx context.Context, id ULID) (*Role, error)
RoleByOrgAndName(ctx context.Context, orgID *ULID, name string) (*Role, error)
RolesByOrganization(ctx context.Context, orgID *ULID) ([]Role, error)
SetRolePermissions(ctx context.Context, roleID ULID, permissionIDs []ULID) error
// PermissionsByRole returns the permission-name slice for one role.
// The names are looked up by joining role_permissions to permissions.
PermissionsByRole(ctx context.Context, roleID ULID) ([]string, error)
GrantUserRole(ctx context.Context, ur UserRole) error
RevokeUserRole(ctx context.Context, userID, roleID ULID) error
RolesForUser(ctx context.Context, userID ULID, orgID *ULID) ([]Role, error)
PermissionsForUser(ctx context.Context, userID ULID, orgID *ULID) ([]string, error)
CountUsersWithPermissionInOrg(ctx context.Context, orgID ULID, perm string) (int, error)
// ---------- v1.0 audit log ----------
// InsertAuditEvents writes a batch in one round trip. Append-only;
// adapters MUST NOT expose UPDATE or DELETE for audit rows. Failure
// returns an error; the writer goroutine logs it and increments
// Stats.AuditFailed without retrying (documented tradeoff).
InsertAuditEvents(ctx context.Context, events []AuditEvent) error
QueryAuditEvents(ctx context.Context, q AuditQuery) (events []AuditEvent, nextCursor string, err error)
}
Storage is the persistence contract TheAuth depends on. Adapters live in sub-packages (storage/memory, storage/postgres). Defined here so that service code in this package can reference it without importing the storage sub-package (which would create an import cycle, because storage imports this package for the model types).
The storage package re-exports this as storage.Storage so consumers can keep importing it from the conventional location.
type TOTPConfig ¶ added in v0.5.0
type TOTPConfig struct {
// Issuer is shown in the authenticator app, e.g. "GLINR Quarter".
Issuer string
// RecoveryCodeCount defaults to 10 when zero. Each code is 10 hex chars
// (40 bits of entropy) generated via crypto/rand.
RecoveryCodeCount int
}
TOTPConfig wires the second-factor TOTP behavior. Algorithm is fixed at SHA-1 / 30s / 6 digits for compatibility with Google Authenticator, Authy, 1Password, and every other mainstream authenticator app. Skew is fixed at one period before/after (the pquerna/otp default) to absorb client clock drift.
type TOTPSecret ¶ added in v0.5.0
type TOTPSecret struct {
UserID ULID `json:"userId"`
SecretEnc []byte `json:"-"`
ConfirmedAt *time.Time `json:"confirmedAt,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
TOTPSecret stores the AES-GCM encrypted shared secret for one user. The secret is base32 plaintext only at enroll-begin and verify time; on disk it is always ciphertext (nonce prepended, courtesy of crypto.Encrypt). ConfirmedAt is NULL until the user proves possession by entering one valid code in /auth/totp/enroll/finish. Unconfirmed rows are overwritten by a subsequent /enroll/begin (no half-state can survive).
type TargetRef ¶ added in v1.0.0
TargetRef points at the resource an audit event acts on. ID is a string (not ULID) because some targets are external identifiers (e.g. SCIM externalId) rather than ULIDs.
type TheAuth ¶
type TheAuth struct {
// contains filtered or unexported fields
}
TheAuth is the public entry point, constructed once at app start and shared across handlers.
func New ¶
New validates the Config, applies defaults, and returns a ready TheAuth.
Example ¶
ExampleNew shows the minimum wiring required to construct a TheAuth instance. Storage and BaseURL are the only mandatory fields; everything else takes a documented default.
package main
import (
"fmt"
"github.com/glincker/theauth-go"
"github.com/glincker/theauth-go/storage/memory"
)
func main() {
a, err := theauth.New(theauth.Config{
Storage: memory.New(),
BaseURL: "http://localhost:8080",
SecureCookie: false,
})
if err != nil {
panic(err)
}
defer a.Close()
fmt.Println("ready")
}
Output: ready
func (*TheAuth) AddOrganizationMember ¶ added in v0.7.0
AddOrganizationMember adds (or updates the role of) a user inside an organization. Roles must be one of "owner", "admin", "member".
func (*TheAuth) AuthenticateSCIMToken ¶ added in v0.7.0
AuthenticateSCIMToken is the entry point invoked by the SCIM bearer middleware on every request. Returns the bound organization or an error. Touches last_used_at on success.
func (*TheAuth) Authn ¶
Authn looks for a session cookie, validates it, and adds the user + session to the request context. Does NOT reject anonymous requests. Pair with RequireAuth (full) or RequirePendingOrFull (TOTP verify routes only).
Example ¶
ExampleTheAuth_Authn wraps a handler with the Authn middleware, which resolves the session cookie (when present) and attaches the user to the request context without rejecting anonymous traffic.
package main
import (
"fmt"
"net/http"
"github.com/glincker/theauth-go"
"github.com/glincker/theauth-go/storage/memory"
)
func main() {
a, err := theauth.New(theauth.Config{
Storage: memory.New(),
BaseURL: "http://localhost:8080",
SecureCookie: false,
})
if err != nil {
panic(err)
}
defer a.Close()
handler := a.Authn()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if _, ok := theauth.UserFromContext(r.Context()); ok {
_, _ = w.Write([]byte("authed"))
return
}
_, _ = w.Write([]byte("anon"))
}))
_ = handler
fmt.Println("wired")
}
Output: wired
func (*TheAuth) BeginPasskeyLogin ¶ added in v0.5.0
func (a *TheAuth) BeginPasskeyLogin(_ context.Context) (*protocol.CredentialAssertion, string, error)
BeginPasskeyLogin starts a discoverable-credential login. The user is not identified up-front; the authenticator returns its userHandle inside the assertion, which we look up in FinishPasskeyLogin.
func (*TheAuth) BeginPasskeyRegistration ¶ added in v0.5.0
func (a *TheAuth) BeginPasskeyRegistration(ctx context.Context, userID ULID) (*protocol.CredentialCreation, string, error)
BeginPasskeyRegistration starts the registration ceremony for a signed-in user. Returns the upstream CredentialCreation options for the browser to pass to navigator.credentials.create plus the opaque challenge token the handler binds in a cookie.
func (*TheAuth) BeginSAMLLogin ¶ added in v0.7.0
func (a *TheAuth) BeginSAMLLogin(ctx context.Context, connectionID ULID, relayState string) (string, error)
BeginSAMLLogin returns the redirect URL for an SP-initiated SSO and records the AuthnRequest ID in the in-memory tracker for replay protection.
func (*TheAuth) BeginTOTPEnrollment ¶ added in v0.5.0
func (a *TheAuth) BeginTOTPEnrollment(ctx context.Context, userID ULID, accountName string) (EnrollTOTPResult, error)
BeginTOTPEnrollment generates a fresh shared secret and otpauth URL, returns them to the caller (the secret is displayed exactly once), and stashes the plaintext in the in-memory pending map keyed by the returned EnrollmentID. The DB row is written immediately with confirmed_at = NULL so a server restart does not strand a half-enrolled account, but verify is gated on the in-memory secret to keep the AES-GCM Decrypt cost off the verify hot path.
func (*TheAuth) Close ¶ added in v0.3.0
func (a *TheAuth) Close()
Close releases background resources started by New: the OAuth state GC loop (v0.3), the WebAuthn challenge / TOTP enrollment GC loops (v0.5), the SAML AuthnRequest GC loop (v0.7), and the audit writer goroutine (v1.0). Audit drain waits up to Config.Audit.DrainTimeout (default 5 seconds) for the writer to flush. Safe to call multiple times.
func (*TheAuth) ConsumeRecoveryCode ¶ added in v0.5.0
func (a *TheAuth) ConsumeRecoveryCode(ctx context.Context, pendingSessionToken, code string) (string, Session, error)
ConsumeRecoveryCode upgrades a pending session by consuming one unused recovery code. Five failures revoke the session just like VerifyTOTP.
func (*TheAuth) CreateOrganization ¶ added in v0.7.0
func (a *TheAuth) CreateOrganization(ctx context.Context, name, slug string, ownerUserID ULID) (Organization, error)
CreateOrganization writes a new org row and adds the supplied user as its owner. Slug is lowercased and validated against the slug rules in validateSlug; the storage layer enforces uniqueness.
func (*TheAuth) CreateRole ¶ added in v1.0.0
func (a *TheAuth) CreateRole(ctx context.Context, orgID ULID, name, description string, perms []string) (Role, error)
CreateRole adds a new role to orgID with the listed permissions. Names are unique per org; returns the storage error (typically a unique-violation translation) on collision. Emits "role.created".
func (*TheAuth) CreateSAMLConnection ¶ added in v0.7.0
func (a *TheAuth) CreateSAMLConnection(ctx context.Context, in SAMLConnectionInput) (SAMLConnection, error)
CreateSAMLConnection inserts a fresh saml_connections row. AttributeMap is normalised: empty fields fall back to the WS-Federation defaults.
func (*TheAuth) CreateSCIMToken ¶ added in v0.7.0
func (a *TheAuth) CreateSCIMToken(ctx context.Context, orgID ULID, name string) (string, SCIMToken, error)
CreateSCIMToken mints a fresh 256-bit token, stores its sha256 hash, and returns the plaintext to the caller. The plaintext is the only point at which it leaves the library; subsequent reads only ever see the hash.
func (*TheAuth) DeleteRole ¶ added in v1.0.0
DeleteRole removes a role. Returns ErrRoleInUse when the role is the sole grantor of users:admin in its organization (lockout protection). Emits "role.deleted".
func (*TheAuth) DeleteSAMLConnection ¶ added in v0.7.0
DeleteSAMLConnection removes the connection + cascades its identities.
func (*TheAuth) EmitAudit ¶ added in v1.0.0
func (a *TheAuth) EmitAudit(ctx context.Context, action string, target TargetRef, metadata map[string]any)
EmitAudit enqueues an audit event for asynchronous batched write. Non- blocking: when the writer channel is full the event is dropped and Stats.AuditDropped increments by 1. The actor user / session id and the request IP / user agent are pulled from ctx when present (Authn populates them); callers may also pass a context with those values explicitly attached via WithAuditMetadata.
EmitAudit may be called from any goroutine. The metadata map is mutated in place by the redactor; callers must not retain references after the call (or, equivalently, must defensively copy before passing in).
When Config.Audit is nil EmitAudit is a silent no-op. When Close has been called EmitAudit is also a silent no-op (the writer goroutine has drained and the channel is closed; pushing would panic).
func (*TheAuth) FinishPasskeyLogin ¶ added in v0.5.0
func (a *TheAuth) FinishPasskeyLogin(ctx context.Context, challengeToken string, body io.Reader, ua, ip string) (string, Session, error)
FinishPasskeyLogin completes a discoverable login. The handler hands us the browser's JSON, the challenge token, and the request metadata. On success we issue a full session directly: per NIST SP 800-63B rev 4, a passkey login is a single strong factor that satisfies AAL2 by itself, so we do not gate it on a second TOTP step (single-factor-strong model). See https://pages.nist.gov/800-63-4/sp800-63b.html .
func (*TheAuth) FinishPasskeyRegistration ¶ added in v0.5.0
func (a *TheAuth) FinishPasskeyRegistration(ctx context.Context, userID ULID, challengeToken, name string, body io.Reader) (WebAuthnCredential, error)
FinishPasskeyRegistration completes the registration ceremony. The body is the raw JSON the browser POSTs from navigator.credentials.create. On success the new row is inserted and returned to the caller for display (nickname, AAGUID, transports). Single-use: the challenge entry is removed before the library is invoked so a failed verify burns it.
func (*TheAuth) FinishSAMLLogin ¶ added in v0.7.0
func (a *TheAuth) FinishSAMLLogin(ctx context.Context, connectionID ULID, samlResponseB64 string, ua, ip string) (string, Session, error)
FinishSAMLLogin validates an inbound SAMLResponse, runs find-or-create, issues a session, and returns its token. ua + ip annotate the session for audit.
func (*TheAuth) FinishTOTPEnrollment ¶ added in v0.5.0
func (a *TheAuth) FinishTOTPEnrollment(ctx context.Context, userID ULID, enrollmentID, code string) ([]string, error)
FinishTOTPEnrollment validates one code against the pending secret, confirms the row, generates RecoveryCodeCount single-use recovery codes, and returns them to the caller (the only time the plaintext codes are visible). Idempotency: a second /finish call with the same enrollmentID fails with ErrAlreadyEnrolled.
func (*TheAuth) GrantRole ¶ added in v1.0.0
GrantRole assigns roleID to the target user. The actor must already have permission to grant; callers should run the RequirePermission middleware upstream of this method. Idempotent on (user_id, role_id). Emits action "role.granted" with role_id and role_name in metadata.
func (*TheAuth) HasPermission ¶ added in v1.0.0
func (a *TheAuth) HasPermission(ctx context.Context, userID ULID, orgID *ULID, perm string) (bool, error)
HasPermission returns true when the user holds the named permission in the given organization, OR when the user holds the system super_admin role (which bypasses every check). orgID may be nil to ask only about system permissions.
func (*TheAuth) IssuePending2FA ¶ added in v0.5.0
func (a *TheAuth) IssuePending2FA(ctx context.Context, userID ULID, ua, ip string) (string, Session, error)
IssuePending2FA mints a short-lived session whose AuthLevel is AuthLevelPending2FA. The cookie name is identical to the full session cookie so the browser sends it everywhere; middleware/RequireAuth rejects it except on the two TOTP verify endpoints (handled by the requirePendingOrFull tag in handlers_totp.go).
func (*TheAuth) ListOrganizationMembers ¶ added in v0.7.0
func (a *TheAuth) ListOrganizationMembers(ctx context.Context, orgID ULID) ([]OrganizationMember, error)
ListOrganizationMembers returns every member of the supplied organization.
func (*TheAuth) ListSAMLConnections ¶ added in v0.7.0
ListSAMLConnections returns every connection for one organization.
func (*TheAuth) ListSCIMTokens ¶ added in v0.7.0
ListSCIMTokens returns every token (revoked or not) for the supplied org.
func (*TheAuth) ListUserOrganizations ¶ added in v0.7.0
ListUserOrganizations returns every organization the user is a member of.
func (*TheAuth) Mount ¶
Mount wires TheAuth's HTTP routes onto the supplied chi router under /auth. Routes:
POST /auth/magic-link request a magic link GET /auth/magic-link/verify consume a magic link, set session cookie POST /auth/email-password/signup create user with email + password (rate-limited) POST /auth/email-password/signin sign in with email + password (rate-limited) POST /auth/email-password/forgot request a password reset link (rate-limited) POST /auth/email-password/reset consume a reset token + set new password (rate-limited) GET /auth/me return the authenticated user (RequireAuth) DELETE /auth/sessions/current revoke the current session (RequireAuth)
Default rate limits: 5/min per source IP on every credential endpoint, plus 3/min per email on signin + forgot (most attack-surface). All limits are in-memory + per-process; replace at the LB layer for multi-instance deploys.
Example ¶
ExampleTheAuth_Mount mounts the standard authentication routes onto a chi router. The routes appear under /auth (see Mount godoc for the complete list).
package main
import (
"fmt"
"github.com/glincker/theauth-go"
"github.com/glincker/theauth-go/storage/memory"
"github.com/go-chi/chi/v5"
)
func main() {
a, err := theauth.New(theauth.Config{
Storage: memory.New(),
BaseURL: "http://localhost:8080",
SecureCookie: false,
})
if err != nil {
panic(err)
}
defer a.Close()
r := chi.NewRouter()
a.Mount(r)
fmt.Println("mounted")
}
Output: mounted
func (*TheAuth) OrganizationByID ¶ added in v0.7.0
OrganizationByID looks up an organization by ULID.
func (*TheAuth) OrganizationBySlug ¶ added in v0.7.0
OrganizationBySlug looks up an organization by URL-safe slug.
func (*TheAuth) PermissionsForUser ¶ added in v1.0.0
func (a *TheAuth) PermissionsForUser(ctx context.Context, userID ULID, orgID *ULID) ([]string, error)
PermissionsForUser returns the user's permission set scoped to orgID (nil orgID returns only system-role permissions, i.e. super_admin). Sorted alphabetically for deterministic output.
func (*TheAuth) QueryAudit ¶ added in v1.0.0
func (a *TheAuth) QueryAudit(ctx context.Context, q AuditQuery) ([]AuditEvent, string, error)
QueryAudit returns up to q.Limit events plus an opaque cursor for the next page. Caller is responsible for permission-checking; this method does no auth.
func (*TheAuth) RateLimitByEmail ¶ added in v0.2.0
RateLimitByEmail returns a middleware that limits requests per email body field. Reads the JSON body up to 16 KiB, extracts "email", restores the body so downstream handlers can re-read it. Requests without a parseable email are passed through unlimited (handler will reject them on its own).
func (*TheAuth) RateLimitByIP ¶ added in v0.2.0
RateLimitByIP returns a middleware that limits requests per source IP to perMinute per minute. Use on credential endpoints (signin, signup, forgot, reset). The limiter lives on the returned handler — multiple calls produce independent buckets, so wire it once per route group at startup.
func (*TheAuth) RemoveOrganizationMember ¶ added in v0.7.0
RemoveOrganizationMember removes a user from an organization. Refuses to remove the last remaining owner (returns ErrLastOwner).
func (*TheAuth) RequireAuth ¶
RequireAuth runs Authn, then rejects requests that don't have a FULL session. Pending_2fa sessions are treated as unauthorized here. The two TOTP verify routes opt in to RequirePendingOrFull instead.
func (*TheAuth) RequirePendingOrFull ¶ added in v0.5.0
RequirePendingOrFull (v0.5) accepts both pending_2fa and full sessions. Used exclusively by /auth/totp/verify and /auth/totp/recovery so a user mid-step-up can complete the second factor.
func (*TheAuth) RequirePermission ¶ added in v1.0.0
RequirePermission (v1.0) returns a middleware that enforces the caller holds every named permission inside session.active_organization_id. A session without active_organization_id is 403 rbac.no_active_org. A user with the system super_admin role bypasses the check entirely. The permission lookup hits storage once per request; subsequent middleware in the same chain hit the per-request cache attached to ctx.
When Config.RBAC is nil the middleware short-circuits to 500: an admin trying to wire RequirePermission without enabling RBAC is a programming error worth surfacing loudly.
func (*TheAuth) RevokeRole ¶ added in v1.0.0
RevokeRole removes roleID from the target user. Emits "role.revoked".
func (*TheAuth) RevokeSCIMToken ¶ added in v0.7.0
RevokeSCIMToken marks the named token as revoked. Revoked tokens still resolve via SCIMTokenByHash but the middleware refuses them.
func (*TheAuth) SAMLConnectionByID ¶ added in v0.7.0
SAMLConnectionByID looks up one connection.
func (*TheAuth) SAMLMetadataXML ¶ added in v0.7.0
SAMLMetadataXML serialises the per-connection SP metadata as XML, ready to hand to an IdP admin.
func (*TheAuth) SeedOrganizationRoles ¶ added in v1.0.0
SeedOrganizationRoles creates the three default roles (or whatever the consumer configured) for one organization. Idempotent on (organization_id, name); existing roles keep their IDs and have their permission set reconciled against the seed.
Called automatically from handlers_organizations.go::CreateOrganization; safe to call again manually (e.g. after extending the seed list).
func (*TheAuth) SeedPermissions ¶ added in v1.0.0
func (a *TheAuth) SeedPermissions(ctx context.Context) ([]Permission, error)
SeedPermissions ensures every seeded + consumer-extended permission row exists in storage. Idempotent on the permissions.name unique index. Returns the canonical permission rows (with their persisted IDs) so the caller can reuse them for role assignments.
SeedPermissions runs lazily on first SeedOrganizationRoles / CreateRole invocation; consumers wanting eager seeding at app start may call it directly from their bootstrap.
func (*TheAuth) SetActiveOrganization ¶ added in v0.7.0
SetActiveOrganization sets (or clears, when orgID is nil) the active organization on a session. The caller is responsible for verifying that the session's user is a member of orgID before calling.
func (*TheAuth) Start ¶ added in v1.0.0
Start spawns the audit writer goroutine when Config.Audit is non-nil and the writer is not already running. Idempotent; safe to call multiple times. New calls Start automatically so existing callers that never invoked Start keep working.
func (*TheAuth) UpdateRole ¶ added in v1.0.0
func (a *TheAuth) UpdateRole(ctx context.Context, roleID ULID, name, description string, perms []string) (Role, error)
UpdateRole rewrites name / description / permissions on an existing role. nil-valued fields are not touched (caller passes the current value). Emits "role.updated".
func (*TheAuth) UpdateSAMLConnection ¶ added in v0.7.0
func (a *TheAuth) UpdateSAMLConnection(ctx context.Context, id ULID, in SAMLConnectionInput) (SAMLConnection, error)
UpdateSAMLConnection rewrites an existing connection in place.
func (*TheAuth) VerifyTOTP ¶ added in v0.5.0
func (a *TheAuth) VerifyTOTP(ctx context.Context, pendingSessionToken, code string) (string, Session, error)
VerifyTOTP consumes a 6-digit code against the user's confirmed secret, upgrades their pending session to full, and returns the (same) token with the upgraded session row. On five consecutive failures the pending session is revoked.
type TheAuthError ¶ added in v0.2.0
TheAuthError is the structured error type returned by v0.2+ service methods. Callers can errors.As-extract it and switch on Code for stable handling, or errors.Is-check against a value of the same Code for shorter paths.
func NewError ¶ added in v0.2.0
func NewError(code, message string, inner error) *TheAuthError
NewError constructs a TheAuthError with the supplied code, message, and optional wrapped cause.
func (*TheAuthError) Error ¶ added in v0.2.0
func (e *TheAuthError) Error() string
func (*TheAuthError) Is ¶ added in v0.2.0
func (e *TheAuthError) Is(target error) bool
Is reports whether target is a *TheAuthError with the same Code, OR is the Inner cause. This lets callers do errors.Is(err, &TheAuthError{Code: ...}) for code-only comparisons without caring about the message or inner cause.
func (*TheAuthError) Unwrap ¶ added in v0.2.0
func (e *TheAuthError) Unwrap() error
Unwrap exposes the inner error for errors.Is/errors.As traversal.
type User ¶
type User struct {
ID ULID `json:"id"`
Email string `json:"email"`
EmailVerifiedAt *time.Time `json:"emailVerifiedAt,omitempty"`
Name string `json:"name"`
AvatarURL string `json:"avatarUrl"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
// ExternalID (v0.7 SCIM) stores the SCIM client's stable identifier so
// upsert by externalId works. Empty for users not created via SCIM.
ExternalID string `json:"externalId,omitempty"`
// GivenName / FamilyName / DisplayName (v0.7 SCIM) capture the structured
// name attributes SCIM clients provision; they are best-effort projections
// alongside Name and may be empty for users created via other flows.
GivenName string `json:"givenName,omitempty"`
FamilyName string `json:"familyName,omitempty"`
DisplayName string `json:"displayName,omitempty"`
}
type UserRole ¶ added in v1.0.0
type UserRole struct {
UserID ULID `json:"userId"`
RoleID ULID `json:"roleId"`
GrantedAt time.Time `json:"grantedAt"`
GrantedBy *ULID `json:"grantedBy,omitempty"`
}
UserRole records a grant of one role to one user. GrantedBy is the actor that issued the grant; nil indicates a system grant (e.g. seeded by SeedOrganizationRoles itself).
type WebAuthnConfig ¶ added in v0.5.0
type WebAuthnConfig struct {
// RPID is the Relying Party Server ID. e.g. "glinr.com" (eTLD+1 of the
// origin, no scheme, no port). Required.
RPID string
// RPDisplayName is shown by browsers and authenticators. Required.
RPDisplayName string
// RPOrigins is the fully qualified origins permitted to invoke the
// API, e.g. ["https://glinr.com"]. At least one required.
RPOrigins []string
// ChallengeTTL caps how long the in-memory challenge session is valid.
// Defaults to 5 minutes; challenges are single-use regardless.
ChallengeTTL time.Duration
}
WebAuthnConfig wires the Relying Party identity. Field names mirror the upstream go-webauthn/webauthn.Config so consumers reading either set of docs see the same vocabulary.
type WebAuthnCredential ¶ added in v0.5.0
type WebAuthnCredential struct {
ID ULID `json:"id"`
UserID ULID `json:"userId"`
CredentialID []byte `json:"credentialId"`
PublicKey []byte `json:"-"`
SignCount uint32 `json:"signCount"`
Transports []string `json:"transports"`
AAGUID []byte `json:"aaguid"`
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"`
LastUsedAt *time.Time `json:"lastUsedAt,omitempty"`
}
WebAuthnCredential mirrors the persistent subset of webauthn.Credential. We store the COSE-encoded public key plus the metadata we own (nickname, timestamps, sign count). One row per registered authenticator; the same user can register many. CredentialID is the raw byte string the authenticator returned at registration and must be globally unique to prevent a stolen credential being re-registered against a different user.
SignCount is monotonic per credential. A login that supplies a non-greater value than the stored count is treated as a clone-attempt and refused via ErrReplayDetected (carve-out: an authenticator that never implements sign counts always returns 0, which the library handles as a per-spec exception).
Source Files
¶
- audit.go
- context.go
- doc.go
- errors.go
- handlers.go
- handlers_admin.go
- handlers_oauth.go
- handlers_organizations.go
- handlers_password.go
- handlers_saml.go
- handlers_scim.go
- handlers_totp.go
- handlers_webauthn.go
- middleware.go
- middleware_ratelimit.go
- middleware_scim.go
- models.go
- provider.go
- rbac.go
- saml.go
- saml_base64.go
- scim.go
- scim_filter.go
- scim_patch.go
- service_audit.go
- service_magiclink.go
- service_oauth.go
- service_organizations.go
- service_password.go
- service_rbac.go
- service_saml.go
- service_scim.go
- service_session.go
- service_totp.go
- service_webauthn.go
- theauth.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package admin holds shared helpers for the /admin/v1 HTTP surface: RFC 7807 problem+json marshalling and keyset cursor encoding.
|
Package admin holds shared helpers for the /admin/v1 HTTP surface: RFC 7807 problem+json marshalling and keyset cursor encoding. |
|
Package crypto contains the primitives theauth-go uses for password hashing (Argon2id), opaque session and reset tokens (crypto/rand plus SHA-256), AES-256-GCM symmetric encryption for OAuth tokens at rest, PKCE code verifier and S256 challenge generation, and salted SHA-256 recovery-code hashing.
|
Package crypto contains the primitives theauth-go uses for password hashing (Argon2id), opaque session and reset tokens (crypto/rand plus SHA-256), AES-256-GCM symmetric encryption for OAuth tokens at rest, PKCE code verifier and S256 challenge generation, and salted SHA-256 recovery-code hashing. |
|
Package email defines the Sender interface theauth-go uses to deliver magic-link and password-reset messages, plus a Noop implementation suitable for local development and tests.
|
Package email defines the Sender interface theauth-go uses to deliver magic-link and password-reset messages, plus a Noop implementation suitable for local development and tests. |
|
examples
|
|
|
totp-stepup
command
totp-stepup is a runnable demo of the v0.5 password + TOTP step-up flow.
|
totp-stepup is a runnable demo of the v0.5 password + TOTP step-up flow. |
|
webauthn-passkey
command
webauthn-passkey is a runnable demo of v0.5 passkey registration + login.
|
webauthn-passkey is a runnable demo of v0.5 passkey registration + login. |
|
internal
|
|
|
samltest
Package samltest provides an in-process SAML IdP for end-to-end testing of the SP integration.
|
Package samltest provides an in-process SAML IdP for end-to-end testing of the SP integration. |
|
wavt
Package wavt is the WebAuthn virtual test helper used to drive the v0.5 passkey ceremony tests.
|
Package wavt is the WebAuthn virtual test helper used to drive the v0.5 passkey ceremony tests. |
|
mcpresource
module
|
|
|
Package provider is the namespace root for theauth-go's OAuth 2.0 implementations.
|
Package provider is the namespace root for theauth-go's OAuth 2.0 implementations. |
|
discord
Package discord implements theauth.Provider against Discord's OAuth 2.0 endpoints.
|
Package discord implements theauth.Provider against Discord's OAuth 2.0 endpoints. |
|
github
Package github implements theauth.Provider against GitHub's OAuth 2.0 endpoints.
|
Package github implements theauth.Provider against GitHub's OAuth 2.0 endpoints. |
|
google
Package google implements theauth.Provider against Google's OAuth 2.0 and OpenID Connect endpoints.
|
Package google implements theauth.Provider against Google's OAuth 2.0 and OpenID Connect endpoints. |
|
internal/oauthtest
Package oauthtest provides shared httptest scaffolding for the OAuth provider packages under provider/.
|
Package oauthtest provides shared httptest scaffolding for the OAuth provider packages under provider/. |
|
microsoft
Package microsoft implements theauth.Provider against Microsoft Entra ID (Azure AD) OAuth 2.0 endpoints.
|
Package microsoft implements theauth.Provider against Microsoft Entra ID (Azure AD) OAuth 2.0 endpoints. |
|
Package storage re-exports the theauth.Storage interface and the canonical ErrNotFound sentinel that adapters return on lookup misses.
|
Package storage re-exports the theauth.Storage interface and the canonical ErrNotFound sentinel that adapters return on lookup misses. |
|
memory
Package memory provides an in-process implementation of theauth.Storage backed by Go maps protected by a sync.RWMutex.
|
Package memory provides an in-process implementation of theauth.Storage backed by Go maps protected by a sync.RWMutex. |
|
postgres
Package postgres provides a production-grade implementation of theauth.Storage backed by PostgreSQL through pgx and sqlc-generated query bindings.
|
Package postgres provides a production-grade implementation of theauth.Storage backed by PostgreSQL through pgx and sqlc-generated query bindings. |