service

package
v0.0.0-...-4ad0758 Latest Latest
Warning

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

Go to latest
Published: Aug 19, 2026 License: Apache-2.0 Imports: 56 Imported by: 0

Documentation

Overview

internal/service/lock_mfa.go

internal/service/mfa_gate.go

internal/service/oauth_mfa_gate.go

internal/service/otp_mfa_setup.go

Package service contains the transport-agnostic business logic for Authorizer's public API operations. Each operation accepts a context, a RequestMetadata describing the inbound request, and a typed request object; it returns a typed response, a ResponseSideEffects describing artifacts the transport must apply (cookies, etc.), and an error.

GraphQL resolvers, gRPC handlers, and REST handlers all construct RequestMetadata from their transport and apply ResponseSideEffects back to it. The service layer itself never touches gin.Context, grpc.ServerStream, or any other transport-specific type.

internal/service/skip_mfa_setup.go

Index

Constants

View Source
const FgaAgentSubjectType = "agent"

FgaAgentSubjectType is the OpenFGA object type an agent is represented as when the operator's model declares it.

Chosen to match the shape published by OpenFGA and Auth0 FGA for AI agents, where an agent is a first-class principal appearing wherever `user` does:

type agent

type document
  relations
    define viewer: [user, agent]

Variables

View Source
var ErrFgaNotEnabled = FailedPrecondition("fine-grained authorization is not enabled")

ErrFgaNotEnabled is returned by every fine-grained-authorization (FGA) operation when no authorization engine is configured (no --fga-store). Fail-closed. Typed FailedPrecondition so gRPC/REST callers get codes.FailedPrecondition / HTTP 400 instead of an internal error.

Functions

func AlreadyExists

func AlreadyExists(msg string) error

AlreadyExists reports a request that violates a uniqueness constraint.

func ApplyToGin

func ApplyToGin(gc *gin.Context, side *ResponseSideEffects)

ApplyToGin writes the response side-effects to a gin.Context. The gin-aware transport calls this once per request after a service method returns successfully. A nil receiver is a no-op (service methods that have no side-effects may return nil).

func FailedPrecondition

func FailedPrecondition(msg string) error

FailedPrecondition reports a well-formed request disallowed by current state.

func InvalidArgument

func InvalidArgument(msg string) error

InvalidArgument reports a malformed or semantically invalid request.

func IsVerifyEmailPurpose

func IsVerifyEmailPurpose(req *schemas.VerificationRequest, claim jwt.MapClaims) bool

IsVerifyEmailPurpose is the exported form of the check for the email verification family, for callers outside this package.

Exported because there are TWO implementations of email verification over the same token table — the GraphQL mutation (VerifyEmail, verify_email.go) and the REST handler behind GET /verify_email, which is the URL every verification and magic-link mail actually points at (utils.GetEmailVerificationURL). They share no code, so a gate applied to only one of them is not a gate: a forgot-password token rejected by the mutation stays redeemable at the REST route for a full session. Both must call this.

func NormalizeEmailDomain

func NormalizeEmailDomain(email string) (string, error)

NormalizeEmailDomain extracts the domain part of an email address and normalizes it through the SAME canonical normalizeDomain used for Phase-2 domain verification writes, so a home-realm-discovery lookup resolves to the exact value that was stored (review M3 — one normalizer, no split routing). It splits on the LAST "@" (a valid address has exactly one, but this is robust to quirky local-parts) and requires a non-empty local part.

func NotFound

func NotFound(msg string) error

NotFound reports a referenced resource that does not exist.

func PermissionDenied

func PermissionDenied(msg string) error

PermissionDenied reports an authenticated caller lacking a required permission.

func TooManyRequests

func TooManyRequests(msg string) error

TooManyRequests reports a request rejected for exceeding a rate/attempt limit.

func Unauthenticated

func Unauthenticated(msg string) error

Unauthenticated reports a missing or invalid credential/session.

Types

type AdminProvider

