flow

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 9, 2026 License: Apache-2.0 Imports: 26 Imported by: 1

Documentation

Overview

Package flow provides authentication flows and strategies for Kayan IAM.

The flow package is the core of Kayan's authentication system. It implements a strategy pattern that allows mixing and matching different authentication methods while maintaining a consistent interface.

Strategies

Kayan supports multiple authentication strategies:

  • Password: Traditional username/password with bcrypt or argon2
  • OIDC: Social login with Google, GitHub, Microsoft, etc.
  • WebAuthn: Passkeys and FIDO2 hardware keys
  • Magic Link: Passwordless email authentication
  • TOTP: Time-based one-time passwords for MFA
  • SAML 2.0: Enterprise SSO (see saml package)
  • OTP: SMS/Voice one-time passwords via user-provided OTPSender
  • Step-Up: Step-up authentication for sensitive operations

BYOS (Bring Your Own Schema)

The key feature of Kayan's flow package is BYOS - you use your existing database models. Simply implement the FlowIdentity interface and map your fields:

type User struct {
    ID           string `gorm:"primaryKey"`
    Email        string `gorm:"uniqueIndex"`
    PasswordHash string
}
func (u *User) GetID() any   { return u.ID }
func (u *User) SetID(id any) { u.ID = id.(string) }

// Map your fields
pwStrategy.MapFields([]string{"Email"}, "PasswordHash")

Registration Flow

regManager := flow.NewRegistrationManager(repo, factory)
regManager.RegisterStrategy(pwStrategy)
identity, err := regManager.Submit(ctx, "password", traits, secret)

Login Flow

loginManager := flow.NewLoginManager(repo)
loginManager.RegisterStrategy(pwStrategy)
identity, err := loginManager.Authenticate(ctx, "password", identifier, secret)

Hooks

Register hooks to run before or after authentication events:

regManager.OnAfterRegistration(func(ctx context.Context, ident any) error {
    // Send welcome email, create profile, etc.
    return nil
})

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrIdentityAlreadyExists is returned when a registration attempt matches an existing identity
	// but the duplicate policy prevents automatic linking/capture.
	ErrIdentityAlreadyExists = errors.New("registration: identity already exists")
)
View Source
var ErrMFARequired = errors.New("login: mfa required")

Functions

func CompositeKeyFunc

func CompositeKeyFunc(fns ...func(context.Context, string) string) func(context.Context, string) string

CompositeKeyFunc chains multiple key functions.

func ContextKeyFunc

func ContextKeyFunc(ctxKey ContextKey, separator string) func(context.Context, string) string

func IPKeyFunc

func IPKeyFunc(separator string) func(context.Context, string) string

IPKeyFunc creates a key function that extracts IP from identifier. Useful when identifier contains "email:ip" format.

func IsRateLimitError

func IsRateLimitError(err error) bool

IsRateLimitError checks if an error is a rate limit error.

func PrefixKeyFunc

func PrefixKeyFunc(prefix string) func(context.Context, string) string

PrefixKeyFunc adds a prefix to keys for namespacing.

Types

type Attacher

type Attacher interface {
	Attach(ctx context.Context, ident any, identifier, secret string) error
}

Attacher is an optional interface for strategies that support linking to an existing identity.

type BcryptHasher

type BcryptHasher struct {
	Cost int
}

func NewBcryptHasher

func NewBcryptHasher(cost int) *BcryptHasher

func (*BcryptHasher) Compare

func (h *BcryptHasher) Compare(password, hash string) bool

func (*BcryptHasher) Hash

func (h *BcryptHasher) Hash(password string) (string, error)

type ClaimMapper

type ClaimMapper func(claims map[string]any) identity.JSON

type ConfigurableRateLimiter

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

ConfigurableRateLimiter wraps any RateLimiter with additional callbacks.

func NewConfigurableRateLimiter

func NewConfigurableRateLimiter(inner RateLimiter) *ConfigurableRateLimiter

NewConfigurableRateLimiter wraps a rate limiter with optional callbacks.

func (*ConfigurableRateLimiter) Allow

func (r *ConfigurableRateLimiter) Allow(ctx context.Context, key string, limit int, window time.Duration) (bool, int, error)

func (*ConfigurableRateLimiter) OnAllow

func (r *ConfigurableRateLimiter) OnAllow(fn func(ctx context.Context, key string, remaining int)) *ConfigurableRateLimiter

func (*ConfigurableRateLimiter) OnDeny

func (*ConfigurableRateLimiter) OnRequest

func (r *ConfigurableRateLimiter) OnRequest(fn func(ctx context.Context, key string, limit int, window time.Duration)) *ConfigurableRateLimiter

func (*ConfigurableRateLimiter) Reset

func (r *ConfigurableRateLimiter) Reset(ctx context.Context, key string) error

type ContextKey

type ContextKey string

ContextKeyFunc extracts additional context from ctx to build the key.

type CredentialSource

type CredentialSource interface {
	GetCredentials() []identity.Credential
	SetCredentials([]identity.Credential)
}

CredentialSource is an optional interface for models that support Kayan's discrete Credentials table.

type FixedWindowRateLimiter

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

FixedWindowRateLimiter implements rate limiting using fixed time windows.

func NewFixedWindowRateLimiter

func NewFixedWindowRateLimiter() *FixedWindowRateLimiter

NewFixedWindowRateLimiter creates a new fixed window rate limiter.

func (*FixedWindowRateLimiter) Allow

func (r *FixedWindowRateLimiter) Allow(ctx context.Context, key string, limit int, window time.Duration) (bool, int, error)

func (*FixedWindowRateLimiter) Reset

func (r *FixedWindowRateLimiter) Reset(ctx context.Context, key string) error

