Help us shape the future of Go! Take the 2026 Go Developer Survey

goauth

package module
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 26 Imported by: 14

README

goauth

goauth is a reusable authentication, identity, session, and RBAC Runtime for Go hosts. PostgreSQL stores canonical auth state, Fiber is an optional HTTP adapter, and Redis is needed only for optional OIDC one-time state.

The current release version is v0.4.0. The frozen v0.1 line ends at v0.1.7; consumers crossing that schema boundary must explicitly reset isolated development or test auth state. Existing tags remain immutable.

Supported API

The supported imports are intentionally small:

  • github.com/assurrussa/goauth
  • github.com/assurrussa/goauth/postgres
  • github.com/assurrussa/goauth/redis
  • github.com/assurrussa/goauth/fiber
  • github.com/assurrussa/goauth/oidc
  • github.com/assurrussa/goauth/oidc/provider
  • github.com/assurrussa/goauth/oidc/verifier
  • github.com/assurrussa/goauth/rbac
  • github.com/assurrussa/goauth/testkit

reference/externalconsumer.SupportedPackages is the machine-readable source of truth. Earlier v0.1 releases remain available at their immutable tags; the retired implementation is absent from this repository.

PostgreSQL and Fiber quickstart

The host supplies a PostgreSQL DSN, three distinct 32-byte versioned key rings, and a goauth.NotificationSender implementation backed by its email or other delivery provider. This wiring mounts a minimal user-realm API:

import (
    "context"
    "net/url"

    "github.com/assurrussa/goauth"
    goauthfiber "github.com/assurrussa/goauth/fiber"
    "github.com/assurrussa/goauth/postgres"
    "github.com/gofiber/fiber/v3"
)

func wireAuth(
    dsn string,
    signingKeys, tokenKeys, notificationKeys goauth.KeyRing,
    sender goauth.NotificationSender,
) (*fiber.App, *postgres.Runtime, error) {
    auth, err := postgres.NewRuntime(postgres.Config{
        DSN:                dsn,
        AutoMigrate:        true, // use the host's migration lifecycle in production
        NotificationSender: sender,
        Runtime: goauth.Config{
            Signing: goauth.SigningConfig{
                Issuer: "https://auth.example.com", Audience: "example-app", Keys: signingKeys,
            },
            TokenHMACKeys:  tokenKeys,
            OutboxAEADKeys: notificationKeys,
            URLBuilder: goauth.URLBuilderFunc(func(_ context.Context, token string) (string, error) {
                return "https://app.example.com/reset?token=" + url.QueryEscape(token), nil
            }),
        },
    })
    if err != nil {
        return nil, nil, err
    }
    adapter, err := goauthfiber.New(auth)
    if err != nil {
        _ = auth.Close()
        return nil, nil, err
    }
    app := fiber.New()
    app.Post("/auth/register", adapter.Register)
    app.Post("/auth/login", adapter.Login)
    app.Post("/auth/refresh", adapter.Refresh)
    app.Post("/auth/password-reset", adapter.RequestPasswordReset)
    app.Post("/auth/email-challenge", adapter.RequireRealm(goauth.RealmUser,
        goauthfiber.RealmMiddlewareOptions{Introspect: true, AllowConfirmation: true}),
        adapter.SendEmailChallenge)
    app.Post("/auth/email-challenge/verify", adapter.RequireRealm(goauth.RealmUser,
        goauthfiber.RealmMiddlewareOptions{Introspect: true, AllowConfirmation: true}),
        adapter.VerifyEmailChallenge)
    app.Get("/me", adapter.RequireRealm(goauth.RealmUser,
        goauthfiber.RealmMiddlewareOptions{Introspect: true}), func(c fiber.Ctx) error {
        authContext, _ := goauthfiber.AuthContext(c)
        return c.JSON(fiber.Map{"subjectId": authContext.SubjectID.String()})
    })
    return app, auth, nil
}

To sign in, send POST /auth/login with JSON such as {"scheme":"email","identifier":"user@example.com","password":"...","realm":"user"}. The response contains account and tokens (accessToken, refreshToken, expiry, realm, and scope). Send Authorization: Bearer <accessToken> to /me; the middleware checks the realm and, with Introspect: true, the current server-side session and subject status. Send POST /auth/refresh with {"refreshToken":"..."} to rotate the refresh token. Registration returns a confirmation-scoped session; use that access token to request an email code, then verify it with {"code":"..."}. After verification, log in again to obtain a full-scope session. Runtime.Logout and LogoutAll revoke sessions; the host must expose its chosen logout routes and token storage policy.