type AdminProvider interface {
	// Auth + meta.
	AdminLogin(ctx context.Context, meta RequestMetadata, params *model.AdminLoginRequest) (*model.Response, *ResponseSideEffects, error)
	AdminLogout(ctx context.Context, meta RequestMetadata) (*model.Response, *ResponseSideEffects, error)
	AdminSession(ctx context.Context, meta RequestMetadata) (*model.Response, *ResponseSideEffects, error)
	AdminMeta(ctx context.Context, meta RequestMetadata) (*model.AdminMeta, *ResponseSideEffects, error)

	// Users.
	Users(ctx context.Context, meta RequestMetadata, params *model.ListUsersRequest) (*model.Users, *ResponseSideEffects, error)
	User(ctx context.Context, meta RequestMetadata, params *model.GetUserRequest) (*model.User, *ResponseSideEffects, error)
	UserOrganizations(ctx context.Context, meta RequestMetadata, params *model.UserOrganizationsRequest) (*model.UserOrganizations, *ResponseSideEffects, error)
	UpdateUser(ctx context.Context, meta RequestMetadata, params *model.UpdateUserRequest) (*model.User, *ResponseSideEffects, error)
	DeleteUser(ctx context.Context, meta RequestMetadata, params *model.DeleteUserRequest) (*model.Response, *ResponseSideEffects, error)
	VerificationRequests(ctx context.Context, meta RequestMetadata, params *model.PaginationRequest) (*model.VerificationRequests, *ResponseSideEffects, error)

	// Access.
	RevokeAccess(ctx context.Context, meta RequestMetadata, params *model.UpdateAccessRequest) (*model.Response, *ResponseSideEffects, error)
	EnableAccess(ctx context.Context, meta RequestMetadata, params *model.UpdateAccessRequest) (*model.Response, *ResponseSideEffects, error)
	InviteMembers(ctx context.Context, meta RequestMetadata, params *model.InviteMemberRequest) (*model.InviteMembersResponse, *ResponseSideEffects, error)

	// Webhooks.
	AddWebhook(ctx context.Context, meta RequestMetadata, params *model.AddWebhookRequest) (*model.Response, *ResponseSideEffects, error)
	UpdateWebhook(ctx context.Context, meta RequestMetadata, params *model.UpdateWebhookRequest) (*model.Response, *ResponseSideEffects, error)
	DeleteWebhook(ctx context.Context, meta RequestMetadata, params *model.WebhookRequest) (*model.Response, *ResponseSideEffects, error)
	Webhook(ctx context.Context, meta RequestMetadata, params *model.WebhookRequest) (*model.Webhook, *ResponseSideEffects, error)
	Webhooks(ctx context.Context, meta RequestMetadata, params *model.PaginationRequest) (*model.Webhooks, *ResponseSideEffects, error)
	WebhookLogs(ctx context.Context, meta RequestMetadata, params *model.ListWebhookLogRequest) (*model.WebhookLogs, *ResponseSideEffects, error)
	TestEndpoint(ctx context.Context, meta RequestMetadata, params *model.TestEndpointRequest) (*model.TestEndpointResponse, *ResponseSideEffects, error)

	// Service accounts.
	CreateClient(ctx context.Context, meta RequestMetadata, params *model.CreateClientRequest) (*model.CreateClientResponse, *ResponseSideEffects, error)
	UpdateClient(ctx context.Context, meta RequestMetadata, params *model.UpdateClientRequest) (*model.Client, *ResponseSideEffects, error)
	DeleteClient(ctx context.Context, meta RequestMetadata, params *model.ClientRequest) (*model.Response, *ResponseSideEffects, error)
	RotateClientSecret(ctx context.Context, meta RequestMetadata, params *model.ClientRequest) (*model.CreateClientResponse, *ResponseSideEffects, error)
	Client(ctx context.Context, meta RequestMetadata, params *model.ClientRequest) (*model.Client, *ResponseSideEffects, error)
	Clients(ctx context.Context, meta RequestMetadata, params *model.ListClientsRequest) (*model.Clients, *ResponseSideEffects, error)

	// Trusted issuers.
	AddTrustedIssuer(ctx context.Context, meta RequestMetadata, params *model.AddTrustedIssuerRequest) (*model.TrustedIssuer, *ResponseSideEffects, error)
	UpdateTrustedIssuer(ctx context.Context, meta RequestMetadata, params *model.UpdateTrustedIssuerRequest) (*model.TrustedIssuer, *ResponseSideEffects, error)
	DeleteTrustedIssuer(ctx context.Context, meta RequestMetadata, params *model.TrustedIssuerRequest) (*model.Response, *ResponseSideEffects, error)
	TrustedIssuer(ctx context.Context, meta RequestMetadata, params *model.TrustedIssuerRequest) (*model.TrustedIssuer, *ResponseSideEffects, error)
	TrustedIssuers(ctx context.Context, meta RequestMetadata, params *model.ListTrustedIssuersRequest) (*model.TrustedIssuers, *ResponseSideEffects, error)

	// Per-org SSO OIDC connections (Authorizer as Relying Party).
	CreateOrgOIDCConnection(ctx context.Context, meta RequestMetadata, params *model.CreateOrgOIDCConnectionRequest) (*model.OrgOIDCConnection, *ResponseSideEffects, error)
	UpdateOrgOIDCConnection(ctx context.Context, meta RequestMetadata, params *model.UpdateOrgOIDCConnectionRequest) (*model.OrgOIDCConnection, *ResponseSideEffects, error)
	DeleteOrgOIDCConnection(ctx context.Context, meta RequestMetadata, params *model.OrgOIDCConnectionRequest) (*model.Response, *ResponseSideEffects, error)
	OrgOIDCConnection(ctx context.Context, meta RequestMetadata, params *model.OrgOIDCConnectionRequest) (*model.OrgOIDCConnection, *ResponseSideEffects, error)
	CreateOrgSAMLConnection(ctx context.Context, meta RequestMetadata, params *model.CreateOrgSAMLConnectionRequest) (*model.OrgSAMLConnection, *ResponseSideEffects, error)
	UpdateOrgSAMLConnection(ctx context.Context, meta RequestMetadata, params *model.UpdateOrgSAMLConnectionRequest) (*model.OrgSAMLConnection, *ResponseSideEffects, error)
	DeleteOrgSAMLConnection(ctx context.Context, meta RequestMetadata, params *model.OrgSAMLConnectionRequest) (*model.Response, *ResponseSideEffects, error)
	OrgSAMLConnection(ctx context.Context, meta RequestMetadata, params *model.OrgSAMLConnectionRequest) (*model.OrgSAMLConnection, *ResponseSideEffects, error)

	// SAML IdP: registered downstream SPs, signing-key rotation, SP-metadata import.
	CreateSAMLServiceProvider(ctx context.Context, meta RequestMetadata, params *model.CreateSAMLServiceProviderRequest) (*model.SAMLServiceProvider, *ResponseSideEffects, error)
	UpdateSAMLServiceProvider(ctx context.Context, meta RequestMetadata, params *model.UpdateSAMLServiceProviderRequest) (*model.SAMLServiceProvider, *ResponseSideEffects, error)
	DeleteSAMLServiceProvider(ctx context.Context, meta RequestMetadata, params *model.SAMLServiceProviderRequest) (*model.Response, *ResponseSideEffects, error)
	SAMLServiceProvider(ctx context.Context, meta RequestMetadata, params *model.SAMLServiceProviderRequest) (*model.SAMLServiceProvider, *ResponseSideEffects, error)
	ListSAMLServiceProviders(ctx context.Context, meta RequestMetadata, params *model.ListSAMLServiceProvidersRequest) (*model.SAMLServiceProviders, *ResponseSideEffects, error)
	RotateSAMLIDPCert(ctx context.Context, meta RequestMetadata, params *model.RotateSAMLIDPCertRequest) (*model.SAMLIDPKey, *ResponseSideEffects, error)
	RetireSAMLIDPKey(ctx context.Context, meta RequestMetadata, params *model.RetireSAMLIDPKeyRequest) (*model.Response, *ResponseSideEffects, error)
	ListSAMLIDPKeys(ctx context.Context, meta RequestMetadata, params *model.ListSAMLIDPKeysRequest) ([]*model.SAMLIDPKey, *ResponseSideEffects, error)
	ImportSAMLSPMetadata(ctx context.Context, meta RequestMetadata, params *model.ImportSAMLSPMetadataRequest) (*model.SAMLSPMetadataParseResult, *ResponseSideEffects, error)

	// Organizations and per-org membership.
	CreateOrganization(ctx context.Context, meta RequestMetadata, params *model.CreateOrganizationRequest) (*model.Organization, *ResponseSideEffects, error)
	UpdateOrganization(ctx context.Context, meta RequestMetadata, params *model.UpdateOrganizationRequest) (*model.Organization, *ResponseSideEffects, error)
	DeleteOrganization(ctx context.Context, meta RequestMetadata, params *model.OrganizationRequest) (*model.Response, *ResponseSideEffects, error)
	Organization(ctx context.Context, meta RequestMetadata, params *model.OrganizationRequest) (*model.Organization, *ResponseSideEffects, error)
	Organizations(ctx context.Context, meta RequestMetadata, params *model.ListOrganizationsRequest) (*model.Organizations, *ResponseSideEffects, error)
	AddOrgMember(ctx context.Context, meta RequestMetadata, params *model.AddOrgMemberRequest) (*model.OrgMember, *ResponseSideEffects, error)
	RemoveOrgMember(ctx context.Context, meta RequestMetadata, params *model.RemoveOrgMemberRequest) (*model.Response, *ResponseSideEffects, error)
	OrgMembers(ctx context.Context, meta RequestMetadata, params *model.ListOrgMembersRequest) (*model.OrgMembers, *ResponseSideEffects, error)

	// Per-org inbound SCIM 2.0 endpoints. The bearer token is revealed once.
	CreateScimEndpoint(ctx context.Context, meta RequestMetadata, params *model.CreateScimEndpointRequest) (*model.CreateScimEndpointResponse, *ResponseSideEffects, error)
	RotateScimToken(ctx context.Context, meta RequestMetadata, params *model.ScimEndpointRequest) (*model.CreateScimEndpointResponse, *ResponseSideEffects, error)
	DeleteScimEndpoint(ctx context.Context, meta RequestMetadata, params *model.ScimEndpointRequest) (*model.Response, *ResponseSideEffects, error)
	ScimEndpoint(ctx context.Context, meta RequestMetadata, params *model.ScimEndpointRequest) (*model.ScimEndpoint, *ResponseSideEffects, error)

	// Per-org verified domains for home-realm discovery. request/verify/list/
	// delete are org-admin gated; AddVerifiedOrgDomain is super-admin only.
	RequestOrgDomain(ctx context.Context, meta RequestMetadata, params *model.RequestOrgDomainRequest) (*model.OrgDomainChallenge, *ResponseSideEffects, error)
	VerifyOrgDomain(ctx context.Context, meta RequestMetadata, params *model.VerifyOrgDomainRequest) (*model.OrgDomain, *ResponseSideEffects, error)
	AddVerifiedOrgDomain(ctx context.Context, meta RequestMetadata, params *model.AddVerifiedOrgDomainRequest) (*model.OrgDomain, *ResponseSideEffects, error)
	OrgDomains(ctx context.Context, meta RequestMetadata, params *model.ListOrgDomainsRequest) (*model.OrgDomains, *ResponseSideEffects, error)
	DeleteOrgDomain(ctx context.Context, meta RequestMetadata, params *model.DeleteOrgDomainRequest) (*model.Response, *ResponseSideEffects, error)

	// Email templates.
	AddEmailTemplate(ctx context.Context, meta RequestMetadata, params *model.AddEmailTemplateRequest) (*model.Response, *ResponseSideEffects, error)
	UpdateEmailTemplate(ctx context.Context, meta RequestMetadata, params *model.UpdateEmailTemplateRequest) (*model.Response, *ResponseSideEffects, error)
	DeleteEmailTemplate(ctx context.Context, meta RequestMetadata, params *model.DeleteEmailTemplateRequest) (*model.Response, *ResponseSideEffects, error)
	EmailTemplates(ctx context.Context, meta RequestMetadata, params *model.PaginationRequest) (*model.EmailTemplates, *ResponseSideEffects, error)

	// Audit.
	AuditLogs(ctx context.Context, meta RequestMetadata, params *model.ListAuditLogRequest) (*model.AuditLogs, *ResponseSideEffects, error)

	// FGA admin.
	FgaWriteModel(ctx context.Context, meta RequestMetadata, params *model.FgaWriteModelInput) (*model.FgaModel, *ResponseSideEffects, error)
	FgaWriteTuples(ctx context.Context, meta RequestMetadata, params *model.FgaWriteTuplesInput) (*model.Response, *ResponseSideEffects, error)
	FgaDeleteTuples(ctx context.Context, meta RequestMetadata, params *model.FgaWriteTuplesInput) (*model.Response, *ResponseSideEffects, error)
	FgaReset(ctx context.Context, meta RequestMetadata) (*model.Response, *ResponseSideEffects, error)
	FgaGetModel(ctx context.Context, meta RequestMetadata) (*model.FgaModel, *ResponseSideEffects, error)
	FgaReadTuples(ctx context.Context, meta RequestMetadata, params *model.FgaReadTuplesInput) (*model.FgaTuples, *ResponseSideEffects, error)
	FgaListUsers(ctx context.Context, meta RequestMetadata, params *model.FgaListUsersInput) (*model.FgaListUsersResponse, *ResponseSideEffects, error)
	FgaExpand(ctx context.Context, meta RequestMetadata, params *model.FgaExpandInput) (*model.FgaExpandResponse, *ResponseSideEffects, error)
}

