Files

27 lines
1.5 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const test = require('node:test');
const { createTotpService, base32Decode, base32Encode, generateTotp } = require('../../src/security/totp');
test('TOTP uses RFC-compatible base32 and time steps', () => {
assert.equal(base32Encode(Buffer.from('foo')), 'MZXW6');
assert.equal(base32Decode('MZXW6').toString(), 'foo');
assert.equal(generateTotp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 59_000, { digits: 8 }), '94287082');
});
test('TOTP enrollment is opt-in and recovery codes are one-use', () => {
const values = new Map(); const secretValues = new Map(); const now = 1_700_000_000_000;
const settings = { get: (key, fallback) => values.has(key) ? values.get(key) : fallback, set: (key, value) => values.set(key, value) };
const secrets = { has: key => secretValues.has(key), get: key => secretValues.get(key) || '', set: (key, value) => value ? secretValues.set(key, value) : secretValues.delete(key) };
const service = createTotpService({ settings, secrets, now: () => now });
assert.equal(service.status().enabled, false);
const enrollment = service.begin({ account: 'Jens' });
const confirmed = service.confirm({ code: generateTotp(enrollment.secret, now) });
assert.equal(confirmed.enabled, true);
assert.equal(confirmed.recoveryCodes.length, 8);
assert.equal(service.verifyLoginCode(confirmed.recoveryCodes[0]), true);
assert.equal(service.verifyLoginCode(confirmed.recoveryCodes[0]), false);
assert.equal(service.status().recoveryCodesRemaining, 7);
});