type Flow

type Flow struct {
	ID        uuid.UUID `json:"id"`
	Type      FlowType  `json:"type"`
	ExpiresAt time.Time `json:"expires_at"`
	Active    bool      `json:"active"`
	IssuedAt  time.Time `json:"issued_at"`

	// Methods could be "password", "oidc", etc.
	Methods []string `json:"methods"`
}

Flow represents a transient state for an authentication process.

func NewFlow

func NewFlow(t FlowType, methods []string) *Flow

type FlowIdentity

type FlowIdentity interface {
	GetID() any
	SetID(any)
}

FlowIdentity defines the minimum interface that any identity model must satisfy.

type FlowType

type FlowType string

FlowType represents the type of authentication flow.

const (
	FlowTypeRegistration FlowType = "registration"
	FlowTypeLogin        FlowType = "login"
)

type Hook

type Hook func(ctx context.Context, ident any) error

Hook defines a function that runs before or after a flow action.

type IdentityRepository

type IdentityRepository = domain.IdentityStorage

IdentityRepository is an alias for domain.IdentityStorage. This provides a clearer name within the flow package context.

type Initiator

type Initiator interface {
	Initiate(ctx context.Context, identifier string) (any, error)
}

Initiator is an optional interface for login strategies that support a multi-step initiation (e.g. Magic Link, OTP).

type Linker

type Linker interface {
	// FindExisting searches for an identity that matches the provided traits.
	// Typically checks for verified emails or phone numbers.
	FindExisting(ctx context.Context, traits identity.JSON) (any, error)

	// Link attaches a new authentication method to an existing identity.
	Link(ctx context.Context, ident any, method string, identifier, secret string) error
}

Linker handles account linking and identity unification.

func NewDefaultLinker

func NewDefaultLinker(repo IdentityRepository, factory func() any) Linker

type LockoutConfig

type LockoutConfig struct {
	// MaxFailures is the number of failures before lockout (e.g. 5)
	MaxFailures int

	// LockoutDuration is how long to lock the account (e.g. 15 minutes)
	LockoutDuration time.Duration

	// FailureWindow is how long failures are remembered (e.g. 15 minutes)
	FailureWindow time.Duration

	// FailOpen determines behavior when store errors occur.
	// If true, allow requests when store fails. If false (default), deny.
	FailOpen bool

	// Hooks for customizing behavior.
	Hooks LockoutHooks
}

LockoutConfig holds configuration for the lockout decorator.

type LockoutHooks

type LockoutHooks struct {
	// OnFailure is called when an authentication failure is recorded.
	// Receives the current failure count. Return error to override default behavior.
	OnFailure func(ctx context.Context, info *LockoutInfo) error

	// OnLocked is called when an account becomes locked.
	// Use for alerting, logging, or custom actions.
	OnLocked func(ctx context.Context, info *LockoutInfo)

	// OnUnlocked is called when an account is unlocked (on successful login).
	OnUnlocked func(ctx context.Context, identifier string)

	// OnLockoutCheck is called before checking if account is locked.
	// Return true, time.Time, nil to override with custom lock status.
	// Return false, _, nil to use default behavior.
	OnLockoutCheck func(ctx context.Context, identifier string) (locked bool, until time.Time, handled bool)

	// CreateLockError allows customizing the error when locked.
	CreateLockError func(info *LockoutInfo) error

	// KeyFunc extracts the lockout key from the identifier.
	// Useful for grouping by IP, tenant, etc.
	KeyFunc func(ctx context.Context, identifier string) string

	// ShouldRecordFailure determines if a failure should be counted.
	// Return false to skip recording (e.g., for certain error types).
	ShouldRecordFailure func(ctx context.Context, identifier string, err error) bool

	// ShouldClearOnSuccess determines if failures should clear on success.
	// Default is true. Set to false for accumulating counters.
	ShouldClearOnSuccess func(ctx context.Context, identifier string) bool
}

LockoutHooks provides extension points for customizing lockout behavior.

type LockoutInfo

type LockoutInfo struct {
	Identifier      string
	FailureCount    int
	MaxFailures     int
	LockedUntil     time.Time
	LockoutDuration time.Duration
}

LockoutInfo contains information about a lockout event.

type LockoutStore

type LockoutStore interface {
	// RecordFailure increments the failure count for the identifier.
	// ttl defines how long this failure record should be kept.
	RecordFailure(ctx context.Context, identifier string, ttl time.Duration) (int, error)

	// ClearFailures resets the failure count for the identifier.
	ClearFailures(ctx context.Context, identifier string) error

	// Lock manually locks the identifier for the given duration.
	Lock(ctx context.Context, identifier string, duration time.Duration) error

	// IsLocked checks if the identifier is currently locked.
	// Returns true and the expiry time if locked.
	IsLocked(ctx context.Context, identifier string) (bool, time.Time, error)
}

LockoutStore defines the storage for tracking login failures and lockouts.

type LockoutStrategy

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

LockoutStrategy is a decorator that adds brute-force protection to a LoginStrategy.

func NewLockoutStrategy

func NewLockoutStrategy(next LoginStrategy, store LockoutStore, maxFailures int, lockoutDuration, failureWindow time.Duration) *LockoutStrategy

NewLockoutStrategy creates a new lockout decorator.

func NewLockoutStrategyWithConfig

func NewLockoutStrategyWithConfig(next LoginStrategy, store LockoutStore, config LockoutConfig) *LockoutStrategy

NewLockoutStrategyWithConfig creates a lockout decorator with full configuration.

func (*LockoutStrategy) Authenticate

func (s *LockoutStrategy) Authenticate(ctx context.Context, identifier, secret string) (any, error)

func (*LockoutStrategy) ID

