emailotp

package
v2.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 25, 2026 License: MIT Imports: 19 Imported by: 0

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:

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

Email+Password Flow:

  1. User registers with email+password via /register endpoint
  2. Password hashed with bcrypt and stored
  3. User can login with email+password via /login endpoint
  4. 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

View Source
const (
	// Request schemas
	SchemaLoginWithEmailRequest     = "LoginWithEmailRequest"
	SchemaRegisterWithEmailRequest  = "RegisterWithEmailRequest"
	SchemaSendOTPRequest            = "SendOTPRequest"
	SchemaVerifyOTPRequest          = "VerifyOTPRequest"
	SchemaForgotPasswordRequest     = "ForgotPasswordRequest"
	SchemaResetPasswordRequest      = "ResetPasswordRequest"
	SchemaRequestEmailChangeRequest = "RequestEmailChangeRequest"
	SchemaConfirmEmailChangeRequest = "ConfirmEmailChangeRequest"
)

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

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

GetMigrations returns all database migrations for the emailotp 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 ValidateEmail

func ValidateEmail(email string) error

ValidateEmail validates an email address format. Delegates to core.ValidateEmail instead of maintaining a second regex here.

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)

	// PasswordResetURL is the link template used for password-reset mails.
	// "{token}" is replaced with the reset token, e.g.
	// "https://app.example.com/reset-password?token={token}".
	// When empty, the raw token is mailed as a code instead.
	PasswordResetURL string

	// EmailChangeURL is the link template used for email-change confirmation
	// mails, e.g. "https://app.example.com/account/email?token={token}".
	// When empty, the raw token is mailed as a code instead.
	EmailChangeURL string
}

Config holds Email OTP plugin configuration.

Example:

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

type Handlers

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

Handlers encapsulates Email OTP plugin HTTP handlers.

func NewHandlers

func NewHandlers(plugin *Plugin) *Handlers

NewHandlers creates Email OTP plugin handlers.

Parameters:

  • plugin: Initialized Email OTP plugin

Returns:

  • *Handlers: Handler instance ready for route registration

func (*Handlers) ConfirmEmailChangeHandler added in v2.1.0

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

ConfirmEmailChangeHandler completes an email change with the token mailed to the new address.

Endpoint:

  • Method: POST
  • Path: /email-otp/email-change/confirm
  • Auth: Required (session)

func (*Handlers) ForgotPasswordHandler added in v2.1.0

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

ForgotPasswordHandler starts the password-reset flow. The response is the same whether or not the address has an account, so the endpoint cannot be used to enumerate users; delivery failures are logged, not surfaced.

Endpoint:

  • Method: POST
  • Path: /email-otp/forgot-password
  • Auth: Public (rate-limited per email when a rate limiter is configured)

func (*Handlers) RequestEmailChangeHandler added in v2.1.0

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

RequestEmailChangeHandler starts the verified email-change flow for the authenticated user and mails a confirmation token to the new address.

Endpoint:

  • Method: POST
  • Path: /email-otp/email-change
  • Auth: Required (session)

func (*Handlers) ResetPasswordHandler added in v2.1.0

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

ResetPasswordHandler completes a password reset.

Endpoint:

  • Method: POST
  • Path: /email-otp/reset-password
  • Auth: Public

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) SendVerificationOTPHandler

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

SendVerificationOTPHandler sends an email-verification OTP to an address without requiring authentication, so newly registered but unverified users can prove ownership. Endpoint is public; apply rate limiting at the router or gateway to prevent abuse.

Endpoint:

  • Method: POST
  • Path: /email-otp/send-verification
  • Auth: Public

func (*Handlers) VerifyOTPHandler

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

VerifyOTPHandler handles verifying OTP codes.

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 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

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

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) ConfirmEmailChange added in v2.1.0

func (p *Plugin) ConfirmEmailChange(ctx context.Context, userID, token string) (auth.User, error)

ConfirmEmailChange redeems an email-change token for the authenticated user.

func (*Plugin) CreateUserWithEmailAndPassword

func (p *Plugin) CreateUserWithEmailAndPassword(ctx context.Context, name, email, password string) (*emailotptypes.User, error)

