phonenumber

package
v0.27.2 Latest Latest
Warning

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

Go to latest
Published: Aug 31, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package phonenumber provides SMS OTP (One-Time Password) and phone number-based authentication for go-modular-auth, supporting passwordless sign-in, phone number verification and updates, phone + password login, SMS password resets, and attempt budgeting.

Index

Constants

View Source
const (
	// EventPhoneNumberOTPSendBefore is emitted right before dispatching an OTP to the recipient phone number.
	// Payload: *OTPSentPayload
	EventPhoneNumberOTPSendBefore = "phonenumber:otp:send:before"

	// EventPhoneNumberOTPSent is emitted immediately after an OTP has been successfully dispatched.
	// Payload: *OTPSentPayload
	EventPhoneNumberOTPSent = "phonenumber:otp:send:after"

	// EventPhoneNumberOTPVerifyBefore is emitted right before verifying a submitted OTP code.
	// Payload: *OTPSentPayload
	EventPhoneNumberOTPVerifyBefore = "phonenumber:otp:verify:before"

	// EventPhoneNumberOTPVerified is emitted after an OTP code has been successfully verified.
	// Payload: *OTPVerifiedPayload
	EventPhoneNumberOTPVerified = "phonenumber:otp:verify:after"

	// EventPhoneNumberOTPFailed is emitted when an incorrect OTP is submitted.
	// Payload: *OTPFailedPayload
	EventPhoneNumberOTPFailed = "phonenumber:otp:verify:failed"

	// EventPhoneNumberOTPAttemptsExceeded is emitted when all allowed attempts on an OTP have been exhausted.
	// Payload: *OTPFailedPayload
	EventPhoneNumberOTPAttemptsExceeded = "phonenumber:otp:attempts:exceeded"

	// EventPhoneNumberOTPExpired is emitted when verification is attempted on an expired OTP.
	// Payload: *OTPFailedPayload
	EventPhoneNumberOTPExpired = "phonenumber:otp:expired"

	// EventPhoneNumberSignInSuccess is emitted when a user successfully authenticates via phone OTP.
	// Payload: *SignInSuccessPayload
	EventPhoneNumberSignInSuccess = "phonenumber:signin:success"

	// EventPhoneNumberSignUpSuccess is emitted when a new user is provisioned upon phone OTP verification.
	// Payload: *SignInSuccessPayload
	EventPhoneNumberSignUpSuccess = "phonenumber:signup:success"

	// EventPhoneNumberSignInPasswordSuccess is emitted when a user authenticates using phone + password.
	// Payload: *SignInSuccessPayload
	EventPhoneNumberSignInPasswordSuccess = "phonenumber:signin_password:success"

	// EventPhoneNumberUpdated is emitted when a user's phone number is updated after verification.
	// Payload: *entity.User
	EventPhoneNumberUpdated = "phonenumber:updated"

	// EventPhoneNumberUnlinked is emitted when a user's phone number is removed.
	// Payload: *entity.User
	EventPhoneNumberUnlinked = "phonenumber:unlinked"

	// EventPhoneNumberPasswordResetRequested is emitted when a password reset OTP is requested for a phone number.
	// Payload: *OTPSentPayload
	EventPhoneNumberPasswordResetRequested = "phonenumber:password_reset:requested"

	// EventPhoneNumberPasswordResetSuccess is emitted when a user resets their password using a phone OTP.
	// Payload: *PasswordResetPayload
	EventPhoneNumberPasswordResetSuccess = "phonenumber:password_reset:success"
)

Event bus topic string constants emitted during Phone Number lifecycle operations.

View Source
const (
	ExtraKeyPhoneNumber         = "phone_number"
	ExtraKeyPhoneNumberVerified = "phone_number_verified"
	ExtraKeyOTPCode             = "otp_code"
	ExtraKeyOTPType             = "otp_type"
	ExtraKeyRememberMe          = "remember_me"
	ExtraKeyUpdatePhone         = "update_phone_number"
	ExtraKeyDisableSession      = "disable_session"
	ExtraKeyDeviceID            = "device_id"
	ExtraKeyIPAddress           = "ip_address"
	ExtraKeyUserAgent           = "user_agent"
)

Standard Extra metadata keys that can be set or consumed in Phone Number parameters and Event payloads.

View Source
const PluginID = "phone-number"

PluginID is the unique string identifier for the Phone Number plugin ("phone-number").

Variables

