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 ¶
- Variables
- func CompositeKeyFunc(fns ...func(context.Context, string) string) func(context.Context, string) string
- func ContextKeyFunc(ctxKey ContextKey, separator string) func(context.Context, string) string
- func IPKeyFunc(separator string) func(context.Context, string) string
- func IsRateLimitError(err error) bool
- func PrefixKeyFunc(prefix string) func(context.Context, string) string
- type Attacher
- type BcryptHasher
- type ClaimMapper
- type ConfigurableRateLimiter
- func (r *ConfigurableRateLimiter) Allow(ctx context.Context, key string, limit int, window time.Duration) (bool, int, error)
- func (r *ConfigurableRateLimiter) OnAllow(fn func(ctx context.Context, key string, remaining int)) *ConfigurableRateLimiter
- func (r *ConfigurableRateLimiter) OnDeny(fn func(ctx context.Context, key string)) *ConfigurableRateLimiter
- func (r *ConfigurableRateLimiter) OnRequest(fn func(ctx context.Context, key string, limit int, window time.Duration)) *ConfigurableRateLimiter
- func (r *ConfigurableRateLimiter) Reset(ctx context.Context, key string) error
- type ContextKey
- type CredentialSource
- type FixedWindowRateLimiter
- type Flow
- type FlowIdentity
- type FlowType
- type Hook
- type IdentityRepository
- type Initiator
- type Linker
- type LockoutConfig
- type LockoutHooks
- type LockoutInfo
- type LockoutStore
- type LockoutStrategy
- type LoginManager
- func (m *LoginManager) AddPostHook(h Hook)
- func (m *LoginManager) AddPreHook(h Hook)
- func (m *LoginManager) Authenticate(ctx context.Context, method, identifier, secret string) (any, error)
- func (m *LoginManager) FindIdentity(ctx context.Context, identifier string) (any, error)
- func (m *LoginManager) InitiateLogin(ctx context.Context, method, identifier string) (any, error)
- func (m *LoginManager) LinkMethod(ctx context.Context, ident any, method, identifier, secret string) error
- func (m *LoginManager) RegisterStrategy(s LoginStrategy)
- func (m *LoginManager) Registry() *StrategyRegistry
- func (m *LoginManager) ReloadStrategies(ctx context.Context) error
- func (m *LoginManager) SetFactory(f func() any)
- func (m *LoginManager) SetStrategyStore(store domain.StrategyStore)
- func (m *LoginManager) VerifyMFA(ctx context.Context, ident any, code string) (bool, error)
- type LoginStrategy
- type MagicLinkStrategy
- type MemoryLockoutStore
- func (s *MemoryLockoutStore) ClearFailures(ctx context.Context, identifier string) error
- func (s *MemoryLockoutStore) IsLocked(ctx context.Context, identifier string) (bool, time.Time, error)
- func (s *MemoryLockoutStore) Lock(ctx context.Context, identifier string, duration time.Duration) error
- func (s *MemoryLockoutStore) RecordFailure(ctx context.Context, identifier string, ttl time.Duration) (int, error)
- type MemoryRateLimiter
- type MemoryStepUpStore
- type MemoryWebAuthnSessionStore
- func (s *MemoryWebAuthnSessionStore) DeleteSession(ctx context.Context, sessionID string) error
- func (s *MemoryWebAuthnSessionStore) GetSession(ctx context.Context, sessionID string) (*WebAuthnSessionData, error)
- func (s *MemoryWebAuthnSessionStore) SaveSession(ctx context.Context, sessionID string, data *WebAuthnSessionData) error
- type OIDCManager
- func (m *OIDCManager) Attach(ctx context.Context, ident any, identifier, secret string) error
- func (m *OIDCManager) GetAuthURL(providerID, state string) (string, error)
- func (m *OIDCManager) HandleCallback(ctx context.Context, providerID, code string) (any, error)
- func (m *OIDCManager) SetClaimMapper(mapper ClaimMapper)
- func (m *OIDCManager) SetIDGenerator(g domain.IDGenerator)
- func (m *OIDCManager) SetLinker(l Linker)
- type OIDCProviderData
- type OTPOption
- type OTPSender
- type OTPStrategy
- type PasswordStrategy
- func (s *PasswordStrategy) Attach(ctx context.Context, ident any, identifier, secret string) error
- func (s *PasswordStrategy) Authenticate(ctx context.Context, identifier, password string) (any, error)
- func (s *PasswordStrategy) ID() string
- func (s *PasswordStrategy) MapFields(identifiers []string, password string)
- func (s *PasswordStrategy) Register(ctx context.Context, traits identity.JSON, password string) (any, error)
- func (s *PasswordStrategy) SetIDGenerator(g domain.IDGenerator)
- type RateLimitConfig
- type RateLimitError
- type RateLimitHooks
- type RateLimitInfo
- type RateLimitStrategy
- func (s *RateLimitStrategy) Authenticate(ctx context.Context, identifier, secret string) (any, error)
- func (s *RateLimitStrategy) ID() string
- func (s *RateLimitStrategy) Initiate(ctx context.Context, identifier string) (any, error)
- func (s *RateLimitStrategy) SetDynamicLimit(fn func(ctx context.Context, identifier string) (int, time.Duration))
- func (s *RateLimitStrategy) SetHooks(hooks RateLimitHooks)
- func (s *RateLimitStrategy) SetKeyFunc(fn func(ctx context.Context, identifier string) string)
- func (s *RateLimitStrategy) SetSkipFunc(fn func(ctx context.Context, identifier string) bool)
- type RateLimiter
- type RecoveryManager
- type RedisLockoutStore
- func (s *RedisLockoutStore) ClearFailures(ctx context.Context, identifier string) error
- func (s *RedisLockoutStore) IsLocked(ctx context.Context, identifier string) (bool, time.Time, error)
- func (s *RedisLockoutStore) Lock(ctx context.Context, identifier string, duration time.Duration) error
- func (s *RedisLockoutStore) RecordFailure(ctx context.Context, identifier string, ttl time.Duration) (int, error)
- type RedisRateLimiter
- type RedisWebAuthnSessionStore
- func (s *RedisWebAuthnSessionStore) DeleteSession(ctx context.Context, sessionID string) error
- func (s *RedisWebAuthnSessionStore) GetSession(ctx context.Context, sessionID string) (*WebAuthnSessionData, error)
- func (s *RedisWebAuthnSessionStore) SaveSession(ctx context.Context, sessionID string, data *WebAuthnSessionData) error
- type RegistrationManager
- func (m *RegistrationManager) AddPostHook(h Hook)
- func (m *RegistrationManager) AddPreHook(h Hook)
- func (m *RegistrationManager) RegisterStrategy(s RegistrationStrategy)
- func (m *RegistrationManager) SetLinker(l Linker)
- func (m *RegistrationManager) SetSchema(s identity.Schema)
- func (m *RegistrationManager) Submit(ctx context.Context, method string, traits identity.JSON, secret string) (any, error)
- type RegistrationStrategy
- type StepUpLevel
- type StepUpManager
- type StepUpManagerOption
- type StepUpPolicy
- type StepUpRecord
- type StepUpResult
- type StepUpStore
- type StrategyFactory
- type StrategyRegistry
- type TOTPStrategy
- type TokenBucketRateLimiter
- type TraitSource
- type VerificationManager
- type WebAuthnConfig
- type WebAuthnCredentialData
- type WebAuthnHooks
- type WebAuthnSessionData
- type WebAuthnSessionStore
- type WebAuthnStrategy
- func (s *WebAuthnStrategy) Authenticate(ctx context.Context, identifier, secret string) (any, error)
- func (s *WebAuthnStrategy) BeginLogin(ctx context.Context, identifier string) (*protocol.CredentialAssertion, string, error)
- func (s *WebAuthnStrategy) BeginRegistration(ctx context.Context, ident any, userName, displayName string) (*protocol.CredentialCreation, string, error)
- func (s *WebAuthnStrategy) FinishLogin(ctx context.Context, identifier string, sessionID string, ...) (any, error)
- func (s *WebAuthnStrategy) FinishRegistration(ctx context.Context, ident any, sessionID string, userName, displayName string, ...) (*identity.Credential, error)
- func (s *WebAuthnStrategy) ID() string
- func (s *WebAuthnStrategy) SetHooks(hooks WebAuthnHooks)
- func (s *WebAuthnStrategy) SetIDGenerator(g domain.IDGenerator)
- func (s *WebAuthnStrategy) SetSessionTTL(ttl time.Duration)
- type WebAuthnUser
Constants ¶
This section is empty.
Variables ¶
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") )
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 IPKeyFunc ¶
IPKeyFunc creates a key function that extracts IP from identifier. Useful when identifier contains "email:ip" format.
func IsRateLimitError ¶
IsRateLimitError checks if an error is a rate limit error.
Types ¶
type Attacher ¶
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
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) OnAllow ¶
func (r *ConfigurableRateLimiter) OnAllow(fn func(ctx context.Context, key string, remaining int)) *ConfigurableRateLimiter
func (*ConfigurableRateLimiter) OnDeny ¶
func (r *ConfigurableRateLimiter) OnDeny(fn func(ctx context.Context, key string)) *ConfigurableRateLimiter
func (*ConfigurableRateLimiter) OnRequest ¶
func (r *ConfigurableRateLimiter) OnRequest(fn func(ctx context.Context, key string, limit int, window time.Duration)) *ConfigurableRateLimiter
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.
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.
type FlowIdentity ¶
FlowIdentity defines the minimum interface that any identity model must satisfy.
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 ¶
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 (*LockoutStrategy) ID ¶
func (s *LockoutStrategy) ID() string
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 (*LoginManager) FindIdentity ¶
func (*LoginManager) InitiateLogin ¶
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)
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
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) RecordFailure ¶
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.
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 ¶
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 (*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 OTPOption ¶
type OTPOption func(*OTPStrategy)
OTPOption configures an OTPStrategy.
func WithOTPCodeLength ¶
WithOTPCodeLength sets the number of digits in the OTP code. Default is 6.
func WithOTPTTL ¶
WithOTPTTL sets the expiration duration for OTP codes. Default is 5 minutes.
type OTPSender ¶
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 ¶
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
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) Authenticate ¶
func (*PasswordStrategy) ID ¶
func (s *PasswordStrategy) ID() string
func (*PasswordStrategy) MapFields ¶
func (s *PasswordStrategy) MapFields(identifiers []string, password string)
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 (*RateLimitStrategy) ID ¶
func (s *RateLimitStrategy) ID() string
func (*RateLimitStrategy) Initiate ¶
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.
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)
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 ¶
func (r *StrategyRegistry) Build(config *domain.StrategyConfig) (LoginStrategy, error)
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
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.
type TraitSource ¶
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
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