Documentation
¶
Overview ¶
Package emailotp provides email-based OTP (One-Time Password) verification and authentication.
This plugin enables:
- Email address verification via OTP codes
- Email+password registration and login
- Multi-factor authentication (MFA) via email
- Password reset via email verification
OTP Flow:
- User requests OTP via SendOTP endpoint (requires authentication)
- Plugin generates 6-digit code (configurable length)
- Code sent via configured email provider (SMTP, SendGrid, etc.)
- Code stored in database with expiry (default: 10 minutes)
- User submits code via VerifyOTP endpoint
- Plugin validates code and marks email as verified
Email+Password Flow:
- User registers with email+password via /register endpoint
- Password hashed with bcrypt and stored
- User can login with email+password via /login endpoint
- Session created on successful authentication
Route Structure:
- POST /email-otp/send - Send OTP code (protected)
- POST /email-otp/verify - Verify OTP code (public)
- POST /email-otp/login - Login with email+password (public)
- POST /email-otp/register - Register with email+password (public)
Provider Integration: Implement the Provider interface to use your email service:
type MySMTPProvider struct { host, user, pass string }
func (p *MySMTPProvider) SendOTP(to, code string) error {
// Send email with OTP code
}
Index ¶
- Constants
- func GetMigrations(dialect plugins.Dialect) ([]plugins.Migration, error)
- func GetSchema(dialect plugins.Dialect) (*plugins.Schema, error)
- func ValidateEmail(email string) error
- type Config
- type DefaultEmailOTPStore
- type Handlers
- type LoginWithEmailRequest
- type Plugin
- func (p *Plugin) CreateUserWithEmailAndPassword(ctx context.Context, name, email, password string) (*User, error)
- func (p *Plugin) Dependencies() []plugins.Dependency
- func (p *Plugin) Description() string
- func (p *Plugin) EnrichUser(ctx context.Context, user *core.EnrichedUser) error
- func (p *Plugin) GetMigrations() []plugins.Migration
- func (p *Plugin) GetSchemas() []plugins.Schema
- func (p *Plugin) GetUserByEmail(ctx context.Context, email string) (*auth.User, error)
- func (p *Plugin) Init(_ context.Context, a plugins.Aegis) error
- func (p *Plugin) MarkEmailVerified(ctx context.Context, email string) error
- func (p *Plugin) MountRoutes(router router.Router, prefix string)
- func (p *Plugin) Name() string
- func (p *Plugin) ProvidesAuthMethods() []string
- func (p *Plugin) RequiresTables() []string
- func (p *Plugin) SendOTP(ctx context.Context, emailAddress, purpose string) error
- func (p *Plugin) UpdateUserEmail(ctx context.Context, userID, email string, verified bool) error
- func (p *Plugin) VerifyOTP(ctx context.Context, emailAddress, code string) (bool, error)
- func (p *Plugin) Version() string
- type Provider
- type RegisterWithEmailRequest
- type SendOTPRequest
- type Store
- type User
- type VerifyOTPRequest
Constants ¶
const ( // Request schemas SchemaLoginWithEmailRequest = "LoginWithEmailRequest" SchemaRegisterWithEmailRequest = "RegisterWithEmailRequest" SchemaSendOTPRequest = "SendOTPRequest" SchemaVerifyOTPRequest = "VerifyOTPRequest" )
Schema names for OpenAPI specification generation.
These constants define the OpenAPI schema names for emailotp 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 ¶
GetMigrations returns all database migrations for the emailotp plugin.
This function loads migrations from embedded SQL files and returns them in version order. The initial schema is always treated as version 001.
Version Numbering:
- Version 001: Initial schema from internal/sql/<dialect>/schema.sql
- Version 002+: Additional migrations from migrations/<dialect>/<version>_<description>.<up|down>.sql
Migration File Format:
- Up migration: 002_add_email_verification.up.sql
- Down migration: 002_add_email_verification.down.sql
Parameters:
- dialect: Database dialect (postgres, mysql, sqlite)
Returns:
- []plugins.Migration: Sorted list of migrations (oldest first)
- error: If schema files cannot be read or parsed
func GetSchema ¶
GetSchema returns the database schema for the emailotp plugin.
The schema extends the 'user' table with email-specific columns:
- email (VARCHAR, unique): User email address
- email_verified (BOOLEAN): Email verification status (default: false)
These extensions enable email+password authentication and email verification.
Parameters:
- dialect: Database dialect (postgres, mysql)
Returns:
- *plugins.Schema: Schema definition with SQL DDL
- error: If dialect is not supported
func ValidateEmail ¶
ValidateEmail validates an email address format using RFC 5322 regex.
Parameters:
- email: Email address to validate
Returns:
- error: If email is empty or has invalid format
Types ¶
type Config ¶
type Config struct {
Provider Provider // Email sending provider (required for production)
OTPExpiry time.Duration // OTP expiry duration (default: 10 minutes)
OTPLength int // OTP code length (default: 6)
}
Config holds Email OTP plugin configuration.
Example:
cfg := &emailotp.Config{
Provider: myEmailProvider,
OTPExpiry: 15 * time.Minute,
OTPLength: 8,
}
type DefaultEmailOTPStore ¶
type DefaultEmailOTPStore struct {
// contains filtered or unexported fields
}
DefaultEmailOTPStore implements EmailOTPStore using a SQL database.
This implementation uses sqlc-generated type-safe queries to manage users with email addresses in PostgreSQL, MySQL, or SQLite.
Database Schema: Extends the 'user' table with:
- email (VARCHAR, unique): User email address
- email_verified (BOOLEAN): Email verification status
Thread Safety: Safe for concurrent use through database transactions.
func NewDefaultEmailOTPStore ¶
func NewDefaultEmailOTPStore(db *sql.DB) *DefaultEmailOTPStore
NewDefaultEmailOTPStore creates a new DefaultEmailOTPStore backed by SQL.
The provided database connection must have the emailotp schema applied.
Parameters:
- db: Active SQL database connection
Returns:
- *DefaultEmailOTPStore: Configured store ready for use
func (*DefaultEmailOTPStore) CreateUser ¶
CreateUser creates a new user with email address.
func (*DefaultEmailOTPStore) GetUserByEmail ¶
GetUserByEmail retrieves a user by email address
func (*DefaultEmailOTPStore) UpdateUserEmail ¶
func (s *DefaultEmailOTPStore) UpdateUserEmail(ctx context.Context, userID, email string, verified bool) error
UpdateUserEmail updates a user's email address and verification status
type Handlers ¶
type Handlers struct {
// contains filtered or unexported fields
}
Handlers encapsulates Email OTP plugin HTTP handlers.
func NewHandlers ¶
NewHandlers creates Email OTP plugin handlers.
Parameters:
- plugin: Initialized Email OTP plugin
Returns:
- *Handlers: Handler instance ready for route registration
func (*Handlers) SendOTPHandler ¶
func (h *Handlers) SendOTPHandler(w http.ResponseWriter, r *http.Request)
SendOTPHandler handles sending OTP via email.
This endpoint is protected to prevent spam/abuse. Only authenticated users can request OTP codes.
Endpoint:
- Method: POST
- Path: /email-otp/send
- Auth: Required (session)
Request Body:
{
"email": "user@example.com",
"userId": "user_123",
"purpose": "email_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 OTP codes.
This endpoint is public to allow users to verify their email addresses without requiring prior authentication.
Endpoint:
- Method: POST
- Path: /email-otp/verify
- Auth: Public
Request Body:
{
"email": "user@example.com",
"code": "123456",
"purpose": "email_verification"
}
Response (200 OK):
{
"success": true,
"message": "OTP verified successfully"
}
Response (400 Bad Request):
{
"success": false,
"error": "Invalid or expired OTP"
}
type LoginWithEmailRequest ¶
type LoginWithEmailRequest struct {
Email string `json:"email"` // User email address
Password string `json:"password"` // User password
}
LoginWithEmailRequest represents email+password login request.
Example:
{
"email": "user@example.com",
"password": "SecurePassword123!"
}
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin provides email-based OTP verification and authentication.
This plugin integrates with email service providers to send OTP codes and supports email+password authentication as an alternative auth method.
Features:
- Configurable OTP length and expiry
- Pluggable email providers (SMTP, SendGrid, SES, etc.)
- Email address validation
- Password hashing with bcrypt
- Session management integration
func New ¶
New creates a new Email OTP plugin instance.
Parameters:
- cfg: Plugin configuration (can be nil for defaults)
- store: Custom Store implementation (can be nil, will use DefaultEmailOTPStore)
- dialect: Database dialect (optional, defaults to PostgreSQL)
Returns:
- *Plugin: Configured plugin ready for initialization
Example:
plugin := emailotp.New(&emailotp.Config{
Provider: mySMTPProvider,
OTPExpiry: 15 * time.Minute,
OTPLength: 8,
}, nil, plugins.DialectPostgres)
func (*Plugin) CreateUserWithEmailAndPassword ¶
func (p *Plugin) CreateUserWithEmailAndPassword(ctx context.Context, name, email, password string) (*User, error)
CreateUserWithEmailAndPassword creates a new user with email+password authentication.
Registration Process:
- Create user record with email (unverified)
- Hash password with bcrypt
- Create password account in auth.accounts table
- Return user for session creation
Parameters:
- ctx: Request context
- name: User display name
- email: User email address (becomes primary identifier)
- password: Plain text password (will be hashed)
Returns:
- *User: Created user with email field
- error: If user creation or password hashing fails
Example:
user, err := plugin.CreateUserWithEmailAndPassword(ctx, "John Doe", "john@example.com", "SecurePass123!")
func (*Plugin) Dependencies ¶
func (p *Plugin) Dependencies() []plugins.Dependency
Dependencies returns external package dependencies
func (*Plugin) Description ¶
Description returns a human-readable description for logging.
func (*Plugin) EnrichUser ¶ added in v1.2.1
EnrichUser implements plugins.UserEnricher to add email verification status.
This method is called automatically by the authentication system after user lookup. It adds the user's email verification status to the EnrichedUser, making it available in API responses without requiring separate queries.
Fields Added:
- "emailVerified" (bool): Whether the user's email has been verified
Parameters:
- ctx: Request context
- user: EnrichedUser to populate with email verification data
Returns:
- error: Always nil (lookup failure is not an error)
func (*Plugin) GetMigrations ¶
GetMigrations returns the plugin migrations
func (*Plugin) GetSchemas ¶
GetSchemas returns all schemas for all supported dialects
func (*Plugin) GetUserByEmail ¶
GetUserByEmail retrieves a user by email address
func (*Plugin) MarkEmailVerified ¶ added in v1.2.2
MarkEmailVerified marks a user's email as verified in the database.
This is called automatically by VerifyOTPHandler after successful OTP verification. It can also be called programmatically to mark an email as verified.
Parameters:
- ctx: Request context
- email: Email address to mark as verified
Returns:
- error: If user lookup or update fails
Example:
err := plugin.MarkEmailVerified(ctx, "user@example.com")
func (*Plugin) MountRoutes ¶
MountRoutes registers HTTP routes for the Email OTP plugin
func (*Plugin) ProvidesAuthMethods ¶
ProvidesAuthMethods returns authentication methods provided
func (*Plugin) RequiresTables ¶
RequiresTables returns core tables this plugin depends on
func (*Plugin) SendOTP ¶
SendOTP generates and sends an OTP code via email.
OTP Generation and Delivery:
- Generate random N-digit code (configurable length)
- Send code via email provider
- Store code in verification service with expiry
- Invalidate any previous OTPs for same email+purpose
Parameters:
- ctx: Request context
- emailAddress: Recipient email address
- purpose: OTP purpose ("email_verification", "password_reset", "login_mfa")
Returns:
- error: If OTP generation or sending fails
Example:
err := plugin.SendOTP(ctx, "user@example.com", "email_verification")
func (*Plugin) UpdateUserEmail ¶ added in v1.2.2
UpdateUserEmail updates a user's email address and verification status programmatically.
Parameters:
- ctx: Request context
- userID: User ID to update
- email: New email address
- verified: Whether the email is verified
Returns:
- error: If update fails
Example:
err := plugin.UpdateUserEmail(ctx, "user_123", "new@example.com", false)
func (*Plugin) VerifyOTP ¶
VerifyOTP verifies an OTP code for an email address.
Verification Process:
- Check if provider supports OTP verification (rare)
- Fall back to core verification service (standard)
- Validate code and check expiry
- Return success/failure
Parameters:
- ctx: Request context
- emailAddress: Email address to verify
- code: OTP code to verify (e.g., "123456")
Returns:
- bool: true if OTP is valid and not expired
- error: If verification fails
VerifyOTP verifies an OTP code for the given email address.
Example:
valid, err := plugin.VerifyOTP(ctx, "user@example.com", "123456")
if valid {
// Mark email as verified
}
type Provider ¶
type Provider interface {
// SendOTP sends a one-time password to the specified email address.
//
// Parameters:
// - to: Recipient email address
// - code: OTP code to send (e.g., "123456")
//
// Returns:
// - error: If email sending fails
SendOTP(to, code string) error
// VerifyOTP verifies an OTP code for an email address.
//
// Note: Most implementations delegate verification to the plugin's OTP storage.
// This method is available for provider-specific validation if needed.
//
// Parameters:
// - to: Email address 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 email service providers must implement.
Users should create their own implementation based on their email service (SMTP, SendGrid, Resend, AWS SES, Postmark, Mailgun, etc.).
Abstraction Benefits:
- Swap email providers without changing plugin code
- Test with mock providers
- Use different providers for different environments
Example Implementation (SMTP):
type SMTPProvider struct {
host string
port int
username string
password string
from string
}
func (p *SMTPProvider) SendOTP(to, code string) error {
subject := "Your Verification Code"
body := fmt.Sprintf("Your verification code is: %s\n\nThis code will expire in 10 minutes.", code)
auth := smtp.PlainAuth("", p.username, p.password, p.host)
msg := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s", p.from, to, subject, body)
return smtp.SendMail(p.host+":"+strconv.Itoa(p.port), auth, p.from, []string{to}, []byte(msg))
}
func (p *SMTPProvider) VerifyOTP(to, code string) (bool, error) {
// Verification logic handled by plugin's OTP storage
// This method can be used for provider-specific validation if needed
return true, nil
}
Example Implementation (SendGrid):
type SendGridProvider struct {
apiKey string
from string
}
func (p *SendGridProvider) SendOTP(to, code string) error {
message := mail.NewV3Mail()
message.SetFrom(mail.NewEmail("", p.from))
message.AddContent(mail.NewContent("text/plain", fmt.Sprintf("Your OTP: %s", code)))
personalization := mail.NewPersonalization()
personalization.AddTos(mail.NewEmail("", to))
message.AddPersonalizations(personalization)
client := sendgrid.NewSendClient(p.apiKey)
_, err := client.Send(message)
return err
}
type RegisterWithEmailRequest ¶
type RegisterWithEmailRequest struct {
Avatar *string `json:"avatar"` // Optional avatar URL
Name *string `json:"name"` // User display name (required)
Email string `json:"email"` // User email address (required)
Password string `json:"password"` // User password (required)
}
RegisterWithEmailRequest represents email+password registration request.
Example:
{
"name": "John Doe",
"email": "john@example.com",
"password": "SecurePassword123!",
"avatar": "https://example.com/avatar.jpg"
}
type SendOTPRequest ¶
type SendOTPRequest struct {
Email string `json:"email"` // Email address to send OTP to
UserID string `json:"userId"` // User ID requesting OTP
Purpose string `json:"purpose"` // OTP purpose ("email_verification", "password_reset", "login_mfa")
}
SendOTPRequest represents the request to send an OTP code.
Example:
{
"email": "user@example.com",
"userId": "user_123",
"purpose": "email_verification"
}
type Store ¶
type Store interface {
// CreateUser creates a new user with an email address.
//
// The email is initially unverified (emailVerified: false).
//
// Parameters:
// - ctx: Request context
// - user: User with email field populated
//
// Returns:
// - *User: Created user
// - error: If user creation fails (e.g., duplicate email)
CreateUser(ctx context.Context, user User) (*User, error)
// GetUserByEmail retrieves a user by email address.
//
// Used for:
// - Email+password login
// - Checking if email already exists during registration
// - Email verification lookup
//
// Parameters:
// - ctx: Request context
// - email: Email address to lookup
//
// Returns:
// - *User: User with matching email
// - error: If user not found or database error
GetUserByEmail(ctx context.Context, email string) (*User, error)
// UpdateUserEmail updates a user's email address and verification status.
//
// Used after successful OTP verification to mark email as verified.
//
// Parameters:
// - ctx: Request context
// - userID: User ID
// - email: New email address
// - verified: Email verification status
//
// Returns:
// - error: If update fails
UpdateUserEmail(ctx context.Context, userID, email string, verified bool) error
}
Store defines the interface for Email OTP verification storage operations.
This interface provides email-specific user management operations:
- User creation with email addresses
- Email lookup for authentication
- Email verification status updates
Thread Safety: Implementations must be safe for concurrent use.
type User ¶
type User struct {
auth.User
// TODO: email is alredy provided by the user, look into this
Email *string `json:"email,omitempty"` // User email address
EmailVerified bool `json:"emailVerified"` // Email verification status
}
User extends the core User model with email-specific fields.
This model adds email verification status for display in API responses.
Use this when:
- Returning user data after email registration
- Displaying email verification status in user profiles
- Checking if user has verified their email
Example:
user := emailotp.User{
User: auth.User{ID: "user_123", Name: "John Doe"},
Email: ptr("john@example.com"),
EmailVerified: true,
}
type VerifyOTPRequest ¶
type VerifyOTPRequest struct {
Email string `json:"email"` // Email address to verify
Code string `json:"code"` // OTP code to verify
Purpose string `json:"purpose"` // OTP purpose
}
VerifyOTPRequest represents the request to verify an OTP code.
Example:
{
"email": "user@example.com",
"code": "123456",
"purpose": "email_verification"
}