func (s *LockoutStrategy) ID() string

func (*LockoutStrategy) Initiate

func (s *LockoutStrategy) Initiate(ctx context.Context, identifier string) (any, error)

Initiate supports Initiator interface for multi-step strategies.

func (*LockoutStrategy) SetHooks

func (s *LockoutStrategy) SetHooks(hooks LockoutHooks)

SetHooks allows updating hooks after creation.

type LoginManager

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

func NewLoginManager

func NewLoginManager(repo IdentityRepository) *LoginManager

func (*LoginManager) AddPostHook

func (m *LoginManager) AddPostHook(h Hook)

func (*LoginManager) AddPreHook

func (m *LoginManager) AddPreHook(h Hook)

func (*LoginManager) Authenticate

func (m *LoginManager) Authenticate(ctx context.Context, method, identifier, secret string) (any, error)

func (*LoginManager) FindIdentity

func (m *LoginManager) FindIdentity(ctx context.Context, identifier string) (any, error)

func (*LoginManager) InitiateLogin

func (m *LoginManager) InitiateLogin(ctx context.Context, method, identifier string) (any, error)

func (*LoginManager) LinkMethod

func (m *LoginManager) LinkMethod(ctx context.Context, ident any, method, identifier, secret string) error

LinkMethod links a new authentication method to an existing authenticated identity.

func (*LoginManager) RegisterStrategy

func (m *LoginManager) RegisterStrategy(s LoginStrategy)

func (*LoginManager) Registry

func (m *LoginManager) Registry() *StrategyRegistry

func (*LoginManager) ReloadStrategies

func (m *LoginManager) ReloadStrategies(ctx context.Context) error

ReloadStrategies fetches configs from the store and rebuilds strategies.

func (*LoginManager) SetFactory

func (m *LoginManager) SetFactory(f func() any)

func (*LoginManager) SetStrategyStore

func (m *LoginManager) SetStrategyStore(store domain.StrategyStore)

func (*LoginManager) VerifyMFA

func (m *LoginManager) VerifyMFA(ctx context.Context, ident any, code string) (bool, error)

VerifyMFA checks the second factor (e.g. TOTP) for an identity.

type LoginStrategy

type LoginStrategy interface {
	ID() string
	Authenticate(ctx context.Context, identifier, secret string) (any, error)
}

LoginStrategy defines how an identity is authenticated for a specific method.

type MagicLinkStrategy

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

func NewMagicLinkStrategy

func NewMagicLinkStrategy(repo IdentityRepository, store domain.TokenStore) *MagicLinkStrategy

func (*MagicLinkStrategy) Authenticate

func (s *MagicLinkStrategy) Authenticate(ctx context.Context, identifier, secret string) (any, error)

Authenticate verifies the magic link token. 'identifier' is the email (for double check, optional) or identity ID? 'secret' is the token.

func (*MagicLinkStrategy) ID

func (s *MagicLinkStrategy) ID() string

func (*MagicLinkStrategy) Initiate

func (s *MagicLinkStrategy) Initiate(ctx context.Context, identifier string) (any, error)

Initiate generates a token and prepares it for sending. Returns the token object (to be sent via email by the caller/event system).

type MemoryLockoutStore

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

func NewMemoryLockoutStore

func NewMemoryLockoutStore() *MemoryLockoutStore

func (*MemoryLockoutStore) ClearFailures

func (s *MemoryLockoutStore) ClearFailures(ctx context.Context, identifier string) error

func (*MemoryLockoutStore) IsLocked

func (s *MemoryLockoutStore) IsLocked(ctx context.Context, identifier string) (bool, time.Time, error)

func (*MemoryLockoutStore) Lock

func (s *MemoryLockoutStore) Lock(ctx context.Context, identifier string, duration time.Duration) error

func (*MemoryLockoutStore) RecordFailure

func (s *MemoryLockoutStore) RecordFailure(ctx context.Context, identifier string, ttl time.Duration) (int, error)

type MemoryRateLimiter

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

MemoryRateLimiter implements rate limiting using in-memory sliding window. For production, use Redis-based implementation.

func NewMemoryRateLimiter

func NewMemoryRateLimiter() *MemoryRateLimiter

NewMemoryRateLimiter creates a new memory-based rate limiter.

func (*MemoryRateLimiter) Allow

func (r *MemoryRateLimiter) Allow(ctx context.Context, key string, limit int, window time.Duration) (bool, int, error)

func (*MemoryRateLimiter) Reset

func (r *MemoryRateLimiter) Reset(ctx context.Context, key string) error

type MemoryStepUpStore

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

MemoryStepUpStore is an in-memory implementation of StepUpStore for testing.

func NewMemoryStepUpStore

func NewMemoryStepUpStore() *MemoryStepUpStore

NewMemoryStepUpStore creates a new in-memory step-up store.

func (*MemoryStepUpStore) DeleteStepUp

func (s *MemoryStepUpStore) DeleteStepUp(ctx context.Context, sessionID string) error

func (*MemoryStepUpStore) GetStepUp

func (s *MemoryStepUpStore) GetStepUp(ctx context.Context, sessionID string) (*StepUpRecord, error)

func (*MemoryStepUpStore) SaveStepUp

func (s *MemoryStepUpStore) SaveStepUp(ctx context.Context, record *StepUpRecord) error

type MemoryWebAuthnSessionStore

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

MemoryWebAuthnSessionStore is an in-memory implementation of WebAuthnSessionStore. Use Redis in production.

func NewMemoryWebAuthnSessionStore

func NewMemoryWebAuthnSessionStore() *MemoryWebAuthnSessionStore

func (*MemoryWebAuthnSessionStore) DeleteSession

