All files / lib/crypto otp.ts

100% Statements 21/21
100% Branches 7/7
100% Functions 5/5
100% Lines 19/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39      87x 2x   85x 85x 85x 85x 85x 85x       47x 47x 47x 1504x         11x 10x 10x 518x   10x             8x 8x    
import { WHATSAPP_OTP_LENGTH } from "@interioring/utils/constants/whatsapp";
 
export function generateOtp(length: number = WHATSAPP_OTP_LENGTH): string {
	if (length < 4 || length > 10) {
		throw new Error("OTP length must be between 4 and 10");
	}
	const min = 10 ** (length - 1);
	const range = 9 * min;
	const buf = new Uint8Array(4);
	crypto.getRandomValues(buf);
	const n = new DataView(buf.buffer).getUint32(0);
	return String(min + (n % range));
}
 
export async function hashOtp(code: string): Promise<string> {
	const data = new TextEncoder().encode(code);
	const digest = await crypto.subtle.digest("SHA-256", data);
	return [...new Uint8Array(digest)]
		.map((b) => b.toString(16).padStart(2, "0"))
		.join("");
}
 
export function constantTimeEqual(a: string, b: string): boolean {
	if (a.length !== b.length) return false;
	let result = 0;
	for (let i = 0; i < a.length; i++) {
		result |= a.charCodeAt(i) ^ b.charCodeAt(i);
	}
	return result === 0;
}
 
export async function verifyOtp(
	candidate: string,
	storedHash: string,
): Promise<boolean> {
	const candidateHash = await hashOtp(candidate);
	return constantTimeEqual(candidateHash, storedHash);
}