import test from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { AccessStore } from '../src/store.js'; import { fixture, grant } from './helpers.js'; test('the same number works on multiple devices for 15 days; all sessions stop at code expiry', async () => { const f = fixture(); const { code, request } = await grant(f); assert.equal(code.length, 10); assert.equal(request.codeExpiresAt - request.approvedAt, 15 * 86400_000); const first = f.store.redeem(code); f.advance(14 * 86400_000); const second = f.store.redeem(code); assert.notEqual(first, second); assert.ok(f.store.session(first)); assert.equal(f.store.session(second).expiresAt, request.codeExpiresAt); assert.equal(JSON.stringify(f.store.state).includes(code), false); assert.equal(JSON.stringify(f.store.state).includes(first), false); f.advance(86400_000); assert.equal(f.store.redeem(code), null); assert.equal(f.store.session(first), null); assert.equal(f.store.session(second), null); await f.service.request({ platform: 'telegram', user_id: '5' }); assert.equal(f.store.state.requests.length, 2); assert.equal(f.store.state.requests[1].status, 'pending'); }); test('only configured admins from the correct platform can approve; repeated reviews do not send another code', async () => { const f = fixture(); await f.service.request({ platform: 'telegram', user_id: '5' }); const id = f.store.state.requests[0].id; assert.match(await f.service.decide('telegram', '999', id, 'approve'), /Only configured/); assert.match(await f.service.decide('discord', '200', id, 'approve'), /not found/); assert.equal(f.store.getRequest(id).status, 'pending'); const results = await Promise.all([f.service.decide('telegram', '100', id, 'approve'), f.service.decide('telegram', '101', id, 'approve')]); assert.equal(results.filter(r => r.startsWith('Approved.')).length, 1); assert.equal(f.messages.filter(m => m.text.includes('Your number:')).length, 1); assert.match(await f.service.decide('telegram', '100', id, 'deny'), /already approved/); }); test('failed code delivery leaves the request retryable and the attempted number unusable', async () => { const f = fixture(); await f.service.request({ platform: 'telegram', user_id: '5' }); const id = f.store.state.requests[0].id; let attemptedCode; const original = f.provider.sendUser; f.provider.sendUser = async (_id, message) => { attemptedCode = message.match(/Your number: (\d+)/)[1]; assert.equal(f.store.redeem(attemptedCode), null); throw new Error('DM blocked'); }; assert.match(await f.service.decide('telegram', '100', id, 'approve'), /No number was activated/); assert.equal(f.store.getRequest(id).status, 'pending'); assert.equal(f.store.getRequest(id).codeHash, undefined); assert.equal(f.store.redeem(attemptedCode), null); f.provider.sendUser = original; assert.match(await f.service.decide('telegram', '100', id, 'approve'), /Approved/); }); test('duplicate requests are coalesced; failed admin notifications can be retried', async () => { const f = fixture(); const send = f.provider.sendUser; f.provider.sendUser = async (id, ...args) => { if (id === '100' || id === '101') throw new Error('Unavailable'); return send(id, ...args); }; const identity = { platform: 'telegram', user_id: '5' }; assert.match(await f.service.request(identity), /could not contact/); f.provider.sendUser = send; await Promise.all([f.service.request(identity), f.service.request(identity)]); assert.equal(f.store.state.requests.length, 1); assert.deepEqual(f.store.state.requests[0].notifiedAdmins, ['100', '101']); assert.equal(f.messages.filter(m => m.buttons.length === 2).length, 2); }); test('denial, pending expiry, malformed numbers, logout and revocation fail closed', async () => { const f = fixture(); await f.service.request({ platform: 'telegram', user_id: '5' }); const id = f.store.state.requests[0].id; await f.service.decide('telegram', '100', id, 'deny'); assert.equal(f.store.getRequest(id).status, 'denied'); await assert.rejects(f.service.request({ platform: 'telegram', user_id: '5' }), /wait a few minutes/); await f.service.request({ platform: 'telegram', user_id: '6' }); f.advance(f.config.requestTtlMs); assert.match(await f.service.decide('telegram', '100', f.store.state.requests[1].id, 'approve'), /expired/); for (const code of [null, {}, 1234567890, '', '1'.repeat(11), 'abcd123456']) assert.equal(f.store.redeem(code), null); const { code, request } = await grant(f, '7'); const token = f.store.redeem(code); f.store.logout(token); assert.equal(f.store.session(token), null); const other = f.store.redeem(code); f.store.updateRequest(request.id, r => { r.status = 'revoked'; }); assert.equal(f.store.session(other), null); assert.equal(f.store.redeem(code), null); }); test('per-IP and global login limits use fixed windows and do not store raw IPs', () => { const f = fixture({ loginAttempts: 2, globalLoginAttempts: 3 }); assert.equal(f.store.attempt('192.0.2.1'), 0); assert.equal(f.store.attempt('192.0.2.1'), 0); assert.equal(f.store.attempt('192.0.2.1'), 900); assert.equal(f.store.attempt('192.0.2.2'), 0); assert.equal(f.store.attempt('192.0.2.3'), 900); assert.equal(JSON.stringify(f.store.state).includes('192.0.2.'), false); f.advance(900_000); assert.equal(f.store.attempt('192.0.2.1'), 0); }); test('atomic storage survives restart, preserves limits, locks a second writer, and rejects corrupt data', async () => { const dir = mkdtempSync(join(tmpdir(), 'dng-store-')); const f = fixture({ dataFile: join(dir, 'access.json') }); let disk; try { disk = new AccessStore(f.config); const { request } = disk.request({ platform: 'telegram', user_id: '5' }); const code = disk.reserveCode(request.id, '100'); disk.updateRequest(request.id, r => { r.status = 'approved'; }); const token = disk.redeem(code); disk.attempt('192.0.2.1'); assert.throws(() => new AccessStore(f.config), /locked/); assert.equal(statSync(f.config.dataFile).mode & 0o777, 0o600); assert.equal(readFileSync(f.config.dataFile, 'utf8').includes(code), false); disk.close(); disk = new AccessStore(f.config); assert.ok(disk.session(token)); assert.ok(disk.redeem(code)); assert.equal(disk.state.limits.global.count, 1); disk.close(); writeFileSync(f.config.dataFile, 'invalid json'); assert.throws(() => new AccessStore(f.config)); } finally { disk?.close(); rmSync(dir, { recursive: true, force: true }); } }); test('interrupted approval is reset on restart without activating the undelivered code', () => { const dir = mkdtempSync(join(tmpdir(), 'dng-recovery-')); const f = fixture({ dataFile: join(dir, 'access.json') }); let disk = new AccessStore(f.config); try { const { request } = disk.request({ platform: 'telegram', user_id: '5' }); const code = disk.reserveCode(request.id, '100'); disk.close(); disk = new AccessStore(f.config); assert.equal(disk.getRequest(request.id).status, 'pending'); assert.equal(disk.redeem(code), null); } finally { disk.close(); rmSync(dir, { recursive: true, force: true }); } });