func (s *MemoryWebAuthnSessionStore) DeleteSession(ctx context.Context, sessionID string) error

func (*MemoryWebAuthnSessionStore) GetSession

func (s *MemoryWebAuthnSessionStore) GetSession(ctx context.Context, sessionID string) (*WebAuthnSessionData, error)

func (*MemoryWebAuthnSessionStore) SaveSession

func (s *MemoryWebAuthnSessionStore) SaveSession(ctx context.Context, sessionID string, data *WebAuthnSessionData) error

type OIDCManager

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

func NewOIDCManager

func NewOIDCManager(repo domain.Storage, configs map[string]config.OIDCProvider, factory func() any) (*OIDCManager, error)

func (*OIDCManager) Attach

func (m *OIDCManager) Attach(ctx context.Context, ident any, identifier, secret string) error

Attach implements the Attacher interface for OIDC. identifier = subject, secret = providerID

func (*OIDCManager) GetAuthURL

func (m *OIDCManager) GetAuthURL(providerID, state string) (string, error)

func (*OIDCManager) HandleCallback

func (m *OIDCManager) HandleCallback(ctx context.Context, providerID, code string) (any, error)

func (*OIDCManager) SetClaimMapper

func (m *OIDCManager) SetClaimMapper(mapper ClaimMapper)

func (*OIDCManager) SetIDGenerator

func (m *OIDCManager) SetIDGenerator(g domain.IDGenerator)

func (*OIDCManager) SetLinker

func (m *OIDCManager) SetLinker(l Linker)

type OIDCProviderData

type OIDCProviderData struct {
	Provider    *oidc.Provider
	OAuthConfig *oauth2.Config
}

type OTPOption

type OTPOption func(*OTPStrategy)

OTPOption configures an OTPStrategy.

func WithOTPCodeLength

func WithOTPCodeLength(length int) OTPOption

WithOTPCodeLength sets the number of digits in the OTP code. Default is 6.

func WithOTPTTL

func WithOTPTTL(ttl time.Duration) OTPOption

WithOTPTTL sets the expiration duration for OTP codes. Default is 5 minutes.

type OTPSender

type OTPSender interface {
	Send(ctx context.Context, recipient, code string) error
}

OTPSender is the interface that the user must implement to deliver OTP codes. Kayan is headless and never sends messages directly. The user provides their own delivery mechanism (Twilio, AWS SNS, email, etc.).

Example:

type TwilioSender struct{ client *twilio.Client }
func (s *TwilioSender) Send(ctx context.Context, recipient, code string) error {
    _, err := s.client.SendSMS(recipient, "Your code is: "+code)
    return err
}

type OTPStrategy

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

OTPStrategy implements passwordless login via one-time passwords delivered through SMS, voice, email, or any channel via the OTPSender interface.

This strategy implements LoginStrategy and Initiator. It is not a RegistrationStrategy — OTP is used for login and verification, not registration.

Usage:

sender := &TwilioSender{client: twilioClient}
otpStrategy := flow.NewOTPStrategy(repo, tokenStore, sender)
loginManager.RegisterStrategy(otpStrategy)

// 1. Initiate: sends code to user
result, _ := loginManager.InitiateLogin(ctx, "otp", "user@example.com")

// 2. Authenticate: user provides the code
ident, _ := loginManager.Authenticate(ctx, "otp", "user@example.com", "123456")

func NewOTPStrategy

func NewOTPStrategy(repo IdentityRepository, tokenStore domain.TokenStore, sender OTPSender, opts ...OTPOption) *OTPStrategy

NewOTPStrategy creates a new OTP authentication strategy.

Parameters:

  • repo: identity storage for looking up users
  • tokenStore: storage for OTP tokens (uses the existing AuthToken system)
  • sender: user-provided delivery mechanism (SMS, voice, email, etc.)
  • opts: optional configuration (TTL, code length)

func (*OTPStrategy) Authenticate

func (s *OTPStrategy) Authenticate(ctx context.Context, identifier, secret string) (any, error)

Authenticate verifies the OTP code provided by the user. The identifier is the phone number or email, and the secret is the OTP code.

func (*OTPStrategy) ID

func (s *OTPStrategy) ID() string

func (*OTPStrategy) Initiate

func (s *OTPStrategy) Initiate(ctx context.Context, identifier string) (any, error)

Initiate generates an OTP code, stores it, and delivers it via the OTPSender. The identifier is typically a phone number or email address. Returns the AuthToken (the caller may use the token ID for flow tracking).

type PasswordStrategy

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

func NewPasswordStrategy

func NewPasswordStrategy(repo IdentityRepository, hasher domain.Hasher, identifierField string, factory func() any) *PasswordStrategy

func (*PasswordStrategy) Attach

func (s *PasswordStrategy) Attach(ctx context.Context, ident any, identifier, secret string) error

func (*PasswordStrategy) Authenticate

func (s *PasswordStrategy) Authenticate(ctx context.Context, identifier, password string) (any, error)

func (*PasswordStrategy) ID

func (s *PasswordStrategy) ID() string

func (*PasswordStrategy) MapFields

func (s *PasswordStrategy) MapFields(identifiers []string, password string)

func (*PasswordStrategy) Register

func (s *PasswordStrategy) Register(ctx context.Context, traits identity.JSON, password string) (any, error)

func (*PasswordStrategy) SetIDGenerator

func (s *PasswordStrategy) SetIDGenerator(g domain.IDGenerator)

type RateLimitConfig