CreateUserWithEmailAndPassword creates a new user with email+password authentication.

Registration Process:

  1. Create user record with email (unverified)
  2. Hash password with bcrypt
  3. Create password account in auth.accounts table
  4. 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

func (p *Plugin) Description() string

Description returns a human-readable description for logging.

func (*Plugin) EnrichUser

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

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

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

GetMigrations returns the plugin migrations

func (*Plugin) GetUserByEmail

func (p *Plugin) GetUserByEmail(ctx context.Context, email string) (*auth.User, error)

GetUserByEmail retrieves a user by email address

func (*Plugin) Init

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

Init initializes the plugin.

func (*Plugin) MarkEmailVerified

func (p *Plugin) MarkEmailVerified(ctx context.Context, email string) error

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

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

MountRoutes registers HTTP routes for the Email OTP 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) RequestEmailChange added in v2.1.0

func (p *Plugin) RequestEmailChange(ctx context.Context, userID, newEmail string) error

RequestEmailChange issues a confirmation token for the new address and mails it there. The change is applied only when ConfirmEmailChange redeems it.

func (*Plugin) RequestPasswordReset added in v2.1.0

func (p *Plugin) RequestPasswordReset(ctx context.Context, email string) error

RequestPasswordReset issues a password-reset token for the account that owns email and mails it to that address. It returns nil when the address has no account, so HTTP callers can keep responses enumeration-safe; delivery failures are returned.

func (*Plugin) RequiresTables

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

RequiresTables returns the core tables this plugin reads from.

func (*Plugin) ResetPassword added in v2.1.0

func (p *Plugin) ResetPassword(ctx context.Context, token, newPassword string) error

ResetPassword redeems a password-reset token and sets the account's new password. All existing sessions are revoked by the core flow.

func (*Plugin) SendEmail

func (p *Plugin) SendEmail(ctx context.Context, to, subject, body string) error

SendEmail sends an arbitrary email using the configured email provider.

This method is exposed so other plugins (e.g., organizations) can reuse the same email provider for their own email needs (invitations, notifications, etc.) without configuring a second email sender.

If no provider is configured, the email is not sent and no error is returned (the caller can decide how to handle delivery in this case).

Parameters:

  • ctx: Request context
  • to: Recipient email address
  • subject: Email subject line
  • body: Plain-text email body

Returns:

  • error: If provider is configured but sending fails

Example:

err := plugin.SendEmail(ctx, "user@example.com", "Welcome!", "Thank you for joining.")

func (*Plugin) SendOTP

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

SendOTP generates and sends an OTP code via email.

OTP Generation and Delivery:

  1. Generate random N-digit code (configurable length)
  2. Send code via email provider
  3. Store code in verification service with expiry
  4. 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

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

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

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

VerifyOTP verifies an OTP code for the given email address. The code is bound to the email address and purpose it was issued for, and is consumed on success (single use).

Example:

valid, err := plugin.VerifyOTP(ctx, "user@example.com", "email_verification", "123456")
if valid {
  // Mark email as verified
}

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 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

	// SendEmail sends an arbitrary email with the given subject and body.
	//
	// This method allows other plugins (e.g. organizations) to reuse the same
	// email provider for sending non-OTP emails such as invitation links.
	//
	// Parameters:
	//   - to: Recipient email address
	//   - subject: Email subject line
	//   - body: Plain-text email body
	//
	// Returns:
	//   - error: If email sending fails
	SendEmail(to, subject, body 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) SendEmail(to, subject, body string) error {
    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
}

func (p *SendGridProvider) SendEmail(to, subject, body string) error {
    message := mail.NewV3Mail()
    message.SetFrom(mail.NewEmail("", p.from))
    message.Subject = subject
    message.AddContent(mail.NewContent("text/plain", body))
    personalization := mail.NewPersonalization()
    personalization.AddTos(mail.NewEmail("", to))
    message.AddPersonalizations(personalization)

    client := sendgrid.NewSendClient(p.apiKey)
    _, err := client.Send(message)
    return err
}

Directories

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

Jump to

Keyboard shortcuts

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