NotificationSender.SendNotification(ctx, delivery) receives the typed notification, a stable delivery ID, and its validity deadline. Return success only after the host delivery provider accepts responsibility; it does not mean that the recipient received the message. Retries may repeat an ID, and expiry or the attempt limit can end processing without a successful send. The sender must honor context cancellation and set network timeouts so shutdown can join the worker. NotificationWorkerConfig.OnBlocked reports a delivery ID and safe reason when decryption fails; alert on this signal without logging payloads. Run auth.RunNotifications(ctx) in a supervised worker, cancel and join it on shutdown, schedule auth.Cleanup(ctx, postgres.CleanupPolicy{}), and close the Runtime when the process stops. The host chooses how to serve the Fiber app. The queue checks one-time state before sending; an account change during an in-flight external send can still reach the provider. Token validity is always enforced by the Runtime when the recipient acts on the message. Use goauth.NewKeyRing with separate random material for signing, token HMAC, and notification encryption; retain prior key versions through rotation. Managed delivery cannot be combined with a custom EventSink, NotificationRenderer, AuditSink, or NotificationTransaction hook. The PostgreSQL adapter can use a custom event sink without managed delivery; custom transaction wiring belongs to direct root Runtime assembly. A custom NotificationTransaction keeps its configured NotificationRenderer and event sink. The managed PostgreSQL transaction covers participating writes in notification operations; it is not a general unit of work for arbitrary Runtime calls. Direct RuntimeStore implementations must guard reset issuance against the expected primary email, security version, and active status and invalidate outstanding reset records on password, email, or status changes. They must also reject email-challenge issuance when the expected primary email or security version no longer matches, and invalidate old challenges when the primary email changes.

Runtime contract

postgres.NewRuntime assembles the canonical store, Argon2id password hashing, realm sessions, refresh families, password reset, email confirmation, identity links, trusted bootstrap, password/profile/email lifecycle, logout, audit persistence, cleanup, atomic rate limiting, and a digest-only OIDC refresh store. The native PostgreSQL notification queue is selected by providing a goauth.NotificationSender; it stores encrypted deliveries and exposes a worker and cleanup operation. Optional OIDC and RBAC are assembled through Runtime methods. The host supplies:

  • a PostgreSQL connection or DSN;
  • distinct versioned keys for JWT signing, token HMAC, and outbox AES-256-GCM;
  • a notification sender and a supervised worker lifecycle, or an advanced encrypted event sink with delivery acknowledgement and retention cleanup;
  • a password-reset URL builder;
  • optional membership, claims, and identifier hooks. Custom rendering and audit sinks are available with the advanced event-sink integration path.

Fiber owns only JSON handlers, typed error mapping, and realm middleware. Hosts continue to own route prefixes, cookies, redirects, UI, projection tables, and application permission catalogs. auth_subjects is the canonical principal; host users and admin rows are projections or memberships keyed by its subject_id. Realm plus host membership is the admin boundary. RBAC role assignments use that same canonical ID; hosts extend the permission catalog and own their presentation DTOs.

See docs/v0.2-runtime.md for the underlying model, docs/compatibility.md for the verified consumer line, and AUTH_INVARIANTS.md for security invariants. See LICENSE and SECURITY.md for reuse and private security reporting.

Verification

make prepare
make check
make integration-local
make vulnerability-check

For the unpublished v0.4 work, start the integration services and run the combined local gate:

make integration-up
make release-candidate-readiness
make integration-down

After the new tag is published, run make release-readiness VERSION=v0.4.0. The published clean-consumer probe runs an executable wiring example for the Runtime, Fiber, OIDC/Redis, and RBAC without a local replace. The candidate gate does not establish publication.

Documentation

Index

Constants

View Source
const (
	DefaultPasswordMinLength = 8
	DefaultPasswordMaxLength = 128
	DefaultArgon2MemoryKiB   = 19 * 1024
	DefaultArgon2Iterations  = 2
	DefaultArgon2Parallelism = 1
)
View Source
const (
	MaxAccessTokenTTL = 5 * time.Minute
)

Variables

View Source
var (
	ErrInvalidSubjectID          = errors.New("invalid subject ID")
	ErrInvalidSubjectStatus      = errors.New("invalid subject status")
	ErrInvalidIdentifierScheme   = errors.New("invalid identifier scheme")
	ErrInvalidIdentifier         = errors.New("invalid identifier")
	ErrIdentifierAlreadyExists   = errors.New("identifier already exists")
	ErrInvalidRealm              = errors.New("invalid realm")
	ErrRealmNotRegistered        = errors.New("realm is not registered")
	ErrInvalidChallengePurpose   = errors.New("invalid email challenge purpose")
	ErrAccountNotFound           = errors.New("account not found")
	ErrAccountUnavailable        = errors.New("account unavailable")
	ErrInvalidCredentials        = errors.New("invalid credentials")
	ErrCurrentPasswordInvalid    = errors.New("current password is invalid")
	ErrPasswordUnchanged         = errors.New("new password matches current password")
	ErrPasswordChangeConflict    = errors.New("password changed concurrently")
	ErrAuthenticationRateLimited = errors.New("authentication rate limited")
	ErrMembershipDenied          = errors.New("realm membership denied")
	ErrEmailVerificationRequired = errors.New("verified email required")
	ErrInvalidPassword           = errors.New("invalid password")
	ErrCommonPassword            = errors.New("common password is not allowed")
	ErrInvalidToken              = errors.New("invalid token")
	ErrReservedAccessTokenClaim  = errors.New("claims enricher set a reserved access-token claim")
	ErrExpiredToken              = errors.New("token expired")
	ErrRefreshReplay             = errors.New("refresh token replay detected")
	ErrSessionRevoked            = errors.New("session revoked")
	ErrSecurityVersionMismatch   = errors.New("security version mismatch")
	ErrResetAlreadyUsed          = errors.New("password reset token already used")
	ErrInvalidConfirmationCode   = errors.New("invalid confirmation code")
	ErrConfirmationExpired       = errors.New("confirmation code expired")
	ErrConfirmationAttempts      = errors.New("confirmation attempts exhausted")
	ErrConfirmationRateLimited   = errors.New("confirmation rate limited")
	ErrConfirmationResendDelay   = errors.New("wait before resending confirmation")
	ErrEmailChangeNotFound       = errors.New("pending email change not found")
	ErrEmailChangeSameValue      = errors.New("new email matches current email")
	ErrKeyRingInvalid            = errors.New("invalid key ring")
	ErrKeyPurposeCollision       = errors.New("cryptographic key reused across purposes")
	ErrNotificationSinkRequired  = errors.New("encrypted notification sink is required")
	ErrURLBuilderRequired        = errors.New("URL builder is required")
	ErrIdentityLinkNotFound      = errors.New("identity link not found")
	ErrIdentityLinkConflict      = errors.New("identity link conflict")
	ErrExplicitIdentityLink      = errors.New("explicit authenticated identity linking is required")
)
View Source
var NilSubjectID = SubjectID(uuid.Nil)