AdminProvider is the transport-agnostic API for Authorizer's super-admin operations (the `_`-prefixed GraphQL queries/mutations). The same concrete *provider that implements Provider also implements AdminProvider; the interface is split to keep the public Provider focused. Every method enforces super-admin auth via requireSuperAdmin except AdminLogin, which establishes it.

During the staged migration this interface grows one domain group at a time (see specs/2026-06-15-authorizer-admin-service-plan.md). The compile-time assertion that *provider satisfies AdminProvider is added once every method has landed (final phase).

type DNSResolver

type DNSResolver interface {
	LookupTXT(ctx context.Context, name string) ([]string, error)
}

DNSResolver is the minimal resolver surface the domain-verification flow needs. *net.Resolver satisfies it; tests inject a mock so no real DNS is hit.

type Dependencies

type Dependencies struct {
	Log *zerolog.Logger

	AuditProvider audit.Provider
	// AuthenticatorProvider registers and validates TOTP authenticators
	// (Google Authenticator) and recovery codes for MFA flows.
	AuthenticatorProvider authenticators.Provider
	// WebAuthnProvider runs WebAuthn/passkey registration and login ceremonies.
	WebAuthnProvider webauthn.Provider
	// AuthzEngine is the fine-grained authorization (FGA) engine.
	// It is nil unless an FGA store is configured (--fga-store);
	// FGA-gated operations MUST fail closed (return an error) when it is nil.
	AuthzEngine         engine.AuthorizationEngine
	EmailProvider       email.Provider
	EventsProvider      events.Provider
	MemoryStoreProvider memory_store.Provider
	SMSProvider         sms.Provider
	StorageProvider     storage.Provider
	TokenProvider       token.Provider
	// RateLimitProvider throttles abuse-prone admin ops (e.g. per-org domain
	// verification, which drives an outbound DNS lookup). Nil disables the limit.
	RateLimitProvider rate_limit.Provider
	// DNSResolver resolves TXT records for domain verification. Nil uses
	// net.DefaultResolver; tests inject a mock so no real DNS is hit.
	DNSResolver DNSResolver
}

