"use strict"; const test = require("node:test"); const assert = require("node:assert/strict"); const { HoloLakeSessionManager, } = require("./hololake-session"); function fixture(overrides = {}) { let now = 1_800_000_000_000; const mail = []; const manager = new HoloLakeSessionManager({ registeredEmails: ["owner@example.invalid"], pepper: "test-only-pepper-with-enough-entropy", stateFile: "", now: () => now, sendEmail: async message => { mail.push(message); return true; }, ...overrides, }); return { manager, mail, advance(milliseconds) { now += milliseconds; }, }; } test("OTP request is non-enumerating and stores no plaintext code", async () => { const known = fixture(); const requested = await known.manager.requestOtp({ email: "Owner@Example.Invalid", deviceId: "ios-device-001", }); assert.equal(requested.accepted, true); assert.match(requested.request_id, /^[0-9a-f-]{36}$/); assert.equal(known.mail.length, 1); assert.match(known.mail[0].text, /\b\d{6}\b/); const otp = known.mail[0].text.match(/\b\d{6}\b/)[0]; assert.doesNotMatch(JSON.stringify(known.manager.inspectState()), new RegExp(otp)); const unknown = fixture(); const decoy = await unknown.manager.requestOtp({ email: "nobody@example.invalid", deviceId: "ios-device-001", }); assert.equal(decoy.accepted, requested.accepted); assert.match(decoy.request_id, /^[0-9a-f-]{36}$/); assert.equal(unknown.mail.length, 0); }); test("OTP verification is device-bound, attempt-limited, and returns a one-time session token", async () => { const state = fixture(); const requested = await state.manager.requestOtp({ email: "owner@example.invalid", deviceId: "ios-device-001", }); const otp = state.mail[0].text.match(/\b\d{6}\b/)[0]; const wrongDevice = state.manager.verifyOtp({ requestId: requested.request_id, code: otp, deviceId: "ios-device-002", }); assert.equal(wrongDevice.ok, false); assert.equal(wrongDevice.error, "invalid_or_expired_code"); const verified = state.manager.verifyOtp({ requestId: requested.request_id, code: otp, deviceId: "ios-device-001", }); assert.equal(verified.ok, true); assert.match(verified.session_token, /^[A-Za-z0-9_-]{40,}$/); assert.doesNotMatch( JSON.stringify(state.manager.inspectState()), new RegExp(verified.session_token), ); const reused = state.manager.verifyOtp({ requestId: requested.request_id, code: otp, deviceId: "ios-device-001", }); assert.equal(reused.ok, false); assert.equal(reused.error, "invalid_or_expired_code"); }); test("session authentication, expiry, and revocation never return the stored token", async () => { const state = fixture({ sessionTtlSeconds: 60 }); const requested = await state.manager.requestOtp({ email: "owner@example.invalid", deviceId: "ios-device-001", }); const otp = state.mail[0].text.match(/\b\d{6}\b/)[0]; const verified = state.manager.verifyOtp({ requestId: requested.request_id, code: otp, deviceId: "ios-device-001", }); const active = state.manager.authenticate( verified.session_token, "ios-device-001", ); assert.equal(active.ok, true); assert.equal(active.session.device_id, "ios-device-001"); assert.equal(Object.hasOwn(active.session, "token"), false); assert.equal( state.manager.authenticate(verified.session_token, "ios-device-002").error, "session_device_mismatch", ); assert.equal( state.manager.revoke(verified.session_token, "ios-device-002").error, "session_device_mismatch", ); assert.equal( state.manager.revoke(verified.session_token, "ios-device-001").ok, true, ); assert.equal( state.manager.authenticate(verified.session_token, "ios-device-001").error, "session_invalid", ); const second = await state.manager.requestOtp({ email: "owner@example.invalid", deviceId: "ios-device-001", }); const secondOtp = state.mail[1].text.match(/\b\d{6}\b/)[0]; const secondSession = state.manager.verifyOtp({ requestId: second.request_id, code: secondOtp, deviceId: "ios-device-001", }); state.advance(61_000); assert.equal( state.manager.authenticate( secondSession.session_token, "ios-device-001", ).error, "session_expired", ); }); test("OTP expires, locks after five failed attempts, and request rate is bounded", async () => { const expired = fixture({ otpTtlSeconds: 30 }); const requested = await expired.manager.requestOtp({ email: "owner@example.invalid", deviceId: "ios-device-001", }); const otp = expired.mail[0].text.match(/\b\d{6}\b/)[0]; expired.advance(31_000); assert.equal( expired.manager.verifyOtp({ requestId: requested.request_id, code: otp, deviceId: "ios-device-001", }).error, "invalid_or_expired_code", ); const locked = fixture({ maxOtpAttempts: 5 }); const lockRequest = await locked.manager.requestOtp({ email: "owner@example.invalid", deviceId: "ios-device-001", }); const realOtp = locked.mail[0].text.match(/\b\d{6}\b/)[0]; for (let attempt = 0; attempt < 5; attempt += 1) { assert.equal( locked.manager.verifyOtp({ requestId: lockRequest.request_id, code: "000000", deviceId: "ios-device-001", }).ok, false, ); } assert.equal( locked.manager.verifyOtp({ requestId: lockRequest.request_id, code: realOtp, deviceId: "ios-device-001", }).error, "invalid_or_expired_code", ); const limited = fixture({ requestLimit: 2 }); await limited.manager.requestOtp({ email: "owner@example.invalid", deviceId: "ios-device-001", networkKey: "198.51.100.7", }); await limited.manager.requestOtp({ email: "owner@example.invalid", deviceId: "ios-device-001", networkKey: "198.51.100.7", }); const denied = await limited.manager.requestOtp({ email: "owner@example.invalid", deviceId: "ios-device-001", networkKey: "198.51.100.7", }); assert.equal(denied.accepted, false); assert.equal(denied.error, "rate_limited"); assert.equal(limited.mail.length, 2); });