Functions

func NormalizeEmail

func NormalizeEmail(value string) (string, error)

Types

type Account

type Account struct {
	Subject      Subject
	PrimaryEmail Identifier
	Profile      BasicProfile
}

func (Account) EmailVerified

func (a Account) EmailVerified() bool

func (Account) IsZero

func (a Account) IsZero() bool

type AccountStore

type AccountStore interface {
	CreateLocalAccount(ctx context.Context, record LocalAccountRecord) (Account, error)
	FindAccount(ctx context.Context, identifier IdentifierInput) (Account, error)
	FindLocalAccount(ctx context.Context, identifier IdentifierInput) (LocalAccountRecord, error)
	GetLocalAccount(ctx context.Context, subjectID SubjectID) (LocalAccountRecord, error)
	GetAccount(ctx context.Context, subjectID SubjectID) (Account, error)
	UpdateBasicProfile(ctx context.Context, subjectID SubjectID, profile BasicProfile, now time.Time) (Account, error)
	ChangePassword(ctx context.Context, request PasswordChangeStoreRequest) (PasswordChangeStoreResult, error)
}

type Argon2idConfig

type Argon2idConfig struct {
	MemoryKiB   uint32
	Iterations  uint32
	Parallelism uint8
	SaltLength  uint32
	KeyLength   uint32
	Random      io.Reader
}

type Argon2idHasher

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

func NewArgon2idHasher

func NewArgon2idHasher(config Argon2idConfig) (*Argon2idHasher, error)

func (*Argon2idHasher) HashPassword

func (h *Argon2idHasher) HashPassword(password string) (string, error)

func (*Argon2idHasher) VerifyPassword

func (h *Argon2idHasher) VerifyPassword(phc, password string) error

type AuditSink

type AuditSink interface {
	RecordSecurityEvent(ctx context.Context, event SecurityEvent) error
}

type AuditSinkFunc

type AuditSinkFunc func(context.Context, SecurityEvent) error

func (AuditSinkFunc) RecordSecurityEvent

func (f AuditSinkFunc) RecordSecurityEvent(ctx context.Context, event SecurityEvent) error

type AuthContext

type AuthContext struct {
	SubjectID       SubjectID
	Realm           Realm
	Scope           SessionScope
	SessionID       string
	SecurityVersion int64
	Claims          map[string]any
}

type BasicProfile

type BasicProfile struct {
	Username    string
	DisplayName string
	GivenName   string
	FamilyName  string
}

type ChangePasswordRequest

type ChangePasswordRequest struct {
	SubjectID       SubjectID
	CurrentPassword string
	NewPassword     string
}

type ClaimsEnricher

type ClaimsEnricher interface {
	EnrichClaims(ctx context.Context, realm Realm, account Account, claims map[string]any) error
}

type ClaimsEnricherFunc

type ClaimsEnricherFunc func(context.Context, Realm, Account, map[string]any) error

func (ClaimsEnricherFunc) EnrichClaims

func (f ClaimsEnricherFunc) EnrichClaims(ctx context.Context, realm Realm, account Account, claims map[string]any) error

type CommonPasswordChecker

type CommonPasswordChecker interface {
	IsCommonPassword(password string) bool
}

type CommonPasswordCheckerFunc

type CommonPasswordCheckerFunc func(string) bool

func (CommonPasswordCheckerFunc) IsCommonPassword

func (f CommonPasswordCheckerFunc) IsCommonPassword(password string) bool

type Config

type Config struct {
	Store                   RuntimeStore
	Signing                 SigningConfig
	TokenHMACKeys           KeyRing
	OutboxAEADKeys          KeyRing
	EventSink               EncryptedEventSink
	NotificationTransaction NotificationTransaction
	// ManagedNotificationDelivery enables typed, lease-owned PostgreSQL delivery.
	// A custom transaction alone keeps the configured renderer and event sink.
	ManagedNotificationDelivery bool
	IdentifierResolvers         map[IdentifierScheme]IdentifierResolver
	MembershipGate              MembershipGate
	ClaimsEnricher              ClaimsEnricher
	NotificationRenderer        NotificationRenderer
	URLBuilder                  URLBuilder
	AuditSink                   AuditSink
	PasswordHasher              PasswordHasher
	PasswordPolicy              PasswordPolicy
	AdditionalRealms            []Realm
	AccessTTL                   time.Duration
	SessionTTL                  time.Duration
	RefreshTTL                  time.Duration
	PasswordResetTTL            time.Duration
	ChallengeTTL                time.Duration
	EmailChangeTTL              time.Duration
	EnvelopeRetention           time.Duration
	ResetResponseFloor          time.Duration
	IdentityLinkAuthMaxAge      time.Duration
	LoginRateLimit              RateLimitPolicy
	PasswordResetRateLimit      RateLimitPolicy
	Now                         func() time.Time
	Random                      io.Reader
}

