// Trusted-device store. Thin logic layer over trustedDevices.db โ€” mirrors the // mobileSessions model split (.db = SQL, .model = the API the rest of the app // calls). The opaque trust token lives client-side; only its sha256 hash is // persisted (hashing is done by the session service so caller + store agree, the // same seam as mobile refresh tokens). const db = require('./trustedDevices.db') // Max active trusted devices per user. Enforced by assertUnderCap (no silent // pruning โ€” an over-cap trust attempt is refused so the client can prompt the user // to revoke one first). See docs/website/TRUSTED_DEVICES_MFA.md ยง5. const MAX_TRUSTED_DEVICES = Number(process.env.MAX_TRUSTED_DEVICES) || 10 // Persist a newly trusted device (by token hash). Returns the row id. async function store({ userId, tokenHash, platform, deviceName, deviceHash, userAgent, expiresAt }) { return db.insert({ userId, tokenHash, platform, deviceName, deviceHash, userAgent, expiresAt }) } // Return the stored row for a still-valid (unrevoked, unexpired) trust token, else // null. Used by the login path to decide whether the TOTP step can be skipped. async function findValidByHash(tokenHash) { return db.findValidByHash(tokenHash) } // Stamp last_used_at when a device's trust is honored at login. async function touchLastUsed(id) { return db.touchLastUsed(id) } // List a user's active trusted devices (self-service list + admin per-user view). async function listActiveForUser(userId) { return db.listActiveForUser(userId) } // True if the user is at/over the trusted-device cap. Callers refuse the insert and // signal the client to revoke one first, rather than pruning silently. async function isAtCap(userId) { const n = await db.countActiveForUser(userId) return n >= MAX_TRUSTED_DEVICES } // Revoke one of a user's trusted devices by row id (ownership-scoped). Returns rows // changed (0 if it wasn't theirs / already gone โ€” treat idempotently). async function revokeByIdForUser(id, userId) { return db.revokeByIdForUser(id, userId) } // Revoke all of a user's trusted devices ("untrust everywhere" + the invalidation // hook on password change/reset / TOTP disable). Returns rows changed. async function revokeAllForUser(userId) { return db.revokeAllForUser(userId) } // Drop expired/revoked rows. async function pruneExpired() { return db.pruneExpired() } module.exports = { MAX_TRUSTED_DEVICES, store, findValidByHash, touchLastUsed, listActiveForUser, isAtCap, revokeByIdForUser, revokeAllForUser, pruneExpired, }