type RateLimitConfig struct {
	// Limit is the maximum number of requests allowed in the window.
	Limit int

	// Window is the time window for the rate limit.
	Window time.Duration

	// KeyFunc extracts the rate limit key from the identifier.
	// If nil, the identifier itself is used as the key.
	// Receives context for access to request metadata.
	KeyFunc func(ctx context.Context, identifier string) string

	// DynamicLimit allows per-request limit customization.
	// If set, overrides the static Limit value.
	// Useful for tiered rate limits (e.g., premium users get higher limits).
	DynamicLimit func(ctx context.Context, identifier string) (limit int, window time.Duration)

	// SkipFunc determines if rate limiting should be skipped for this request.
	// Return true to bypass rate limiting entirely.
	SkipFunc func(ctx context.Context, identifier string) bool

	// FailOpen determines behavior when rate limiter errors occur.
	// If true, allow requests when rate limiter fails.
	// If false (default), deny requests when rate limiter fails.
	FailOpen bool

	// Hooks for customizing behavior at various points.
	Hooks RateLimitHooks
}

RateLimitConfig holds configuration for the rate limiter decorator.

type RateLimitError

type RateLimitError struct {
	RetryAfter time.Duration
	Remaining  int
	Message    string // Custom message (optional)
}

RateLimitError is returned when a request is rate limited.

func AsRateLimitError

func AsRateLimitError(err error) (*RateLimitError, bool)

AsRateLimitError extracts RateLimitError from error if possible.

func (*RateLimitError) Error

func (e *RateLimitError) Error() string

type RateLimitHooks

type RateLimitHooks struct {
	// OnAllow is called when a request is allowed.
	// Return error to reject the request even if the rate limiter allowed it.
	OnAllow func(ctx context.Context, info *RateLimitInfo) error

	// OnDeny is called when a request is denied by rate limiting.
	// This is informational - the request will still be denied.
	// Use to log, alert, or perform custom actions.
	OnDeny func(ctx context.Context, info *RateLimitInfo)

	// OnError is called when the rate limiter encounters an error.
	// Return a new error to override the default behavior.
	// Return nil to fail open (allow the request despite error).
	OnError func(ctx context.Context, err error, info *RateLimitInfo) error

	// CreateError allows customizing the error returned when rate limited.
	// If nil, returns RateLimitError.
	CreateError func(info *RateLimitInfo) error
}

RateLimitHooks provides extension points for customizing rate limit behavior.

type RateLimitInfo

type RateLimitInfo struct {
	Key        string
	Identifier string
	Limit      int
	Window     time.Duration
	Remaining  int
	Allowed    bool
	RetryAfter time.Duration
}

RateLimitInfo contains information about a rate limit check.

type RateLimitStrategy

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

RateLimitStrategy is a decorator that adds rate limiting to any LoginStrategy.

func NewRateLimitStrategy

func NewRateLimitStrategy(next LoginStrategy, limiter RateLimiter, config RateLimitConfig) *RateLimitStrategy

NewRateLimitStrategy creates a new rate limiting decorator.

func (*RateLimitStrategy) Authenticate

func (s *RateLimitStrategy) Authenticate(ctx context.Context, identifier, secret string) (any, error)

func (*RateLimitStrategy) ID

func (s *RateLimitStrategy) ID() string

func (*RateLimitStrategy) Initiate

func (s *RateLimitStrategy) Initiate(ctx context.Context, identifier string) (any, error)

Initiate supports Initiator interface for multi-step strategies.

func (*RateLimitStrategy) SetDynamicLimit

func (s *RateLimitStrategy) SetDynamicLimit(fn func(ctx context.Context, identifier string) (int, time.Duration))

SetDynamicLimit allows updating the dynamic limit function after creation.

func (*RateLimitStrategy) SetHooks

func (s *RateLimitStrategy) SetHooks(hooks RateLimitHooks)

SetHooks allows updating hooks after creation.

func (*RateLimitStrategy) SetKeyFunc

func (s *RateLimitStrategy) SetKeyFunc(fn func(ctx context.Context, identifier string) string)

SetKeyFunc allows updating the key function after creation.

func (*RateLimitStrategy) SetSkipFunc

func (s *RateLimitStrategy) SetSkipFunc(fn func(ctx context.Context, identifier string) bool)

SetSkipFunc allows updating the skip function after creation.

type RateLimiter

type RateLimiter interface {
	// Allow checks if the request should be allowed based on the key and rate limit.
	// Returns true if allowed, false if rate limited.
	// remaining indicates how many requests are left in the current window.
	Allow(ctx context.Context, key string, limit int, window time.Duration) (allowed bool, remaining int, err error)

	// Reset clears the rate limit counter for the given key.
	Reset(ctx context.Context, key string) error
}

RateLimiter defines the interface for rate limiting implementations.

type RecoveryManager

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

func NewRecoveryManager

func NewRecoveryManager(repo IdentityRepository, store domain.TokenStore, hasher domain.Hasher) *RecoveryManager

func (*RecoveryManager) Initiate

func (m *RecoveryManager) Initiate(ctx context.Context, identifier string) (*domain.AuthToken, error)

Initiate generates a recovery token.

func (*RecoveryManager) ResetPassword

func (m *RecoveryManager) ResetPassword(ctx context.Context, tokenStr string, newPassword string) error

ResetPassword consumes the token and updates the password.

type RedisLockoutStore

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

RedisLockoutStore implements LockoutStore using Redis for distributed deployments.

func NewRedisLockoutStore

func NewRedisLockoutStore(client *redis.Client, prefix string) *RedisLockoutStore

NewRedisLockoutStore creates a new Redis-based lockout store.

func (*RedisLockoutStore) ClearFailures

func (s *RedisLockoutStore) ClearFailures(ctx context.Context, identifier string) error

ClearFailures resets the failure count for the identifier.

func (*RedisLockoutStore) IsLocked

func (s *RedisLockoutStore) IsLocked(ctx context.Context, identifier string) (bool, time.Time, error)

