httpapi

package
v0.147.0 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const (
	// 2FA-specific rate limit buckets
	RL2FAStartPhone      = "auth_2fa_start_phone"
	RL2FAStartTOTP       = "auth_2fa_start_totp"
	RL2FAStartEmail      = "auth_2fa_start_email"
	RL2FAEnable          = "auth_2fa_enable"
	RL2FADisable         = "auth_2fa_disable"
	RL2FARegenerateCodes = "auth_2fa_regenerate_codes"
	RL2FAVerify          = "auth_2fa_verify"

	RLAuthToken                = "auth_token"
	RLAuthRegister             = "auth_register"
	RLAuthRegisterAvailability = "auth_register_availability"
	RLAuthRegisterAbandon      = "auth_register_abandon"
	RLInviteCreate             = "auth_invite_create"
	RLInviteRedeem             = "auth_invite_redeem"
	RLAPIKeyMint               = "auth_api_key_mint"
	RLPasswordLogin            = "auth_password_login"
	RLPasswordStepUp           = "auth_password_step_up"
	RLPasswordlessStart        = "auth_passwordless_start"
	RLPasswordlessConfirm      = "auth_passwordless_confirm"
	RLPasskeyRegister          = "auth_passkey_register"
	RLPasskeyLogin             = "auth_passkey_login"
	RLDeviceKeyEnrollBegin     = "auth_device_key_enroll_begin"
	RLDeviceKeyEnrollFinish    = "auth_device_key_enroll_finish"
	RLDeviceKeyLoginBegin      = "auth_device_key_login_begin"
	RLDeviceKeyLoginFinish     = "auth_device_key_login_finish"
	RLDeviceKeysManage         = "auth_device_keys_manage"
	RLAuthLogout               = "auth_logout"
	RLAuthSessionsList         = "auth_sessions_list"
	RLAuthSessionsRevoke       = "auth_sessions_revoke"
	RLAuthSessionsRevokeAll    = "auth_sessions_revoke_all"

	// #261 delegated-token mint (authenticated; bounds signing cost per IP).
	RLDelegatedTokenMint = "delegated_token_mint"

	RLPasswordResetRequest = "auth_pwd_reset_request"
	RLPasswordResetConfirm = "auth_pwd_reset_confirm"
	// #312: one bucket per contact flow, whichever channel the identifier names.
	RLVerifyRequest        = "auth_verify_request"
	RLVerifyConfirm        = "auth_verify_confirm"
	RLContactChangeRequest = "auth_contact_change_request"

	RLOIDCStart    = "auth_oidc_start"
	RLOIDCCallback = "auth_oidc_callback"

	RLUserPasswordChange    = "auth_user_password_change"
	RLUserMe                = "auth_user_me"
	RLUserUpdateUsername    = "auth_user_update_username"
	RLUserPreferredLanguage = "auth_user_preferred_language"

	RLUserDelete         = "auth_user_delete"
	RLUserUnlinkProvider = "auth_user_unlink_provider"

	RLAdminUserSessionsList = "auth_admin_user_sessions_list"
	// The admin session route revokes ALL of a user's sessions; there is no
	// single-session admin revoke, so no RLAdminUserSessionsRevoke bucket.
	RLAdminUserSessionsRevokeAll = "auth_admin_user_sessions_revoke_all"

	// Solana SIWS authentication
	RLSolanaChallenge = "auth_solana_challenge"
	RLSolanaLogin     = "auth_solana_login"
	RLSolanaLink      = "auth_solana_link"
)

Bucket names used by authkit endpoints; they key HTTPConfig.RateLimits.

View Source
const (
	CookieRefresh   cookieKind = "refresh"
	CookieOIDCState cookieKind = "oidc_state"
	OIDCStatePrefix            = "authkit_oauth_state_"
)
View Source
const OIDCPath = "/oidc"

