Go: Handle OTP creation & validation in memory

ยท

3 min read

To generate a unique token/OTP for each verification request and associate it with the user's email or session in Go, you can use a combination of cryptographic libraries and data structures. Here's an example implementation:

package main

import (
    "crypto/rand"
    "encoding/base64"
    "fmt"
    "sync"
    "time"
)

// OTP struct represents an OTP with its associated email and expiration time
type OTP struct {
    Email       string
    Token       string
    ExpiresAt   time.Time
}

// OTPManager manages the generation and verification of OTPs
type OTPManager struct {
    otps map[string]OTP
    mu   sync.Mutex
}

// NewOTPManager creates a new OTPManager instance
func NewOTPManager() *OTPManager {
    return &OTPManager{
        otps: make(map[string]OTP),
    }
}

// GenerateOTP generates a unique OTP for the given email with a specified expiration time
func (m *OTPManager) GenerateOTP(email string, expiration time.Duration) (string, error) {
    m.mu.Lock()
    defer m.mu.Unlock()

    // Generate a random token
    tokenBytes := make([]byte, 32) // Adjust the length as per your requirements
    _, err := rand.Read(tokenBytes)
    if err != nil {
        return "", fmt.Errorf("failed to generate OTP: %w", err)
    }

    token := base64.URLEncoding.EncodeToString(tokenBytes)

    // Calculate the expiration time
    expiresAt := time.Now().Add(expiration)

    // Store the OTP in the manager
    m.otps[email] = OTP{
        Email:     email,
        Token:     token,
        ExpiresAt: expiresAt,
    }

    return token, nil
}

// VerifyOTP verifies the provided OTP for the given email
func (m *OTPManager) VerifyOTP(email, token string) bool {
    m.mu.Lock()
    defer m.mu.Unlock()

    // Retrieve the stored OTP for the email
    otp, ok := m.otps[email]
    if !ok {
        return false
    }

    // Check if the OTP is expired
    if time.Now().After(otp.ExpiresAt) {
        return false
    }

    // Compare the provided token with the stored OTP token
    return otp.Token == token
}

func main() {
    // Example usage
    manager := NewOTPManager()

    // Generate OTP for a specific email with an expiration time of 5 minutes
    email := "example@example.com"
    token, err := manager.GenerateOTP(email, 5*time.Minute)
    if err != nil {
        fmt.Printf("Error generating OTP: %v\n", err)
        return
    }

    // Simulate sending the OTP via email
    fmt.Printf("OTP: %s\n", token)

    // Simulate user entering the OTP for verification
    enteredToken := "abc123" // Replace with the OTP entered by the user

    // Verify the OTP
    isValid := manager.VerifyOTP(email, enteredToken)
    if isValid {
        fmt.Println("Verification successful!")
        // Mark the user's account as verified
        // Update the verification status in the user table or session data
    } else {
        fmt.Println("Verification failed!")
        // Handle the verification failure
    }
}

In this example, the OTPManager struct manages the generation and verification of OTPs. The GenerateOTP method generates a random token/OTP, associates it with the provided email, and stores it in the otps map along with its expiration time. The VerifyOTP method retrieves the stored OTP for the email and compares it with the provided token to validate the verification.

You can integrate the OTPManager into your application's

verification flow, associating each generated OTP with the user's email or session data for proper verification. Remember to adjust the length and expiration time of the OTPs as per your specific requirements.