emailverification

package
v0.0.9 Latest Latest
Warning

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

Go to latest
Published: Jan 3, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

README

Email Verification Plugin

The Email Verification plugin provides complete email verification workflow for AuthSome applications. It handles sending verification emails, validating tokens, and marking users as verified.

Features

  • Automatic Verification Emails: Optionally send verification emails after user signup
  • Secure Token Generation: Cryptographically secure random tokens
  • Rate Limiting: Prevents abuse with configurable limits (default: 3 per hour)
  • Token Expiry: Configurable expiration time (default: 24 hours)
  • Auto-Login: Optionally create session after successful verification
  • Resend Support: Users can request new verification emails
  • Status Checking: Check verification status for authenticated users
  • Notification Integration: Uses AuthSome notification system for email delivery

Installation

import (
    "github.com/xraph/authsome"
    "github.com/xraph/authsome/plugins/emailverification"
)

func main() {
    // Create AuthSome instance
    auth := authsome.New(/* ... */)
    
    // Register email verification plugin
    emailVerif := emailverification.NewPlugin(
        emailverification.WithAutoSendOnSignup(true),
        emailverification.WithExpiryHours(24),
        emailverification.WithMaxResendPerHour(3),
    )
    
    auth.RegisterPlugin(emailVerif)
}

Configuration

Via Code (Functional Options)
emailVerif := emailverification.NewPlugin(
    emailverification.WithTokenLength(32),              // Token length in bytes
    emailverification.WithExpiryHours(24),              // Token expiry time
    emailverification.WithMaxResendPerHour(3),          // Rate limit
    emailverification.WithAutoSendOnSignup(true),       // Auto-send on signup
    emailverification.WithAutoLoginAfterVerify(true),   // Auto-login after verify
    emailverification.WithVerificationURL("https://myapp.com/verify"), // Frontend URL
)
Via Configuration File
auth:
  auth:
    requireEmailVerification: true  # Block sign-in until verified
    
  emailverification:
    tokenLength: 32
    expiryHours: 24
    maxResendPerHour: 3
    autoSendOnSignup: true
    autoLoginAfterVerify: true
    verificationURL: "https://myapp.com/verify"
    devExposeToken: false  # Set to true in development to see tokens

Configuration Options

Option Type Default Description
tokenLength int 32 Length of verification token in bytes
expiryHours int 24 Token expiration time in hours
maxResendPerHour int 3 Maximum resend requests per hour per user
autoSendOnSignup bool true Automatically send verification email after signup
autoLoginAfterVerify bool true Create session after successful verification
verificationURL string "" Frontend URL template for verification links
devExposeToken bool false Expose token in response (development only)

API Endpoints

Verify Email

Verifies an email address using a token from the verification link.

GET /api/auth/email-verification/verify?token={token}

Response (200 OK):

{
  "success": true,
  "user": {
    "id": "abc123",
    "email": "user@example.com",
    "emailVerified": true,
    "emailVerifiedAt": "2024-12-13T15:45:00Z"
  },
  "session": {
    "id": "session123",
    "token": "session_token_abc",
    "expiresAt": "2024-12-14T15:45:00Z"
  },
  "token": "session_token_abc"
}

Errors:

  • 404 - Token not found or invalid
  • 410 - Token expired or already used
  • 400 - Email already verified
Resend Verification Email

Requests a new verification email to be sent.

POST /api/auth/email-verification/resend
Content-Type: application/json

{
  "email": "user@example.com"
}

Response (200 OK):

{
  "status": "sent"
}

Errors:

  • 404 - User not found
  • 400 - Email already verified
  • 429 - Rate limit exceeded (too many requests)
Send Verification Email

Manually sends a verification email (useful for admin tools).

POST /api/auth/email-verification/send
Content-Type: application/json

{
  "email": "user@example.com"
}

Response (200 OK):

{
  "status": "sent",
  "devToken": "abc123xyz789"  // Only in dev mode
}
Check Verification Status

Returns the email verification status for the current authenticated user.

GET /api/auth/email-verification/status
Cookie: session_token=...

Response (200 OK):

{
  "emailVerified": true,
  "emailVerifiedAt": "2024-12-13T15:45:00Z"
}

User Flow

1. Sign Up Flow
User signs up → User created (EmailVerified=false) → 
Verification email sent automatically → User receives email
2. Verification Flow
User clicks link in email → GET /verify?token=xyz → 
Token validated → User marked as verified → 
Optional: Session created (auto-login) → Success
3. Sign In Flow
User attempts sign in → Password validated → 
Check EmailVerified → If false: Reject with ErrEmailNotVerified → 
User must verify email first