Mount layout. The whole surface lives beneath one base path: the path of the issuer, so verifiers find JWKS at the issuer plus iam.JWKSPath. Beneath it, browser OIDC sits at OIDCPath and the JSON API at APIPath. The surface is ONE handler.

Variables

View Source
var CookieRegistry = []CookieVariant{
	{Kind: CookieRefresh, Name: "authkit_rt", Path: "/", Current: true},
	{Kind: CookieRefresh, Name: "__Host-authkit_rt", Path: "/", Secure: true, Current: true},
	{Kind: CookieOIDCState, Name: OIDCStatePrefix, Path: "/", Current: true},
	{Kind: CookieOIDCState, Name: "__Host-" + OIDCStatePrefix, Path: "/", Secure: true, Current: true},
}

CookieRegistry is append-only.

View Source
var GroupRoutes = []GroupRoute{
	{http.MethodGet, "/groups/:group_id/members", OpMembersList},
	{http.MethodPost, "/groups/:group_id/members", OpMemberAdd},
	{http.MethodDelete, "/groups/:group_id/members/:user", OpMemberRemove},
	{http.MethodPut, "/groups/:group_id/members/:user/roles/:role", OpMemberRoleAssign},
	{http.MethodGet, "/groups/:group_id/roles", OpRolesList},
	{http.MethodGet, "/groups/:group_id/api-keys", OpAPIKeysList},
	{http.MethodPost, "/groups/:group_id/api-keys", OpAPIKeyMint},
	{http.MethodDelete, "/groups/:group_id/api-keys/:key", OpAPIKeyRevoke},
	{http.MethodGet, "/groups/:group_id/invites/links", OpInviteLinkList},
	{http.MethodPost, "/groups/:group_id/invites/links", OpInviteLinkMint},
	{http.MethodDelete, "/groups/:group_id/invites/links/:link", OpInviteLinkRevoke},
}

GroupRoutes is the whole group-management surface.

Functions

func DefaultRateLimits

func DefaultRateLimits() map[string]ratelimit.Limit

DefaultRateLimits returns AuthKit's built-in per-endpoint rate limits, per client IP; "default" applies to any bucket not listed. Hosts overlay them with HTTPConfig.RateLimits or replace the limiter.

func MuxPath

func MuxPath(p string) string

MuxPath rewrites colon-style params (":group_id", ":user", ...) into net/http ServeMux wildcards ("{group_id}", "{user}", ...). ServeMux wildcard names may not contain '-', so hyphens become underscores; pathParam() reverses this when reading r.PathValue.

func SanitizeReturnTo

func SanitizeReturnTo(value string) string

sanitizeReturnTo admits only a same-origin absolute path: a leading "/" but not "//" or "/\" (browsers read both as scheme-relative), no control characters, no scheme or host. Anything else becomes "/".

Types

type AuthCapabilities

type AuthCapabilities struct {
	Registration           AuthRegistrationCapabilities `json:"registration"`
	ExternalLoginProviders []AuthProviderSummary        `json:"external_login_providers"`
	Username               AuthUsernameCapabilities     `json:"username"`
	Password               AuthPasswordCapabilities     `json:"password"`
	Passwordless           AuthPasswordlessCapabilities `json:"passwordless"`
	Passkeys               AuthPasskeyCapabilities      `json:"passkeys"`
	Solana                 AuthSolanaCapabilities       `json:"solana"`
	Verification           AuthVerificationCapabilities `json:"verification"`
	Channels               AuthChannelCapabilities      `json:"channels"`
	Languages              []string                     `json:"languages,omitempty"`
	Paths                  AuthPaths                    `json:"paths"`
}

AuthCapabilities is the public, static auth feature-discovery response.

type AuthChannelCapabilities

type AuthChannelCapabilities struct {
	Email bool `json:"email"`
	SMS   bool `json:"sms"`
}

AuthChannelCapabilities says which contact channels can deliver now: a sender is configured and, for SMS, its latest health check passed.