Dependencies are the subsystems a Provider needs. The set will grow as more operations migrate from internal/graphql into this package.

type EmailVerification

type EmailVerification struct {
	User *schemas.User
	// Request is the consumed verification row. The caller deletes it once it
	// has finished with it — deletion is deliberately NOT done here, because the
	// two callers finish at different points.
	Request *schemas.VerificationRequest
	// LoginMethod is basic_auth, or magic_link_login when the token came from a
	// magic link.
	LoginMethod string
	// IsSignUp reports whether THIS redemption flipped the address to verified,
	// which is what the callers key their signup-vs-login webhook on.
	IsSignUp bool
	// RedirectURI is the `redirect_uri` claim the token was minted with. The
	// REST handler falls back to it when the query string carries none; it is
	// returned here so the claim never has to leave this function.
	RedirectURI string
}

EmailVerification is the outcome of redeeming a verification token: who the principal is, which flow the token belonged to, and whether this redemption is the one that verified the address.

type Error

type Error struct {
	Kind ErrorKind
	// contains filtered or unexported fields
}

Error is a typed service error carrying a transport-neutral Kind alongside a human-readable message. It implements error and unwraps to any underlying cause so errors.Is/errors.As keep working.

func (*Error) Error

func (e *Error) Error() string

Error returns the human-readable message. When constructed from an underlying error without an explicit message, it falls back to that error's text so existing message-based assertions keep passing.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap exposes the underlying cause for errors.Is / errors.As.

