Files
outline/server/utils/VerificationCode.ts

96 lines
2.7 KiB
TypeScript

import { randomInt } from "crypto";
import { Minute } from "@shared/utils/time";
import Redis from "@server/storage/redis";
import { safeEqual } from "./crypto";
/**
* This class manages verification codes for email authentication.
* It stores and retrieves 6-digit codes in Redis with a 10-minute TTL.
*/
export class VerificationCode {
/**
* Redis client instance (lazy initialized)
*/
private static get redis() {
return Redis.defaultClient;
}
/**
* TTL for verification codes in milliseconds (10 minutes)
*/
private static readonly TTL = Minute.ms * 10;
/**
* Prefix for Redis keys
*/
private static readonly KEY_PREFIX = "email_verification_code:";
/**
* Generate a random 6-digit code
*
* @returns A string representing a 6-digit code
*/
public static generate(): string {
// Generate a random integer between 100000 and 999999 (6 digits)
return randomInt(100000, 1000000).toString().padStart(6, "0");
}
/**
* Store a verification code in Redis with a 10-minute TTL
*
* @param email The email address associated with the code
* @param code The 6-digit verification code
* @returns Promise resolving to true if successful
*/
public static async store(email: string, code: string): Promise<boolean> {
const key = this.getKey(email);
await this.redis.set(key, code, "PX", this.TTL);
return true;
}
/**
* Retrieve a verification code from Redis
*
* @param email The email address associated with the code
* @returns Promise resolving to the code or null if not found
*/
public static async retrieve(email: string): Promise<string | undefined> {
const key = this.getKey(email);
return (await this.redis.get(key)) ?? undefined;
}
/**
* Verify if a given code matches the stored code for an email
*
* @param email The email address associated with the code
* @param code The code to verify
* @returns Promise resolving to true if the code matches, false otherwise
*/
public static async verify(email: string, code: string): Promise<boolean> {
const storedCode = await this.retrieve(email);
return safeEqual(storedCode, code);
}
/**
* Delete a verification code from Redis
*
* @param email The email address associated with the code
* @returns Promise resolving to true if successful
*/
public static async delete(email: string): Promise<boolean> {
const key = this.getKey(email);
await this.redis.del(key);
return true;
}
/**
* Get the Redis key for an email address
*
* @param email The email address
* @returns The Redis key
*/
private static getKey(email: string): string {
return `${this.KEY_PREFIX}${email.toLowerCase()}`;
}
}