type AuthPasskeyCapabilities

type AuthPasskeyCapabilities struct {
	Login bool `json:"login"`
}

type AuthPasswordCapabilities

type AuthPasswordCapabilities struct {
	MinLength        int  `json:"min_length"`
	MaxLength        int  `json:"max_length"`
	RequireUppercase bool `json:"require_uppercase"`
	RequireLowercase bool `json:"require_lowercase"`
	RequireDigit     bool `json:"require_digit"`
	RequireSymbol    bool `json:"require_symbol"`
	RejectCommon     bool `json:"reject_common"`
}

AuthPasswordCapabilities publishes everything a browser needs to pre-validate a new password except the blocklist itself.

type AuthPasswordlessCapabilities

type AuthPasswordlessCapabilities struct {
	Enabled  bool     `json:"enabled"`
	Channels []string `json:"channels,omitempty"`
}

type AuthPaths

type AuthPaths struct {
	API  string `json:"api"`
	OIDC string `json:"oidc,omitempty"`
	JWKS string `json:"jwks,omitempty"`
}

AuthPaths are the serving mount's anchors as full paths, so a client that knows one AuthKit URL finds the rest. Unmounted anchors are omitted.

type AuthProviderSummary

type AuthProviderSummary struct {
	ID                   string `json:"id"`
	Name                 string `json:"name"`
	SupportsLogin        bool   `json:"supports_login"`
	SupportsRegistration bool   `json:"supports_registration"`
	SupportsLink         bool   `json:"supports_link"`
}

type AuthRegistrationCapabilities

type AuthRegistrationCapabilities struct {
	Mode                string `json:"mode"`
	InviteTokenRequired bool   `json:"invite_token_required"`
}

type AuthSolanaCapabilities

type AuthSolanaCapabilities struct {
	Login bool `json:"login"`
}

type AuthUsernameCapabilities

type AuthUsernameCapabilities struct {
	MinLength             int               `json:"min_length"`
	MaxLength             int               `json:"max_length"`
	Pattern               string            `json:"pattern"`
	Renames               bool              `json:"renames"`
	RenameIntervalSeconds int64             `json:"rename_interval_seconds"`
	FormerNames           naming.PolicyInfo `json:"former_names"`
}

AuthUsernameCapabilities publishes the interactive username rule. Pattern is the fixed character rule; length is bounded separately. Renames says whether users may rename themselves, and how often.

type AuthVerificationCapabilities

type AuthVerificationCapabilities struct {
	Registration string `json:"registration"`
}

type Backend

type Backend interface {
	ops.Operations
	// contains filtered or unexported methods
}

Backend is the engine capability the HTTP layer drives: the operations the Client exposes (ops.Operations) plus the flows only the HTTP layer runs. The engine implements it; hosts never see it. Each domain's flow methods live in its own backend_<domain>.go.

type ClientIPFunc

type ClientIPFunc func(r *http.Request) string

ClientIPFunc determines the client IP used for rate limiting and auditing.

Returning an empty string means "unknown" and causes rate limiting to fail open.

func ClientIPFromForwardedHeaders

func ClientIPFromForwardedHeaders(trusted, cloudflare []netip.Prefix) ClientIPFunc