type Credential

type Credential struct {
	Identifier IdentifierInput
	Password   string
}

type EmailChallengeIssueResult

type EmailChallengeIssueResult struct {
	Status  EmailChallengeIssueStatus
	RetryAt time.Time
}

type EmailChallengeIssueStatus

type EmailChallengeIssueStatus string
const (
	EmailChallengeIssued      EmailChallengeIssueStatus = "issued"
	EmailChallengeWait        EmailChallengeIssueStatus = "wait"
	EmailChallengeHourlyLimit EmailChallengeIssueStatus = "hourly_limit"
	EmailChallengeDailyLimit  EmailChallengeIssueStatus = "daily_limit"
)

type EmailChallengeLimits

type EmailChallengeLimits struct {
	MinResendInterval time.Duration
	PerHour           int
	PerDay            int
}

type EmailChallengePurpose

type EmailChallengePurpose string
const EmailChallengePurposeVerification EmailChallengePurpose = "verification"

func (EmailChallengePurpose) Validate

func (p EmailChallengePurpose) Validate() error

type EmailChallengeRecord

type EmailChallengeRecord struct {
	ID           string
	SubjectID    SubjectID
	IdentifierID string
	// Runtime supplies these together so a store can reject a challenge if
	// the account changes after the caller reads its email destination.
	ExpectedNormalizedEmail string
	ExpectedSecurityVersion int64
	Purpose                 EmailChallengePurpose
	Digest                  SecretDigest
	RateDigest              SecretDigest
	MaxAttempts             int
	ExpiresAt               time.Time
	CreatedAt               time.Time
}

type EmailChallengeStore

type EmailChallengeStore interface {
	IssueEmailChallenge(
		ctx context.Context,
		record EmailChallengeRecord,
		limits EmailChallengeLimits,
	) (EmailChallengeIssueResult, error)
	VerifyEmailChallenge(ctx context.Context, request EmailChallengeVerifyRequest) (EmailChallengeVerifyResult, error)
}

type EmailChallengeVerifyRequest

type EmailChallengeVerifyRequest struct {
	SubjectID SubjectID
	Purpose   EmailChallengePurpose
	Digests   []SecretDigest
	Now       time.Time
}

type EmailChallengeVerifyResult

type EmailChallengeVerifyResult struct {
	Status   EmailChallengeVerifyStatus
	Account  Account
	Attempts int
}

type EmailChallengeVerifyStatus

type EmailChallengeVerifyStatus string
const (
	EmailChallengeVerified        EmailChallengeVerifyStatus = "verified"
	EmailChallengeInvalid         EmailChallengeVerifyStatus = "invalid"
	EmailChallengeExpired         EmailChallengeVerifyStatus = "expired"
	EmailChallengeAttemptsUsed    EmailChallengeVerifyStatus = "attempts_used"
	EmailChallengeAlreadyVerified EmailChallengeVerifyStatus = "already_verified"
)

type EmailChangeIssueResult

type EmailChangeIssueResult struct {
	Status  EmailChangeIssueStatus
	RetryAt time.Time
}

type EmailChangeIssueStatus

type EmailChangeIssueStatus string
const (
	EmailChangeIssued      EmailChangeIssueStatus = "issued"
	EmailChangeWait        EmailChangeIssueStatus = "wait"
	EmailChangeHourlyLimit EmailChangeIssueStatus = "hourly_limit"
	EmailChangeDailyLimit  EmailChangeIssueStatus = "daily_limit"
	EmailChangeSameValue   EmailChangeIssueStatus = "same_value"
)

type EmailChangeRecord

type EmailChangeRecord struct {
	ID                 string
	SubjectID          SubjectID
	NewDisplayValue    string
	NewNormalizedValue string
	Digest             SecretDigest
	RateDigest         SecretDigest
	MaxAttempts        int
	ExpiresAt          time.Time
	CreatedAt          time.Time
}

type EmailChangeStore

type EmailChangeStore interface {
	IssueEmailChange(
		ctx context.Context,
		record EmailChangeRecord,
		limits EmailChallengeLimits,
	) (EmailChangeIssueResult, error)
	GetPendingEmailChange(ctx context.Context, subjectID SubjectID, now time.Time) (PendingEmailChange, error)
	VerifyEmailChange(ctx context.Context, request EmailChangeVerifyRequest) (EmailChangeVerifyResult, error)
}

type EmailChangeVerifyRequest

type EmailChangeVerifyRequest struct {
	SubjectID SubjectID
	Digests   []SecretDigest
	Now       time.Time
}

type EmailChangeVerifyResult

type EmailChangeVerifyResult struct {
	Status   EmailChangeVerifyStatus
	Account  Account
	Attempts int
}

type EmailChangeVerifyStatus