View Source
var (
	// ErrInvalidPhoneNumber is returned when a phone number format validation fails or is empty.
	ErrInvalidPhoneNumber = errors.New("phonenumber: invalid phone number")

	// ErrPhoneNumberAlreadyExists is returned when attempting to assign a phone number that already belongs to another user.
	ErrPhoneNumberAlreadyExists = errors.New("phonenumber: phone number already in use by another user")

	// ErrPhoneNumberNotRegistered is returned when attempting to sign in with a phone number that does not exist in storage and auto-signup is disabled.
	ErrPhoneNumberNotRegistered = errors.New("phonenumber: phone number is not registered")

	// ErrInvalidPhoneNumberOrPassword is returned when phone + password login credentials do not match.
	ErrInvalidPhoneNumberOrPassword = errors.New("phonenumber: invalid phone number or password")

	// ErrPhoneNumberNotVerified is returned when attempting password sign-in with an unverified phone number while RequireVerification is enabled.
	ErrPhoneNumberNotVerified = errors.New("phonenumber: phone number is not verified")

	// ErrPhoneNumberCannotBeUpdated is returned when direct phone mutation is disallowed without prior verification.
	ErrPhoneNumberCannotBeUpdated = errors.New("phonenumber: phone number cannot be updated directly without verification")

	// ErrSendOTPNotImplemented is returned when attempting to dispatch an OTP without a configured SendOTP callback.
	ErrSendOTPNotImplemented = errors.New("phonenumber: send OTP callback is not configured")

	// ErrOTPNotFound is returned when no active OTP record exists for the given phone number identifier.
	ErrOTPNotFound = errors.New("phonenumber: OTP not found or already consumed")

	// ErrOTPExpired is returned when attempting to verify an OTP that has passed its expiration duration.
	ErrOTPExpired = errors.New("phonenumber: OTP expired")

	// ErrInvalidOTP is returned when the provided OTP code does not match the stored code or hash.
	ErrInvalidOTP = errors.New("phonenumber: invalid OTP")

	// ErrTooManyAttempts is returned when the maximum number of failed verification attempts on an active OTP has been exhausted.
	ErrTooManyAttempts = errors.New("phonenumber: maximum verification attempt limit reached")

	// ErrPasswordTooShort is returned when a new password does not satisfy the minimum length requirement.
	ErrPasswordTooShort = errors.New("phonenumber: password is too short")

	// ErrPasswordTooLong is returned when a new password exceeds the maximum allowed length.
	ErrPasswordTooLong = errors.New("phonenumber: password is too long")

	// ErrUserNotFound is returned when no user matches the queried ID or phone number.
	ErrUserNotFound = errors.New("phonenumber: user not found")

	// ErrCredentialAccountNotFound is returned when credential provider credentials are missing for the user.
	ErrCredentialAccountNotFound = errors.New("phonenumber: credential account not found")

	// ErrCannotRetrieveHashed is returned when attempting to inspect a plain text OTP while hashed storage mode is active.
	ErrCannotRetrieveHashed = errors.New("phonenumber: OTP is hashed, cannot retrieve plain text")

	// ErrInvalidStoredFormat is returned when a stored OTP record does not conform to the expected format.
	ErrInvalidStoredFormat = errors.New("phonenumber: invalid stored OTP record format")
)

Sentinel errors for the Phone Number plugin.

Functions

func DefaultNumericOTPGenerator

func DefaultNumericOTPGenerator(length int) (string, error)

DefaultNumericOTPGenerator generates a cryptographically secure random numeric string of length N (default: 6).

func SplitAtLastColon

func SplitAtLastColon(input string) (string, string)

SplitAtLastColon splits a stored value string into the stored OTP payload and the attempt counter ("<stored_otp>:<attempts>").

func ToOTPIdentifier

func ToOTPIdentifier(phoneNumber string) string

ToOTPIdentifier formats the standard storage identifier for phone verification or sign-in OTP. Format: "phone-verification-otp-<normalized_phone>"

func ToPasswordResetOTPIdentifier

func ToPasswordResetOTPIdentifier(phoneNumber string) string

ToPasswordResetOTPIdentifier formats the storage identifier for SMS password reset OTP. Format: "phone-password-reset-otp-<normalized_phone>"

Types

type AESGCMCipher

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

AESGCMCipher implements Cipher using AES-256-GCM with key derivation via SHA-256.

func NewAESGCMCipher

func NewAESGCMCipher(secretKey string) (*AESGCMCipher, error)

NewAESGCMCipher instantiates a new AES-256-GCM cipher using the provided secret key.

func (*AESGCMCipher) Decrypt

func (c *AESGCMCipher) Decrypt(ciphertext string) (string, error)

Decrypt decrypts a Base64 Raw URL encoded ciphertext string back into the original plain text OTP.

func (*AESGCMCipher) Encrypt

func (c *AESGCMCipher) Encrypt(plaintext string) (string, error)

Encrypt encrypts the plain text OTP using AES-256-GCM and returns a Base64 Raw URL encoded string.

type CallbackOnVerificationFunc

type CallbackOnVerificationFunc func(ctx context.Context, data OnVerificationData) error

CallbackOnVerificationFunc defines a hook executed after a phone number is successfully verified.

type CheckVerificationOTPParams

type CheckVerificationOTPParams struct {
	// PhoneNumber is the target phone number.
	PhoneNumber string `json:"phone_number"`

	// Type specifies the OTP workflow type.
	Type OTPType `json:"type"`

	// OTP is the code to check.
	OTP string `json:"otp"`

	plugin.ExtraContainer
}

CheckVerificationOTPParams defines parameters for validating an OTP without consuming it.

type CheckVerificationOTPResult

type CheckVerificationOTPResult struct {
	// Success indicates whether the OTP is currently valid.
	Success bool `json:"success"`
}

CheckVerificationOTPResult reports whether the tested OTP code is currently valid.

type Cipher

type Cipher interface {
	// Encrypt encrypts a plain text OTP into a secure string representation.
	Encrypt(plaintext string) (string, error)

	// Decrypt decrypts an encrypted string back into the original plain text OTP.
	Decrypt(ciphertext string) (string, error)
}

Cipher defines the contract for symmetric reversible encryption of OTP codes.

type Config

