sms

package
v1.5.0 Latest Latest
Warning

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

Go to latest
Published: Mar 30, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package sms provides phone-based OTP (One-Time Password) verification and authentication.

This plugin enables:

  • Phone number verification via SMS OTP codes
  • Phone+password registration and login
  • Multi-factor authentication (MFA) via SMS
  • Password reset via phone verification

OTP Flow:

  1. User requests OTP via SendOTP endpoint (requires authentication)
  2. Plugin generates 6-digit code (configurable length)
  3. Code sent via configured SMS provider (Twilio, AWS SNS, etc.)
  4. Code stored in database with expiry (default: 10 minutes)
  5. User submits code via VerifyOTP endpoint
  6. Plugin validates code and marks phone as verified

Phone+Password Flow:

  1. User registers with phone+password via /register endpoint
  2. Password hashed with bcrypt and stored
  3. User can login with phone+password via /login endpoint
  4. Session created on successful authentication

Route Structure:

  • POST /sms/send - Send SMS OTP code (protected)
  • POST /sms/verify - Verify SMS OTP code (public)
  • POST /sms/login - Login with phone+password (public)
  • POST /sms/register - Register with phone+password (public)

Provider Integration: Implement the Provider interface to use your SMS service:

type MyTwilioProvider struct { accountSID, authToken, from string }
func (p *MyTwilioProvider) SendOTP(to, code string) error {
  // Send SMS with OTP code via Twilio API
}

Index

Constants

View Source
const (
	// Request schemas
	SchemaLoginWithPhoneRequest    = "LoginWithPhoneRequest"
	SchemaRegisterWithPhoneRequest = "RegisterWithPhoneRequest"
	SchemaSendOTPRequest           = "SendOTPRequest"
	SchemaVerifyOTPRequest         = "VerifyOTPRequest"
)

Schema names for OpenAPI specification generation. These constants define the OpenAPI schema names for SMS request/response types. They are used in route metadata to generate accurate API documentation with typed request/response examples.

Variables

This section is empty.

Functions

func GetMigrations

func GetMigrations(dialect plugins.Dialect) ([]plugins.Migration, error)

GetMigrations returns all database migrations for the SMS plugin.

This function loads migrations from embedded SQL files and returns them in version order.

Version Numbering:

  • Version 001+: Migrations from migrations/<dialect>/<version>_<description>.<up|down>.sql

Migration File Format:

  • Up migration: 001_initial.up.sql
  • Down migration: 001_initial.down.sql

Parameters:

  • dialect: Database dialect (postgres, mysql, sqlite)

Returns:

  • []plugins.Migration: Sorted list of migrations (oldest first)
  • error: If migration files cannot be read or parsed

func ValidatePhoneNumber

func ValidatePhoneNumber(phone string) error

ValidatePhoneNumber validates a phone number format using libphonenumber.

This function performs comprehensive phone number validation:

  • Basic format check (regex)
  • International format validation (E.164)
  • Country code verification
  • Number length validation

Parameters:

  • phone: Phone number to validate (can include country code prefix)

Returns:

  • error: If phone number is empty or invalid

Example:

err := ValidatePhoneNumber("+14155551234")  // Valid US number
err := ValidatePhoneNumber("+442071838750") // Valid UK number

Types

type Config

type Config struct {
	Provider  Provider      // SMS sending provider (required for production)
	OTPExpiry time.Duration // OTP expiry duration (default: 10 minutes)
	OTPLength int           // OTP code length (default: 6)
}

Config holds SMS plugin configuration.

Example:

cfg := &sms.Config{
  Provider:  myTwilioProvider,
  OTPExpiry: 15 * time.Minute,
  OTPLength: 8,
}

type Handlers

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

Handlers encapsulates SMS plugin HTTP handlers.

func NewHandlers

func NewHandlers(plugin *Plugin) *Handlers

NewHandlers creates SMS plugin handlers.

Parameters:

  • plugin: Initialized SMS plugin

Returns:

  • *Handlers: Handler instance ready for route registration

func (*Handlers) LoginWithPhoneHandler

func (h *Handlers) LoginWithPhoneHandler(w http.ResponseWriter, r *http.Request)

LoginWithPhoneHandler handles phone+password login.

Authentication Flow:

  1. Validate phone number format (E.164)
  2. Retrieve user by phone number
  3. Verify password with bcrypt
  4. Create session
  5. Set session cookie

Endpoint:

  • Method: POST
  • Path: /sms/login
  • Auth: Public

Request Body:

{
  "phoneNumber": "+14155551234",
  "password": "SecurePassword123!"
}

Response (200 OK):

{
  "success": true,
  "message": "Login successful",
  "data": {
    "user": {"id": "user_123", "phone": "+14155551234", ...}
  }
}

func (*Handlers) RegisterWithPhoneHandler