IsLocked checks if the identifier is currently locked.

func (*RedisLockoutStore) Lock

func (s *RedisLockoutStore) Lock(ctx context.Context, identifier string, duration time.Duration) error

Lock manually locks the identifier for the given duration.

func (*RedisLockoutStore) RecordFailure

func (s *RedisLockoutStore) RecordFailure(ctx context.Context, identifier string, ttl time.Duration) (int, error)

RecordFailure increments the failure count for the identifier.

type RedisRateLimiter

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

RedisRateLimiter implements RateLimiter using Redis for distributed rate limiting.

func NewRedisRateLimiter

func NewRedisRateLimiter(client *redis.Client, prefix string) *RedisRateLimiter

NewRedisRateLimiter creates a new Redis-based rate limiter.

func (*RedisRateLimiter) Allow

func (r *RedisRateLimiter) Allow(ctx context.Context, key string, limit int, window time.Duration) (bool, int, error)

Allow checks if the request should be allowed using sliding window log algorithm.

func (*RedisRateLimiter) Reset

func (r *RedisRateLimiter) Reset(ctx context.Context, key string) error

Reset clears the rate limit counter for the given key.

type RedisWebAuthnSessionStore

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

RedisWebAuthnSessionStore implements WebAuthnSessionStore using Redis.

func NewRedisWebAuthnSessionStore

func NewRedisWebAuthnSessionStore(client *redis.Client, prefix string) *RedisWebAuthnSessionStore

NewRedisWebAuthnSessionStore creates a new Redis-based WebAuthn session store.

func (*RedisWebAuthnSessionStore) DeleteSession

func (s *RedisWebAuthnSessionStore) DeleteSession(ctx context.Context, sessionID string) error

func (*RedisWebAuthnSessionStore) GetSession

func (s *RedisWebAuthnSessionStore) GetSession(ctx context.Context, sessionID string) (*WebAuthnSessionData, error)

func (*RedisWebAuthnSessionStore) SaveSession

func (s *RedisWebAuthnSessionStore) SaveSession(ctx context.Context, sessionID string, data *WebAuthnSessionData) error

type RegistrationManager

type RegistrationManager struct {
	PreventPasswordCapture bool
	// contains filtered or unexported fields
}

func NewRegistrationManager

func NewRegistrationManager(repo IdentityRepository, factory func() any) *RegistrationManager

func (*RegistrationManager) AddPostHook

func (m *RegistrationManager) AddPostHook(h Hook)

func (*RegistrationManager) AddPreHook

func (m *RegistrationManager) AddPreHook(h Hook)

func (*RegistrationManager) RegisterStrategy

func (m *RegistrationManager) RegisterStrategy(s RegistrationStrategy)

func (*RegistrationManager) SetLinker

func (m *RegistrationManager) SetLinker(l Linker)

func (*RegistrationManager) SetSchema

func (m *RegistrationManager) SetSchema(s identity.Schema)

func (*RegistrationManager) Submit

func (m *RegistrationManager) Submit(ctx context.Context, method string, traits identity.JSON, secret string) (any, error)

type RegistrationStrategy

type RegistrationStrategy interface {
	ID() string
	Register(ctx context.Context, traits identity.JSON, secret string) (any, error)
}

RegistrationStrategy defines how an identity is created for a specific method.

type StepUpLevel

type StepUpLevel string

StepUpLevel represents the authentication assurance level required for an action.

const (
	// StepUpNone requires only a basic session — no additional authentication.
	StepUpNone StepUpLevel = "none"
	// StepUpRecent requires the user to have authenticated within a recency window.
	StepUpRecent StepUpLevel = "recent"
	// StepUpMFA requires the user to have completed MFA verification.
	StepUpMFA StepUpLevel = "mfa"
	// StepUpPassword requires the user to re-enter their password.
	StepUpPassword StepUpLevel = "password"
)

type StepUpManager

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

StepUpManager orchestrates step-up authentication checks.

Usage:

store := flow.NewMemoryStepUpStore()
policy := &BankingPolicy{}
mgr := flow.NewStepUpManager(store, flow.WithStepUpPolicy(policy))

// Check before a sensitive action
result, _ := mgr.Evaluate(ctx, "session-123", "transfer_funds", nil)
if !result.Allowed {
    // Prompt user for re-authentication
}

// After re-authentication succeeds
mgr.RecordStepUp(ctx, "session-123", flow.StepUpPassword)

func NewStepUpManager

func NewStepUpManager(store StepUpStore, opts ...StepUpManagerOption) *StepUpManager

NewStepUpManager creates a new step-up authentication manager.

func (*StepUpManager) Evaluate

func (m *StepUpManager) Evaluate(ctx context.Context, sessionID, action string, resource any) (*StepUpResult, error)

Evaluate checks whether the current session meets the required assurance level for a given action.

func (*StepUpManager) RecordStepUp

func (m *StepUpManager) RecordStepUp(ctx context.Context, sessionID string, level StepUpLevel) error

RecordStepUp records that a step-up authentication was completed.

type StepUpManagerOption

type StepUpManagerOption func(*StepUpManager)

StepUpManagerOption configures a StepUpManager.

func WithRecencyWindow

func WithRecencyWindow(d time.Duration) StepUpManagerOption

WithRecencyWindow sets how long a step-up authentication remains valid. Default is 15 minutes.

func WithStepUpPolicy

func WithStepUpPolicy(policy StepUpPolicy) StepUpManagerOption

WithStepUpPolicy sets the policy for determining required levels.

type StepUpPolicy

type StepUpPolicy interface {
	RequiredLevel(ctx context.Context, action string, resource any) StepUpLevel
}