type Config struct {
	// SendOTP is the required SMS delivery callback.
	SendOTP SendOTPFunc

	// VerifyOTP is an optional delegated verification callback.
	VerifyOTP VerifyOTPFunc

	// SendPasswordResetOTP is an optional dedicated callback for password reset SMS OTPs.
	SendPasswordResetOTP SendOTPFunc

	// PhoneNumberValidator is an optional callback to validate phone number format.
	PhoneNumberValidator PhoneNumberValidatorFunc

	// CallbackOnVerification is an optional callback executed upon successful phone verification.
	CallbackOnVerification CallbackOnVerificationFunc

	// OnPasswordReset is an optional callback executed upon password reset confirmation.
	OnPasswordReset OnPasswordResetFunc

	// SignUpOnVerification configures temporary credentials for auto-created users.
	SignUpOnVerification *SignUpOnVerificationConfig

	// GenerateOTP is an optional custom OTP generator function.
	GenerateOTP GenerateOTPFunc

	// OTPLength is the number of digits in generated numeric OTPs (default: 6).
	OTPLength int

	// ExpiresIn is the duration after which an unverified OTP expires (default: 5 minutes).
	ExpiresIn time.Duration

	// AllowedAttempts is the maximum number of failed verification tries permitted before invalidating the code (default: 3).
	AllowedAttempts int

	// RequireVerification enforces that the phone number must already be verified before allowing password-based sign-in.
	RequireVerification bool

	// DisableSignUp prevents creating a new user if an account does not exist during phone verification.
	DisableSignUp bool

	// RevokeSessionsOnPasswordReset invalidates all active user sessions after a successful password reset (default: true).
	RevokeSessionsOnPasswordReset bool

	// MinPasswordLength is the minimum password length enforced during password resets (default: 8).
	MinPasswordLength int

	// MaxPasswordLength is the maximum password length enforced during password resets (default: 128).
	MaxPasswordLength int

	// StoreOTPMode defines how the OTP is persisted ("plain", "hashed", "encrypted", default: "plain").
	StoreOTPMode StoreOTPMode

	// SecretKey is the symmetric key used when StoreOTPMode is "encrypted".
	SecretKey string

	// ResendStrategy specifies behavior on resend requests ("rotate" or "reuse", default: "rotate").
	ResendStrategy ResendStrategy

	// CustomHasher is an optional custom Hasher implementation for "hashed" mode.
	CustomHasher Hasher

	// CustomCipher is an optional custom Cipher implementation for "encrypted" mode.
	CustomCipher Cipher
}

Config structures all configuration options for the Phone Number plugin.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns recommended production default settings for the Phone Number plugin.

type CreateVerificationOTPParams

type CreateVerificationOTPParams struct {
	// PhoneNumber is the target phone number.
	PhoneNumber string `json:"phone_number"`

	// Type specifies the OTP workflow type.
	Type OTPType `json:"type"`

	plugin.ExtraContainer
}

CreateVerificationOTPParams defines parameters for server-side OTP generation without SMS dispatch.

type CreateVerificationOTPResult

type CreateVerificationOTPResult struct {
	// Success indicates if the OTP record was created.
	Success bool `json:"success"`

	// ExpiresAt indicates when the created OTP will expire.
	ExpiresAt time.Time `json:"expires_at"`
}

CreateVerificationOTPResult contains the status and expiry of the created OTP.

type DefaultSHA256Hasher

type DefaultSHA256Hasher struct{}

DefaultSHA256Hasher implements Hasher using SHA-256 encoded in Base64 Raw URL.

func (DefaultSHA256Hasher) Hash

func (h DefaultSHA256Hasher) Hash(code string) (string, error)

Hash computes the SHA-256 hash of the plain OTP code.

func (DefaultSHA256Hasher) Verify

func (h DefaultSHA256Hasher) Verify(code, hashedCode string) bool

Verify compares the plain text OTP against the stored hash using constant-time evaluation.

type GenerateOTPFunc

type GenerateOTPFunc func(ctx context.Context, phoneNumber string, otpType OTPType) (string, error)

GenerateOTPFunc allows overriding the default random numeric code generation routine.

type GetVerificationOTPParams

type GetVerificationOTPParams struct {
	// PhoneNumber is the target phone number.
	PhoneNumber string `json:"phone_number"`

	// Type specifies the OTP workflow type.
	Type OTPType `json:"type"`

	plugin.ExtraContainer
}

GetVerificationOTPParams defines parameters for server-side inspection of an active OTP code.

type GetVerificationOTPResult

type GetVerificationOTPResult struct {
	// OTP is the retrieved plain text code.
	OTP string `json:"otp"`
}

GetVerificationOTPResult contains the plain text OTP code retrieved from storage.

type Hasher

type Hasher interface {
	// Hash computes the one-way cryptographic hash of an OTP code.
	Hash(code string) (string, error)

	// Verify compares a plain text OTP against the stored hash in constant time.
	Verify(code, hashedCode string) bool
}

Hasher defines the contract for hashing and verifying OTP codes in constant time.

type OTPFailedPayload

type OTPFailedPayload struct {
	PhoneNumber       string  `json:"phone_number"`
	Type              OTPType `json:"type"`
	AttemptsUsed      int     `json:"attempts_used"`
	AttemptsRemaining int     `json:"attempts_remaining"`
	Reason            string  `json:"reason"`
	plugin.ExtraContainer
}

OTPFailedPayload contains details when OTP verification fails or reaches limits.

type OTPSentPayload