ClientIPFromForwardedHeaders derives the client IP behind proxies the host declared. A peer inside trusted or cloudflare enables the right-to-left X-Forwarded-For walk (hops in either set are skipped as our own). Only a peer inside cloudflare may additionally be trusted for CF-Connecting-IP, and only as a fallback when X-Forwarded-For yields nothing: a generic reverse proxy forwards CF-Connecting-IP verbatim, so honouring it from any trusted peer let a client pick its own rate-limit key (ak#298). Any other peer resolves to itself.

Hosts that pass a cloudflare set must also lock the origin down to Cloudflare ingress; otherwise a client that reaches the origin directly is its own peer and both headers are ignored, which is the safe outcome.

func DefaultClientIP

func DefaultClientIP() ClientIPFunc

DefaultClientIP returns the immediate peer IP from RemoteAddr.

This intentionally includes private and loopback peers so embedded/local deployments still get default rate-limit protection. Hosts behind reverse proxies should use ClientIPFromForwardedHeaders with trusted proxy CIDRs when they need the original public client IP instead of the proxy peer.

type CookieVariant

type CookieVariant struct {
	Kind   cookieKind
	Name   string
	Path   string
	Domain string // AuthKit never sets Domain: every variant is host-only
	Secure bool   // issued on HTTPS deployments (always true for __Host-)
	// Current marks the variant AuthKit issues now, per Secure mode.
	Current bool
}

CookieVariant is one cookie shape AuthKit issues. An OIDC state name is a prefix completed by stateCookieName.

func CurrentCookie

func CurrentCookie(kind cookieKind, secure bool) CookieVariant

func (CookieVariant) Identity

func (v CookieVariant) Identity() string

Identity names a variant in testdata/cookie-registry.golden.

type DelegatedTokenResponse

type DelegatedTokenResponse struct {
	Token     string    `json:"token"`
	ExpiresAt time.Time `json:"expires_at"`
	TokenType string    `json:"token_type,omitempty"`
}

type GroupOp

type GroupOp int

GroupOp is the operation a group route performs.

const (
	OpMembersList GroupOp = iota + 1
	OpMemberAdd
	OpMemberRemove
	OpMemberRoleAssign
	OpRolesList
	OpAPIKeysList
	OpAPIKeyMint
	OpAPIKeyRevoke
	OpInviteLinkList
	OpInviteLinkMint
	OpInviteLinkRevoke
)

func (GroupOp) Available

func (op GroupOp) Available(p rbac.Persona) bool

Available reports whether groups of persona p have the operation. Root's members are managed through the admin routes.

func (GroupOp) Perms

func (op GroupOp) Perms(p rbac.Persona) []iam.Perm

Perms returns the permissions of persona p that admit the operation: any one of them suffices.

type GroupRoute

type GroupRoute struct {
	Method string
	Path   string // e.g. /groups/:group_id/members
	Op     GroupOp
}

GroupRoute is one group-management endpoint.

func MountedGroupRoutes

func MountedGroupRoutes(s *rbac.Schema) []GroupRoute

MountedGroupRoutes returns the group routes some persona of s has.

type Mount

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

Mount is the canonical HTTP handler and its route catalog. Framework adapters use the catalog to register native routes, delegating requests to ServeHTTP so AuthKit still owns path values, authentication, JSON and cookie guards.

func NewMount

func NewMount(svc *Service) (result *Mount, err error)

NewMount builds the full AuthKit surface — JSON API, browser OIDC and JWKS — as ONE net/http handler plus its route catalog, as Config.HTTP declares it. Every route keeps the gate its RouteSpec carries; the mount adds no auth and removes none. Excluding a route does not alter the MFA-enrollment exempt set, so a shadowed enroll route stays reachable through the host's replacement.

func (*Mount) Routes

func (m *Mount) Routes() []iam.Route

Routes returns a copy of the configured endpoints, including JWKS and enabled document/OIDC routes; GET endpoints also have a HEAD entry. It never advertises disabled or excluded endpoints.

func (*Mount) ServeHTTP

func (m *Mount) ServeHTTP(w http.ResponseWriter, r *http.Request)

type RateLimitResult

type RateLimitResult struct {
	Allowed      bool
	RetryAfter   time.Duration
	Availability *authflow.ActionAvailability
}

type RateLimiter

type RateLimiter interface {
	AllowNamed(bucket string, key string) (bool, error)
}

RateLimiter is a minimal interface used by adapters.

type RateLimiterWithResult

type RateLimiterWithResult interface {
	AllowNamedResult(bucket string, key string) (ratelimit.Result, error)
}

type RouteSpec

type RouteSpec struct {
	Method  string
	Path    string
	Group   iam.RouteGroup
	Handler http.Handler
	// Auth is the tier the handler wrapper enforces before the handler runs;
	// Permission names the root/group permission for AuthPermission (#328).
	Auth       iam.RouteAuthTier
	Permission string
	// Bucket is the per-IP rate-limit bucket APIRoutes applies in front of the
	// handler ("" = none). Per-identifier and branch-specific buckets stay in
	// the handler.
	Bucket string
	// MFAEnrollmentExempt marks a route as part of the 2FA enroll/challenge/
	// verify surface a forced-enrollment-gated user (TwoFactor.Mode required)
	// must still be able to reach. NewMount derives the engine's exempt-path
	// allowlist from routes tagged here (#243) — the route table is the single
	// source of truth, so a rename/add stays consistent by construction.
	MFAEnrollmentExempt bool
}

RouteSpec is a concrete, prefix-neutral route with its AuthKit handler attached. Path parameters use net/http ServeMux syntax, e.g. "/namespaces/{slug}".

type Service

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

Service wraps the internal AuthKit engine with net/http mounting helpers.

func New

func New(client Backend, cfg config.Config, deps config.Deps) (*Service, error)

New assembles the HTTP layer over the engine, which also authenticates its requests, from the normalized configuration. authkit.New is the only production caller.

func (*Service) APIRoutes

func (s *Service) APIRoutes(groups ...iam.RouteGroup) []RouteSpec

APIRoutes returns AuthKit's enabled JSON API routes. With no groups it returns the default API surface. With groups, it returns only matching routes.

func (*Service) Backend

func (s *Service) Backend() Backend

Backend returns the engine the service drives.

func (*Service) Capabilities

func (s *Service) Capabilities() AuthCapabilities

func (*Service) Close

func (s *Service) Close()

Close stops the background work New started: the memory limiter sweep. The engine and Redis client are borrowed and remain owned by the host. Idempotent; safe on a nil Service.

func (*Service) GroupHandler

func (s *Service) GroupHandler(gr GroupRoute) http.HandlerFunc

GroupHandler returns the handler for one group route. It:

  1. derives the caller's actor (401 if none; 403 for a delegation);
  2. resolves :group_id to a live group;
  3. refuses a group whose persona lacks the route, like an unknown group;
  4. authorizes the route's permission on the group with the engine's live Can, for every actor kind (403 on deny);
  5. performs the operation, whose engine call applies its own rules.

func (*Service) JWKSHandler

func (s *Service) JWKSHandler() http.Handler

JWKSHandler returns a handler for GET /.well-known/jwks.json. The key set is read per request so a hot-reloaded rotation or key removal is published immediately (ak#392).

func (*Service) OIDCBrowserRoutes

func (s *Service) OIDCBrowserRoutes(groups ...iam.RouteGroup) []RouteSpec

OIDCBrowserRoutes returns browser redirect routes with no mount prefix.

func (*Service) PermissionGroupRoutes

func (s *Service) PermissionGroupRoutes() []RouteSpec

PermissionGroupRoutes returns the group-management routes some persona has, plus the caller's own groups and permissions. Mirrors APIRoutes: prefix-neutral RouteSpecs, rate-limited by their bucket, language-wrapped and gated by their tier.

func (*Service) SMSAvailable

func (s *Service) SMSAvailable() bool

SMSAvailable reports whether phone-based flows should be offered (a sender is configured and, if checked, found able to deliver).

type TwoFactorFactorResponse

type TwoFactorFactorResponse struct {
	ID          string  `json:"id,omitempty"`
	Method      string  `json:"method"`
	IsDefault   bool    `json:"is_default,omitempty"`
	PhoneNumber *string `json:"phone_number,omitempty"`
	// Email is the masked address an email factor's codes go to.
	Email *string `json:"email,omitempty"`
}

Jump to

Keyboard shortcuts

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