StepUpPolicy defines when step-up authentication is required. Implementations map actions and resources to required assurance levels.

Example:

type BankingPolicy struct{}
func (p *BankingPolicy) RequiredLevel(ctx context.Context, action string, resource any) StepUpLevel {
    switch action {
    case "transfer_funds", "change_password":
        return flow.StepUpPassword
    case "view_transactions":
        return flow.StepUpRecent
    default:
        return flow.StepUpNone
    }
}

type StepUpRecord

type StepUpRecord struct {
	SessionID   string      `json:"session_id"`
	Level       StepUpLevel `json:"level"`
	CompletedAt time.Time   `json:"completed_at"`
}

StepUpRecord tracks when a step-up authentication was completed for a session.

type StepUpResult

type StepUpResult struct {
	// Allowed is true if the current session meets the required level.
	Allowed bool `json:"allowed"`
	// RequiredLevel is the level required for the action.
	RequiredLevel StepUpLevel `json:"required_level"`
	// CurrentLevel is the highest level the session currently has.
	CurrentLevel StepUpLevel `json:"current_level"`
	// ChallengeType suggests which authentication method to use if not allowed.
	ChallengeType string `json:"challenge_type,omitempty"`
}

StepUpResult contains the outcome of evaluating a step-up requirement.

type StepUpStore

type StepUpStore interface {
	SaveStepUp(ctx context.Context, record *StepUpRecord) error
	GetStepUp(ctx context.Context, sessionID string) (*StepUpRecord, error)
	DeleteStepUp(ctx context.Context, sessionID string) error
}

StepUpStore persists step-up authentication records.

type StrategyFactory

type StrategyFactory func(config *domain.StrategyConfig) (LoginStrategy, error)

StrategyFactory is a function that creates a LoginStrategy from a config.

type StrategyRegistry

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

StrategyRegistry maintains a map of factory functions for creating strategies.

func NewStrategyRegistry

func NewStrategyRegistry() *StrategyRegistry

func (*StrategyRegistry) Build

Build creates a strategy instance from a configuration.

func (*StrategyRegistry) RegisterFactory

func (r *StrategyRegistry) RegisterFactory(typeKey string, factory StrategyFactory)

RegisterFactory registers a factory function for a specific strategy type (e.g. "password", "oidc").

type TOTPStrategy

type TOTPStrategy struct{}

TOTPStrategy implements Multi-Factor Authentication using TOTP.

func (*TOTPStrategy) ID

func (s *TOTPStrategy) ID() string

func (*TOTPStrategy) Verify

func (s *TOTPStrategy) Verify(secret string, code string) bool

Verify checks a 6-digit TOTP code against a secret.

type TokenBucketRateLimiter

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

TokenBucketRateLimiter implements rate limiting using token bucket algorithm. Allows for burst capacity while maintaining a steady rate.

func NewTokenBucketRateLimiter

func NewTokenBucketRateLimiter() *TokenBucketRateLimiter

NewTokenBucketRateLimiter creates a new token bucket rate limiter.

func (*TokenBucketRateLimiter) Allow

func (r *TokenBucketRateLimiter) Allow(ctx context.Context, key string, limit int, window time.Duration) (bool, int, error)

func (*TokenBucketRateLimiter) Reset

func (r *TokenBucketRateLimiter) Reset(ctx context.Context, key string) error

type TraitSource

type TraitSource interface {
	GetTraits() identity.JSON
	SetTraits(identity.JSON)
}

TraitSource is an optional interface for models that support Kayan's dynamic Traits.

type VerificationManager

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

func NewVerificationManager

func NewVerificationManager(repo IdentityRepository, store domain.TokenStore) *VerificationManager

func (*VerificationManager) Initiate

func (m *VerificationManager) Initiate(ctx context.Context, ident any) (*domain.AuthToken, error)

Initiate generates a verification token.

func (*VerificationManager) Verify

func (m *VerificationManager) Verify(ctx context.Context, tokenStr string) error

Verify consumes the token and marks identity as verified.

type WebAuthnConfig

type WebAuthnConfig struct {
	RPDisplayName string   // Relying Party display name (e.g., "Kayan Auth")
	RPID          string   // Relying Party ID (e.g., "example.com")
	RPOrigins     []string // Allowed origins (e.g., ["https://example.com"])

	// SessionTTL is how long WebAuthn sessions are valid (default: 5 minutes)
	SessionTTL time.Duration

	// Hooks for customizing behavior
	Hooks WebAuthnHooks
}

WebAuthnConfig holds configuration for WebAuthn.

type WebAuthnCredentialData

type WebAuthnCredentialData struct {
	CredentialID    []byte `json:"credential_id"`
	PublicKey       []byte `json:"public_key"`
	AttestationType string `json:"attestation_type"`
	AAGUID          []byte `json:"aaguid"`
	SignCount       uint32 `json:"sign_count"`
	CloneWarning    bool   `json:"clone_warning"`
	BackupEligible  bool   `json:"backup_eligible"`
	BackupState     bool   `json:"backup_state"`
}

WebAuthnCredentialData stores WebAuthn credential details in the Config field.

type WebAuthnHooks

