import { createHmac } from 'crypto';
const TOTP_PERIOD = 30;
const TOTP_DIGITS = 6;
export function generateTOTPSecret() {
    const bytes = new Uint8Array(20);
    crypto.getRandomValues(bytes);
    return base32Encode(bytes);
}
export function generateTOTPUri(secret, email, issuer = 'Actuate CMS') {
    return `otpauth://totp/${encodeURIComponent(issuer)}:${encodeURIComponent(email)}?secret=${secret}&issuer=${encodeURIComponent(issuer)}&algorithm=SHA1&digits=${TOTP_DIGITS}&period=${TOTP_PERIOD}`;
}
export function verifyTOTP(token, secret, window = 1) {
    const now = Math.floor(Date.now() / 1000);
    for (let i = -window; i <= window; i++) {
        const counter = Math.floor((now + i * TOTP_PERIOD) / TOTP_PERIOD);
        const expected = generateHOTP(secret, counter);
        if (timingSafeEqual(token, expected))
            return true;
    }
    return false;
}
function generateHOTP(secret, counter) {
    const decodedSecret = base32Decode(secret);
    const buffer = Buffer.alloc(8);
    for (let i = 7; i >= 0; i--) {
        buffer[i] = counter & 0xff;
        counter = counter >> 8;
    }
    const hmac = createHmac('sha1', decodedSecret);
    hmac.update(buffer);
    const digest = hmac.digest();
    const offset = digest[digest.length - 1] & 0x0f;
    const code = ((digest[offset] & 0x7f) << 24) |
        ((digest[offset + 1] & 0xff) << 16) |
        ((digest[offset + 2] & 0xff) << 8) |
        (digest[offset + 3] & 0xff);
    return String(code % 10 ** TOTP_DIGITS).padStart(TOTP_DIGITS, '0');
}
function timingSafeEqual(a, b) {
    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;
}
const BASE32_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
function base32Encode(data) {
    let bits = 0, value = 0, output = '';
    for (const byte of data) {
        value = (value << 8) | byte;
        bits += 8;
        while (bits >= 5) {
            output += BASE32_CHARS[(value >>> (bits - 5)) & 31];
            bits -= 5;
        }
    }
    if (bits > 0)
        output += BASE32_CHARS[(value << (5 - bits)) & 31];
    return output;
}
function base32Decode(encoded) {
    const cleaned = encoded.replace(/=+$/, '').toUpperCase();
    let bits = 0, value = 0;
    const output = [];
    for (const char of cleaned) {
        const idx = BASE32_CHARS.indexOf(char);
        if (idx === -1)
            continue;
        value = (value << 5) | idx;
        bits += 5;
        if (bits >= 8) {
            output.push((value >>> (bits - 8)) & 0xff);
            bits -= 8;
        }
    }
    return Buffer.from(output);
}
/**
 * Normalize a user-entered backup code: strip whitespace/dashes and lowercase
 * so "ABCD-1234" and "abcd 1234" both match the stored "abcd1234".
 */
export function normalizeBackupCode(input) {
    return input.replace(/[\s-]/g, '').toLowerCase();
}
/**
 * Find the index of the stored backup code matching the submitted value, or -1.
 * Compares against every stored code in constant time so timing can't reveal
 * which/whether a code matched. Callers must treat a match as single-use and
 * remove the code from storage.
 */
export function findBackupCodeMatch(codes, submitted) {
    const normalized = normalizeBackupCode(submitted);
    if (!normalized)
        return -1;
    let matchIdx = -1;
    for (let i = 0; i < codes.length; i++) {
        if (timingSafeEqual(codes[i].toLowerCase(), normalized))
            matchIdx = i;
    }
    return matchIdx;
}
export function generateBackupCodes(count = 8) {
    const codes = [];
    for (let i = 0; i < count; i++) {
        const bytes = new Uint8Array(5);
        crypto.getRandomValues(bytes);
        codes.push(Array.from(bytes)
            .map((b) => b.toString(16).padStart(2, '0'))
            .join('')
            .slice(0, 8));
    }
    return codes;
}
//# sourceMappingURL=totp.js.map