type ErrorKind

type ErrorKind int

ErrorKind classifies a service error independently of any transport.

const (
	// KindInternal is an unexpected server-side failure. Default for any
	// error not explicitly classified. Maps to gRPC Internal / HTTP 500.
	KindInternal ErrorKind = iota
	// KindInvalidArgument is a malformed or semantically invalid request.
	// Maps to gRPC InvalidArgument / HTTP 400.
	KindInvalidArgument
	// KindUnauthenticated is a missing or invalid credential/session.
	// Maps to gRPC Unauthenticated / HTTP 401.
	KindUnauthenticated
	// KindPermissionDenied is an authenticated caller lacking the required
	// permission. Maps to gRPC PermissionDenied / HTTP 403.
	KindPermissionDenied
	// KindNotFound is a referenced resource that does not exist.
	// Maps to gRPC NotFound / HTTP 404.
	KindNotFound
	// KindFailedPrecondition is a request that is well-formed but not
	// permitted in the server's current state (e.g. signup disabled).
	// Maps to gRPC FailedPrecondition / HTTP 400.
	KindFailedPrecondition
	// KindTooManyRequests is a request rejected because the caller exceeded
	// a rate/attempt limit (e.g. MFA verification locked after repeated
	// failures). Maps to gRPC ResourceExhausted / HTTP 429.
	KindTooManyRequests
	// KindAlreadyExists is a request that violates a uniqueness constraint
	// (e.g. an organization/email/issuer that is already registered).
	// Maps to gRPC AlreadyExists / HTTP 409.
	KindAlreadyExists
)

type Provider