type WebAuthnHooks struct {
	// BeforeBeginRegistration is called before starting registration.
	// Return error to prevent registration from starting.
	BeforeBeginRegistration func(ctx context.Context, ident any, userName string) error

	// AfterBeginRegistration is called after registration options are created.
	// Can be used for logging or modifying options.
	AfterBeginRegistration func(ctx context.Context, ident any, sessionID string) error

	// BeforeFinishRegistration is called before completing registration.
	BeforeFinishRegistration func(ctx context.Context, ident any, sessionID string) error

	// AfterFinishRegistration is called after credential is created.
	// Receives the new credential for additional processing.
	AfterFinishRegistration func(ctx context.Context, ident any, cred *identity.Credential) error

	// BeforeBeginLogin is called before starting login ceremony.
	BeforeBeginLogin func(ctx context.Context, identifier string) error

	// AfterBeginLogin is called after login options are created.
	AfterBeginLogin func(ctx context.Context, identifier string, sessionID string) error

	// BeforeFinishLogin is called before completing login.
	BeforeFinishLogin func(ctx context.Context, identifier string, sessionID string) error

	// AfterFinishLogin is called after successful login.
	// Receives the authenticated identity.
	AfterFinishLogin func(ctx context.Context, ident any) error

	// OnCloneWarning is called when a potential credential cloning is detected.
	// This is a security event - log it and consider action.
	OnCloneWarning func(ctx context.Context, ident any, credentialID string)

	// CredentialFilter allows filtering which credentials to use.
	// Return true to include the credential, false to exclude.
	CredentialFilter func(cred *identity.Credential) bool

	// CreateSessionID allows custom session ID generation.
	// If nil, uses default random generation.
	CreateSessionID func() string

	// UserLoader allows custom identity loading for login.
	// If set, bypasses the default identifier-based lookup.
	UserLoader func(ctx context.Context, identifier string) (any, error)

	// CredentialSaver allows custom credential storage.
	// If set, handles credential persistence instead of default behavior.
	CredentialSaver func(ctx context.Context, ident any, cred *identity.Credential) error
}

WebAuthnHooks provides extension points for customizing WebAuthn behavior.

type WebAuthnSessionData

type WebAuthnSessionData struct {
	Challenge        string    `json:"challenge"`
	UserID           []byte    `json:"user_id"`
	AllowedCredIDs   [][]byte  `json:"allowed_cred_ids,omitempty"`
	UserVerification string    `json:"user_verification"`
	ExpiresAt        time.Time `json:"expires_at"`
}

WebAuthnSessionData stores session data during registration/login ceremonies.

type WebAuthnSessionStore

type WebAuthnSessionStore interface {
	SaveSession(ctx context.Context, sessionID string, data *WebAuthnSessionData) error
	GetSession(ctx context.Context, sessionID string) (*WebAuthnSessionData, error)
	DeleteSession(ctx context.Context, sessionID string) error
}

WebAuthnSessionStore interface for storing WebAuthn ceremony sessions.

type WebAuthnStrategy

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

WebAuthnStrategy implements WebAuthn/Passkeys authentication.

func NewWebAuthnStrategy

func NewWebAuthnStrategy(
	repo IdentityRepository,
	config WebAuthnConfig,
	factory func() any,
	sessionStore WebAuthnSessionStore,
) (*WebAuthnStrategy, error)

NewWebAuthnStrategy creates a new WebAuthn strategy.

func (*WebAuthnStrategy) Authenticate

func (s *WebAuthnStrategy) Authenticate(ctx context.Context, identifier, secret string) (any, error)

Authenticate completes the WebAuthn login ceremony. Implements LoginStrategy interface. identifier = email/username, secret = JSON-encoded assertion response + sessionID

func (*WebAuthnStrategy) BeginLogin

func (s *WebAuthnStrategy) BeginLogin(
	ctx context.Context,
	identifier string,
) (*protocol.CredentialAssertion, string, error)

BeginLogin starts the WebAuthn login ceremony. Returns the options to send to the client and a session ID for verification.

func (*WebAuthnStrategy) BeginRegistration

func (s *WebAuthnStrategy) BeginRegistration(
	ctx context.Context,
	ident any,
	userName, displayName string,
) (*protocol.CredentialCreation, string, error)

BeginRegistration starts the WebAuthn registration ceremony. Returns the options to send to the client and a session ID for verification.

func (*WebAuthnStrategy) FinishLogin

func (s *WebAuthnStrategy) FinishLogin(
	ctx context.Context,
	identifier string,
	sessionID string,
	response *protocol.ParsedCredentialAssertionData,
) (any, error)

FinishLogin completes the WebAuthn login ceremony.

func (*WebAuthnStrategy) FinishRegistration

func (s *WebAuthnStrategy) FinishRegistration(
	ctx context.Context,
	ident any,
	sessionID string,
	userName, displayName string,
	response *protocol.ParsedCredentialCreationData,
) (*identity.Credential, error)

FinishRegistration completes the WebAuthn registration ceremony. Returns the created credential.

func (*WebAuthnStrategy) ID

func (s *WebAuthnStrategy) ID() string

func (*WebAuthnStrategy) SetHooks

func (s *WebAuthnStrategy) SetHooks(hooks WebAuthnHooks)

SetHooks allows updating hooks after creation.

func (*WebAuthnStrategy) SetIDGenerator

func (s *WebAuthnStrategy) SetIDGenerator(g domain.IDGenerator)

SetIDGenerator sets the ID generator for new credentials.

func (*WebAuthnStrategy) SetSessionTTL

func (s *WebAuthnStrategy) SetSessionTTL(ttl time.Duration)

SetSessionTTL allows updating the session TTL after creation.

type WebAuthnUser

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

WebAuthnUser adapts an identity to the webauthn.User interface.

func (*WebAuthnUser) WebAuthnCredentials

func (u *WebAuthnUser) WebAuthnCredentials() []webauthn.Credential

func (*WebAuthnUser) WebAuthnDisplayName

func (u *WebAuthnUser) WebAuthnDisplayName() string

func (*WebAuthnUser) WebAuthnID

func (u *WebAuthnUser) WebAuthnID() []byte

func (*WebAuthnUser) WebAuthnName

func (u *WebAuthnUser) WebAuthnName() string

Jump to

Keyboard shortcuts

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