Integration with Auth Service

The plugin works seamlessly with AuthSome's core authentication:

// When RequireEmailVerification is enabled in auth config
auth.Config{
    RequireEmailVerification: true,
}

// Sign-in will automatically check if user is verified
// and reject with types.ErrEmailNotVerified if not verified

Email Templates

The plugin uses the notification system's built-in auth.verify_email template with these variables:

{
    "userName":        "John Doe",
    "verificationURL": "https://myapp.com/verify?token=abc123",
    "code":            "abc123",  // For manual entry
    "appName":         "MyApp"
}

Frontend Implementation

React Example
// Verification page component
const VerifyEmailPage = () => {
  const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
  const [error, setError] = useState<string>('');
  
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const token = params.get('token');
    
    if (!token) {
      setStatus('error');
      setError('No verification token provided');
      return;
    }
    
    // Verify email
    fetch(`/api/auth/email-verification/verify?token=${token}`)
      .then(res => res.json())
      .then(data => {
        if (data.success) {
          setStatus('success');
          // Optionally redirect to dashboard if auto-login enabled
          if (data.session) {
            window.location.href = '/dashboard';
          }
        } else {
          setStatus('error');
          setError(data.error || 'Verification failed');
        }
      })
      .catch(() => {
        setStatus('error');
        setError('Network error');
      });
  }, []);
  
  if (status === 'loading') return <div>Verifying...</div>;
  if (status === 'success') return <div>Email verified successfully!</div>;
  return <div>Error: {error}</div>;
};
Resend Verification
const resendVerification = async (email: string) => {
  const response = await fetch('/api/auth/email-verification/resend', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email }),
  });
  
  if (response.ok) {
    alert('Verification email sent!');
  } else {
    const data = await response.json();
    alert(data.error || 'Failed to send email');
  }
};

Security Considerations

  1. Secure Token Generation: Uses crypto/rand for cryptographically secure tokens
  2. Rate Limiting: Prevents abuse with max 3 requests per hour per user
  3. Token Expiry: Tokens expire after 24 hours by default
  4. One-Time Use: Tokens are marked as used and cannot be reused
  5. HTTPS: Verification links should always use HTTPS in production
  6. Email Ownership: Only sends to registered email addresses

Error Handling

The plugin defines specific error codes:

Error Code HTTP Status Description
TOKEN_NOT_FOUND 404 Token not found or invalid
TOKEN_EXPIRED 410 Token has expired
TOKEN_USED 410 Token already used
ALREADY_VERIFIED 400 Email already verified
RATE_LIMIT_EXCEEDED 429 Too many requests
USER_NOT_FOUND 404 User not found

Testing

Development Mode

Enable devExposeToken in development to receive tokens in API responses:

emailverification:
  devExposeToken: true

This allows testing without email delivery.

Example Test
func TestEmailVerification(t *testing.T) {
    // Setup
    plugin := emailverification.NewPlugin(
        emailverification.WithDevExposeToken(true),
    )
    
    // Send verification
    resp := sendVerification("test@example.com")
    token := resp.DevToken
    
    // Verify
    verifyResp := verify(token)
    assert.True(t, verifyResp.Success)
    assert.True(t, verifyResp.User.EmailVerified)
}

Database Schema

Uses the existing verifications table:

CREATE TABLE verifications (
    id VARCHAR(20) PRIMARY KEY,
    app_id VARCHAR(20) NOT NULL,
    user_id VARCHAR(20) NOT NULL,
    token VARCHAR(255) NOT NULL UNIQUE,
    type VARCHAR(50) NOT NULL,  -- 'email' for this plugin
    expires_at TIMESTAMP NOT NULL,
    used BOOLEAN DEFAULT FALSE,
    used_at TIMESTAMP,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Cleanup

The plugin provides a method to clean up expired tokens:

// Run periodically (e.g., daily cron job)
count, err := plugin.Service().CleanupExpiredTokens(ctx)
if err != nil {
    log.Printf("Failed to cleanup: %v", err)
} else {
    log.Printf("Cleaned up %d expired tokens", count)
}

Troubleshooting

Email Not Received
  1. Check notification plugin is configured
  2. Verify email provider settings
  3. Check spam folder
  4. Enable devExposeToken to test without email
Token Invalid
  1. Check token hasn't expired (default: 24 hours)
  2. Verify token hasn't been used already
  3. Ensure token is complete (no truncation)
Rate Limit Hit
  1. Default: 3 requests per hour per user
  2. Increase maxResendPerHour if needed
  3. Wait before retrying

Best Practices

  1. Always use HTTPS for verification links in production
  2. Set appropriate expiry - Balance security vs. user experience
  3. Monitor rate limits - Adjust based on legitimate user patterns
  4. Graceful degradation - Don't block signup if email fails to send
  5. Clear error messages - Help users understand what went wrong
  6. Provide resend option - Make it easy to get a new verification email

License

Part of AuthSome - see main LICENSE file.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrTokenNotFound     = errs.New("TOKEN_NOT_FOUND", "Verification token not found or invalid", 404)
	ErrTokenExpired      = errs.New("TOKEN_EXPIRED", "Verification token has expired", 410)
	ErrTokenAlreadyUsed  = errs.New("TOKEN_USED", "Verification token has already been used", 410)
	ErrAlreadyVerified   = errs.New("ALREADY_VERIFIED", "Email address is already verified", 400)
	ErrRateLimitExceeded = errs.New("RATE_LIMIT_EXCEEDED", "Too many verification requests, please try again later", 429)
	ErrUserNotFound      = errs.New("USER_NOT_FOUND", "User not found", 404)
	ErrInvalidEmail      = errs.New("INVALID_EMAIL", "Invalid email address", 400)
)