type Provider interface {
	// SignUp registers a new user. Public — no authentication required.
	SignUp(ctx context.Context, meta RequestMetadata, params *model.SignUpRequest) (*model.AuthResponse, *ResponseSideEffects, error)

	// Meta returns server discovery information (feature flags + provider
	// availability). Public — no authentication required.
	Meta(ctx context.Context, meta RequestMetadata) (*model.Meta, *ResponseSideEffects, error)

	// Profile returns the authenticated user. Requires session/bearer auth.
	Profile(ctx context.Context, meta RequestMetadata) (*model.User, *ResponseSideEffects, error)

	// EnrolledMFAMethods returns the MFA method identifiers the user has
	// verified/enrolled ("totp", "webauthn", "email_otp", "sms_otp"). Read
	// helper backing the User.enrolled_mfa_methods GraphQL field resolver;
	// takes a resolved user ID (not caller input), so it carries no meta and
	// no side effects. Never nil.
	EnrolledMFAMethods(ctx context.Context, userID string) ([]string, error)

	// CheckPermissions evaluates one or more fine-grained permission checks
	// for the caller (or, for super-admins, an explicit subject). Requires
	// session/bearer auth and a configured FGA engine (fail-closed).
	CheckPermissions(ctx context.Context, meta RequestMetadata, params *model.CheckPermissionsInput) (*model.CheckPermissionsResponse, *ResponseSideEffects, error)

	// ListPermissions enumerates what the caller (or, for super-admins, an
	// explicit subject) can access. Requires session/bearer auth and a
	// configured FGA engine (fail-closed).
	ListPermissions(ctx context.Context, meta RequestMetadata, params *model.ListPermissionsInput) (*model.ListPermissionsResponse, *ResponseSideEffects, error)

	// Logout ends the caller's current session. Browser callers get
	// expired Set-Cookie headers via side-effects. Requires auth.
	Logout(ctx context.Context, meta RequestMetadata) (*model.Response, *ResponseSideEffects, error)

	// Revoke invalidates a refresh token. Typed mirror of RFC 7009.
	Revoke(ctx context.Context, meta RequestMetadata, params *model.OAuthRevokeRequest) (*model.Response, *ResponseSideEffects, error)

	// ValidateJwtToken validates a JWT (access/id/refresh) without rotation.
	ValidateJwtToken(ctx context.Context, meta RequestMetadata, params *model.ValidateJWTTokenRequest) (*model.ValidateJWTTokenResponse, *ResponseSideEffects, error)

	// ValidateSession validates a cookie session without rotation.
	ValidateSession(ctx context.Context, meta RequestMetadata, params *model.ValidateSessionRequest) (*model.ValidateSessionResponse, *ResponseSideEffects, error)

	// Session returns the AuthResponse bound to the caller's cookie/bearer
	// AND rotates the session token. Browser callers get a fresh
	// Set-Cookie via side-effects.
	Session(ctx context.Context, meta RequestMetadata, params *model.SessionQueryRequest) (*model.AuthResponse, *ResponseSideEffects, error)

	// DeactivateAccount marks the authenticated caller's account as revoked
	// and drops all of their sessions. Requires auth.
	DeactivateAccount(ctx context.Context, meta RequestMetadata) (*model.Response, *ResponseSideEffects, error)

	// SkipMFASetup completes a token-withheld first-time MFA offer by
	// recording the decline and issuing the previously-withheld token.
	// Identified via the MFA session cookie, not a bearer token — none
	// exists yet at this point in the flow.
	SkipMFASetup(ctx context.Context, meta RequestMetadata, params *model.SkipMfaSetupRequest) (*model.AuthResponse, *ResponseSideEffects, error)

	// LockMFA records that the authenticated-in-progress caller lost access
	// to their only MFA factor(s). Requires no verified Email/SMS OTP
	// fallback exists for the user — otherwise that should be used instead.
	// Does not issue a token.
	LockMFA(ctx context.Context, meta RequestMetadata, params *model.LockMfaRequest) (*model.Response, *ResponseSideEffects, error)

	// EmailOTPMFASetup sends a one-time code to the caller's own email and
	// begins an email-OTP MFA enrollment. Verified via VerifyOTP. Dual-mode:
	// an authenticated caller (bearer token, params ignored) — the
	// settings-screen action — OR, absent a token, the MFA session cookie
	// plus params.email/phone_number for a caller in the withheld
	// first-time-offer state.
	EmailOTPMFASetup(ctx context.Context, meta RequestMetadata, params *model.OtpMfaSetupRequest) (*model.Response, *ResponseSideEffects, error)
	// SMSOTPMFASetup is EmailOTPMFASetup's SMS twin.
	SMSOTPMFASetup(ctx context.Context, meta RequestMetadata, params *model.OtpMfaSetupRequest) (*model.Response, *ResponseSideEffects, error)
	// TOTPMFASetup generates a fresh TOTP secret/QR/recovery-codes for the
	// caller to enroll as an MFA method, same dual-mode permissions as
	// EmailOTPMFASetup/SMSOTPMFASetup. Unlike those, nothing is sent
	// anywhere - the enrollment payload is returned directly (same shape
	// login.go's gate response uses) so the caller scans the QR/enters the
	// code, then completes enrollment via VerifyOTP(is_totp: true).
	TOTPMFASetup(ctx context.Context, meta RequestMetadata, params *model.OtpMfaSetupRequest) (*model.AuthResponse, *ResponseSideEffects, error)

	// ResendVerifyEmail re-issues a pending email-verification link. Public —
	// response is generic to avoid account enumeration.
	ResendVerifyEmail(ctx context.Context, meta RequestMetadata, params *model.ResendVerifyEmailRequest) (*model.Response, *ResponseSideEffects, error)

	// ResendOTP re-issues a one-time passcode for an MFA/verification
	// challenge. Public.
	ResendOTP(ctx context.Context, meta RequestMetadata, params *model.ResendOTPRequest) (*model.Response, *ResponseSideEffects, error)

	// ForgotPassword issues a password-reset token (email) or OTP (SMS).
	// Public — response is generic to avoid account enumeration.
	ForgotPassword(ctx context.Context, meta RequestMetadata, params *model.ForgotPasswordRequest) (*model.ForgotPasswordResponse, *ResponseSideEffects, error)

	// ResetPassword completes a password reset using a verification token
	// (email) or OTP (SMS). Public.
	ResetPassword(ctx context.Context, meta RequestMetadata, params *model.ResetPasswordRequest) (*model.Response, *ResponseSideEffects, error)

	// UpdateProfile updates the authenticated caller's profile. Requires auth.
	// May rotate/clear the session cookie (e.g. on email change) via
	// side-effects.
	UpdateProfile(ctx context.Context, meta RequestMetadata, params *model.UpdateProfileRequest) (*model.Response, *ResponseSideEffects, error)

	// MagicLinkLogin sends a passwordless login link. Public — response is
	// generic to avoid account enumeration.
	MagicLinkLogin(ctx context.Context, meta RequestMetadata, params *model.MagicLinkLoginRequest) (*model.Response, *ResponseSideEffects, error)

	// Login authenticates a user via email/phone + password, issuing tokens
	// or initiating an MFA challenge. Browser callers get Set-Cookie via
	// side-effects. Public.
	Login(ctx context.Context, meta RequestMetadata, params *model.LoginRequest) (*model.AuthResponse, *ResponseSideEffects, error)

	// VerifyEmail completes email verification and logs the user in. Browser
	// callers get a session cookie via side-effects. Public.
	VerifyEmail(ctx context.Context, meta RequestMetadata, params *model.VerifyEmailRequest) (*model.AuthResponse, *ResponseSideEffects, error)
	// ConsumeEmailVerificationToken is the shared decision core behind both
	// implementations of email verification (the GraphQL/gRPC mutation and the
	// REST handler behind GET /verify_email). See verify_email_core.go for why
	// it is shared rather than duplicated.
	ConsumeEmailVerificationToken(ctx context.Context, hostname, rawToken string) (*EmailVerification, error)

	// VerifyOTP validates an email/SMS OTP or TOTP/recovery code and logs the
	// user in. Browser callers get a session cookie via side-effects. Public.
	VerifyOTP(ctx context.Context, meta RequestMetadata, params *model.VerifyOTPRequest) (*model.AuthResponse, *ResponseSideEffects, error)

	// WebauthnRegistrationOptions begins a passkey registration ceremony for the
	// caller: bearer-token authenticated (settings page) or, mid MFA-offer,
	// MFA-session-cookie authenticated. Public (self-service).
	WebauthnRegistrationOptions(ctx context.Context, meta RequestMetadata, email, phoneNumber *string) (*model.WebauthnRegistrationOptionsResponse, error)
	// WebauthnRegistrationVerify verifies the attestation and stores the passkey
	// for the caller. When MFA-session authenticated, also completes the MFA
	// gate and issues the withheld auth token. Public (self-service).
	WebauthnRegistrationVerify(ctx context.Context, meta RequestMetadata, params *model.WebauthnRegistrationVerifyRequest) (*model.AuthResponse, *ResponseSideEffects, error)
	// WebauthnLoginOptions begins a passkey login ceremony — usernameless when
	// email is nil, else scoped to that user's credentials. Public.
	WebauthnLoginOptions(ctx context.Context, meta RequestMetadata, email *string) (*model.WebauthnLoginOptionsResponse, error)
	// WebauthnLoginVerify verifies a passkey assertion and logs the user in.
	// Browser callers get a session cookie via side-effects. Public.
	WebauthnLoginVerify(ctx context.Context, meta RequestMetadata, params *model.WebauthnLoginVerifyRequest) (*model.AuthResponse, *ResponseSideEffects, error)
	// WebauthnCredentials lists the authenticated caller's own passkeys. Requires
	// a session. Public (self-service).
	WebauthnCredentials(ctx context.Context, meta RequestMetadata) ([]*model.WebauthnCredentialInfo, error)
	// WebauthnDeleteCredential deletes one of the authenticated caller's own
	// passkeys. Requires a session. Public (self-service).
	WebauthnDeleteCredential(ctx context.Context, meta RequestMetadata, id string) (*model.Response, error)

	// EvaluateMFAGateForOAuth runs the same MFA gate Login/SignUp/
	// WebauthnLoginVerify use, for a user who just completed an OAuth/
	// social-provider callback - or, via VerifyEmailHandler, a magic-link/
	// email-verification click-through, which needs the identical
	// gate-then-redirect shape. On a withhold-group outcome it sets the MFA
	// session cookie via side and returns (true, redirectSuffix) where
	// redirectSuffix is the query string to append instead of the normal
	// state/code params. On mfaGateNone/mfaGateSkippedSetup it returns
	// (false, "") and the caller proceeds with cookie.SetSession as today.
	EvaluateMFAGateForOAuth(ctx context.Context, meta RequestMetadata, side *ResponseSideEffects, user *schemas.User) (withheld bool, redirectSuffix string, err error)
}