type OTPSentPayload struct {
	PhoneNumber string    `json:"phone_number"`
	Type        OTPType   `json:"type"`
	ExpiresAt   time.Time `json:"expires_at"`
	plugin.ExtraContainer
}

OTPSentPayload contains recipient and expiration details for dispatched OTPs.

type OTPType

type OTPType string

OTPType defines the valid types of OTP operations supported by the Phone Number plugin.

const (
	// OTPTypeVerification represents phone number verification or passwordless sign-in via OTP.
	OTPTypeVerification OTPType = "phone-verification"

	// OTPTypePasswordReset represents password recovery and reset via phone OTP.
	OTPTypePasswordReset OTPType = "phone-password-reset"
)

type OTPVerifiedPayload

type OTPVerifiedPayload struct {
	UserID      string    `json:"user_id"`
	PhoneNumber string    `json:"phone_number"`
	Type        OTPType   `json:"type"`
	Timestamp   time.Time `json:"timestamp"`
	plugin.ExtraContainer
}

OTPVerifiedPayload reports details of a successful OTP code verification.

type OnPasswordResetFunc

type OnPasswordResetFunc func(ctx context.Context, user *entity.User) error

OnPasswordResetFunc defines a hook executed after a password reset is confirmed.

type OnVerificationData

type OnVerificationData struct {
	// PhoneNumber is the verified phone number.
	PhoneNumber string `json:"phone_number"`

	// User is the updated or newly created user entity.
	User *entity.User `json:"user"`

	// Extra holds dynamic metadata passed through event interceptors.
	Extra map[string]any `json:"extra,omitempty"`
}

OnVerificationData contains the context delivered to the post-verification callback.

type Option

type Option func(*Config)

Option represents a configuration modifier function.

func WithAllowedAttempts

func WithAllowedAttempts(attempts int) Option

WithAllowedAttempts sets the maximum number of failed verification tries allowed on an active OTP before locking out.

func WithCallbackOnVerification

func WithCallbackOnVerification(fn CallbackOnVerificationFunc) Option

WithCallbackOnVerification registers a hook to be called after a phone number is verified.

func WithCustomCipher

func WithCustomCipher(cipher Cipher) Option

WithCustomCipher registers a custom Cipher for StoreOTPEncrypted mode.

func WithCustomHasher

func WithCustomHasher(hasher Hasher) Option

WithCustomHasher registers a custom Hasher for StoreOTPHashed mode.

func WithDisableSignUp

func WithDisableSignUp(disable bool) Option

WithDisableSignUp disables automatic user provisioning when verifying an unregistered phone number.

func WithExpiresIn

func WithExpiresIn(d time.Duration) Option

WithExpiresIn configures the lifespan of generated OTPs.

func WithGenerateOTP

func WithGenerateOTP(fn GenerateOTPFunc) Option

WithGenerateOTP registers a custom OTP code generation routine.

func WithMaxPasswordLength

func WithMaxPasswordLength(maxLen int) Option

WithMaxPasswordLength configures the maximum allowed password length.

func WithMinPasswordLength

func WithMinPasswordLength(minLen int) Option

WithMinPasswordLength configures the minimum required password length.

func WithOTPLength

func WithOTPLength(length int) Option

WithOTPLength configures the number of digits in generated numeric OTPs.

func WithOnPasswordReset

func WithOnPasswordReset(fn OnPasswordResetFunc) Option

WithOnPasswordReset sets a callback hook executed upon successful password reset.

func WithPasswordPolicy

func WithPasswordPolicy(minLen, maxLen int, revokeOnReset bool) Option

WithPasswordPolicy sets password length constraints and session revocation behavior upon password reset.

func WithPhoneNumberValidator

func WithPhoneNumberValidator(fn PhoneNumberValidatorFunc) Option

WithPhoneNumberValidator sets a custom validator function for phone number formats.

func WithRequireVerification

func WithRequireVerification(require bool) Option

WithRequireVerification enforces that phone numbers must be verified before allowing phone + password sign-in.

func WithResendStrategy

func WithResendStrategy(strategy ResendStrategy) Option

WithResendStrategy configures the behavior when a user requests an OTP while an active one exists.

func WithRevokeSessionsOnPasswordReset

func WithRevokeSessionsOnPasswordReset(revoke bool) Option

WithRevokeSessionsOnPasswordReset configures whether to revoke all active sessions on password reset.

func WithSendOTP

func WithSendOTP(fn SendOTPFunc) Option

WithSendOTP configures the SMS delivery callback function.

func WithSendPasswordResetOTP

func WithSendPasswordResetOTP(fn SendOTPFunc) Option

WithSendPasswordResetOTP configures a dedicated SMS delivery callback for password resets.

func WithSignUpOnVerification

func WithSignUpOnVerification(cfg SignUpOnVerificationConfig) Option

WithSignUpOnVerification configures temporary naming and email resolution for auto-registered users.

func WithStoreOTP

func WithStoreOTP(mode StoreOTPMode, secretKey ...string) Option

WithStoreOTP configures the persistence strategy and optional secret key for encrypted mode.

func WithVerifyOTP

func WithVerifyOTP(fn VerifyOTPFunc) Option

WithVerifyOTP configures an optional delegated verification callback (e.g. Twilio Verify API).

type PasswordResetPayload

type PasswordResetPayload struct {
	UserID      string    `json:"user_id"`
	PhoneNumber string    `json:"phone_number"`
	Timestamp   time.Time `json:"timestamp"`
	plugin.ExtraContainer
}