Error definitions

Functions

This section is empty.

Types

type Config

type Config struct {
	// TokenLength is the length of the verification token in bytes
	TokenLength int `json:"tokenLength"`
	// ExpiryHours is the token expiry time in hours
	ExpiryHours int `json:"expiryHours"`
	// MaxResendPerHour is the maximum resend requests per hour per user
	MaxResendPerHour int `json:"maxResendPerHour"`
	// AutoSendOnSignup automatically sends verification email after signup
	AutoSendOnSignup bool `json:"autoSendOnSignup"`
	// AutoLoginAfterVerify creates a session after successful verification
	AutoLoginAfterVerify bool `json:"autoLoginAfterVerify"`
	// VerificationURL is the frontend URL template for verification links
	VerificationURL string `json:"verificationURL"`
	// DevExposeToken exposes token in response for development/testing
	DevExposeToken bool `json:"devExposeToken"`
}

Config holds the email verification plugin configuration

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the default email verification plugin configuration

type ErrorResponse

type ErrorResponse = responses.ErrorResponse

Response types - use shared responses from core

type Handler

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

Handler handles email verification HTTP endpoints

func NewHandler

func NewHandler(svc *Service, authInst core.Authsome) *Handler

NewHandler creates a new email verification handler

func (*Handler) Resend

func (h *Handler) Resend(c forge.Context) error

Resend handles resending verification email POST /email-verification/resend

func (*Handler) Send

func (h *Handler) Send(c forge.Context) error

Send handles manual verification email sending POST /email-verification/send

func (*Handler) Status

func (h *Handler) Status(c forge.Context) error

Status handles checking verification status for current user GET /email-verification/status (requires authentication)

func (*Handler) Verify

func (h *Handler) Verify(c forge.Context) error

Verify handles email verification via token GET /email-verification/verify?token=xyz

type Plugin

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

Plugin implements the email verification plugin

func NewPlugin

func NewPlugin(opts ...PluginOption) *Plugin

NewPlugin creates a new email verification plugin instance

func (*Plugin) ID

func (p *Plugin) ID() string

func (*Plugin) Init

func (p *Plugin) Init(authInst core.Authsome) error

func (*Plugin) Migrate

func (p *Plugin) Migrate() error

func (*Plugin) RegisterHooks

func (p *Plugin) RegisterHooks(hookRegistry *hooks.HookRegistry) error

func (*Plugin) RegisterRoutes

func (p *Plugin) RegisterRoutes(router forge.Router) error

func (*Plugin) RegisterServiceDecorators

func (p *Plugin) RegisterServiceDecorators(_ *registry.ServiceRegistry) error

type PluginOption

type PluginOption func(*Plugin)

PluginOption is a functional option for configuring the email verification plugin

func WithAutoLoginAfterVerify

func WithAutoLoginAfterVerify(enable bool) PluginOption

WithAutoLoginAfterVerify sets whether to auto-login after verification

func WithAutoSendOnSignup

func WithAutoSendOnSignup(enable bool) PluginOption

WithAutoSendOnSignup sets whether to automatically send verification on signup

func WithDefaultConfig

func WithDefaultConfig(cfg Config) PluginOption

WithDefaultConfig sets the default configuration for the plugin

func WithExpiryHours

func WithExpiryHours(hours int) PluginOption

WithExpiryHours sets the token expiry time in hours

