Documentation
¶
Overview ¶
Package realmid — error taxonomy (SPEC §3).
Every failure surfaced by the SDK is a *RealmError. Callers branch on Code (a stable string from the taxonomy in SPEC §3.1) and read envelope siblings (e.g. mfa_challenge_token) from Details directly, avoiding a second JSON parse.
Partner OTP primitive client (issue / view / verify) — see docs/proposals/partner-otp-primitive.md in the auth repo for design.
All three calls require a tenant-scoped user/service identity. The SDK supports both shapes:
- UserBearer: legacy / public-client mode. The user's access JWT rides as Authorization: Bearer.
- UserID: BFF mode. The SDK uses its cached platform token as bearer and forwards X-On-Behalf-Of-User: <UserID>.
Mirrors the dual-token semantics of the existing user-scoped endpoints (sessions list / revoke).
Package realmid is the Go SDK for Realm ID — covers login, refresh, MFA, verify, and the management surface (tenants, users, invitations, domains, API keys, config).
A partner application using this SDK should never need to call auth.realmid.dev directly. Construct one *Realm at startup with a realm id and API key; every operation on that handle threads the dual-token (API-key → short-lived platform-token) flow internally so the raw API key never crosses login traffic.
Usage:
realm, err := realmid.NewRealm(realmid.Config{
RealmID: "01HXYZ...",
APIKey: "rk_live_...",
})
if err != nil { ... }
claims, err := realm.Verify(ctx, accessToken, nil)
Stdlib only.
Package realmid — platform-defined custom roles (ADR-040).
Realms own a `realm_roles` catalog. `RoleOwner` and `RoleMember` are the only system roles; everything else is partner-defined per realm. Use the named constants for the load-bearing system names; everywhere else `Role` is just a string alias so partners can declare their own names (e.g. "salesman", "dispatch") and pass them through.
Package realmid — access-token revocation cache (SPEC §6.7).
Server-side, RealmID revokes refresh tokens via POST /auth/logout. Access tokens are stateless RS256 JWTs, so once minted they verify on signature + exp alone until natural expiry. This client adds partner-side defense-in-depth: on logout, the access token's jti is parked in an in-memory cache with TTL = exp - now(). Subsequent requests presenting that jti are rejected without a server round trip.
Multi-pod caveat: this cache is per-process. A logout served by pod A does not propagate to pod B; a stolen access token can still be replayed against pod B for up to its remaining TTL. v1.1 will ship a Redis-backed swap-in for cross-pod coherence.
Index ¶
- Constants
- Variables
- func IsCode(err error, code ErrorCode) bool
- func NormalizeOrigin(raw string) string
- type APIKey
- type APIKeyCreate
- type APIKeysClient
- type AdminClient
- func (c *AdminClient) ListEvents(ctx ctxpkg.Context, p ListEventsParams) (*AdminEventsResponse, error)
- func (c *AdminClient) ListPlatforms(ctx ctxpkg.Context, p ListPlatformsParams) (*AdminPlatformsResponse, error)
- func (c *AdminClient) Search(ctx ctxpkg.Context, q string, limit int) (*AdminSearchResponse, error)
- func (c *AdminClient) Stats(ctx ctxpkg.Context) (*AdminStats, error)
- type AdminEventsResponse
- type AdminPlatformsResponse
- type AdminSearchResponse
- type AdminStats
- type AuditEvent
- type AuthClient
- func (a *AuthClient) ListSessions(ctx ctxpkg.Context, req ListSessionsRequest) iter.Seq2[*SessionInfo, error]
- func (a *AuthClient) Login(ctx ctxpkg.Context, req LoginRequest) (*Session, error)
- func (a *AuthClient) Logout(ctx ctxpkg.Context, req *LogoutRequest) error
- func (a *AuthClient) MFAVerify(ctx ctxpkg.Context, req MFAVerifyRequest) (*Session, error)
- func (a *AuthClient) MFAVerifyOTP(ctx ctxpkg.Context, req MFAVerifyOTPRequest) (*Session, error)
- func (a *AuthClient) MintMFAChallenge(ctx ctxpkg.Context, req MFAChallengeRequest) (string, []string, error)
- func (a *AuthClient) OTPLogin(ctx ctxpkg.Context, req OTPLoginRequest) (*Session, error)
- func (a *AuthClient) RevokeSession(ctx ctxpkg.Context, req RevokeSessionRequest) error
- func (a *AuthClient) Token(ctx ctxpkg.Context, req TokenRequest) (*MintResult, error)
- type Claims
- type Config
- type ConfigClient
- type ConfigPatch
- type DomainClaim
- type DomainTxt
- type DomainVerifyResult
- type DomainsClient
- type ErrorCode
- type Invitation
- type InvitationCreate
- type InvitationsClient
- func (c *InvitationsClient) Create(ctx context.Context, tenantID string, body InvitationCreate) (*Invitation, error)
- func (c *InvitationsClient) Delete(ctx context.Context, tenantID, invitationID string) error
- func (c *InvitationsClient) List(ctx context.Context, tenantID string) *Paginated[Invitation]
- type IssueRequest
- type IssueResponse
- type ListEventsParams
- type ListOriginsOptions
- type ListPlatformsParams
- type ListSessionsRequest
- type LoginMethod
- type LoginRequest
- type LogoutFn
- type LogoutRequest
- type MFAChallengeRequest
- type MFAEnrollResult
- type MFAGateReason
- type MFARule
- type MFAVerifyOTPRequest
- type MFAVerifyRequest
- type MemRevocationCache
- type MiddlewareOptions
- type MintResult
- type OTPClient
- type OTPLoginRequest
- type Origin
- type OriginClaim
- type OriginsClient
- func (c *OriginsClient) Claim(ctx ctxpkg.Context, realmID, hostname string) (*OriginClaim, error)
- func (c *OriginsClient) Invalidate(realmID string)
- func (c *OriginsClient) List(_ ctxpkg.Context, opts ListOriginsOptions) (*Paginated[Origin], error)
- func (c *OriginsClient) Validate(ctx ctxpkg.Context, opts ValidateOriginOptions) (bool, error)
- func (c *OriginsClient) Verify(ctx ctxpkg.Context, realmID, hostname string) (*DomainVerifyResult, error)
- type Page
- type PageOpts
- type Paginated
- type PassthroughOptions
- type PlatformOwner
- type PlatformSummary
- type Realm
- func (r *Realm) BaseURL() string
- func (r *Realm) Do(ctx ctxpkg.Context, method, path string, body io.Reader, ...) (*http.Response, error)
- func (r *Realm) Info(ctx context.Context) (*RealmInfo, error)
- func (r *Realm) Middleware(opts MiddlewareOptions) func(http.Handler) http.Handler
- func (r *Realm) RealmID() string
- func (r *Realm) Revocation() RevocationCache
- func (r *Realm) Verify(ctx context.Context, token string, opts *VerifyOptions) (*Claims, error)
- type RealmError
- type RealmInfo
- type RevocationCache
- type RevokeSessionRequest
- type Role
- type RoleCreate
- type RoleDeleteResult
- type RoleListOpts
- type RoleListPage
- type RoleObject
- type RolePatch
- type RolesClient
- func (c *RolesClient) Create(ctx ctxpkg.Context, body RoleCreate) (*RoleObject, error)
- func (c *RolesClient) Delete(ctx ctxpkg.Context, roleID string) (*RoleDeleteResult, error)
- func (c *RolesClient) List(ctx ctxpkg.Context, opts *RoleListOpts) (*RoleListPage, error)
- func (c *RolesClient) Rename(ctx ctxpkg.Context, roleID string, to string) (*RoleObject, error)
- func (c *RolesClient) Update(ctx ctxpkg.Context, roleID string, patch RolePatch) (*RoleObject, error)
- type SearchHit
- type Session
- type SessionInfo
- type SignupMode
- type Tenant
- type TenantCreate
- type TenantDomainClaim
- type TenantDomainClaimRequest
- type TenantPatch
- type TenantRef
- type TenantsClient
- func (c *TenantsClient) ClaimDomain(ctx ctxpkg.Context, req TenantDomainClaimRequest) (*TenantDomainClaim, error)
- func (c *TenantsClient) Create(ctx context.Context, body TenantCreate) (*Tenant, error)
- func (c *TenantsClient) Delete(ctx context.Context, id string) error
- func (c *TenantsClient) Get(ctx context.Context, id string) (*Tenant, error)
- func (c *TenantsClient) List(ctx context.Context) *Paginated[Tenant]
- func (c *TenantsClient) TransferOwner(ctx context.Context, id, newOwnerUserID string) (*Tenant, error)
- func (c *TenantsClient) Update(ctx context.Context, id string, patch TenantPatch) (*Tenant, error)
- func (c *TenantsClient) UpdateConfig(ctx context.Context, id string, patch map[string]any) (*Tenant, error)
- func (c *TenantsClient) UpdateUserRole(ctx ctxpkg.Context, tenantID, userID, role string) (*UpdateUserRoleResult, error)
- func (c *TenantsClient) VerifyDomain(ctx ctxpkg.Context, platformID, tenantID, domain string) (*DomainVerifyResult, error)
- type TokenRequest
- type TokensClient
- func (t *TokensClient) Evict(jti string)
- func (t *TokensClient) GateRequest(accessToken string) error
- func (t *TokensClient) IsRevoked(accessToken string) bool
- func (t *TokensClient) Len() int
- func (t *TokensClient) MarkRevoked(accessToken string)
- func (t *TokensClient) RevokeOnLogout(logoutFn LogoutFn) func(ctx ctxpkg.Context, accessToken string, req *LogoutRequest) error
- type UpdateUserRoleResult
- type User
- type UserStatus
- type UserSummary
- type UsersClient
- func (c *UsersClient) ConfirmMFA(ctx context.Context, tenantID, userID, code string) error
- func (c *UsersClient) EnrollMFA(ctx context.Context, tenantID, userID string) (*MFAEnrollResult, error)
- func (c *UsersClient) Get(ctx context.Context, tenantID, userID string) (*User, error)
- func (c *UsersClient) List(ctx context.Context, tenantID string) *Paginated[User]
- func (c *UsersClient) ResetMFA(ctx context.Context, tenantID, userID string) error
- func (c *UsersClient) UpdateStatus(ctx context.Context, tenantID, userID string, status UserStatus) (*User, error)
- type ValidateOriginOptions
- type VerifyOptions
- type VerifyRequest
- type VerifyResponse
- type ViewOptions
- type ViewResponse
Constants ¶
const DefaultBaseURL = "https://auth.realmid.dev"
DefaultBaseURL is the canonical Realm ID issuer host.
const Version = "0.10.0"
Version is the published SDK version (semver). 0.10.0 corresponds to RealmID v0.7.0 — the two-endpoint auth surface (ADR-051). Breaking: the SDK now hits POST /auth/login {grant_type:"platform_api_key"} instead of the deleted POST /auth/platform-token, then refreshes via POST /auth/token with the refresh-token bearer. Refresh rotation is gated by the realm's `platform_refresh_rotates` config (default off, non-rotating). 0.5.0 was the platforms-namespace cut (ADR-044) and the signup_mode enum (ADR-045).
Variables ¶
var ( ErrRoleNotFound = errors.New("realmid: role not found") ErrRoleExists = errors.New("realmid: role already exists") ErrRoleInUse = errors.New("realmid: role still attached to users/invitations") ErrSystemRoleImmutable = errors.New("realmid: system role is immutable") )
Typed errors mirrored from the server taxonomy. Use errors.Is to branch on these.
var ErrTokenRevoked = errors.New("realmid: access token revoked")
ErrTokenRevoked is returned by TokensClient.GateRequest when the access token's jti is in the per-process revoked cache. Wrapped in a *RealmError so callers can unify on RealmError-style branching; errors.Is(err, ErrTokenRevoked) also works.
Functions ¶
func NormalizeOrigin ¶
NormalizeOrigin lowercases an origin and strips scheme + port + path to a bare hostname. Mirrors `domainmapping.Normalize` server-side.
Types ¶
type APIKey ¶
type APIKey struct {
ID string `json:"id"`
DisplayName string `json:"display_name,omitempty"`
Prefix string `json:"prefix,omitempty"`
// Secret is only present on creation.
Secret string `json:"secret,omitempty"`
Scopes []string `json:"scopes,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
RevokedAt string `json:"revoked_at,omitempty"`
}
APIKey is one entry returned from realm.APIKeys.* (SPEC §6.5).
type APIKeyCreate ¶
type APIKeyCreate struct {
DisplayName string `json:"display_name"`
Scopes []string `json:"scopes,omitempty"`
}
APIKeyCreate is the create payload.
type APIKeysClient ¶
type APIKeysClient struct {
// contains filtered or unexported fields
}
APIKeysClient is realm.APIKeys.
func (*APIKeysClient) Create ¶
func (c *APIKeysClient) Create(ctx context.Context, body APIKeyCreate) (*APIKey, error)
type AdminClient ¶ added in v0.10.0
type AdminClient struct {
// contains filtered or unexported fields
}
AdminClient is realm.Admin. ADR-048 / SPEC §7.5.
func (*AdminClient) ListEvents ¶ added in v0.10.0
func (c *AdminClient) ListEvents(ctx ctxpkg.Context, p ListEventsParams) (*AdminEventsResponse, error)
ListEvents wraps GET /admin/events.
func (*AdminClient) ListPlatforms ¶ added in v0.10.0
func (c *AdminClient) ListPlatforms(ctx ctxpkg.Context, p ListPlatformsParams) (*AdminPlatformsResponse, error)
ListPlatforms wraps GET /admin/platforms.
func (*AdminClient) Search ¶ added in v0.10.0
func (c *AdminClient) Search(ctx ctxpkg.Context, q string, limit int) (*AdminSearchResponse, error)
Search wraps GET /admin/search. limit ≤ 0 omits the param (server default applies).
func (*AdminClient) Stats ¶ added in v0.10.0
func (c *AdminClient) Stats(ctx ctxpkg.Context) (*AdminStats, error)
Stats wraps GET /admin/stats.
type AdminEventsResponse ¶ added in v0.10.0
type AdminEventsResponse struct {
Items []AuditEvent `json:"items"`
NextCursor *string `json:"next_cursor"`
}
AdminEventsResponse is the GET /admin/events envelope.
type AdminPlatformsResponse ¶ added in v0.10.0
type AdminPlatformsResponse struct {
Items []PlatformSummary `json:"items"`
NextCursor *string `json:"next_cursor"`
Total int `json:"total"`
}
AdminPlatformsResponse is the GET /admin/platforms envelope.
type AdminSearchResponse ¶ added in v0.10.0
type AdminSearchResponse struct {
Items []SearchHit `json:"items"`
}
AdminSearchResponse is the GET /admin/search response.
type AdminStats ¶ added in v0.10.0
type AdminStats struct {
PlatformsCount int `json:"platforms_count"`
TenantsCount int `json:"tenants_count"`
UsersCount int `json:"users_count"`
SessionsActive int `json:"sessions_active"`
Events24h int `json:"events_24h"`
}
AdminStats is the GET /admin/stats response.
type AuditEvent ¶ added in v0.10.0
type AuditEvent struct {
ID int64 `json:"id"`
OccurredAt int64 `json:"occurred_at"`
Kind string `json:"kind"`
ActorUserID string `json:"actor_user_id,omitempty"`
ActorLabel string `json:"actor_label,omitempty"`
PlatformID string `json:"platform_id,omitempty"`
TenantID string `json:"tenant_id,omitempty"`
TargetType string `json:"target_type,omitempty"`
TargetID string `json:"target_id,omitempty"`
Summary string `json:"summary,omitempty"`
}
AuditEvent is one row in AdminEventsResponse.Items.
type AuthClient ¶
type AuthClient struct {
// contains filtered or unexported fields
}
AuthClient implements realm.Auth.* per SPEC §4.
func (*AuthClient) ListSessions ¶
func (a *AuthClient) ListSessions(ctx ctxpkg.Context, req ListSessionsRequest) iter.Seq2[*SessionInfo, error]
ListSessions iterates sessions for the user named in req. Public- client realms set UserBearer; BFF realms set UserID and the SDK attaches the platform token + X-On-Behalf-Of-User (ADR-041 §7).
func (*AuthClient) Login ¶
func (a *AuthClient) Login(ctx ctxpkg.Context, req LoginRequest) (*Session, error)
Login exchanges a provider token for a realm-scoped session. On a 412 mfa_required, returns *RealmError{Code: mfa_required} with Details["mfa_challenge_token"] populated.
func (*AuthClient) Logout ¶
func (a *AuthClient) Logout(ctx ctxpkg.Context, req *LogoutRequest) error
Logout revokes a refresh token. If req.RefreshToken is empty, the caller's cookie / current session is used (server-side).
When req.AccessToken is set AND a RevocationCache is configured on the Realm, the access token's jti is added to the cache on successful logout — bridging the gap between user logout and the access token's stateless natural expiry (ADR-041 follow-up). Failure to push to the cache does NOT fail the logout call; the server-side refresh revocation is the load-bearing operation.
func (*AuthClient) MFAVerify ¶
func (a *AuthClient) MFAVerify(ctx ctxpkg.Context, req MFAVerifyRequest) (*Session, error)
MFAVerify completes an MFA challenge. Same response shape as Login.
func (*AuthClient) MFAVerifyOTP ¶ added in v0.10.0
func (a *AuthClient) MFAVerifyOTP(ctx ctxpkg.Context, req MFAVerifyOTPRequest) (*Session, error)
MFAVerifyOTP completes an otp_internal second-factor challenge. Same response shape as Login. The first-factor login response carries an `mfa_challenge_token` and a `methods` list including "otp_internal" when the user is enrolled (per-user mfa_methods or per-role required_mfa_methods, gated by realms.config.otp_mfa_enabled).
func (*AuthClient) MintMFAChallenge ¶
func (a *AuthClient) MintMFAChallenge(ctx ctxpkg.Context, req MFAChallengeRequest) (string, []string, error)
MintMFAChallenge calls POST /auth/mfa/challenge to mint a step-up challenge token for the verified access token. Used by the middleware when an MFA-protected route receives a token that lacks fresh MFA proof. Returns ("", nil, error) when the server hasn't shipped the endpoint yet — the middleware downgrades to a generic 412 envelope without a pre-minted challenge.
func (*AuthClient) OTPLogin ¶ added in v0.10.0
func (a *AuthClient) OTPLogin(ctx ctxpkg.Context, req OTPLoginRequest) (*Session, error)
OTPLogin exchanges an identifier + manager-issued OTP for a realm- scoped session. Single-factor variant of the partner OTP primitive (proposal §3.2.1). Gated server-side by realms.config.otp_login_enabled.
func (*AuthClient) RevokeSession ¶
func (a *AuthClient) RevokeSession(ctx ctxpkg.Context, req RevokeSessionRequest) error
RevokeSession removes a session by id. The caller identifies the user either via a user-bearer JWT (legacy / public-client realms) or via UserID + the SDK's platform token (BFF realms; ADR-041 §7).
func (*AuthClient) Token ¶
func (a *AuthClient) Token(ctx ctxpkg.Context, req TokenRequest) (*MintResult, error)
Token rotates a refresh token, optionally switching tenants and merging custom claims into the minted access token.
type Claims ¶
type Claims struct {
Issuer string `json:"iss,omitempty"`
Subject string `json:"sub,omitempty"`
Audience string `json:"aud,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Expiry int64 `json:"exp,omitempty"`
JWTID string `json:"jti,omitempty"`
AuthorizedParty string `json:"azp,omitempty"`
TenantID string `json:"tenant_id,omitempty"`
Role string `json:"role,omitempty"`
AMR []string `json:"amr,omitempty"`
ACR string `json:"acr,omitempty"`
// MFAAt is the unix-seconds timestamp of the user's most recent
// successful MFA challenge in this session. Zero means absent — the
// session never completed MFA, or the server hasn't been upgraded
// yet to emit this claim. SPEC §10.4.
MFAAt int64 `json:"mfa_at,omitempty"`
Extra map[string]any `json:"-"`
}
Claims is the verified token payload. Standard JWT fields plus the RealmID-specific extras (azp, tenant_id, role). Unknown fields land in Extra.
func ClaimsFrom ¶
ClaimsFrom extracts the verified Claims from a request context. The second return is false if the middleware did not run on this request.
type Config ¶
type Config struct {
// RealmID — required. Your realm's UUID-ish identifier.
RealmID string
// APIKey — required. The realm's API key (rk_live_...). Never sent
// over login traffic; the SDK exchanges it once for a short-lived
// platform token (SPEC §4.0).
APIKey string
// BaseURL overrides the issuer host. Default: DefaultBaseURL.
BaseURL string
// Origin is the value attached to the Origin header on auth calls.
// If unset, derived from realm.Info().Audience on first use.
Origin string
// Logger is the *slog.Logger the SDK emits diagnostics to.
// Default: a slog logger over io.Discard (no-op).
Logger *slog.Logger
// HTTPClient overrides the underlying http.Client (handy in tests
// for fake transports). Default: 30s timeout.
HTTPClient *http.Client
// Leeway is the verifier's clock-skew tolerance for exp/nbf checks.
// Default 30s.
Leeway time.Duration
// Clock overrides time.Now. Useful in tests.
Clock func() time.Time
// Revocation is an optional JTI denylist consulted by Verify after
// signature + claim checks (ADR-041 follow-up). Lets partners stop
// the bleed on stolen access tokens between user logout and natural
// JWT expiry. Nil → no-op; verifier behaves as before. Pass
// NewMemRevocationCache(nil) for a single-process default, or supply
// a Redis/memcached-backed implementation for multi-replica deploys.
Revocation RevocationCache
}
Config configures NewRealm. RealmID and APIKey are required; the rest have sensible defaults.
type ConfigClient ¶
type ConfigClient struct {
// contains filtered or unexported fields
}
ConfigClient is realm.Config.
func (*ConfigClient) Update ¶
func (c *ConfigClient) Update(ctx context.Context, patch ConfigPatch) error
Update issues PATCH /platforms/{id}/config.
type ConfigPatch ¶
ConfigPatch is a partial patch of realm-level configuration (SPEC §6.5). The server enforces an allowlist of mutable keys; unknown keys are rejected with a 400.
type DomainClaim ¶
type DomainClaim struct {
Hostname string `json:"hostname"`
ClaimToken string `json:"claim_token,omitempty"`
TxtRecord *DomainTxt `json:"txt_record,omitempty"`
Status string `json:"status,omitempty"`
}
DomainClaim is the response from realm.Domains.Claim. The TXT record fields (when populated) tell the partner what to provision in DNS to complete the verify step.
type DomainTxt ¶
DomainTxt is the {name, value} pair the partner must publish at DNS for the verify step.
type DomainVerifyResult ¶
type DomainVerifyResult struct {
Hostname string `json:"hostname,omitempty"`
Verified bool `json:"verified,omitempty"`
Status string `json:"status,omitempty"`
}
DomainVerifyResult is returned from realm.Domains.Verify after the server reads the published TXT record.
type DomainsClient ¶
type DomainsClient struct {
// contains filtered or unexported fields
}
DomainsClient is realm.Domains (SPEC §6.4).
func (*DomainsClient) Claim ¶
func (c *DomainsClient) Claim(ctx context.Context, hostname string) (*DomainClaim, error)
Claim begins a domain claim and returns the TXT record to publish.
func (*DomainsClient) Verify ¶
func (c *DomainsClient) Verify(ctx context.Context, claimToken string) (*DomainVerifyResult, error)
Verify finishes a previously-started domain claim.
type ErrorCode ¶
type ErrorCode string
ErrorCode is a stable, machine-readable identifier for an SDK failure.
const ( // Verifier codes (SPEC §3.1). ErrCodeMalformed ErrorCode = "malformed" ErrCodeWrongAlgorithm ErrorCode = "wrong_algorithm" ErrCodeBadSignature ErrorCode = "bad_signature" ErrCodeWrongIssuer ErrorCode = "wrong_issuer" ErrCodeWrongAudience ErrorCode = "wrong_audience" ErrCodeExpired ErrorCode = "expired" ErrCodeNotYetValid ErrorCode = "not_yet_valid" ErrCodeUnknownKID ErrorCode = "unknown_kid" ErrCodeJWKSFetchFailed ErrorCode = "jwks_fetch_failed" // Auth-flow codes. ErrCodeProviderTokenInvalid ErrorCode = "provider_token_invalid" ErrCodeMFARequired ErrorCode = "mfa_required" ErrCodeSessionLimitReached ErrorCode = "session_limit_reached" ErrCodeTenantRequired ErrorCode = "tenant_required" ErrCodeTenantInvalid ErrorCode = "tenant_invalid" ErrCodeAccountSuspended ErrorCode = "account_suspended" ErrCodeAccountDeactivated ErrorCode = "account_deactivated" ErrCodeRealmOriginMismatch ErrorCode = "realm_origin_mismatch" ErrCodeMissingOrigin ErrorCode = "missing_origin" // Partner OTP primitive (docs/proposals/partner-otp-primitive.md). ErrCodeInvalidOTP ErrorCode = "invalid_otp" ErrCodeOTPExpired ErrorCode = "otp_expired" ErrCodeOTPLocked ErrorCode = "otp_locked" ErrCodeOTPNotFound ErrorCode = "otp_not_found" ErrCodeInvalidPurpose ErrorCode = "invalid_purpose" ErrCodeInvalidSubjectRef ErrorCode = "invalid_subject_ref" // Management / generic codes. ErrCodeForbidden ErrorCode = "forbidden" ErrCodeNotFound ErrorCode = "not_found" ErrCodeConflict ErrorCode = "conflict" ErrCodeRateLimited ErrorCode = "rate_limited" ErrCodeBadRequest ErrorCode = "bad_request" ErrCodeNetwork ErrorCode = "network" ErrCodeServerError ErrorCode = "server_error" )
type Invitation ¶
type Invitation struct {
ID string `json:"id"`
TenantID string `json:"tenant_id"`
Email string `json:"email"`
Role string `json:"role,omitempty"`
Status string `json:"status,omitempty"`
}
Invitation represents a pending tenant invite.
type InvitationCreate ¶
InvitationCreate is the create payload for /tenants/{id}/invitations.
type InvitationsClient ¶
type InvitationsClient struct {
// contains filtered or unexported fields
}
InvitationsClient is realm.Tenants.Invitations.
func (*InvitationsClient) Create ¶
func (c *InvitationsClient) Create(ctx context.Context, tenantID string, body InvitationCreate) (*Invitation, error)
func (*InvitationsClient) Delete ¶
func (c *InvitationsClient) Delete(ctx context.Context, tenantID, invitationID string) error
func (*InvitationsClient) List ¶
func (c *InvitationsClient) List(ctx context.Context, tenantID string) *Paginated[Invitation]
type IssueRequest ¶ added in v0.10.0
type IssueRequest struct {
SubjectRef string
Purpose string
// Auth: exactly one of UserID or UserBearer.
UserID string
UserBearer string
// OnBehalfOfIP, when set, rides as X-On-Behalf-Of-IP.
OnBehalfOfIP string
}
IssueRequest names the entity an OTP is bound to and the partner-side purpose tag (free string, regex `^[a-z][a-z0-9_]{0,63}$`).
type IssueResponse ¶ added in v0.10.0
type IssueResponse struct {
ID string `json:"id"`
Value string `json:"value"`
ExpiresAt time.Time `json:"-"` // parsed from string field below
ExpiresAtS string `json:"expires_at"`
Purpose string `json:"purpose"`
SubjectRef string `json:"subject_ref"`
}
IssueResponse mirrors api/internal/httpapi otpIssueResp.
type ListEventsParams ¶ added in v0.10.0
type ListEventsParams struct {
PlatformID string
TenantID string
ActorID string
Kind []string
Since int64 // unix seconds; 0 → omit
Until int64
Cursor string
Limit int
}
ListEventsParams carries the filters for AdminClient.ListEvents.
type ListOriginsOptions ¶
ListOriginsOptions parameterises Origins.List.
type ListPlatformsParams ¶ added in v0.10.0
type ListPlatformsParams struct {
Q string
Status []string
SignupMode []string
Domain string
OwnerUserID string
HasCustomDomain *bool
CreatedAfter int64 // unix seconds; 0 → omit
CreatedBefore int64
LastActivityAfter int64
LastActivityBefore int64
Sort string
Cursor string
Limit int
}
ListPlatformsParams carries the filter / pagination knobs for AdminClient.ListPlatforms (SPEC §7.5).
type ListSessionsRequest ¶ added in v0.7.0
ListSessionsRequest selects the user whose sessions to list and how to attest the call.
Exactly one of UserID or UserBearer must be set:
- UserID: BFF mode. The SDK uses its cached platform token as the bearer and sends X-On-Behalf-Of-User: <UserID>. Required when realm.config.require_bff_login=true (ADR-041 §7).
- UserBearer: legacy / public-client mode. The user's access JWT rides as Authorization: Bearer. Subject is read from the JWT.
OnBehalfOfIP, when set, is forwarded as X-On-Behalf-Of-IP so the issuer's per-IP rate limits see the SPA's IP, not the BFF's egress (ADR-050 plan §8.2).
type LoginMethod ¶
type LoginMethod string
LoginMethod is the upstream identity provider for a login call. "firebase" and "google" are supported today; others are roadmap.
const ( LoginFirebase LoginMethod = "firebase" LoginGoogle LoginMethod = "google" )
type LoginRequest ¶
type LoginRequest struct {
Method LoginMethod
ProviderToken string
Origin string // optional override of the SDK-derived Origin header
// TenantID disambiguates when the user is a member of multiple
// tenants in the realm. When empty and the user has >1 tenants, the
// auth server returns the tenant list (no tokens) so the caller can
// re-POST with the chosen tenant_id.
TenantID string
}
LoginRequest carries the inputs to realm.Auth.Login. Custom claims are NOT accepted on login (SPEC §4.1) — refresh-token identity only.
type LogoutFn ¶
type LogoutFn func(ctx ctxpkg.Context, req *LogoutRequest) error
LogoutFn is the shape of AuthClient.Logout that TokensClient.RevokeOnLogout wraps. Decoupled as an alias so callers can also wrap their own logout helpers (e.g. ones that talk through a partner BFF).
type LogoutRequest ¶
type LogoutRequest struct {
RefreshToken string
// AccessToken, when set, is pushed to the SDK's RevocationCache (if
// configured) so the JWT's jti is rejected by Verify until natural
// expiry. Bridges the gap between user logout and the access token's
// stateless natural expiry per ADR-041 follow-up. The server-side
// refresh revocation is independent and always happens.
AccessToken string
}
LogoutRequest optionally targets a specific refresh token. If RefreshToken is empty, the server uses the cookie / current session.
type MFAChallengeRequest ¶ added in v0.7.0
MFAChallengeRequest mints a step-up challenge for the user identified by AccessToken. OnBehalfOfIP, when set, is forwarded as X-On-Behalf-Of-IP for per-IP rate-limit attribution on /auth/mfa/challenge (ADR-050 plan §8.2).
type MFAEnrollResult ¶
type MFAEnrollResult struct {
Secret string `json:"secret,omitempty"`
OtpauthURI string `json:"otpauth_uri,omitempty"`
BackupCodes []string `json:"backup_codes,omitempty"`
}
MFAEnrollResult is the response from EnrollMFA.
type MFAGateReason ¶
type MFAGateReason string
MFAGateReason mirrors the wire `reason` field on the 412 envelope.
const ( MFAReasonNoMFA MFAGateReason = "no_mfa" MFAReasonStaleMFA MFAGateReason = "stale_mfa" MFAReasonFreshRequired MFAGateReason = "fresh_required" )
type MFARule ¶
MFARule is one entry in MiddlewareOptions.MFAProtectedPaths.
Per-route MFA freshness policy (SPEC §10.4):
- MaxAge — accept any token whose mfa_at claim is at most that old. Zero means "use the realm-default freshness window" (MiddlewareOptions.MFADefaultMaxAge). Negative is treated as zero.
- RequireFresh — require mfa_at within ~30s. Use for irreversible operations. Strict: a legacy amr/acr-only token (no mfa_at) cannot satisfy this — the gate has no way to prove freshness.
type MFAVerifyOTPRequest ¶ added in v0.10.0
MFAVerifyOTPRequest is the input for AuthClient.MFAVerifyOTP (partner OTP proposal §3.2.2). The MFA challenge token comes from the prior /auth/login response; Presented is the OTP value the user typed.
type MFAVerifyRequest ¶
type MFAVerifyRequest struct {
ChallengeToken string
Code string
Method string // defaults to "totp"
// OnBehalfOfIP forwards the end-user's IP to the issuer via
// X-On-Behalf-Of-IP so per-IP rate limits on /auth/mfa/verify see the
// SPA's IP rather than the BFF's egress (ADR-050 plan §8.2).
OnBehalfOfIP string
}
MFAVerifyRequest carries an MFA challenge response (SPEC §4.3).
type MemRevocationCache ¶
type MemRevocationCache struct {
// contains filtered or unexported fields
}
MemRevocationCache is a single-process implementation suitable for a single partner-API replica or for tests. Multi-replica deployments should wire a shared backend (Redis, etc.) by implementing the RevocationCache interface directly. Lazily evicts expired entries.
func NewMemRevocationCache ¶
func NewMemRevocationCache(now func() time.Time) *MemRevocationCache
NewMemRevocationCache returns an empty MemRevocationCache. now is the clock; pass nil to default to time.Now.
func (*MemRevocationCache) Len ¶
func (m *MemRevocationCache) Len() int
Len returns the current entry count. Useful for tests + instrumentation.
type MiddlewareOptions ¶
type MiddlewareOptions struct {
// ExemptPaths is a list of glob patterns that bypass the
// middleware entirely. Defaults to ["/health", "/public/*"].
ExemptPaths []string
// MFAProtectedPaths declares paths that require MFA. Each entry is
// either a bare path string (sugar for {Path: s} — inherits the
// realm-default freshness window) or a full MFARule for per-route
// override. SPEC §10.4.
MFAProtectedPaths []MFARule
// MFADefaultMaxAge is the realm-wide default freshness window
// applied to MFARule entries that omit MaxAge. Default 15 min.
// Mirrors realms.config.mfa_session_ttl_seconds server-side.
MFADefaultMaxAge time.Duration
// LoginPath, LogoutPath, RefreshPath, MFAVerifyPath are the routes
// the middleware handles directly (POST). Empty strings disable
// the route.
LoginPath string // default "/login"
LogoutPath string // default "/logout"
RefreshPath string // default "/token"
MFAVerifyPath string // default "/mfa/verify"
// TokenDelivery is "cookie" (default) or "body". Cookie mode sets
// a HttpOnly cookie carrying the refresh token; body mode returns
// it inline in the JSON response.
TokenDelivery string
// CookieName, CookieDomain, CookieSecure, CookieSameSite control
// the refresh-token cookie when TokenDelivery == "cookie".
CookieName string // default "realmid_refresh"
CookieDomain string // optional
CookieSecure bool // default true
CookieSameSite http.SameSite // default http.SameSiteLaxMode
// OnAuthFailure overrides the default 401/412 response.
OnAuthFailure func(http.ResponseWriter, *http.Request, *RealmError)
}
MiddlewareOptions configures Realm.Middleware (SPEC §10).
type MintResult ¶
type MintResult struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int `json:"expires_in"`
TenantID string `json:"tenant_id"`
Role string `json:"role"`
}
MintResult is realm.Auth.Token's response.
type OTPClient ¶ added in v0.10.0
type OTPClient struct {
// contains filtered or unexported fields
}
OTPClient implements the partner OTP primitive surface.
func (*OTPClient) Issue ¶ added in v0.10.0
func (c *OTPClient) Issue(ctx ctxpkg.Context, req IssueRequest) (*IssueResponse, error)
Issue mints a fresh OTP. The plaintext Value is returned exactly once — partners deliver it out-of-band (manager UI display, SMS, email).
func (*OTPClient) Verify ¶ added in v0.10.0
func (c *OTPClient) Verify(ctx ctxpkg.Context, req VerifyRequest) (*VerifyResponse, error)
Verify hash-matches a presented value against active OTP rows in (tenant, subject_ref, purpose). On success the matching row is consumed atomically — concurrent verifies can't double-spend.
The response carries IssuerUserID + IssuedAt so the partner backend can attribute the action to the human who minted the code without a follow-up RealmID query.
func (*OTPClient) View ¶ added in v0.10.0
func (c *OTPClient) View(ctx ctxpkg.Context, otpID string, opts ViewOptions) (*ViewResponse, error)
View returns the plaintext value of an OTP — issuer-scoped: only the user who minted the OTP can fetch it (the manager who issued the code, not a colleague). Cross-issuer / cross-tenant attempts return 404 with no info leak.
type OTPLoginRequest ¶ added in v0.10.0
OTPLoginRequest is the input for AuthClient.OTPLogin (partner OTP proposal §3.2.1). RealmID is overridden via the Realm config; pass Identifier (email or E.164 phone) + Presented (the OTP value the user typed). Returns Session on success; an enumeration-safe invalid_credentials on miss.
type Origin ¶
type Origin struct {
ID string `json:"id"`
Domain string `json:"domain"`
EntityType string `json:"entity_type"`
EntityID string `json:"entity_id"`
VerificationID *string `json:"verification_id,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
DetachedAt *string `json:"detached_at,omitempty"`
}
Origin is one row in the per-realm origin allowlist (ADR-049 §A.7.2). A live row (DetachedAt == nil) means the bare hostname routes to the referenced realm or tenant entity — and, by extension, is permitted to make unauthenticated proxy calls to the partner backend that fronts platform-token-gated RealmID routes.
type OriginClaim ¶ added in v0.10.0
type OriginClaim struct {
Domain string `json:"domain"`
Status string `json:"status"`
Method string `json:"method"` // always "dns_txt"
DNSRecordName string `json:"dns_record_name"`
DNSRecordValue string `json:"dns_record_value"`
}
OriginClaim is the response from OriginsClient.Claim. Realm-origin claims are DNS-TXT only — the html_file method is not exposed at the platform surface (see ADR-049 §"Method options").
type OriginsClient ¶
type OriginsClient struct {
// contains filtered or unexported fields
}
OriginsClient implements ADR-047 §1.1 — partner-side origin allowlist enforcement. Concurrent-safe.
func (*OriginsClient) Claim ¶ added in v0.10.0
func (c *OriginsClient) Claim(ctx ctxpkg.Context, realmID, hostname string) (*OriginClaim, error)
Claim begins a realm-owned origin claim. The DV row is owner-scoped to the realm — any admin in this realm can complete the verify step and add the bind. Re-claiming returns the existing pending token (idempotent).
func (*OriginsClient) Invalidate ¶
func (c *OriginsClient) Invalidate(realmID string)
Invalidate drops the cached allowlist for one realm (or all if "").
func (*OriginsClient) List ¶
func (c *OriginsClient) List(_ ctxpkg.Context, opts ListOriginsOptions) (*Paginated[Origin], error)
List returns a paginated iterator over the realm's live origin mappings. Wraps GET /platforms/{realmId}/origins (ADR-049 §A.7.2).
func (*OriginsClient) Validate ¶
func (c *OriginsClient) Validate(ctx ctxpkg.Context, opts ValidateOriginOptions) (bool, error)
Validate reports whether the inbound origin is on the realm's allowlist. The cached allowlist is refreshed on cache miss + TTL expiry. On a 401 from the underlying list call, the platform token is invalidated and the call is retried once.
func (*OriginsClient) Verify ¶ added in v0.10.0
func (c *OriginsClient) Verify(ctx ctxpkg.Context, realmID, hostname string) (*DomainVerifyResult, error)
Verify drives the DNS check on the realm's pending DV row. Does NOT bind — call Origins.Bind (POST /platforms/{id}/origins) afterwards so a single verified apex can serve as the trust anchor for trusted-by-parent subdomain origins.
type Page ¶
type Page[T any] struct { Items []T `json:"items"` NextCursor string `json:"next_cursor,omitempty"` Total *int `json:"total,omitempty"` }
Page is one page of results in the locked wire shape (SPEC §7).
type Paginated ¶
type Paginated[T any] struct { // contains filtered or unexported fields }
Paginated wraps a list endpoint, exposing both an iterator and a manual .Page accessor so callers can choose either style.
type PassthroughOptions ¶ added in v0.8.0
type PassthroughOptions struct {
// OnBehalfOfUser, when non-empty, rides as `X-On-Behalf-Of-User`
// alongside the platform-token bearer. Use this from a BFF / partner
// backend to attribute the call to a specific end-user (per ADR-041
// §7 + ADR-050 plan §8.2).
OnBehalfOfUser string
// OnBehalfOfIP, when non-empty, rides as `X-On-Behalf-Of-IP`. Lets
// the issuer's per-IP rate limits attribute to the SPA's IP rather
// than the BFF's egress.
OnBehalfOfIP string
// UserBearer, when non-empty, REPLACES the platform-token bearer
// with the supplied bearer (typically a user's access JWT or a
// scoped one-shot token like a revocation_token). The platform
// token is still minted (so the cache stays warm + token-mint
// errors propagate), but the wire bearer is the user's. This is
// the auth model needed for the session-limit-modal flow where the
// auth server validates a one-shot revocation_token bearer.
UserBearer string
// Header carries any additional request headers to forward
// verbatim (e.g. `Idempotency-Key`). Authorization is always
// overwritten; do not set it here.
Header http.Header
}
PassthroughOptions configures a single Realm.Do call.
type PlatformOwner ¶ added in v0.10.0
type PlatformOwner struct {
UserID string `json:"user_id"`
Name string `json:"name"`
Email string `json:"email"`
}
PlatformOwner is the embedded owner block on a PlatformSummary.
type PlatformSummary ¶ added in v0.10.0
type PlatformSummary struct {
ID string `json:"id"`
DisplayName string `json:"display_name"`
Slug string `json:"slug"`
Status string `json:"status"`
SignupMode string `json:"signup_mode"`
Domains []string `json:"domains"`
Owner PlatformOwner `json:"owner"`
TenantsCount int `json:"tenants_count"`
UsersCount int `json:"users_count"`
LastActivityAt int64 `json:"last_activity_at"`
CreatedAt int64 `json:"created_at"`
}
PlatformSummary is one row in AdminPlatformsResponse.Items.
type Realm ¶
type Realm struct {
Auth *AuthClient
Tenants *TenantsClient
Domains *DomainsClient
APIKeys *APIKeysClient
Config *ConfigClient
Roles *RolesClient
Origins *OriginsClient
Tokens *TokensClient
Admin *AdminClient
// OTP exposes the partner OTP primitive (issue / view / verify) —
// see docs/proposals/partner-otp-primitive.md in the auth repo.
OTP *OTPClient
// contains filtered or unexported fields
}
Realm is the SDK handle. Construct with NewRealm; safe for concurrent use across goroutines.
func (*Realm) Do ¶ added in v0.8.0
func (r *Realm) Do(ctx ctxpkg.Context, method, path string, body io.Reader, opts *PassthroughOptions) (*http.Response, error)
Do issues an authenticated request to the realm's API and returns the raw *http.Response. The platform token is minted (and cached) behind the scenes; callers must close resp.Body.
This is the escape hatch for BFF / proxy consumers that need to forward arbitrary admin-API calls without re-implementing the dual-token dance. Typed methods (Tenants, Roles, Origins, …) remain the recommended surface for application code.
`path` is joined to the realm's base URL (`/foo/bar?x=1`); a leading slash is added if missing. `body` may be nil. Non-2xx responses are returned with the status intact — the caller decides whether to map them through *RealmError.
func (*Realm) Info ¶
Info returns cached realm metadata. First call hits the network; subsequent calls reuse the cached value for the lifetime of the handle.
func (*Realm) Middleware ¶
Middleware returns an http.Handler middleware implementing SPEC §10.
func (*Realm) Revocation ¶
func (r *Realm) Revocation() RevocationCache
Revocation returns the configured shared revocation cache, or nil when the SDK was constructed without one. Partner code can push directly to the cache (e.g., on detected token theft outside the normal Logout path) by calling cache.Revoke(ctx, jti, exp).
type RealmError ¶
type RealmError struct {
Code ErrorCode
Message string
HTTPStatus int
Details map[string]any
Cause error
}
RealmError is the unified error returned from every SDK operation.
func (*RealmError) Error ¶
func (e *RealmError) Error() string
Error implements the error interface.
func (*RealmError) Unwrap ¶
func (e *RealmError) Unwrap() error
Unwrap exposes the underlying cause for errors.Is / errors.As.
type RealmInfo ¶
type RealmInfo struct {
ID string `json:"id"`
Audience string `json:"audience,omitempty"`
Domain string `json:"domain,omitempty"`
DisplayName string `json:"display_name,omitempty"`
Extra map[string]any `json:"-"`
}
RealmInfo is realm metadata returned by realm.Info(). Audience is the canonical aud value for this realm — used for verifier auto-discovery (SPEC §1) and as the default Origin on auth calls.
type RevocationCache ¶
type RevocationCache interface {
// Revoke marks jti as revoked. expiresAt is the JWT's exp, used as
// the cache entry TTL — partners' implementations should evict on
// expiry so the cache never grows unboundedly.
Revoke(ctx ctxpkg.Context, jti string, expiresAt time.Time) error
// IsRevoked returns true when jti has been revoked and the TTL has
// not elapsed. Errors propagate to the verifier which fails closed
// (request rejected).
IsRevoked(ctx ctxpkg.Context, jti string) (bool, error)
}
RevocationCache is the partner-pluggable JTI denylist. Cheap reads matter — IsRevoked is on the hot path of every authenticated request.
type RevokeSessionRequest ¶ added in v0.7.0
type RevokeSessionRequest struct {
SessionID string
UserID string
UserBearer string
OnBehalfOfIP string
}
RevokeSessionRequest names the session to revoke and how to attest the caller. Auth shape is identical to ListSessionsRequest.
type Role ¶
type Role = string
Role is the wire form of a role name. Stays a string alias — see ADR-040 decision §3 (no fixed enum).
type RoleCreate ¶
type RoleCreate struct {
Name string `json:"name"`
DisplayName string `json:"display_name,omitempty"`
Permissions []string `json:"permissions,omitempty"`
}
RoleCreate is the POST body.
type RoleDeleteResult ¶
type RoleDeleteResult struct {
Status string `json:"status"`
}
RoleDeleteResult is the DELETE acknowledgment.
type RoleListOpts ¶
RoleListOpts are the optional pagination inputs.
type RoleListPage ¶
type RoleListPage struct {
Items []RoleObject `json:"items"`
NextCursor string `json:"next_cursor,omitempty"`
Total *int `json:"total,omitempty"`
}
RoleListPage is one page of `/platforms/{id}/roles` in the locked SPEC §7 envelope shape.
type RoleObject ¶
type RoleObject struct {
ID string `json:"id"`
Name string `json:"name"`
DisplayName string `json:"display_name,omitempty"`
Permissions []string `json:"permissions"`
IsSystem bool `json:"is_system"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
RoleObject is one realm-defined role, as returned by the `/platforms/{id}/roles` endpoints.
type RolePatch ¶
type RolePatch struct {
DisplayName *string `json:"display_name,omitempty"`
Permissions *[]string `json:"permissions,omitempty"`
}
RolePatch is the PATCH body. Pointer fields signal "include in payload"; nil means "don't touch".
type RolesClient ¶
type RolesClient struct {
// contains filtered or unexported fields
}
RolesClient is realm.Roles.
func (*RolesClient) Create ¶
func (c *RolesClient) Create(ctx ctxpkg.Context, body RoleCreate) (*RoleObject, error)
Create creates a custom role. Returns ErrRoleExists if the name is already taken in the realm.
func (*RolesClient) Delete ¶
func (c *RolesClient) Delete(ctx ctxpkg.Context, roleID string) (*RoleDeleteResult, error)
Delete removes a custom role. Returns ErrRoleInUse (409) when the role is still attached to users/invitations, ErrSystemRoleImmutable (400) for `owner`/`member`.
func (*RolesClient) List ¶
func (c *RolesClient) List(ctx ctxpkg.Context, opts *RoleListOpts) (*RoleListPage, error)
List returns one page of `/platforms/{id}/roles`. Unlike the typed iterators on Tenants etc., this surface returns the raw envelope so callers can drive their own paging UI directly.
func (*RolesClient) Rename ¶
func (c *RolesClient) Rename(ctx ctxpkg.Context, roleID string, to string) (*RoleObject, error)
Rename rewrites a role's name in `realm_roles`, `users.role`, and `invitations.role` in one transaction (server-side).
func (*RolesClient) Update ¶
func (c *RolesClient) Update(ctx ctxpkg.Context, roleID string, patch RolePatch) (*RoleObject, error)
Update patches display_name and/or permissions on an existing role. Returns ErrSystemRoleImmutable when called on `owner` or `member`.
type SearchHit ¶ added in v0.10.0
type SearchHit struct {
Type string `json:"type"`
ID string `json:"id"`
Label string `json:"label"`
Sublabel string `json:"sublabel,omitempty"`
PlatformID string `json:"platform_id,omitempty"`
}
SearchHit is one row in AdminSearchResponse.Items.
type Session ¶
type Session struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int `json:"expires_in"`
ExpiresAt string `json:"expires_at,omitempty"`
TenantID string `json:"tenant_id,omitempty"`
Role string `json:"role,omitempty"`
User UserSummary `json:"user"`
Tenants []TenantRef `json:"tenants"`
}
Session is the result of a successful Login or MFA verify.
Login currently returns flat top-level `tenant_id` + `role` (the user's pinned tenant after the login resolved); these surface here so callers don't have to parse Tenants[]. `User` is populated from the access JWT's claims (sub/email) when the wire response omits it.
type SessionInfo ¶
type SessionInfo struct {
ID string `json:"id"`
CreatedAt string `json:"created_at,omitempty"`
LastUsedAt string `json:"last_used_at,omitempty"`
UserAgent string `json:"user_agent,omitempty"`
IP string `json:"ip,omitempty"`
}
SessionInfo is one entry in realm.Auth.ListSessions.
type SignupMode ¶
type SignupMode string
SignupMode is the per-tenant signup policy (SPEC §6.1, ADR-045).
`closed` (default) is invitation-only; `allowlist` auto-provisions users whose verified email domain is in `allowed_domains`; `open` auto-provisions every authenticated user and is reserved for the base admin tenant — partner tenants cannot set this mode.
const ( SignupModeClosed SignupMode = "closed" SignupModeAllowlist SignupMode = "allowlist" SignupModeOpen SignupMode = "open" )
type Tenant ¶
type Tenant struct {
ID string `json:"id"`
DisplayName string `json:"display_name,omitempty"`
OwnerUserID string `json:"owner_user_id,omitempty"`
Config map[string]any `json:"config,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
Tenant is one entry returned from realm.Tenants.* (SPEC §6.1).
type TenantCreate ¶
type TenantCreate struct {
DisplayName string `json:"display_name"`
AllowedDomains []string `json:"allowed_domains,omitempty"`
SignupMode SignupMode `json:"signup_mode,omitempty"`
}
TenantCreate is the create payload (SPEC §6.1).
type TenantDomainClaim ¶ added in v0.10.0
type TenantDomainClaim struct {
Domain string `json:"domain"`
Status string `json:"status"`
Method string `json:"method"`
DNSRecordName string `json:"dns_record_name,omitempty"`
DNSRecordValue string `json:"dns_record_value,omitempty"`
FilePath string `json:"file_path,omitempty"`
FileContent string `json:"file_content,omitempty"`
}
TenantDomainClaim is the response from Tenants.ClaimDomain. Exactly one of (DNSRecordName/DNSRecordValue) and (FilePath/FileContent) is populated, determined by the verification method.
type TenantDomainClaimRequest ¶ added in v0.10.0
type TenantDomainClaimRequest struct {
PlatformID string
TenantID string
Domain string
Method string // "dns_txt" (default) | "html_file"
}
TenantDomainClaimRequest parameterises Tenants.ClaimDomain. Method is optional (defaults to "dns_txt"); accepted values are "dns_txt" and "html_file".
type TenantPatch ¶
type TenantPatch struct {
DisplayName string `json:"display_name,omitempty"`
}
TenantPatch patches mutable tenant fields.
type TenantRef ¶
type TenantRef struct {
ID string `json:"tenant_id"`
IDLegacy string `json:"id,omitempty"`
Role string `json:"role"`
DisplayName string `json:"display_name,omitempty"`
}
TenantRef is the abbreviated tenant info embedded in Session.Tenants.
The wire shape uses `tenant_id` (api/internal/authsvc.TenantMembership); `id` is accepted as a fallback for older / mocked issuers in tests.
type TenantsClient ¶
type TenantsClient struct {
Invitations *InvitationsClient
Users *UsersClient
// contains filtered or unexported fields
}
TenantsClient is realm.Tenants.
func (*TenantsClient) ClaimDomain ¶ added in v0.10.0
func (c *TenantsClient) ClaimDomain(ctx ctxpkg.Context, req TenantDomainClaimRequest) (*TenantDomainClaim, error)
ClaimDomain initiates a tenant-owned domain claim. The DV row is owner-scoped to the tenant: any platform admin can complete a claim another admin started, and the row survives the claimer's user being removed. Re-claiming with the same method is idempotent.
func (*TenantsClient) Create ¶
func (c *TenantsClient) Create(ctx context.Context, body TenantCreate) (*Tenant, error)
Create creates a tenant.
func (*TenantsClient) Delete ¶
func (c *TenantsClient) Delete(ctx context.Context, id string) error
Delete removes a tenant.
func (*TenantsClient) List ¶
func (c *TenantsClient) List(ctx context.Context) *Paginated[Tenant]
List paginates tenants (SPEC §6.1).
func (*TenantsClient) TransferOwner ¶
func (c *TenantsClient) TransferOwner(ctx context.Context, id, newOwnerUserID string) (*Tenant, error)
TransferOwner reassigns tenant ownership.
func (*TenantsClient) Update ¶
func (c *TenantsClient) Update(ctx context.Context, id string, patch TenantPatch) (*Tenant, error)
Update patches an existing tenant.
func (*TenantsClient) UpdateConfig ¶
func (c *TenantsClient) UpdateConfig(ctx context.Context, id string, patch map[string]any) (*Tenant, error)
UpdateConfig patches the per-tenant config blob.
func (*TenantsClient) UpdateUserRole ¶
func (c *TenantsClient) UpdateUserRole(ctx ctxpkg.Context, tenantID, userID, role string) (*UpdateUserRoleResult, error)
UpdateUserRole sets a user's role within a tenant. The role name must exist in the realm's role catalog (see RolesClient.Create). Setting role=owner is rejected — use TransferOwner for the explicit handover. Demoting the last owner returns RealmError(last_owner).
Wraps PATCH /tenants/{id}/users/{uid}/role. Test coverage: TestTenants_UpdateUserRole.
func (*TenantsClient) VerifyDomain ¶ added in v0.10.0
func (c *TenantsClient) VerifyDomain(ctx ctxpkg.Context, platformID, tenantID, domain string) (*DomainVerifyResult, error)
VerifyDomain drives the verification check on the tenant's pending DV row and (on success) inserts the domain_mappings binding in the same call. Method is read off the persisted row, so callers don't pass it again here.
type TokenRequest ¶
TokenRequest is realm.Auth.Token's input — refresh + tenant_id + optional access-token customClaims (SPEC §4.2).
type TokensClient ¶
type TokensClient struct {
// contains filtered or unexported fields
}
TokensClient is the access-token revocation cache surface. Concurrent-safe.
func (*TokensClient) Evict ¶
func (t *TokensClient) Evict(jti string)
Evict drops a single jti, or all entries when jti == "".
func (*TokensClient) GateRequest ¶
func (t *TokensClient) GateRequest(accessToken string) error
GateRequest is the per-request gate partner middleware calls before forwarding upstream. Returns ErrTokenRevoked (wrapped in a *RealmError with code "unauthorized" + details.revoked=true) when accessToken's jti is in the cache. Returns nil otherwise (including for malformed tokens — let the verifier surface that).
func (*TokensClient) IsRevoked ¶
func (t *TokensClient) IsRevoked(accessToken string) bool
IsRevoked returns true iff accessToken's jti is cached and the entry has not expired. Lazy GC: stale entries are evicted on read. Returns false on malformed input.
func (*TokensClient) Len ¶
func (t *TokensClient) Len() int
Len returns the current entry count. Useful for tests + instrumentation.
func (*TokensClient) MarkRevoked ¶
func (t *TokensClient) MarkRevoked(accessToken string)
MarkRevoked extracts jti+exp from accessToken and caches the jti with TTL = exp - now(). No-op when the token is malformed, the jti/exp claims are missing, or exp is already in the past.
func (*TokensClient) RevokeOnLogout ¶
func (t *TokensClient) RevokeOnLogout(logoutFn LogoutFn) func(ctx ctxpkg.Context, accessToken string, req *LogoutRequest) error
RevokeOnLogout wraps a LogoutFn so that the access token's jti is marked revoked on **either success or transport failure**. Rationale: the partner backend should fail closed — if RealmID is unreachable, the access token still gets blackholed locally so the user is logged out from the partner's perspective. Returns the wrapped function; the original logoutFn is called once per invocation.
type UpdateUserRoleResult ¶
type UpdateUserRoleResult struct {
ID string `json:"id"`
Role string `json:"role"`
TenantID string `json:"tenant_id"`
UpdatedAt int64 `json:"updated_at"`
}
UpdateUserRoleResult is the response shape returned by Tenants.UpdateUserRole.
type User ¶
type User struct {
ID string `json:"id"`
Email string `json:"email,omitempty"`
DisplayName string `json:"display_name,omitempty"`
Status string `json:"status,omitempty"`
MFAEnabled bool `json:"mfa_enabled,omitempty"`
Role string `json:"role,omitempty"`
}
User is one entry in realm.Tenants.Users.* (SPEC §6.3).
type UserStatus ¶
type UserStatus string
UserStatus is the discrete status field on a user record.
const ( UserStatusActive UserStatus = "active" UserStatusSuspended UserStatus = "suspended" UserStatusDeactivated UserStatus = "deactivated" )
type UserSummary ¶
type UserSummary struct {
ID string `json:"id"`
Email string `json:"email,omitempty"`
DisplayName string `json:"display_name,omitempty"`
}
UserSummary is the verified-user payload returned from Login / MFAVerify.
type UsersClient ¶
type UsersClient struct {
// contains filtered or unexported fields
}
UsersClient is realm.Tenants.Users.
func (*UsersClient) ConfirmMFA ¶
func (c *UsersClient) ConfirmMFA(ctx context.Context, tenantID, userID, code string) error
func (*UsersClient) EnrollMFA ¶
func (c *UsersClient) EnrollMFA(ctx context.Context, tenantID, userID string) (*MFAEnrollResult, error)
func (*UsersClient) ResetMFA ¶
func (c *UsersClient) ResetMFA(ctx context.Context, tenantID, userID string) error
func (*UsersClient) UpdateStatus ¶
func (c *UsersClient) UpdateStatus(ctx context.Context, tenantID, userID string, status UserStatus) (*User, error)
type ValidateOriginOptions ¶
ValidateOriginOptions parameterises Origins.Validate.
type VerifyOptions ¶
type VerifyOptions struct {
// Audience overrides the auto-discovered audience for this call only.
Audience string
}
VerifyOptions is the per-call override surface for Verify.
type VerifyRequest ¶ added in v0.10.0
type VerifyRequest struct {
SubjectRef string
Purpose string
Presented string
UserID string
UserBearer string
OnBehalfOfIP string
}
VerifyRequest names the entity to match against and the value typed by the end-user (or the delivery agent, for delivery-confirmation flows).
type VerifyResponse ¶ added in v0.10.0
type VerifyResponse struct {
OTPID string `json:"otp_id"`
IssuerUserID string `json:"issuer_user_id"`
IssuedAt time.Time `json:"-"`
IssuedAtS string `json:"issued_at"`
SubjectRef string `json:"subject_ref"`
Purpose string `json:"purpose"`
}
VerifyResponse mirrors api/internal/httpapi otpVerifyResp.
type ViewOptions ¶ added in v0.10.0
ViewOptions selects the caller identity for OTPClient.View.
type ViewResponse ¶ added in v0.10.0
type ViewResponse struct {
ID string `json:"id"`
Value string `json:"value"`
ExpiresAt time.Time `json:"-"`
ExpiresAtS string `json:"expires_at"`
Purpose string `json:"purpose"`
SubjectRef string `json:"subject_ref"`
IssuerUserID string `json:"issuer_user_id"`
}
ViewResponse mirrors api/internal/httpapi otpViewResp.