func (h *Handlers) RegisterWithPhoneHandler(w http.ResponseWriter, r *http.Request)

RegisterWithPhoneHandler handles phone+password registration.

Registration Flow:

  1. Validate phone number format (E.164)
  2. Check if phone already exists
  3. Create user with hashed password
  4. Create session (auto-login)
  5. Set session cookie

Endpoint:

  • Method: POST
  • Path: /sms/register
  • Auth: Public

Request Body:

{
  "name": "John Doe",
  "phoneNumber": "+14155551234",
  "password": "SecurePassword123!",
  "avatar": "https://example.com/avatar.jpg"  // Optional
}

Response (201 Created):

{
  "success": true,
  "message": "Registration successful",
  "data": {
    "user": {"id": "user_123", "phone": "+14155551234", "phoneVerified": false}
  }
}

func (*Handlers) SendOTPHandler

func (h *Handlers) SendOTPHandler(w http.ResponseWriter, r *http.Request)

SendOTPHandler handles sending OTP via SMS.

This endpoint is protected to prevent spam/abuse and SMS cost attacks. Only authenticated users can request OTP codes.

Endpoint:

  • Method: POST
  • Path: /sms/send
  • Auth: Required (session)

Request Body:

{
  "phoneNumber": "+14155551234",
  "userId": "user_123",
  "purpose": "phone_verification"  // or "password_reset", "login_mfa"
}

Response (200 OK):

{
  "success": true,
  "message": "OTP sent successfully"
}

func (*Handlers) VerifyOTPHandler

func (h *Handlers) VerifyOTPHandler(w http.ResponseWriter, r *http.Request)

VerifyOTPHandler handles verifying SMS OTP codes.

This endpoint is public to allow users to verify their phone numbers without requiring prior authentication.

Endpoint:

  • Method: POST
  • Path: /sms/verify
  • Auth: Public

Request Body:

{
  "phoneNumber": "+14155551234",
  "code": "123456",
  "purpose": "phone_verification"
}

Response (200 OK):

{
  "success": true,
  "message": "OTP verified successfully"
}

Response (400 Bad Request):

{
  "success": false,
  "error": "Invalid or expired OTP"
}

type Plugin

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

Plugin provides phone-based OTP verification and authentication.

This plugin integrates with SMS service providers to send OTP codes and supports phone+password authentication as an alternative auth method.

Features:

  • Configurable OTP length and expiry
  • Pluggable SMS providers (Twilio, AWS SNS, Vonage, etc.)
  • International phone number validation (E.164 format)
  • Password hashing with bcrypt
  • Session management integration

func New

func New(cfg *Config, store smstypes.Store, dialect ...plugins.Dialect) *Plugin

New creates a new SMS plugin instance.

Parameters:

  • cfg: Plugin configuration (can be nil for defaults)
  • store: Custom SMSStore implementation (can be nil, will use DefaultSMSStore)
  • dialect: Database dialect (optional, defaults to PostgreSQL)

Returns:

  • *Plugin: Configured plugin ready for initialization

Example:

plugin := sms.New(&sms.Config{
  Provider: myTwilioProvider,
  OTPExpiry: 15 * time.Minute,
  OTPLength: 8,
}, nil, plugins.DialectPostgres)

func (*Plugin) CreateUserWithPhoneAndPassword

func (p *Plugin) CreateUserWithPhoneAndPassword(ctx context.Context, name, phone, password string) (*smstypes.User, error)

CreateUserWithPhoneAndPassword creates a new user with name, password account, and phone number.

func (*Plugin) Dependencies

func (p *Plugin) Dependencies() []plugins.Dependency

Dependencies returns external package dependencies

func (*Plugin) Description

func (p *Plugin) Description() string

Description returns a human-readable description for logging.

func (*Plugin) EnrichUser added in v1.2.1

func (p *Plugin) EnrichUser(ctx context.Context, user *core.EnrichedUser) error

EnrichUser implements plugins.UserEnricher to add phone verification status.

This method is called automatically by the authentication system after user lookup. It adds the user's phone verification status to the EnrichedUser, making it available in API responses without requiring separate queries.

Fields Added:

  • "phoneVerified" (bool): Whether the user's phone has been verified

Parameters:

  • ctx: Request context
  • user: EnrichedUser to populate with phone verification data

Returns:

  • error: If lookup fails

func (*Plugin) GetMigrations

func (p *Plugin) GetMigrations() []plugins.Migration

GetMigrations returns the plugin migrations

func (*Plugin) GetUserByPhone

func (p *Plugin) GetUserByPhone(ctx context.Context, phone string) (*auth.User, error)

GetUserByPhone retrieves a user by phone number

func (*Plugin) Init

func (p *Plugin) Init(_ context.Context, a plugins.Aegis) error