Provider is the transport-agnostic API for Authorizer public operations. Each method takes the inbound RequestMetadata and returns a typed response plus a ResponseSideEffects describing cookies (and other transport artifacts) the caller must apply.

During the staged migration from internal/graphql, this interface grows one method per phase. Operations not yet migrated continue to live as graphqlProvider methods until they're moved here.

func New

func New(cfg *config.Config, deps *Dependencies) (Provider, error)

New constructs a new service provider.

type RequestMetadata

type RequestMetadata struct {
	// HostURL is the authorizer-server base URL as derived from the request
	// (X-Authorizer-URL header, then X-Forwarded-Proto + X-Forwarded-Host,
	// finally Request.Host). Always populated.
	HostURL string

	// IPAddress is the best-effort client IP (honors X-Forwarded-For).
	IPAddress string

	// UserAgent is the User-Agent header value.
	UserAgent string

	// AuthorizationHeader is the raw `Authorization` header (typically
	// "Bearer <token>"). Empty when absent.
	AuthorizationHeader string

	// Cookies sent on the request. Use the typed cookie helpers when reading
	// session/mfa cookies — never reach for these unless you know what you
	// want.
	Cookies []*http.Cookie

	// Request is the raw inbound *http.Request. Provided as an escape hatch
	// for token-provider helpers that still take a gin.Context internally;
	// new service code should prefer the typed fields above.
	Request *http.Request

	// Protocol is the transport the request came in on — one of
	// constants.Protocol{GraphQL,GRPC,REST}. Surfaced in audit logs and the
	// authorizer_api_operations_total metric so each operation is attributable
	// to its protocol. Empty when the transport did not set it.
	Protocol string
}

