import { NobleCryptoPlugin, ScureBase32Plugin, TOTP } from 'otplib'; const ISSUER = 'ApiCore'; // Allows one 30s step of clock drift in either direction between the // authenticator app and the server, matching common 2FA implementations. const EPOCH_TOLERANCE_SECONDS = 30; const crypto = new NobleCryptoPlugin(); const base32 = new ScureBase32Plugin(); function createTotp(secret: string, label: string): TOTP { return new TOTP({ secret, label, issuer: ISSUER, crypto, base32 }); } export function generateTotpSecret(): string { return new TOTP({ crypto, base32 }).generateSecret(); } export function buildTotpUri(secret: string, label: string): string { return createTotp(secret, label).toURI(); } /** * The time step a code belongs to, or null if it is wrong - or not newer * than afterTimeStep (a code that was already used, or an older one). */ export async function verifyTotpStep( secret: string, label: string, code: string, afterTimeStep?: number, ): Promise { const result = await createTotp(secret, label).verify(code, { epochTolerance: EPOCH_TOLERANCE_SECONDS, afterTimeStep, }); return result.valid ? result.timeStep : null; } export async function verifyTotpCode( secret: string, label: string, code: string, ): Promise { const result = await createTotp(secret, label).verify(code, { epochTolerance: EPOCH_TOLERANCE_SECONDS, }); return result.valid; }