type EmailChangeVerifyStatus string
const (
	EmailChangeVerified     EmailChangeVerifyStatus = "verified"
	EmailChangeInvalid      EmailChangeVerifyStatus = "invalid"
	EmailChangeExpired      EmailChangeVerifyStatus = "expired"
	EmailChangeAttemptsUsed EmailChangeVerifyStatus = "attempts_used"
	EmailChangeNotFound     EmailChangeVerifyStatus = "not_found"
)

type EncryptedEnvelope

type EncryptedEnvelope struct {
	KeyID          string
	Nonce          []byte
	Ciphertext     []byte
	AdditionalData []byte
	CreatedAt      time.Time
	DeleteAfter    time.Time
}

type EncryptedEvent

type EncryptedEvent struct {
	ID          string
	Type        string
	SubjectID   SubjectID
	ReferenceID string
	ValidUntil  time.Time
	Envelope    EncryptedEnvelope
}

type EncryptedEventSink

type EncryptedEventSink interface {
	EnqueueEncrypted(ctx context.Context, event EncryptedEvent) error
	DeleteEncrypted(ctx context.Context, eventID string) error
	DeleteExpiredEncrypted(ctx context.Context, before time.Time) (int64, error)
}

type ExternalIdentity

type ExternalIdentity struct {
	Issuer        string
	Subject       string
	Email         string
	EmailVerified bool
	Profile       BasicProfile
}

type ExternalLoginRequest

type ExternalLoginRequest struct {
	Identity ExternalIdentity
	Realm    Realm
}

type Identifier

type Identifier struct {
	ID              string
	SubjectID       SubjectID
	Scheme          IdentifierScheme
	DisplayValue    string
	NormalizedValue string
	VerifiedAt      *time.Time
	CreatedAt       time.Time
	UpdatedAt       time.Time
}

Identifier stores both a display value and its scheme-specific canonical value. VerifiedAt is the only source for verification claims.

func (Identifier) IsVerified

func (i Identifier) IsVerified() bool

type IdentifierInput

type IdentifierInput struct {
	Scheme IdentifierScheme
	Value  string
}

type IdentifierResolver

type IdentifierResolver interface {
	NormalizeIdentifier(ctx context.Context, input IdentifierInput) (IdentifierInput, error)
}

type IdentifierResolverFunc

type IdentifierResolverFunc func(context.Context, IdentifierInput) (IdentifierInput, error)

func (IdentifierResolverFunc) NormalizeIdentifier

func (f IdentifierResolverFunc) NormalizeIdentifier(ctx context.Context, input IdentifierInput) (IdentifierInput, error)

type IdentifierScheme

type IdentifierScheme string
const IdentifierSchemeEmail IdentifierScheme = "email"

func (IdentifierScheme) Validate

func (s IdentifierScheme) Validate() error
type IdentityLink struct {
	ID              string
	SubjectID       SubjectID
	Issuer          string
	ExternalSubject string
	EmailNormalized string
	EmailVerified   bool
	CreatedAt       time.Time
	UpdatedAt       time.Time
}

type IdentityLinkStore

type IdentityLinkStore interface {
	ResolveIdentityLink(ctx context.Context, issuer, externalSubject string) (Account, IdentityLink, error)
	CreateSSOAccount(ctx context.Context, record SSOAccountRecord) (Account, IdentityLink, error)
	LinkIdentity(ctx context.Context, link IdentityLink) (IdentityLink, error)
}

type Key

type Key struct {
	ID       string
	Material []byte
}

type KeyRing

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

KeyRing is an immutable versioned set of 256-bit symmetric keys.

func NewKeyRing

func NewKeyRing(activeKeyID string, keys ...Key) (KeyRing, error)

func (KeyRing) Active

func (r KeyRing) Active() (Key, error)

func (KeyRing) ActiveKeyID

func (r KeyRing) ActiveKeyID() string

func (KeyRing) Get

func (r KeyRing) Get(keyID string) (Key, error)

func (KeyRing) IDs

func (r KeyRing) IDs() []string

type LocalAccountRecord

type LocalAccountRecord struct {
	Account     Account
	PasswordPHC string
}

type LoginRequest

type LoginRequest struct {
	Credential Credential
	Realm      Realm
}

type LoginResult

type LoginResult struct {
	Account Account
	Tokens  TokenPair
}

type MembershipGate

type MembershipGate interface {
	AllowMembership(ctx context.Context, realm Realm, account Account) error
}

type MembershipGateFunc

type MembershipGateFunc func(context.Context, Realm, Account) error

func (MembershipGateFunc) AllowMembership

func (f MembershipGateFunc) AllowMembership(ctx context.Context, realm Realm, account Account) error

type Notification

type Notification struct {
	Template string            `json:"template"`
	To       string            `json:"to"`
	Data     map[string]string `json:"data,omitempty"`
}

type NotificationDelivery

type NotificationDelivery struct {
	ID           string
	Notification Notification
	ValidUntil   time.Time
}

NotificationDelivery may be sent more than once before it is accepted, expires, or exhausts the attempt limit. A sender must use ID as its idempotency key when the underlying transport supports deduplication.

type NotificationRenderer

type NotificationRenderer interface {
	RenderNotification(ctx context.Context, notification Notification) ([]byte, error)
}

type NotificationRendererFunc

type NotificationRendererFunc func(context.Context, Notification) ([]byte, error)