RequestMetadata is the transport-derived context every service method receives. Fields are populated by the transport (see MetaFromGin) and read by handlers; the underlying *http.Request is exposed for legacy helpers that haven't yet been refactored to take this struct directly.

func MetaFromGin

func MetaFromGin(gc *gin.Context) RequestMetadata

MetaFromGin builds a RequestMetadata from a gin.Context. The gin-aware transport (GraphQL resolver, REST handler mounted under Gin) calls this once per request before invoking a service method.

type ResponseSideEffects

type ResponseSideEffects struct {
	// Cookies to set on the response. Each cookie's Domain, Path, Secure,
	// SameSite, and MaxAge fields are honored as set; the transport adds them
	// verbatim (gin: gc.SetSameSite + gc.SetCookie; net/http: http.SetCookie).
	Cookies []*http.Cookie

	// OfferMFASetupQuiet is true when the MFA gate decided the user already
	// skipped setup before — no enrollment payload, no offer flag, just a
	// normal login.
	OfferMFASetupQuiet bool
}

ResponseSideEffects collects out-of-band artifacts produced by a service method that the transport must apply to its response. Today that's just cookies; future additions may include redirect targets or trailing headers.

func (*ResponseSideEffects) AddCookie

func (s *ResponseSideEffects) AddCookie(c *http.Cookie)

AddCookie appends a cookie to the side-effects. Convenience over manual slice ops; safe on a zero-value receiver.

Directories

Path Synopsis
Package clientauth resolves and authenticates the OAuth client presented at the token endpoint (RFC 6749 §2.3).
Package clientauth resolves and authenticates the OAuth client presented at the token endpoint (RFC 6749 §2.3).
Package scim implements a per-organization inbound SCIM 2.0 server for user provisioning and deprovisioning (RFC 7643/7644, users only).
Package scim implements a per-organization inbound SCIM 2.0 server for user provisioning and deprovisioning (RFC 7643/7644, users only).

Jump to

Keyboard shortcuts

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