PasswordResetPayload reports details when a password is reset via SMS OTP.

type PhoneNumberValidatorFunc

type PhoneNumberValidatorFunc func(ctx context.Context, phoneNumber string) (bool, error)

PhoneNumberValidatorFunc defines custom phone number validation (e.g. E.164 format verification).

type Plugin

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

Plugin implements the Phone Number (SMS OTP) authentication plugin.

func New

func New(repo Repository, opts ...Option) *Plugin

New instantiates a new Phone Number plugin configured with the given repository and options.

func (*Plugin) CheckVerificationOTP

func (p *Plugin) CheckVerificationOTP(ctx context.Context, params CheckVerificationOTPParams) (*CheckVerificationOTPResult, error)

CheckVerificationOTP validates an OTP code against storage without consuming it or modifying attempt counters.

func (*Plugin) Config

func (p *Plugin) Config() Config

Config returns the active configuration settings of the Phone Number plugin.

func (*Plugin) CreateVerificationOTP

func (p *Plugin) CreateVerificationOTP(ctx context.Context, params CreateVerificationOTPParams) (*CreateVerificationOTPResult, error)

CreateVerificationOTP generates and persists an OTP record in storage without sending SMS.

func (*Plugin) GetVerificationOTP

func (p *Plugin) GetVerificationOTP(ctx context.Context, params GetVerificationOTPParams) (*GetVerificationOTPResult, error)

GetVerificationOTP retrieves the active plain text OTP code (fails if stored in hashed mode).

func (*Plugin) ID

func (p *Plugin) ID() string

ID returns the unique string identifier for the Phone Number plugin ("phone-number").

func (*Plugin) Init

func (p *Plugin) Init(ctx *plugin.Context) error

Init initializes the plugin within the global GoModularAuth runtime context.

func (*Plugin) Repository

func (p *Plugin) Repository() Repository

Repository returns the underlying storage repository instance.

func (*Plugin) RequestPasswordReset

func (p *Plugin) RequestPasswordReset(ctx context.Context, params RequestPasswordResetParams) (*RequestPasswordResetResult, error)

RequestPasswordReset dispatches a numeric OTP via SMS to enable password resetting.

func (*Plugin) ResetPassword

func (p *Plugin) ResetPassword(ctx context.Context, params ResetPasswordParams) (*ResetPasswordResult, error)

ResetPassword verifies the reset OTP and securely updates the user's password.

func (*Plugin) SendOTP

func (p *Plugin) SendOTP(ctx context.Context, params SendOTPParams) (*SendOTPResult, error)

SendOTP generates and dispatches a numeric OTP via SMS to the specified recipient phone number.

func (*Plugin) SignIn

func (p *Plugin) SignIn(ctx context.Context, params SignInParams) (*SignInResult, error)

SignIn authenticates a user using their phone number and password.

func (*Plugin) UnlinkPhoneNumber

func (p *Plugin) UnlinkPhoneNumber(ctx context.Context, userID string) (*entity.User, error)

UnlinkPhoneNumber removes the phone number from the user's profile and resets its verified status.

func (*Plugin) Verify

func (p *Plugin) Verify(ctx context.Context, params VerifyParams) (*VerifyResult, error)

Verify validates a submitted OTP code and performs user login, auto-registration, or phone number update.

type Repository

