// Point the DB at a closed port BEFORE requiring the modules (they build the pool). // The .db / model layers are monkeypatched so no query runs. process.env.DB_HOST = '127.0.0.1' process.env.DB_PORT = '59999' const { test, after, beforeEach, afterEach } = require('node:test') const assert = require('node:assert/strict') const crypto = require('crypto') // Unit-test the trusted-device seam: the session-service crypto helpers and the // model's cap check. Invariants: // - a trust token is a high-entropy opaque value hashed deterministically with // sha256 (so the login path can look it up by hash); // - resolveTrustedDevice only returns a device when a token is actually present; // - the per-user cap is a hard boundary (>= MAX is "at cap"). const sessionService = require('../src/auth/session.service') const trustedDevices = require('../src/model/trustedDevices/trustedDevices.model') const devicesDb = require('../src/model/trustedDevices/trustedDevices.db') const db = require('../src/utils/db') after(() => db.close()) test('hashTrustToken is deterministic sha256 hex', () => { const h1 = sessionService.hashTrustToken('abc') const h2 = sessionService.hashTrustToken('abc') assert.equal(h1, h2) assert.equal(h1, crypto.createHash('sha256').update('abc').digest('hex')) assert.equal(h1.length, 64) assert.notEqual(sessionService.hashTrustToken('abc'), sessionService.hashTrustToken('abd')) }) test('mintTrustToken emits an opaque token, its matching hash, and a ~30d expiry', () => { const now = 1_000_000_000_000 const out = sessionService.mintTrustToken({ deviceHash: 'dh', userAgent: 'UA' }, now) assert.ok(out.trustToken.length >= 40, 'token carries real entropy') assert.match(out.trustToken, /^[A-Za-z0-9_-]+$/, 'url-safe base64') assert.equal(out.trustHash, sessionService.hashTrustToken(out.trustToken)) assert.equal(out.deviceHash, 'dh') assert.equal(out.userAgent, 'UA') const days = (out.expiresAt.getTime() - now) / (24 * 60 * 60 * 1000) assert.equal(Math.round(days), 30) }) // ── resolveTrustedDevice ───────────────────────────────────────────────── const origFind = trustedDevices.findValidByHash afterEach(() => { trustedDevices.findValidByHash = origFind }) test('resolveTrustedDevice returns null when the request carries no trust token', async () => { let looked = false trustedDevices.findValidByHash = async () => { looked = true; return { id: 1 } } const row = await sessionService.resolveTrustedDevice({ headers: {} }) assert.equal(row, null) assert.equal(looked, false, 'no lookup without a token') }) test('resolveTrustedDevice looks up by the hash of the presented cookie token', async () => { let seenHash = null trustedDevices.findValidByHash = async (h) => { seenHash = h; return { id: 9, user_id: 3 } } const req = { headers: {}, cookies: { rg_trust: 'opaque-raw' } } const row = await sessionService.resolveTrustedDevice(req) assert.equal(row.id, 9) assert.equal(seenHash, sessionService.hashTrustToken('opaque-raw')) }) test('resolveTrustedDevice also accepts the X-Trust-Token header (native clients)', async () => { trustedDevices.findValidByHash = async (h) => (h === sessionService.hashTrustToken('hdr-token') ? { id: 5 } : null) const row = await sessionService.resolveTrustedDevice({ headers: { 'x-trust-token': 'hdr-token' } }) assert.equal(row.id, 5) }) // ── cap ────────────────────────────────────────────────────────────────── const origCount = devicesDb.countActiveForUser beforeEach(() => { devicesDb.countActiveForUser = origCount }) after(() => { devicesDb.countActiveForUser = origCount }) test('isAtCap is false below the max and true at/over it', async () => { const max = trustedDevices.MAX_TRUSTED_DEVICES devicesDb.countActiveForUser = async () => max - 1 assert.equal(await trustedDevices.isAtCap(1), false) devicesDb.countActiveForUser = async () => max assert.equal(await trustedDevices.isAtCap(1), true) devicesDb.countActiveForUser = async () => max + 3 assert.equal(await trustedDevices.isAtCap(1), true) })