func (NotificationRendererFunc) RenderNotification

func (f NotificationRendererFunc) RenderNotification(ctx context.Context, notification Notification) ([]byte, error)

type NotificationSender

type NotificationSender interface {
	SendNotification(ctx context.Context, delivery NotificationDelivery) error
}

NotificationSender is the only delivery integration required from a host using the PostgreSQL managed notification queue.

type NotificationSenderFunc

type NotificationSenderFunc func(context.Context, NotificationDelivery) error

func (NotificationSenderFunc) SendNotification

func (f NotificationSenderFunc) SendNotification(ctx context.Context, delivery NotificationDelivery) error

type NotificationTransaction

type NotificationTransaction interface {
	InNotificationTransaction(ctx context.Context, fn func(context.Context) error) error
}

NotificationTransaction joins participating auth writes, encrypted enqueue, and audit writes in one transaction. PostgreSQL provides this for managed notification operations; it is not a general Runtime unit of work.

type PasswordChangeStoreRequest

type PasswordChangeStoreRequest struct {
	SubjectID           SubjectID
	ExpectedPasswordPHC string
	NewPasswordPHC      string
	Now                 time.Time
}

type PasswordChangeStoreResult

type PasswordChangeStoreResult struct {
	Status  PasswordChangeStoreStatus
	Account Account
}

type PasswordChangeStoreStatus

type PasswordChangeStoreStatus string
const (
	PasswordChangeStoreSucceeded PasswordChangeStoreStatus = "succeeded"
	PasswordChangeStoreConflict  PasswordChangeStoreStatus = "conflict"
	PasswordChangeStoreMissing   PasswordChangeStoreStatus = "missing"
)

type PasswordHasher

type PasswordHasher interface {
	HashPassword(password string) (string, error)
	VerifyPassword(phc, password string) error
}

type PasswordPolicy

type PasswordPolicy struct {
	MinLength int
	MaxLength int
	Blocklist CommonPasswordChecker
}

func DefaultPasswordPolicy

func DefaultPasswordPolicy() PasswordPolicy

func (PasswordPolicy) Validate

func (p PasswordPolicy) Validate(password string) error

type PasswordResetConsumeRequest

type PasswordResetConsumeRequest struct {
	Selector    string
	Digest      SecretDigest
	PasswordPHC string
	Now         time.Time
}

type PasswordResetConsumeResult

type PasswordResetConsumeResult struct {
	Status  PasswordResetConsumeStatus
	Account Account
}

type PasswordResetConsumeStatus

type PasswordResetConsumeStatus string
const (
	PasswordResetConsumed PasswordResetConsumeStatus = "consumed"
	PasswordResetInvalid  PasswordResetConsumeStatus = "invalid"
	PasswordResetExpired  PasswordResetConsumeStatus = "expired"
	PasswordResetUsed     PasswordResetConsumeStatus = "used"
)

type PasswordResetRecord

type PasswordResetRecord struct {
	SubjectID SubjectID
	Selector  string
	Digest    SecretDigest
	// Expected fields reject a reset issued from a stale account lookup.
	ExpectedNormalizedEmail string
	ExpectedSecurityVersion int64
	ExpiresAt               time.Time
	CreatedAt               time.Time
}

type PasswordResetStore

type PasswordResetStore interface {
	CreatePasswordReset(ctx context.Context, record PasswordResetRecord) error
	ConsumePasswordReset(ctx context.Context, request PasswordResetConsumeRequest) (PasswordResetConsumeResult, error)
}

type PendingEmailChange

type PendingEmailChange struct {
	ID              string
	SubjectID       SubjectID
	NewDisplayValue string
	Attempts        int
	MaxAttempts     int
	CreatedAt       time.Time
	ExpiresAt       time.Time
}

type RateLimitPolicy

type RateLimitPolicy struct {
	Window time.Duration
	Limit  int
}

func (RateLimitPolicy) Validate

func (p RateLimitPolicy) Validate() error

type RateLimitRequest

type RateLimitRequest struct {
	SubjectID SubjectID
	Action    string
	Bucket    SecretDigest
	Window    time.Duration
	Limit     int
	Now       time.Time
}

type RateLimitResult

type RateLimitResult struct {
	Allowed bool
	RetryAt time.Time
}

type RateLimitStore

type RateLimitStore interface {
	TakeRateLimit(ctx context.Context, request RateLimitRequest) (RateLimitResult, error)
}

type Realm

type Realm string
const (
	RealmUser  Realm = "user"
	RealmAdmin Realm = "admin"
)

func (Realm) Validate

func (r Realm) Validate() error

type RefreshRotationRequest

type RefreshRotationRequest struct {
	CurrentSelector string
	CurrentDigest   SecretDigest
	NextSelector    string
	NextDigest      SecretDigest
	NextExpiresAt   time.Time
	Now             time.Time
}

type RefreshRotationResult

type RefreshRotationResult struct {
	Status   RefreshRotationStatus
	Account  Account
	Session  Session
	FamilyID string
}

type RefreshRotationStatus

type RefreshRotationStatus string
const (
	RefreshRotationSucceeded RefreshRotationStatus = "succeeded"
	RefreshRotationInvalid   RefreshRotationStatus = "invalid"
	RefreshRotationExpired   RefreshRotationStatus = "expired"
	RefreshRotationRevoked   RefreshRotationStatus = "revoked"
	RefreshRotationReplayed  RefreshRotationStatus = "replayed"
)