type Repository interface {
	// FindVerificationValue retrieves an active verification record matching the given identifier.
	//
	// Function:
	//   Queries storage for an active SMS OTP verification record.
	//
	// Storage:
	//   Cache (Redis / In-Memory TTL) - Ephemeral short-lived SMS OTP token data.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - identifier: The composite OTP key (e.g. "phone-verification-otp-+1234567890").
	//
	// Returns:
	//   - *VerificationRecord: The matching record if found.
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   SELECT id, identifier, value, expires_at, created_at, updated_at FROM verification_tokens WHERE identifier = $1 LIMIT 1;
	//
	// Example Cache (Redis):
	//   val, err := rdb.Get(ctx, "phoneotp:" + identifier).Bytes()
	FindVerificationValue(ctx context.Context, identifier string) (*VerificationRecord, error)

	// CreateVerificationValue creates or replaces a verification record in storage.
	//
	// Function:
	//   Persists a newly generated SMS OTP verification record.
	//
	// Storage:
	//   Cache (Redis / In-Memory TTL) - Short-lived key-value with TTL equal to token validity.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - record: The VerificationRecord entity to persist.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   INSERT INTO verification_tokens (id, identifier, value, expires_at, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6);
	//
	// Example Cache (Redis):
	//   err := rdb.Set(ctx, "phoneotp:" + record.Identifier, bytes, ttl).Err()
	CreateVerificationValue(ctx context.Context, record *VerificationRecord) error

	// UpdateVerificationValue updates the value and expiry of an existing verification record.
	//
	// Function:
	//   Updates attempts counter or regenerates SMS OTP code.
	//
	// Storage:
	//   Cache (Redis / In-Memory TTL) - Key update with adjusted TTL.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - identifier: The composite OTP key.
	//   - value: The updated value payload (e.g. "<stored_otp>:<attempts>").
	//   - expiresAt: The updated expiration timestamp.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   UPDATE verification_tokens SET value = $1, expires_at = $2, updated_at = $3 WHERE identifier = $4;
	//
	// Example Cache (Redis):
	//   err := rdb.Set(ctx, "phoneotp:" + identifier, bytes, ttl).Err()
	UpdateVerificationValue(ctx context.Context, identifier, value string, expiresAt time.Time) error

	// DeleteVerificationValue removes a verification record from storage by identifier.
	//
	// Function:
	//   Explicit removal of an SMS OTP record upon invalidation.
	//
	// Storage:
	//   Cache (Redis / In-Memory TTL) - Key eviction from memory/Redis.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - identifier: The composite OTP key.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   DELETE FROM verification_tokens WHERE identifier = $1;
	//
	// Example Cache (Redis):
	//   err := rdb.Del(ctx, "phoneotp:" + identifier).Err()
	DeleteVerificationValue(ctx context.Context, identifier string) error

	// ConsumeVerificationValue atomically retrieves and deletes a verification record in a single operation.
	// This ensures strictly single-use anti-replay protection under high concurrency.
	//
	// Function:
	//   Single-use SMS OTP verification and atomic consumption.
	//
	// Storage:
	//   Cache (Redis GETDEL / Memory) - Atomic read-and-delete operation.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - identifier: The composite OTP key.
	//
	// Returns:
	//   - *VerificationRecord: The consumed record if it existed and was not expired.
	//   - error: Nil on success, or database error if not found.
	//
	// Example SQL:
	//   DELETE FROM verification_tokens WHERE identifier = $1 AND expires_at > $2 RETURNING id, identifier, value, expires_at, created_at, updated_at;
	//
	// Example Cache (Redis):
	//   val, err := rdb.GetDel(ctx, "phoneotp:" + identifier).Bytes()
	ConsumeVerificationValue(ctx context.Context, identifier string) (*VerificationRecord, error)

	// GetUserByID retrieves a user entity matching the provided unique identifier.
	//
	// Function:
	//   Used to load user details by primary key ID.
	//
	// Storage:
	//   Database (GORM / SQL) - User primary key lookup.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - userID: The user's primary key ID.
	//
	// Returns:
	//   - *entity.User: The matching user entity if found.
	//   - error: ErrUserNotFound if missing, or database error.
	//
	// Example SQL:
	//   SELECT id, name, email, phone_number, phone_number_verified, created_at, updated_at FROM users WHERE id = $1 LIMIT 1;
	GetUserByID(ctx context.Context, userID string) (*entity.User, error)

	// GetUserByPhoneNumber retrieves a user entity matching the provided phone number.
	//
	// Function:
	//   Used during SMS OTP sign-in or signup to locate user account.
	//
	// Storage:
	//   Database (GORM / SQL) - Phone number index query.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - phoneNumber: The normalized phone number to query.
	//
	// Returns:
	//   - *entity.User: The matching user entity if found.
	//   - error: ErrUserNotFound if missing, or database error.
	//
	// Example SQL:
	//   SELECT id, name, email, phone_number, phone_number_verified, created_at, updated_at FROM users WHERE phone_number = $1 LIMIT 1;
	GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*entity.User, error)

	// CreateUser persists a newly registered user in storage.
	//
	// Function:
	//   Called when a new user registers via phone number OTP.
	//
	// Storage:
	//   Database (GORM / SQL) - Relational insert of new User entity.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - params: Parameters containing name, email, phone number, and metadata.
	//
	// Returns:
	//   - *entity.User: The created user entity.
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   INSERT INTO users (id, name, email, phone_number, phone_number_verified, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7);
	CreateUser(ctx context.Context, params *dto.CreateUserParams) (*entity.User, error)

	// UpdateUser updates modified fields of an existing user profile (e.g. PhoneNumber, PhoneNumberVerified).
	//
	// Function:
	//   Called when updating user attributes or setting PhoneNumberVerified to true.
	//
	// Storage:
	//   Database (GORM / SQL) - Relational update.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - user: The modified user entity.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   UPDATE users SET phone_number = $1, phone_number_verified = $2, updated_at = $3 WHERE id = $4;
	UpdateUser(ctx context.Context, user *entity.User) error

	// GetAccountByUserIDAndProvider retrieves an account matching a given user and authentication provider.
	//
	// Function:
	//   Used to locate provider credentials for a user.
	//
	// Storage:
	//   Database (GORM / SQL) - Account credentials record lookup.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - userID: The target user's ID.
	//   - providerID: The provider identifier (e.g. "credential").
	//
	// Returns:
	//   - *entity.Account: The matching account if found.
	//   - error: ErrCredentialAccountNotFound if missing, or database error.
	//
	// Example SQL:
	//   SELECT id, user_id, provider, created_at, updated_at FROM accounts WHERE user_id = $1 AND provider = $2 LIMIT 1;
	GetAccountByUserIDAndProvider(ctx context.Context, userID, providerID string) (*entity.Account, error)

	// CreateAccount associates a new provider authentication account with a user.
	//
	// Function:
	//   Persists account credential linking record.
	//
	// Storage:
	//   Database (GORM / SQL) - Account entity creation.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - account: The Account entity to persist.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   INSERT INTO accounts (id, user_id, provider, created_at, updated_at) VALUES ($1, $2, $3, $4, $5);
	CreateAccount(ctx context.Context, account *entity.Account) error

	// UpdateAccountPassword updates the password hash on a user's credential account.
	//
	// Function:
	//   Called during password reset or credential update.
	//
	// Storage:
	//   Database (GORM / SQL) - Account password hash update.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - userID: The target user's ID.
	//   - passwordHash: The newly calculated password hash string.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   UPDATE accounts SET password = $1, updated_at = $2 WHERE user_id = $3 AND provider = 'credential';
	UpdateAccountPassword(ctx context.Context, userID, passwordHash string) error

	// CreateSession persists a new active user session in storage.
	//
	// Function:
	//   Creates a new active session upon successful SMS OTP verification.
	//
	// Storage:
	//   Database (GORM / SQL) - Active session creation.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - params: Parameters containing userID, token, expiration, and metadata.
	//
	// Returns:
	//   - *entity.Session: The created session entity.
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   INSERT INTO sessions (id, user_id, token, expires_at, ip_address, user_agent, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8);
	CreateSession(ctx context.Context, params *dto.CreateSessionParams) (*entity.Session, error)

	// DeleteSessionsByUserID invalidates all active sessions for a user (used upon password reset).
	//
	// Function:
	//   Bulk invalidation of user sessions.
	//
	// Storage:
	//   Database (GORM / SQL) - Bulk session removal.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - userID: The target user's ID.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   DELETE FROM sessions WHERE user_id = $1;
	DeleteSessionsByUserID(ctx context.Context, userID string) error
}