Init initializes the plugin.

func (*Plugin) MarkPhoneVerified added in v1.2.2

func (p *Plugin) MarkPhoneVerified(ctx context.Context, phone string) error

MarkPhoneVerified marks a user's phone as verified in the database.

This is called automatically by VerifyOTPHandler after successful OTP verification. It can also be called programmatically to mark a phone number as verified.

Parameters:

  • ctx: Request context
  • phone: Phone number to mark as verified (E.164 format)

Returns:

  • error: If user lookup or update fails

Example:

err := plugin.MarkPhoneVerified(ctx, "+14155551234")

func (*Plugin) MountRoutes

func (p *Plugin) MountRoutes(r router.Router, prefix string)

MountRoutes registers HTTP routes for the SMS plugin

func (*Plugin) Name

func (p *Plugin) Name() string

Name returns the plugin identifier.

func (*Plugin) ProvidesAuthMethods

func (p *Plugin) ProvidesAuthMethods() []string

ProvidesAuthMethods returns authentication methods provided

func (*Plugin) RequiresTables

func (p *Plugin) RequiresTables() []string

RequiresTables returns the core tables this plugin reads from.

func (*Plugin) SendOTP

func (p *Plugin) SendOTP(ctx context.Context, phoneNumber, purpose string) error

SendOTP generates and sends an OTP via SMS

func (*Plugin) UpdateUserPhone added in v1.2.2

func (p *Plugin) UpdateUserPhone(ctx context.Context, userID, phone string, verified bool) error

UpdateUserPhone updates a user's phone number and verification status programmatically.

Parameters:

  • ctx: Request context
  • userID: User ID to update
  • phone: New phone number (E.164 format)
  • verified: Whether the phone is verified

Returns:

  • error: If update fails

Example:

err := plugin.UpdateUserPhone(ctx, "user_123", "+14155551234", false)

func (*Plugin) VerifyOTP

func (p *Plugin) VerifyOTP(ctx context.Context, phoneNumber, code string) (bool, error)

VerifyOTP verifies an OTP code

func (*Plugin) Version

func (p *Plugin) Version() string

Version returns the plugin version for compatibility tracking.

type Provider

type Provider interface {
	// SendOTP sends a one-time password to the specified phone number.
	//
	// Parameters:
	//   - to: Recipient phone number in E.164 format (e.g., "+14155551234")
	//   - code: OTP code to send (e.g., "123456")
	//
	// Returns:
	//   - error: If SMS sending fails
	SendOTP(to, code string) error

	// VerifyOTP verifies an OTP code for a phone number.
	//
	// Note: Most implementations delegate verification to the plugin's OTP storage.
	// This method is available for provider-specific validation if needed.
	//
	// Parameters:
	//   - to: Phone number to verify
	//   - code: OTP code to verify
	//
	// Returns:
	//   - bool: true if OTP is valid
	//   - error: If verification fails
	VerifyOTP(to, code string) (bool, error)
}

Provider is the interface that SMS service providers must implement.

Users should create their own implementation based on their SMS service (Twilio, AWS SNS, Vonage, MessageBird, Plivo, etc.).

Abstraction Benefits:

  • Swap SMS providers without changing plugin code
  • Test with mock providers
  • Use different providers for different regions

Example Implementation (Twilio):

type TwilioProvider struct {
    accountSID string
    authToken  string
    from       string  // Twilio phone number
}

func (p *TwilioProvider) SendOTP(to, code string) error {
    msgData := url.Values{}
    msgData.Set("To", to)
    msgData.Set("From", p.from)
    msgData.Set("Body", fmt.Sprintf("Your verification code is: %s", code))

    urlStr := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json", p.accountSID)
    req, _ := http.NewRequest("POST", urlStr, strings.NewReader(msgData.Encode()))
    req.SetBasicAuth(p.accountSID, p.authToken)
    req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

    client := &http.Client{}
    resp, err := client.Do(req)
    return err
}

func (p *TwilioProvider) VerifyOTP(to, code string) (bool, error) {
    return true, nil  // Verification handled by plugin
}

Example Implementation (AWS SNS):

type SNSProvider struct {
    client *sns.Client
}

func (p *SNSProvider) SendOTP(to, code string) error {
    _, err := p.client.Publish(context.TODO(), &sns.PublishInput{
        PhoneNumber: aws.String(to),
        Message:     aws.String(fmt.Sprintf("Your OTP: %s", code)),
    })
    return err
}

Directories

Path Synopsis
Package defaultstore implements the SQL-backed default store for the sms plugin.
Package defaultstore implements the SQL-backed default store for the sms plugin.
internal
Package types defines the domain models and request/response types used by the sms plugin.
Package types defines the domain models and request/response types used by the sms plugin.

Jump to

Keyboard shortcuts

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