Documentation
¶
Overview ¶
Package twofactor implements RFC 6238 TOTP two-factor authentication and recovery codes.
Index ¶
- Constants
- Variables
- func BuildProvisioningURI(issuer, accountName, secret string) string
- func CalculateTOTP(secret string, step int64) (string, error)
- func GenerateRandomID(bytesLen int) (string, error)
- func GenerateRandomKey() ([]byte, error)
- func GenerateRecoveryCode() (string, error)
- func GenerateTOTPSecret() (string, error)
- func HashRecoveryCode(code string) string
- func NormalizeRecoveryCode(code string) string
- func RegisterSchema(s *schema.Schema) error
- func RequireAAL2(ctx context.Context) error
- func ValidateTOTP(secret string, code string, now time.Time, stepDuration time.Duration, ...) (int64, bool)
- func WithAssuranceLevel(ctx context.Context, level AssuranceLevel) context.Context
- type AESGCMProtector
- type AssuranceLevel
- type Challenge
- type ChallengeRecord
- type Config
- type EnrollConfig
- type EnrollOption
- type Factor
- type FactorRecord
- type FactorType
- type Module
- type Option
- func WithChallengeTTL(ttl time.Duration) Option
- func WithDefaultRecoveryCodesCount(count int) Option
- func WithEncryptionKey(key []byte) Option
- func WithEnforceMFAOnSignIn(enforce bool) Option
- func WithIssuer(issuer string) Option
- func WithSecretProtector(p SecretProtector) Option
- func WithSkewSteps(steps int) Option
- type RecoveryCodeRecord
- type SecretProtector
- type Status
- type TOTPEnrollment
- type TwoFactor
- type TwoFactorDisabledPayload
- type TwoFactorEnabledPayload
- type TwoFactorVerifiedPayload
- type VerifyResult
Constants ¶
const ( // EventTwoFactorEnabled is emitted when a user successfully confirms enrollment of their first MFA factor. EventTwoFactorEnabled auth.EventName = "twofactor.enabled" // EventTwoFactorDisabled is emitted when MFA is disabled for a user. EventTwoFactorDisabled auth.EventName = "twofactor.disabled" // EventTwoFactorVerified is emitted when a user successfully verifies a secondary factor (TOTP or recovery code). EventTwoFactorVerified auth.EventName = "twofactor.verified" )
Event names emitted by the twofactor plugin.
const ( ModelTwoFactors = "twofactor_factors" ModelRecoveryCodes = "twofactor_recovery_codes" ModelMFAChallenges = "twofactor_challenges" )
const PluginID = "twofactor"
PluginID identifies this plugin in the GoModularAuth runtime.
Variables ¶
var ( // ErrInvalidInput is returned when required parameters (userID, factorID, code, etc.) are empty or malformed. ErrInvalidInput = errors.New("twofactor: invalid input") // ErrFactorNotFound is returned when the requested MFA factor does not exist. ErrFactorNotFound = errors.New("twofactor: factor not found") // ErrFactorAlreadyExists is returned when attempting to enroll an already existing factor. ErrFactorAlreadyExists = errors.New("twofactor: factor already exists") // ErrFactorAlreadyConfirmed is returned when attempting to confirm an already confirmed factor. ErrFactorAlreadyConfirmed = errors.New("twofactor: factor already confirmed") // ErrFactorNotConfirmed is returned when attempting to authenticate using an unconfirmed factor. ErrFactorNotConfirmed = errors.New("twofactor: factor is not confirmed") // ErrInvalidCode is returned when an OTP or recovery code fails verification. ErrInvalidCode = errors.New("twofactor: invalid authentication code") // ErrReplayDetected is returned when an OTP code has already been consumed for the current or a previous time-step. ErrReplayDetected = errors.New("twofactor: replay detected; code already used") // ErrChallengeNotFound is returned when an MFA challenge token does not exist. ErrChallengeNotFound = errors.New("twofactor: challenge not found") // ErrChallengeExpired is returned when an MFA challenge token has passed its expiration time. ErrChallengeExpired = errors.New("twofactor: challenge expired") // ErrChallengeAlreadyUsed is returned when attempting to verify an already consumed challenge. ErrChallengeAlreadyUsed = errors.New("twofactor: challenge already used") // ErrNoActiveRecoveryCodes is returned when no active recovery codes remain for the user. ErrNoActiveRecoveryCodes = errors.New("twofactor: no active recovery codes available") // ErrRecoveryCodeNotFound is returned when the supplied recovery code does not match any active record. ErrRecoveryCodeNotFound = errors.New("twofactor: recovery code not found") // ErrAssuranceLevelInsufficient is returned when an operation requires AAL2 but the context only holds AAL1. ErrAssuranceLevelInsufficient = errors.New("twofactor: assurance level insufficient; AAL2 required") // ErrTwoFactorNotEnabled is returned when an operation requires 2FA to be enabled for the user. ErrTwoFactorNotEnabled = errors.New("twofactor: two-factor authentication is not enabled") // ErrCryptoOperationFailed is returned when secret encryption or decryption fails. ErrCryptoOperationFailed = errors.New("twofactor: cryptographic operation failed") )
Functions ¶
func BuildProvisioningURI ¶ added in v1.3.0
BuildProvisioningURI constructs a standard otpauth://totp URI for authenticator applications.
func CalculateTOTP ¶ added in v1.3.0
CalculateTOTP generates a 6-digit TOTP code for a base32-encoded secret at a specific time step (RFC 6238 / RFC 4226).
func GenerateRandomID ¶ added in v1.3.0
GenerateRandomID generates a cryptographically secure hex ID.
func GenerateRandomKey ¶ added in v1.3.0
GenerateRandomKey generates a cryptographically secure 32-byte key for AES-256.
func GenerateRecoveryCode ¶ added in v1.3.0
GenerateRecoveryCode generates a formatted backup recovery code (e.g. "XXXX-XXXX-XXXX").
func GenerateTOTPSecret ¶ added in v1.3.0
GenerateTOTPSecret creates a 20-byte cryptographically secure secret, base32 encoded without padding.
func HashRecoveryCode ¶ added in v1.3.0
HashRecoveryCode computes the SHA-256 hash of a normalized recovery code.
func NormalizeRecoveryCode ¶ added in v1.3.0
NormalizeRecoveryCode cleans up user input by uppercasing and stripping hyphens and whitespaces.
func RegisterSchema ¶ added in v1.3.0
RegisterSchema registers all two-factor authentication models into the compiled schema.
func RequireAAL2 ¶ added in v1.3.0
RequireAAL2 checks whether the context has achieved AssuranceLevel 2. Returns ErrAssuranceLevelInsufficient if not.
func ValidateTOTP ¶ added in v1.3.0
func ValidateTOTP(secret string, code string, now time.Time, stepDuration time.Duration, skewSteps int) (int64, bool)
ValidateTOTP validates a 6-digit code against a secret within the allowed clock skew window. Returns the matched time step if valid, or -1 if invalid.
func WithAssuranceLevel ¶ added in v1.3.0
func WithAssuranceLevel(ctx context.Context, level AssuranceLevel) context.Context
WithAssuranceLevel returns a new context bearing the specified AssuranceLevel.
Types ¶
type AESGCMProtector ¶ added in v1.3.0
type AESGCMProtector struct {
// contains filtered or unexported fields
}
AESGCMProtector implements SecretProtector using AES-256-GCM.
func NewAESGCMProtector ¶ added in v1.3.0
func NewAESGCMProtector(key []byte) (*AESGCMProtector, error)
NewAESGCMProtector creates a new protector from a 32-byte master key.
type AssuranceLevel ¶ added in v1.3.0
type AssuranceLevel string
AssuranceLevel classifies the level of confidence in the authenticated identity (NIST SP 800-63B).
const ( // AAL1 represents single-factor authentication (e.g. password, magic link, email OTP alone). AAL1 AssuranceLevel = "aal1" // AAL2 represents multi-factor authentication (primary factor plus a confirmed secondary factor). AAL2 AssuranceLevel = "aal2" )
func AssuranceLevelFrom ¶ added in v1.3.0
func AssuranceLevelFrom(ctx context.Context) (AssuranceLevel, bool)
AssuranceLevelFrom retrieves the AssuranceLevel from the context, defaulting to AAL1 if absent.
type Challenge ¶ added in v1.3.0
type Challenge struct {
ID string `json:"id"`
UserID string `json:"user_id"`
ExpiresAt time.Time `json:"expires_at"`
CreatedAt time.Time `json:"created_at"`
}
Challenge represents an ephemeral MFA challenge issued when a user completes primary authentication and must satisfy a second factor challenge to gain full session privileges.
type ChallengeRecord ¶ added in v0.7.0
type ChallengeRecord struct {
ID string `json:"id"`
UserID string `json:"user_id"`
ExpiresAt time.Time `json:"expires_at"`
UsedAt *time.Time `json:"used_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
ChallengeRecord is the persistent fallback model for ephemeral MFA challenges.
type Config ¶
type Config struct {
// Protector encrypts and decrypts factor secrets at rest.
// If nil, defaults to an AES-256-GCM protector initialized with a secure random key.
Protector SecretProtector
// Issuer is the default organization or application name included in otpauth:// URIs.
Issuer string
// StepDuration is the time interval for TOTP tokens (RFC 6238 recommends 30 seconds).
StepDuration time.Duration
// SkewSteps is the number of time-steps allowed before and after current time to tolerate clock drift.
// Default is 1 (covering +/- 30 seconds).
SkewSteps int
// ChallengeTTL is the lifetime for ephemeral MFA challenges.
ChallengeTTL time.Duration
// DefaultRecoveryCodesCount is the number of recovery codes generated by default.
DefaultRecoveryCodesCount int
// EnforceMFAOnSignIn controls whether the plugin verifies that users with active 2FA
// are not granted final sessions without completing MFA verification.
EnforceMFAOnSignIn bool
}
Config holds the configuration options for the twofactor plugin module.
type EnrollConfig ¶ added in v1.3.0
EnrollConfig holds optional parameters for TOTP enrollment.
type EnrollOption ¶ added in v1.3.0
type EnrollOption func(*EnrollConfig)
EnrollOption configures an enrollment request.
func WithEnrollAccountName ¶ added in v1.3.0
func WithEnrollAccountName(accountName string) EnrollOption
WithEnrollAccountName sets the account name in the otpauth URI (e.g. user's email).
func WithEnrollIssuer ¶ added in v1.3.0
func WithEnrollIssuer(issuer string) EnrollOption
WithEnrollIssuer sets a custom issuer name for the otpauth URI.
func WithEnrollLabel ¶ added in v1.3.0
func WithEnrollLabel(label string) EnrollOption
WithEnrollLabel sets a human-friendly label for the factor (e.g. "Work iPhone").
type Factor ¶ added in v1.3.0
type Factor struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Type FactorType `json:"type"`
Label string `json:"label,omitempty"`
Confirmed bool `json:"confirmed"`
ConfirmedAt *time.Time `json:"confirmed_at,omitempty"`
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Factor represents a registered second factor. It deliberately omits any plaintext or encrypted secrets so normal APIs cannot leak sensitive material.
type FactorRecord ¶ added in v1.3.0
type FactorRecord struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Type string `json:"type"`
Label string `json:"label,omitempty"`
EncryptedSecret string `json:"encrypted_secret"`
Confirmed bool `json:"confirmed"`
ConfirmedAt *time.Time `json:"confirmed_at,omitempty"`
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
LastUsedTimeStep int64 `json:"last_used_time_step"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
FactorRecord is the persistent model for an MFA factor.
type FactorType ¶ added in v1.3.0
type FactorType string
FactorType represents an authentication second factor kind.
const ( // FactorTypeTOTP represents Time-based One-Time Password (RFC 6238). FactorTypeTOTP FactorType = "totp" // FactorTypeRecoveryCodes represents single-use backup recovery codes. FactorTypeRecoveryCodes FactorType = "recovery_codes" )
type Module ¶ added in v1.3.0
type Module struct {
// contains filtered or unexported fields
}
Module implements auth.PluginModule and auth.SchemaProvider for two-factor authentication.
type Option ¶
type Option func(*Config)
Option configures the Config struct.
func WithChallengeTTL ¶ added in v1.3.0
WithChallengeTTL sets the duration for ephemeral MFA challenges.
func WithDefaultRecoveryCodesCount ¶ added in v1.3.0
WithDefaultRecoveryCodesCount sets the default number of recovery codes generated.
func WithEncryptionKey ¶ added in v1.3.0
WithEncryptionKey sets the 32-byte master key for the default AES-GCM protector.
func WithEnforceMFAOnSignIn ¶ added in v1.3.0
WithEnforceMFAOnSignIn configures whether to require MFA challenges upon primary sign in.
func WithIssuer ¶
WithIssuer sets the default issuer name for TOTP provisioning URIs.
func WithSecretProtector ¶ added in v1.3.0
func WithSecretProtector(p SecretProtector) Option
WithSecretProtector sets a custom SecretProtector implementation.
func WithSkewSteps ¶ added in v1.3.0
WithSkewSteps sets the allowed clock skew steps (e.g. 1 allows +/- 1 step).
type RecoveryCodeRecord ¶ added in v1.3.0
type RecoveryCodeRecord struct {
ID string `json:"id"`
UserID string `json:"user_id"`
CodeHash string `json:"code_hash"`
UsedAt *time.Time `json:"used_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
RecoveryCodeRecord is the persistent model for a hashed backup recovery code.
type SecretProtector ¶ added in v1.3.0
type SecretProtector interface {
Encrypt(plaintext []byte) (string, error)
Decrypt(ciphertext string) ([]byte, error)
}
SecretProtector encrypts and decrypts sensitive factor secrets at rest.
type Status ¶ added in v1.3.0
type Status struct {
Enabled bool `json:"enabled"`
Factors []Factor `json:"factors"`
RecoveryCodesCount int `json:"recovery_codes_count"`
PrimaryFactorType FactorType `json:"primary_factor_type,omitempty"`
}
Status summarizes the two-factor authentication configuration for a user.
type TOTPEnrollment ¶ added in v1.3.0
type TOTPEnrollment struct {
FactorID string `json:"factor_id"`
Secret string `json:"secret"` // Plaintext base32 secret; returned only during enrollment
ProvisioningURI string `json:"provisioning_uri"` // Standard otpauth:// URI for QR code generation
Issuer string `json:"issuer"`
AccountName string `json:"account_name"`
}
TOTPEnrollment contains the setup data returned ONCE during TOTP enrollment initiation. Applications present this secret or URI to the user (e.g., via QR code).
type TwoFactor ¶ added in v0.3.0
type TwoFactor interface {
// GetStatus returns the complete 2FA status and configured factors for a user.
GetStatus(ctx context.Context, userID string) (*Status, error)
// IsEnabled reports whether the user has at least one confirmed MFA factor.
IsEnabled(ctx context.Context, userID string) (bool, error)
// ListFactors returns all registered factors (confirmed or pending) for a user.
ListFactors(ctx context.Context, userID string) ([]Factor, error)
// EnrollTOTP initiates TOTP enrollment. It creates a pending factor and returns the secret & provisioning URI.
// The factor remains inactive until confirmed via ConfirmTOTP.
EnrollTOTP(ctx context.Context, userID string, opts ...EnrollOption) (*TOTPEnrollment, error)
// ConfirmTOTP confirms a pending TOTP factor with a valid code from the authenticator app.
// Upon success, the factor is activated and EventTwoFactorEnabled is emitted.
ConfirmTOTP(ctx context.Context, userID, factorID, code string) error
// VerifyTOTP verifies a TOTP code for a user against their confirmed TOTP factors.
// It applies clock-skew tolerance and prevents replay attacks. Emits EventTwoFactorVerified.
VerifyTOTP(ctx context.Context, userID, code string) (*VerifyResult, error)
// VerifyRecoveryCode verifies and consumes a single-use backup recovery code.
// Emits EventTwoFactorVerified upon successful consumption.
VerifyRecoveryCode(ctx context.Context, userID, code string) (*VerifyResult, error)
// CreateChallenge creates a short-lived MFA challenge for two-step authentication flows.
CreateChallenge(ctx context.Context, userID string) (*Challenge, error)
// VerifyChallenge verifies a challenge token with either a TOTP code or a recovery code.
VerifyChallenge(ctx context.Context, challengeID, code string) (*VerifyResult, error)
// GenerateRecoveryCodes generates a fresh set of recovery codes, hashing them before storing.
// Returns the plaintext codes ONCE.
GenerateRecoveryCodes(ctx context.Context, userID string, count int) ([]string, error)
// RotateRecoveryCodes revokes all existing recovery codes for a user and generates a fresh set.
RotateRecoveryCodes(ctx context.Context, userID string) ([]string, error)
// GetRemainingRecoveryCodesCount returns the number of unused active recovery codes.
GetRemainingRecoveryCodesCount(ctx context.Context, userID string) (int, error)
// Disable disables MFA for a user by removing all factors and recovery codes.
// Emits EventTwoFactorDisabled.
Disable(ctx context.Context, userID string) error
// DisableFactor removes a specific factor. If no confirmed factors remain, MFA is effectively disabled.
DisableFactor(ctx context.Context, userID, factorID string) error
}
TwoFactor is the strongly typed capability provided by the twofactor plugin.
type TwoFactorDisabledPayload ¶ added in v1.3.0
type TwoFactorDisabledPayload struct {
UserID string `json:"user_id"`
DisabledAt time.Time `json:"disabled_at"`
Reason string `json:"reason,omitempty"`
}
TwoFactorDisabledPayload represents observational data when 2FA is disabled.
type TwoFactorEnabledPayload ¶ added in v1.3.0
type TwoFactorEnabledPayload struct {
UserID string `json:"user_id"`
FactorID string `json:"factor_id"`
FactorType FactorType `json:"factor_type"`
EnabledAt time.Time `json:"enabled_at"`
}
TwoFactorEnabledPayload represents observational data when 2FA is enabled.
type TwoFactorVerifiedPayload ¶ added in v1.3.0
type TwoFactorVerifiedPayload struct {
UserID string `json:"user_id"`
FactorID string `json:"factor_id,omitempty"`
FactorType FactorType `json:"factor_type"`
AssuranceLevel AssuranceLevel `json:"assurance_level"`
VerifiedAt time.Time `json:"verified_at"`
}
TwoFactorVerifiedPayload represents observational data when a factor is verified.
type VerifyResult ¶ added in v0.7.0
type VerifyResult struct {
UserID string `json:"user_id"`
FactorID string `json:"factor_id,omitempty"`
FactorType FactorType `json:"factor_type"`
AssuranceLevel AssuranceLevel `json:"assurance_level"`
VerifiedAt time.Time `json:"verified_at"`
}
VerifyResult details the successful verification of an MFA factor.