Repository defines the persistent storage contract required by the Phone Number plugin. Implement this interface on your custom database adapter (e.g. PostgreSQL, MySQL, SQLite, MongoDB, GORM).

Implementation Example (GORM / database/sql):

type GormPhoneNumberRepository struct {
	db *gorm.DB
}

func (r *GormPhoneNumberRepository) FindVerificationValue(ctx context.Context, identifier string) (*phonenumber.VerificationRecord, error) {
	var rec phonenumber.VerificationRecord
	if err := r.db.WithContext(ctx).Where("identifier = ?", identifier).First(&rec).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, err
	}
	return &rec, nil
}

Storage and Caching Recommendation (Redis TTL Storage):

SMS OTP codes (`VerificationRecord`) are short-lived, single-use credentials. Storing them in Redis with automatic key expiration (TTL) guarantees auto-cleanup without periodic DB purges:

type RedisPhoneNumberRepository struct {
	redis *redis.Client
}

func (r *RedisPhoneNumberRepository) CreateVerificationValue(ctx context.Context, record *phonenumber.VerificationRecord) error {
	bytes, _ := json.Marshal(record)
	ttl := time.Until(record.ExpiresAt)
	return r.redis.Set(ctx, "phoneotp:"+record.Identifier, bytes, ttl).Err()
}

func (r *RedisPhoneNumberRepository) ConsumeVerificationValue(ctx context.Context, identifier string) (*phonenumber.VerificationRecord, error) {
	key := "phoneotp:" + identifier
	val, err := r.redis.GetDel(ctx, key).Bytes() // Atomic single-use retrieval & deletion
	if err != nil {
		return nil, phonenumber.ErrOTPNotFound
	}
	var rec phonenumber.VerificationRecord
	_ = json.Unmarshal(val, &rec)
	return &rec, nil
}

type RequestPasswordResetParams

type RequestPasswordResetParams struct {
	// PhoneNumber is the account phone number requesting a password reset.
	PhoneNumber string `json:"phone_number"`

	plugin.ExtraContainer
}

RequestPasswordResetParams defines parameters to request a password reset OTP via SMS.

type RequestPasswordResetResult

type RequestPasswordResetResult struct {
	// Success indicates if the reset OTP was successfully dispatched.
	Success bool `json:"success"`
}

RequestPasswordResetResult reports the result of the password reset dispatch request.

type ResendStrategy

type ResendStrategy string

ResendStrategy defines the behavior when requesting a new OTP while an active one exists.

const (
	// ResendStrategyRotate always invalidates the previous OTP and generates a fresh code.
	ResendStrategyRotate ResendStrategy = "rotate"

	// ResendStrategyReuse resends the existing active OTP and extends its expiration (plain/encrypted only).
	ResendStrategyReuse ResendStrategy = "reuse"
)

type ResetPasswordParams

type ResetPasswordParams struct {
	// PhoneNumber is the account phone number.
	PhoneNumber string `json:"phone_number"`

	// OTP is the reset code submitted by the user.
	OTP string `json:"otp"`

	// NewPassword is the new password string to set.
	NewPassword string `json:"new_password"`

	plugin.ExtraContainer
}

ResetPasswordParams defines parameters for setting a new password using a verified SMS OTP.

type ResetPasswordResult

type ResetPasswordResult struct {
	// Success indicates if the password was successfully reset.
	Success bool `json:"success"`
}

ResetPasswordResult reports whether the password was successfully reset.

type SendOTPData

type SendOTPData struct {
	// PhoneNumber is the destination recipient phone number.
	PhoneNumber string `json:"phone_number"`

	// Code is the raw numeric OTP verification code to deliver.
	Code string `json:"code"`

	// Type indicates the specific operation requiring verification ("phone-verification", "phone-password-reset").
	Type OTPType `json:"type"`

	// Extra holds dynamic metadata passed through event interceptors.
	Extra map[string]any `json:"extra,omitempty"`
}

SendOTPData contains the parameters delivered to the transactional SMS delivery callback.

type SendOTPFunc

type SendOTPFunc func(ctx context.Context, data SendOTPData) error

SendOTPFunc defines the callback function invoked to dispatch an SMS OTP.

