Documentation
¶
Overview ¶
Package phone provides phone number verification and SMS-based OTP authentication.
Index ¶
- Constants
- Variables
- func CompareOTP(candidateCode, storedHash, salt string) bool
- func GenerateCode(length int) (string, error)
- func GenerateID() (string, error)
- func GenerateSalt() (string, error)
- func HashOTP(code, salt string) string
- func RegisterSchema(s *schema.Schema) error
- type Capability
- type Config
- type ConfirmChangeInput
- type ConfirmVerificationInput
- type Hooks
- func (h *Hooks) BeforeChange(fn func(ctx context.Context, in *RequestChangeInput) error)
- func (h *Hooks) BeforeRemove(fn func(ctx context.Context, in *RemoveInput) error)
- func (h *Hooks) BeforeRequestOTP(fn func(ctx context.Context, in *RequestOTPInput) error)
- func (h *Hooks) BeforeVerifyOTP(fn func(ctx context.Context, in *VerifyOTPInput) error)
- type Message
- type MessageBuilder
- type MockProvider
- type Module
- type Normalizer
- type OTPRecord
- type OTPRejectedPayload
- type OTPRequestedPayload
- type OTPVerifiedPayload
- type Option
- func WithAllowSignUp(allow bool) Option
- func WithCacheFailurePolicy(policy cache.FailurePolicy) Option
- func WithCacheTTL(ttl time.Duration) Option
- func WithHooks(h *Hooks) Option
- func WithMaxAttempts(attempts int) Option
- func WithMessageBuilder(mb MessageBuilder) Option
- func WithNormalizer(n Normalizer) Option
- func WithOTPLength(length int) Option
- func WithProvider(p PhoneProvider) Option
- func WithRateLimit(max int, window, cooldown time.Duration) Option
- func WithRevokePreviousOnRequest(revoke bool) Option
- func WithTTL(ttl time.Duration) Option
- func WithValidator(v Validator) Option
- type Phone
- type PhoneCache
- func (c *PhoneCache) GetByNumber(ctx context.Context, phone string) (*PhoneRecord, error)
- func (c *PhoneCache) GetByUserID(ctx context.Context, userID string) (*PhoneRecord, error)
- func (c *PhoneCache) Invalidate(ctx context.Context, userID, phone string)
- func (c *PhoneCache) SetByNumber(ctx context.Context, phone string, p *PhoneRecord) error
- func (c *PhoneCache) SetByUserID(ctx context.Context, userID string, p *PhoneRecord) error
- type PhoneChangedPayload
- type PhoneProvider
- type PhoneRecord
- type PhoneRemovedPayload
- type PhoneVerifiedPayload
- type RateLimitConfig
- type Record
- type RemoveInput
- type RequestChangeInput
- type RequestOTPInput
- type RequestVerificationInput
- type Validator
- type VerificationError
- type VerifyOTPInput
Constants ¶
const ( // DefaultOTPLength defines the default digit count for phone OTPs. DefaultOTPLength = 6 // MinOTPLength defines the minimum acceptable OTP length. MinOTPLength = 4 // MaxOTPLength defines the maximum acceptable OTP length. MaxOTPLength = 12 // SaltBytes defines the random entropy size for per-OTP salts. SaltBytes = 16 )
const ( // EventPhoneOTPRequested is published when an OTP is successfully generated and dispatched. EventPhoneOTPRequested = "phone.otp.requested" // EventPhoneOTPVerified is published when an OTP code is verified and authentication completes. EventPhoneOTPVerified = "phone.otp.verified" // EventPhoneOTPRejected is published when an OTP verification attempt fails. EventPhoneOTPRejected = "phone.otp.rejected" // EventPhoneVerified is published when a phone number is validated and confirmed for a user. EventPhoneVerified = "phone.verified" // EventPhoneChanged is published when a user replaces their phone number with a new verified one. EventPhoneChanged = "phone.changed" // EventPhoneRemoved is published when a user unlinks or removes their phone number. EventPhoneRemoved = "phone.removed" )
const ( // MinE164Digits is the minimum digit count for a valid international phone number. MinE164Digits = 7 // MaxE164Digits is the maximum digit count defined by ITU-T E.164 recommendation. MaxE164Digits = 15 )
const ( // AccountProvider identifies Core accounts provisioned through this plugin. AccountProvider = "phone" // PurposeAuthenticate namespaces verification tokens used for OTP authentication. PurposeAuthenticate = "phone:authenticate" // PurposeVerify namespaces verification tokens used for initial phone ownership confirmation. PurposeVerify = "phone:verify" // PurposeChange namespaces verification tokens used for changing phone numbers. PurposeChange = "phone:change" )
const ( ModelPhones = "phones" ModelPhoneOTPs = "phone_otps" )
const PluginID = "phone"
PluginID is the unique identifier for this plugin module in GoModularAuth.
Variables ¶
var ( // ErrPhoneRequired is returned when a phone number input is empty. ErrPhoneRequired = errors.New("phone: phone number is required") // ErrInvalidPhone is returned when a phone number fails E.164 normalization or validation. ErrInvalidPhone = errors.New("phone: invalid phone number format (must be E.164, e.g. +12025550123)") // ErrPhoneAlreadyExists is returned when trying to assign a phone number that is already linked to another account. ErrPhoneAlreadyExists = errors.New("phone: phone number is already registered to an account") // ErrPhoneNotFound is returned when no phone record exists for the given user. ErrPhoneNotFound = errors.New("phone: phone record not found") // ErrCodeRequired is returned when an OTP code is empty. ErrCodeRequired = errors.New("phone: verification code is required") // ErrInvalidCode is returned when the provided OTP code does not match the stored hash. ErrInvalidCode = errors.New("phone: invalid verification code") // ErrCodeExpired is returned when the OTP code has surpassed its time-to-live. ErrCodeExpired = errors.New("phone: verification code has expired") // ErrCodeAlreadyUsed is returned when the OTP code was already consumed. ErrCodeAlreadyUsed = errors.New("phone: verification code has already been consumed") // ErrMaxAttemptsExceeded is returned when failed verification attempts exceed the allowed threshold. ErrMaxAttemptsExceeded = errors.New("phone: maximum verification attempts exceeded") // ErrCooldownActive is returned when a new OTP request is made before the cooldown duration has elapsed. ErrCooldownActive = errors.New("phone: cooldown period active, please wait before requesting another code") // ErrRateLimitExceeded is returned when too many OTP requests are made within the rate limit window. ErrRateLimitExceeded = errors.New("phone: rate limit exceeded, please try again later") // ErrProviderRequired is returned when no PhoneProvider is configured on the module or host. ErrProviderRequired = errors.New("phone: a PhoneProvider must be configured") // ErrSignUpDisabled is returned when an unregistered phone number attempts authentication while AllowSignUp is false. ErrSignUpDisabled = errors.New("phone: automatic sign-up is disabled for unregistered phone numbers") // ErrUserNotFound is returned when the referenced Core user does not exist. ErrUserNotFound = errors.New("phone: user not found") // ErrCacheFailure is returned when cache operations fail under a FailClosed policy. ErrCacheFailure = errors.New("phone: cache operation failed") )
Functions ¶
func CompareOTP ¶
CompareOTP verifies a candidate code against the stored hash in constant time.
func GenerateCode ¶
GenerateCode creates a cryptographically secure numeric OTP of the specified length. It eliminates modulo bias by discarding random bytes >= 250 (the largest multiple of 10 below 256).
func GenerateID ¶
GenerateID produces a random hex-encoded unique identifier.
func GenerateSalt ¶
GenerateSalt produces a random hex-encoded salt string.
func HashOTP ¶
HashOTP produces a salted SHA-256 hash string for the OTP code. Plaintext codes are never stored in persistence layer.
func RegisterSchema ¶
RegisterSchema registers the phone identity and OTP verification models in the compiled schema.
Types ¶
type Capability ¶
type Capability = Phone
Capability is an alias for Phone for idiomatic capability retrieval.
type Config ¶
type Config struct {
// Provider delivers transactional SMS/voice messages.
Provider PhoneProvider
// TTL defines the lifespan of an issued OTP code. Default: 5 minutes.
TTL time.Duration
// OTPLength defines the numeric digit count of the OTP. Default: 6.
OTPLength int
// MaxAttempts defines the maximum failed attempts permitted before locking the OTP. Default: 3.
MaxAttempts int
// AllowSignUp allows automatic creation of accounts for unknown phone numbers upon verification. Default: true.
AllowSignUp bool
// RevokePreviousOnRequest revokes active OTPs for the phone prior to issuing a new code. Default: true.
RevokePreviousOnRequest bool
// RateLimit holds rate limiting and cooldown rules.
RateLimit RateLimitConfig
// CacheTTL defines the TTL for ephemeral phone lookups. Default: 15 minutes.
CacheTTL time.Duration
// Normalizer normalizes raw phone strings into canonical E.164 format.
Normalizer Normalizer
// Validator validates canonical phone strings.
Validator Validator
// MessageBuilder formats message body and delivery parameters.
MessageBuilder MessageBuilder
// Hooks provides workflow extension points.
Hooks *Hooks
}
Config defines the configuration for the Phone authentication plugin.
type ConfirmChangeInput ¶
type ConfirmChangeInput struct {
// UserID identifies the user. Required.
UserID string
// NewPhoneNumber is the new phone number to confirm. Required.
NewPhoneNumber string
// Code is the numeric verification code sent to the new phone number. Required.
Code string
}
ConfirmChangeInput contains parameters for confirming a phone number change.
type ConfirmVerificationInput ¶
type ConfirmVerificationInput struct {
// UserID identifies the user. Required.
UserID string
// PhoneNumber is the phone number to confirm. Required.
PhoneNumber string
// Code is the numeric OTP code received on the device. Required.
Code string
}
ConfirmVerificationInput contains parameters for confirming ownership of a phone number.
type Hooks ¶
type Hooks struct {
// contains filtered or unexported fields
}
Hooks provides lifecycle extension points for phone workflows.
func (*Hooks) BeforeChange ¶
func (h *Hooks) BeforeChange(fn func(ctx context.Context, in *RequestChangeInput) error)
BeforeChange registers a hook invoked before initiating a phone change.
func (*Hooks) BeforeRemove ¶
func (h *Hooks) BeforeRemove(fn func(ctx context.Context, in *RemoveInput) error)
BeforeRemove registers a hook invoked before removing a user's phone record.
func (*Hooks) BeforeRequestOTP ¶
func (h *Hooks) BeforeRequestOTP(fn func(ctx context.Context, in *RequestOTPInput) error)
BeforeRequestOTP registers a hook invoked before an OTP is generated and dispatched.
func (*Hooks) BeforeVerifyOTP ¶
func (h *Hooks) BeforeVerifyOTP(fn func(ctx context.Context, in *VerifyOTPInput) error)
BeforeVerifyOTP registers a hook invoked before an OTP is verified.
type Message ¶
type Message struct {
// To is the destination phone number formatted in E.164.
To string
// Body is the rendered text body delivered to the recipient.
Body string
// Code is the raw numeric verification code.
Code string
// Purpose is the semantic action (e.g. "authenticate", "verify", "change").
Purpose string
// TTL is the active validity lifespan of the verification code.
TTL time.Duration
// Metadata carries optional vendor-specific or application-level parameters.
Metadata map[string]string
}
Message encapsulates a dispatched verification SMS or voice message.
type MessageBuilder ¶
MessageBuilder formats the SMS message body and parameters before transmission.
type MockProvider ¶
type MockProvider struct {
// contains filtered or unexported fields
}
MockProvider is an in-memory, thread-safe PhoneProvider for unit and integration testing.
func NewMockProvider ¶
func NewMockProvider() *MockProvider
NewMockProvider creates an empty MockProvider.
func (*MockProvider) LastMessage ¶
func (m *MockProvider) LastMessage() *Message
LastMessage returns the most recently sent message, or nil if none.
func (*MockProvider) Messages ¶
func (m *MockProvider) Messages() []Message
Messages returns a copy of all recorded dispatched messages.
func (*MockProvider) Reset ¶
func (m *MockProvider) Reset()
Reset clears recorded messages and resets errors.
func (*MockProvider) Send ¶
func (m *MockProvider) Send(ctx context.Context, msg Message) error
Send records the message in memory or returns the simulated error.
func (*MockProvider) SetError ¶
func (m *MockProvider) SetError(err error)
SetError instructs the mock provider to return an error on subsequent Send calls.
type Module ¶
type Module struct {
// contains filtered or unexported fields
}
Module implements auth.PluginModule for Phone identity and OTP authentication using plugin-sdk.
type Normalizer ¶
Normalizer transforms a raw phone number input into a canonical format (E.164).
func DefaultNormalizer ¶
func DefaultNormalizer() Normalizer
DefaultNormalizer returns a canonical E.164 string (+[country_code][number]). It strips spaces, hyphens, periods, and parentheses, normalizes international exit codes (00 -> +), and ensures the output conforms to ITU-T E.164 format.
type OTPRecord ¶
type OTPRecord struct {
ID string `json:"id"`
PhoneNumber string `json:"phone_number"`
Subject string `json:"subject"`
Purpose string `json:"purpose"`
OTPHash string `json:"otp_hash"`
Salt string `json:"salt"`
ExpiresAt time.Time `json:"expires_at"`
MaxAttempts int `json:"max_attempts"`
Attempts int `json:"attempts"`
UsedAt *time.Time `json:"used_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
OTPRecord represents the persistent verification state of an issued phone OTP.
func (*OTPRecord) Expired ¶
Expired reports whether the OTP token has expired at the specified instant.
type OTPRejectedPayload ¶
type OTPRejectedPayload struct {
PhoneNumber string `json:"phone_number"`
Reason string `json:"reason"`
Attempts int `json:"attempts"`
Time time.Time `json:"time"`
}
OTPRejectedPayload carries diagnostic information when an OTP verification fails.
type OTPRequestedPayload ¶
type OTPRequestedPayload struct {
PhoneNumber string `json:"phone_number"`
Subject string `json:"subject"`
Purpose string `json:"purpose"`
ExpiresAt time.Time `json:"expires_at"`
}
OTPRequestedPayload carries metadata about an OTP dispatch event. Sensitive data (raw code, salt, hash) is strictly excluded.
type OTPVerifiedPayload ¶
type OTPVerifiedPayload struct {
UserID string `json:"user_id"`
PhoneNumber string `json:"phone_number"`
SessionID string `json:"session_id,omitempty"`
VerifiedAt time.Time `json:"verified_at"`
}
OTPVerifiedPayload carries metadata about an authenticated session established via phone OTP.
type Option ¶
type Option func(*Config)
Option applies a functional configuration option.
func WithAllowSignUp ¶
WithAllowSignUp specifies whether unregistered phone numbers can register automatically.
func WithCacheFailurePolicy ¶
func WithCacheFailurePolicy(policy cache.FailurePolicy) Option
WithCacheFailurePolicy explicitly defines fail-open vs fail-closed handling for cache errors.
func WithCacheTTL ¶
WithCacheTTL sets the duration for ephemeral cache entries.
func WithMaxAttempts ¶
WithMaxAttempts sets the maximum verification attempts allowed per OTP.
func WithMessageBuilder ¶
func WithMessageBuilder(mb MessageBuilder) Option
WithMessageBuilder sets a custom message constructor.
func WithNormalizer ¶
func WithNormalizer(n Normalizer) Option
WithNormalizer sets a custom phone normalizer.
func WithOTPLength ¶
WithOTPLength sets the digit count of generated OTP codes.
func WithProvider ¶
func WithProvider(p PhoneProvider) Option
WithProvider sets the message delivery provider.
func WithRateLimit ¶
WithRateLimit enables rate limiting and cooldown rules.
func WithRevokePreviousOnRequest ¶
WithRevokePreviousOnRequest controls whether existing active OTPs are revoked when a new code is requested.
func WithValidator ¶
WithValidator sets a custom phone validator.
type Phone ¶
type Phone interface {
// RequestOTP dispatches an authentication OTP code to the provided phone number.
// If AllowSignUp is false and the phone number is unregistered, this method returns nil
// without dispatching an SMS, protecting against user enumeration.
RequestOTP(ctx context.Context, in RequestOTPInput) error
// VerifyOTP validates and atomically consumes the single-use OTP code.
// On success, it provisions or retrieves the user, links the phone account, and returns an AuthResult.
// On failure (incorrect code, expired, replay, max attempts exceeded), it emits a rejection event
// and returns a domain error.
VerifyOTP(ctx context.Context, in VerifyOTPInput) (*auth.AuthResult, error)
// RequestVerification dispatches an OTP to verify a user's ownership of a phone number.
RequestVerification(ctx context.Context, in RequestVerificationInput) error
// ConfirmVerification validates the OTP and marks the user's phone record as verified.
ConfirmVerification(ctx context.Context, in ConfirmVerificationInput) (*Record, error)
// RequestChange dispatches an OTP to a new phone number to begin changing the user's phone number.
RequestChange(ctx context.Context, in RequestChangeInput) error
// ConfirmChange validates the OTP sent to the new phone and atomically updates the user's phone number.
ConfirmChange(ctx context.Context, in ConfirmChangeInput) (*Record, error)
// Remove unlinks and removes the phone record for the specified user.
Remove(ctx context.Context, in RemoveInput) error
// GetByUserID retrieves the phone record associated with a Core user ID.
GetByUserID(ctx context.Context, userID string) (*Record, error)
// GetByPhone retrieves the phone record associated with a normalized phone number.
GetByPhone(ctx context.Context, phoneNumber string) (*Record, error)
// Normalize transforms and validates a raw phone string into canonical E.164 format.
Normalize(raw string) (string, error)
// Validate checks whether a normalized phone number complies with ITU-T E.164 format.
Validate(normalized string) error
}
Phone is the strongly typed public capability exposed by this plugin. Applications consume it via auth.Get[plugins.Phone](a) or auth.Get[phone.Phone](a).
type PhoneCache ¶
type PhoneCache struct {
// contains filtered or unexported fields
}
PhoneCache accelerates read queries for PhoneRecords with automatic invalidation.
func NewPhoneCache ¶
func NewPhoneCache(c corecache.Cache, ttl time.Duration) *PhoneCache
NewPhoneCache creates a PhoneCache adapter.
func (*PhoneCache) GetByNumber ¶
func (c *PhoneCache) GetByNumber(ctx context.Context, phone string) (*PhoneRecord, error)
GetByNumber retrieves a cached phone record by phone number.
func (*PhoneCache) GetByUserID ¶
func (c *PhoneCache) GetByUserID(ctx context.Context, userID string) (*PhoneRecord, error)
GetByUserID retrieves a cached phone record by user ID.
func (*PhoneCache) Invalidate ¶
func (c *PhoneCache) Invalidate(ctx context.Context, userID, phone string)
Invalidate invalidates cached phone records for both user ID and phone number.
func (*PhoneCache) SetByNumber ¶
func (c *PhoneCache) SetByNumber(ctx context.Context, phone string, p *PhoneRecord) error
SetByNumber caches a phone record by phone number.
func (*PhoneCache) SetByUserID ¶
func (c *PhoneCache) SetByUserID(ctx context.Context, userID string, p *PhoneRecord) error
SetByUserID caches a phone record by user ID.
type PhoneChangedPayload ¶
type PhoneChangedPayload struct {
UserID string `json:"user_id"`
OldPhone string `json:"old_phone"`
NewPhone string `json:"new_phone"`
ChangedAt time.Time `json:"changed_at"`
}
PhoneChangedPayload carries metadata when a user's phone number is updated.
type PhoneProvider ¶
PhoneProvider defines the vendor-agnostic interface for delivering phone messages (SMS/voice). Implementations adapt services such as Twilio, Vonage, AWS SNS, Infobip, or internal gateways.
type PhoneRecord ¶
type PhoneRecord struct {
ID string `json:"id"`
UserID string `json:"user_id"`
PhoneNumber string `json:"phone_number"` // Canonical E.164 string
Verified bool `json:"verified"`
VerifiedAt *time.Time `json:"verified_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
PhoneRecord represents the persistent mapping between a Core User and a validated phone number.
type PhoneRemovedPayload ¶
type PhoneRemovedPayload struct {
UserID string `json:"user_id"`
PhoneNumber string `json:"phone_number"`
RemovedAt time.Time `json:"removed_at"`
}
PhoneRemovedPayload carries metadata when a user's phone number is deleted.
type PhoneVerifiedPayload ¶
type PhoneVerifiedPayload struct {
UserID string `json:"user_id"`
PhoneNumber string `json:"phone_number"`
VerifiedAt time.Time `json:"verified_at"`
}
PhoneVerifiedPayload carries metadata when a user successfully verifies their phone number.
type RateLimitConfig ¶
type RateLimitConfig struct {
Enabled bool
Max int
Window time.Duration
Cooldown time.Duration
FailurePolicy cache.FailurePolicy
}
RateLimitConfig configures throttling and abuse mitigation.
type RemoveInput ¶
type RemoveInput struct {
// UserID identifies the user whose phone record is being deleted. Required.
UserID string
}
RemoveInput contains parameters for unlinking and deleting a user's phone number.
type RequestChangeInput ¶
type RequestChangeInput struct {
// UserID identifies the user changing their phone number. Required.
UserID string
// NewPhoneNumber is the replacement phone number. Required.
NewPhoneNumber string
// Metadata carries optional context.
Metadata map[string]string
}
RequestChangeInput contains parameters for initiating a phone number change for an existing user.
type RequestOTPInput ¶
type RequestOTPInput struct {
// PhoneNumber is the destination mobile number (E.164 or unformatted). Required.
PhoneNumber string
// Metadata carries optional application context or vendor tags.
Metadata map[string]string
}
RequestOTPInput contains parameters for requesting an authentication OTP code.
type RequestVerificationInput ¶
type RequestVerificationInput struct {
// UserID identifies the user to whom the phone is being linked. Required.
UserID string
// PhoneNumber is the phone number to verify. Required.
PhoneNumber string
// Metadata carries optional context.
Metadata map[string]string
}
RequestVerificationInput contains parameters for requesting ownership verification for an existing user.
type Validator ¶
Validator validates whether a normalized phone number complies with domain rules.
func DefaultValidator ¶
func DefaultValidator() Validator
DefaultValidator checks that the normalized number meets ITU-T E.164 requirements.
type VerificationError ¶
VerificationError is a typed error detailing verification failure status and attempt counts.
func (*VerificationError) Error ¶
func (e *VerificationError) Error() string
func (*VerificationError) Unwrap ¶
func (e *VerificationError) Unwrap() error
Unwrap exposes the underlying sentinel error for errors.Is checks.
type VerifyOTPInput ¶
type VerifyOTPInput struct {
// PhoneNumber is the phone number that requested the code. Required.
PhoneNumber string
// Code is the numeric OTP received by the user. Required.
Code string
// Email is an optional email address used when registering a new user upon first sign-in.
Email string
// Session holds optional IP address and User Agent metadata for the session.
Session auth.SessionOptions
}
VerifyOTPInput contains parameters for verifying an OTP code and establishing a session.