type RegisterRequest

type RegisterRequest struct {
	Email    string
	Password string
	Profile  BasicProfile
}

type RegisterResult

type RegisterResult struct {
	Account Account
	Tokens  TokenPair
}

type Runtime

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

func NewRuntime

func NewRuntime(config Config) (*Runtime, error)

func (*Runtime) AcknowledgeEncryptedEvent

func (r *Runtime) AcknowledgeEncryptedEvent(ctx context.Context, eventID string) error

func (*Runtime) ChangePassword

func (r *Runtime) ChangePassword(ctx context.Context, request ChangePasswordRequest) (Account, error)

func (*Runtime) CleanupExpiredEncryptedEvents

func (r *Runtime) CleanupExpiredEncryptedEvents(ctx context.Context) (int64, error)

func (*Runtime) ConfirmEmailChange

func (r *Runtime) ConfirmEmailChange(ctx context.Context, subjectID SubjectID, code string) (Account, error)

func (*Runtime) DecryptNotificationEvent

func (r *Runtime) DecryptNotificationEvent(event EncryptedEvent) (Notification, error)

func (*Runtime) FindAccount

func (r *Runtime) FindAccount(ctx context.Context, identifier IdentifierInput) (Account, error)

func (*Runtime) GetAccount

func (r *Runtime) GetAccount(ctx context.Context, subjectID SubjectID) (Account, error)

func (*Runtime) LinkExternalIdentity

func (r *Runtime) LinkExternalIdentity(
	ctx context.Context,
	auth AuthContext,
	external ExternalIdentity,
) (IdentityLink, error)

func (*Runtime) Login

func (r *Runtime) Login(ctx context.Context, request LoginRequest) (LoginResult, error)

func (*Runtime) LoginExternal

func (r *Runtime) LoginExternal(ctx context.Context, request ExternalLoginRequest) (LoginResult, error)

LoginExternal resolves or provisions an SSO-only account and creates a realm-bound session. An unverified email can receive only the user realm's limited confirmation session.

func (*Runtime) Logout

func (r *Runtime) Logout(ctx context.Context, subjectID SubjectID, sessionID string) error

func (*Runtime) LogoutAll

func (r *Runtime) LogoutAll(ctx context.Context, subjectID SubjectID) (int64, error)

func (*Runtime) PendingEmailChange

func (r *Runtime) PendingEmailChange(ctx context.Context, subjectID SubjectID) (PendingEmailChange, error)

func (*Runtime) ProvisionTrustedLocalAccount

func (r *Runtime) ProvisionTrustedLocalAccount(ctx context.Context, request RegisterRequest) (Account, error)

ProvisionTrustedLocalAccount creates a verified local account for an operator-controlled bootstrap or seed path. Interactive registration must use Register so that the account starts in confirmation scope.

func (*Runtime) Refresh

func (r *Runtime) Refresh(ctx context.Context, rawRefreshToken string) (TokenPair, error)

func (*Runtime) Register

func (r *Runtime) Register(ctx context.Context, request RegisterRequest) (RegisterResult, error)

func (*Runtime) RequestEmailChange

func (r *Runtime) RequestEmailChange(ctx context.Context, subjectID SubjectID, newEmail string) error

func (*Runtime) RequestPasswordReset

func (r *Runtime) RequestPasswordReset(ctx context.Context, email string) error

func (*Runtime) ResetPassword

func (r *Runtime) ResetPassword(ctx context.Context, rawToken, newPassword string) error

func (*Runtime) ResolveExternalIdentity

func (r *Runtime) ResolveExternalIdentity(
	ctx context.Context,
	external ExternalIdentity,
) (Account, error)

func (*Runtime) SendEmailChallenge

func (r *Runtime) SendEmailChallenge(
	ctx context.Context,
	subjectID SubjectID,
	purpose EmailChallengePurpose,
) error

func (*Runtime) SetSubjectStatus

func (r *Runtime) SetSubjectStatus(ctx context.Context, subjectID SubjectID, status SubjectStatus) (Subject, error)

func (*Runtime) UpdateBasicProfile

func (r *Runtime) UpdateBasicProfile(
	ctx context.Context,
	subjectID SubjectID,
	profile BasicProfile,
) (Account, error)

func (*Runtime) VerifyAccessToken

func (r *Runtime) VerifyAccessToken(ctx context.Context, rawAccessToken string, introspect bool) (AuthContext, error)

func (*Runtime) VerifyCredential

func (r *Runtime) VerifyCredential(ctx context.Context, credential Credential) (Account, error)

VerifyCredential verifies a local credential without creating a session. It is intended for recent-authentication checks and trusted bootstrap flows.

func (*Runtime) VerifyEmailChallenge

func (r *Runtime) VerifyEmailChallenge(
	ctx context.Context,
	subjectID SubjectID,
	purpose EmailChallengePurpose,
	code string,
) (Account, error)

type SSOAccountRecord

type SSOAccountRecord struct {
	Account Account
	Link    IdentityLink
}

type SecretDigest

type SecretDigest struct {
	KeyID  string
	Digest []byte
}

type SecurityEvent

type SecurityEvent struct {
	Type       SecurityEventType
	SubjectID  SubjectID
	Realm      Realm
	At         time.Time
	Attributes map[string]string
}