type SendOTPParams

type SendOTPParams struct {
	// PhoneNumber is the destination recipient phone number (required).
	PhoneNumber string `json:"phone_number"`

	plugin.ExtraContainer
}

SendOTPParams defines parameters required to dispatch an OTP to a user's phone number.

type SendOTPResult

type SendOTPResult struct {
	// Success indicates if the OTP was successfully generated and dispatched.
	Success bool `json:"success"`

	// ExpiresAt indicates when the dispatched OTP code will expire.
	ExpiresAt time.Time `json:"expires_at"`
}

SendOTPResult contains the delivery status and expiry of the dispatched OTP.

type SignInParams

type SignInParams struct {
	// PhoneNumber is the registered user's phone number.
	PhoneNumber string `json:"phone_number"`

	// Password is the plain text password.
	Password string `json:"password"`

	// RememberMe extends session lifespan if set to true.
	RememberMe *bool `json:"remember_me,omitempty"`

	plugin.ExtraContainer
}

SignInParams defines parameters for credential-based phone number + password login.

type SignInResult

type SignInResult struct {
	// User is the authenticated user entity.
	User *entity.User `json:"user"`

	// SessionToken is the raw session token.
	SessionToken string `json:"session_token"`

	// Session is the persisted active session entity.
	Session *entity.Session `json:"session"`
}

SignInResult contains the authenticated user and active session.

type SignInSuccessPayload

type SignInSuccessPayload struct {
	User      *entity.User    `json:"user"`
	Session   *entity.Session `json:"session,omitempty"`
	IsNewUser bool            `json:"is_new_user"`
	plugin.ExtraContainer
}

SignInSuccessPayload reports authentication or auto-registration details upon phone login.

type SignUpOnVerificationConfig

type SignUpOnVerificationConfig struct {
	// GetTempEmail resolves a fallback email for auto-created users given their phone number.
	GetTempEmail func(phoneNumber string) string

	// GetTempName resolves a fallback display name for auto-created users given their phone number.
	GetTempName func(phoneNumber string) string
}

SignUpOnVerificationConfig configures automatic user creation upon phone verification.

type StoreOTPMode

type StoreOTPMode string

StoreOTPMode defines how the OTP code is persisted in storage.

const (
	// StoreOTPPlain stores the OTP code in plain text.
	StoreOTPPlain StoreOTPMode = "plain"

	// StoreOTPHashed stores the OTP code using constant-time SHA-256 hash.
	StoreOTPHashed StoreOTPMode = "hashed"

	// StoreOTPEncrypted stores the OTP code using AES-256-GCM symmetric encryption.
	StoreOTPEncrypted StoreOTPMode = "encrypted"
)

type VerificationRecord

type VerificationRecord struct {
	// ID is the unique database record identifier.
	ID string `json:"id"`

	// Identifier is the composite lookup key (e.g. "phone-verification-otp-+1234567890").
	Identifier string `json:"identifier"`

	// Value stores the code and attempt counter formatted as "<stored_otp>:<attempts>".
	Value string `json:"value"`

	// ExpiresAt specifies the exact timestamp after which this verification value is invalid.
	ExpiresAt time.Time `json:"expires_at"`

	// CreatedAt records when the verification record was initialized.
	CreatedAt time.Time `json:"created_at"`

	// UpdatedAt records when the verification record was last modified.
	UpdatedAt time.Time `json:"updated_at"`
}

VerificationRecord represents the persistent storage entity for an OTP verification value.

type VerifyOTPData

type VerifyOTPData struct {
	// PhoneNumber is the destination recipient phone number.
	PhoneNumber string `json:"phone_number"`

	// Code is the verification code submitted by the user.
	Code string `json:"code"`

	// Extra holds dynamic metadata passed through event interceptors.
	Extra map[string]any `json:"extra,omitempty"`
}

VerifyOTPData contains the parameters passed to the custom verification callback.

type VerifyOTPFunc

type VerifyOTPFunc func(ctx context.Context, data VerifyOTPData) (bool, error)

VerifyOTPFunc defines an optional external OTP verification callback (e.g. Twilio Verify).

type VerifyParams

type VerifyParams struct {
	// PhoneNumber is the phone number being verified (required).
	PhoneNumber string `json:"phone_number"`

	// Code is the verification code submitted by the user (required).
	Code string `json:"code"`

	// UserID is the ID of the authenticated user (required if UpdatePhoneNumber is true).
	UserID string `json:"user_id,omitempty"`

	// UpdatePhoneNumber indicates whether to attach the verified phone number to an existing user session.
	UpdatePhoneNumber bool `json:"update_phone_number,omitempty"`

	// DisableSession prevents creating an active authentication session upon successful verification.
	DisableSession bool `json:"disable_session,omitempty"`

	plugin.ExtraContainer
}

VerifyParams defines parameters to verify a phone number OTP for login, registration, or profile update.

type VerifyResult

type VerifyResult struct {
	// Success indicates successful phone verification.
	Success bool `json:"success"`

	// User is the authenticated, newly provisioned, or updated user entity.
	User *entity.User `json:"user"`

	// SessionToken is the raw session token if a session was created.
	SessionToken string `json:"session_token,omitempty"`

	// Session is the active session entity if created.
	Session *entity.Session `json:"session,omitempty"`
}

VerifyResult contains the updated user profile and optional created session.

Jump to

Keyboard shortcuts

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