func WithMaxResendPerHour

func WithMaxResendPerHour(max int) PluginOption

WithMaxResendPerHour sets the maximum resend requests per hour

func WithTokenLength

func WithTokenLength(length int) PluginOption

WithTokenLength sets the verification token length

func WithVerificationURL

func WithVerificationURL(url string) PluginOption

WithVerificationURL sets the frontend verification URL

type ResendRequest

type ResendRequest struct {
	Email string `json:"email" validate:"required,email" example:"user@example.com"`
}

type ResendResponse

type ResendResponse struct {
	Status string `json:"status" example:"sent"`
}

type SendRequest

type SendRequest struct {
	Email string `json:"email" validate:"required,email" example:"user@example.com"`
}

Request types

type SendResponse

type SendResponse struct {
	Status   string `json:"status" example:"sent"`
	DevToken string `json:"devToken,omitempty" example:"abc123xyz789"`
}

type Service

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

Service implements email verification logic

func NewService

func NewService(
	repo *VerificationRepository,
	userSvc user.ServiceInterface,
	sessionSvc session.ServiceInterface,
	notifAdapter *notificationPlugin.Adapter,
	cfg Config,
	logger forge.Logger,
) *Service

NewService creates a new email verification service

func (*Service) CleanupExpiredTokens

func (s *Service) CleanupExpiredTokens(ctx context.Context) (int64, error)

CleanupExpiredTokens removes expired verification tokens (for scheduled cleanup)

func (*Service) GetStatus

func (s *Service) GetStatus(ctx context.Context, userID xid.ID) (*StatusResponse, error)

GetStatus returns the email verification status for a user

func (*Service) ResendVerification

func (s *Service) ResendVerification(ctx context.Context, appID xid.ID, email string) error

ResendVerification sends a new verification email

func (*Service) SendVerification

func (s *Service) SendVerification(ctx context.Context, appID, userID xid.ID, email string) (string, error)

SendVerification generates a verification token and sends verification email

func (*Service) VerifyToken

func (s *Service) VerifyToken(ctx context.Context, appID xid.ID, token string, autoLogin bool, ip, ua string) (*VerifyResponse, error)

VerifyToken validates and consumes a verification token

type StatusResponse

type StatusResponse struct {
	EmailVerified   bool       `json:"emailVerified" example:"true"`
	EmailVerifiedAt *time.Time `json:"emailVerifiedAt,omitempty" example:"2024-12-13T15:45:00Z"`
}

type VerificationRepository

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

VerificationRepository handles database operations for email verification tokens

func NewVerificationRepository

func NewVerificationRepository(db *bun.DB) *VerificationRepository

NewVerificationRepository creates a new verification repository

func (*VerificationRepository) CountRecentByUser

func (r *VerificationRepository) CountRecentByUser(ctx context.Context, userID xid.ID, since time.Time) (int, error)

CountRecentByUser counts recent verification requests by a user (for rate limiting)

func (*VerificationRepository) Create

func (r *VerificationRepository) Create(ctx context.Context, appID, userID xid.ID, token string, expiresAt time.Time) error

Create creates a new email verification record

func (*VerificationRepository) DeleteExpired

func (r *VerificationRepository) DeleteExpired(ctx context.Context, before time.Time) (int64, error)

DeleteExpired deletes expired verification tokens

func (*VerificationRepository) FindByToken

func (r *VerificationRepository) FindByToken(ctx context.Context, token string) (*schema.Verification, error)

FindByToken finds a verification record by token

func (*VerificationRepository) FindByUserID

func (r *VerificationRepository) FindByUserID(ctx context.Context, userID xid.ID) (*schema.Verification, error)

FindByUserID finds the latest email verification for a user

func (*VerificationRepository) InvalidateOldTokens

func (r *VerificationRepository) InvalidateOldTokens(ctx context.Context, userID xid.ID) error

InvalidateOldTokens marks all unused tokens for a user as used (when sending new verification)

func (*VerificationRepository) MarkAsUsed

func (r *VerificationRepository) MarkAsUsed(ctx context.Context, verificationID xid.ID) error

MarkAsUsed marks a verification token as used

type VerifyRequest

type VerifyRequest struct {
	Token string `query:"token" validate:"required" example:"abc123xyz789"`
}

type VerifyResponse

type VerifyResponse struct {
	Success bool             `json:"success" example:"true"`
	User    *user.User       `json:"user"`
	Session *session.Session `json:"session,omitempty"`
	Token   string           `json:"token,omitempty" example:"session_token_abc123"`
}

Jump to

Keyboard shortcuts

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