type SecurityEventType

type SecurityEventType string
const (
	SecurityEventLoginFailed          SecurityEventType = "login.failed"
	SecurityEventRefreshReplay        SecurityEventType = "refresh.replay"
	SecurityEventPasswordResetIssued  SecurityEventType = "password_reset.issued"
	SecurityEventPasswordResetUsed    SecurityEventType = "password_reset.used"
	SecurityEventPasswordChanged      SecurityEventType = "password.changed"
	SecurityEventEmailChallengeIssued SecurityEventType = "email_challenge.issued"
	SecurityEventEmailVerified        SecurityEventType = "email.verified"
	SecurityEventEmailChangeIssued    SecurityEventType = "email_change.issued"
	SecurityEventEmailChanged         SecurityEventType = "email.changed"
	SecurityEventSessionRevoked       SecurityEventType = "session.revoked"
	SecurityEventSessionsRevoked      SecurityEventType = "sessions.revoked"
	SecurityEventSubjectStatusChanged SecurityEventType = "subject.status_changed"
	SecurityEventIdentityLinked       SecurityEventType = "identity.linked"
)

type Session

type Session struct {
	ID              string
	SubjectID       SubjectID
	Realm           Realm
	Scope           SessionScope
	SecurityVersion int64
	CreatedAt       time.Time
	ExpiresAt       time.Time
	RevokedAt       *time.Time
}

func (Session) Active

func (s Session) Active(now time.Time) bool

type SessionRecord

type SessionRecord struct {
	Session          Session
	FamilyID         string
	RefreshSelector  string
	RefreshDigest    SecretDigest
	RefreshExpiresAt time.Time
}

type SessionScope

type SessionScope string
const (
	SessionScopeAuthenticated SessionScope = "authenticated"
	SessionScopeConfirmation  SessionScope = "confirmation"
)

type SessionSecurity

type SessionSecurity struct {
	Session        Session
	SubjectStatus  SubjectStatus
	CurrentVersion int64
}

type SessionStore

type SessionStore interface {
	CreateSession(ctx context.Context, record SessionRecord) error
	RotateRefresh(ctx context.Context, request RefreshRotationRequest) (RefreshRotationResult, error)
	IntrospectSession(ctx context.Context, sessionID string) (SessionSecurity, error)
	RevokeSession(ctx context.Context, subjectID SubjectID, sessionID string, now time.Time) (bool, error)
	RevokeSubjectSessions(ctx context.Context, subjectID SubjectID, now time.Time) (int64, error)
	SetSubjectStatus(ctx context.Context, subjectID SubjectID, status SubjectStatus, now time.Time) (Subject, error)
}

type SigningConfig

type SigningConfig struct {
	Issuer   string
	Audience string
	Keys     KeyRing
}

type Subject

type Subject struct {
	ID              SubjectID
	Status          SubjectStatus
	SecurityVersion int64
	CreatedAt       time.Time
	UpdatedAt       time.Time
}

Subject is the canonical security principal. Profile, identifiers, credentials, memberships, and permissions deliberately live elsewhere.

func (Subject) IsZero

func (s Subject) IsZero() bool

type SubjectID

type SubjectID uuid.UUID

SubjectID is the canonical identifier shared by every goauth relation.

func MustParseSubjectID

func MustParseSubjectID(value string) SubjectID

func NewSubjectID

func NewSubjectID() SubjectID

func ParseSubjectID

func ParseSubjectID(value string) (SubjectID, error)

func (SubjectID) IsZero

func (id SubjectID) IsZero() bool

func (SubjectID) MarshalText

func (id SubjectID) MarshalText() ([]byte, error)

func (*SubjectID) Scan

func (id *SubjectID) Scan(source any) error

func (SubjectID) String

func (id SubjectID) String() string

func (*SubjectID) UnmarshalText

func (id *SubjectID) UnmarshalText(value []byte) error

func (SubjectID) Validate

func (id SubjectID) Validate() error

func (SubjectID) Value

func (id SubjectID) Value() (driver.Value, error)

type SubjectStatus

type SubjectStatus string
const (
	SubjectStatusActive    SubjectStatus = "active"
	SubjectStatusSuspended SubjectStatus = "suspended"
	SubjectStatusDisabled  SubjectStatus = "disabled"
)

func (SubjectStatus) Validate

func (s SubjectStatus) Validate() error

type TokenPair

type TokenPair struct {
	AccessToken      string
	RefreshToken     string
	AccessExpiresAt  time.Time
	RefreshExpiresAt time.Time
	Session          Session
}

type URLBuilder

type URLBuilder interface {
	PasswordResetURL(ctx context.Context, token string) (string, error)
}

type URLBuilderFunc

type URLBuilderFunc func(context.Context, string) (string, error)

func (URLBuilderFunc) PasswordResetURL

func (f URLBuilderFunc) PasswordResetURL(ctx context.Context, token string) (string, error)

Directories

Path Synopsis
cmd
internal
reference
externalconsumer
Package externalconsumer is a compile-checked manifest of the goauth packages that are currently supported for external consumers such as backend and goadmin.
Package externalconsumer is a compile-checked manifest of the goauth packages that are currently supported for external consumers such as backend and goadmin.

Jump to

Keyboard shortcuts

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