// @pdsjs/spaces/single-use - spending a token identifier once. // // A delegation token, a client attestation and a DPoP proof each authorize one // exchange. Nothing in their signature stops a second presentation, so the // server remembers the identifier until the token expires and refuses it after // the first use. // // Held in memory, which is sound for the same reason the write queue in // writer.js is: one space is served by exactly one process, a Node PDS or a // single Durable Object. /** @type {Map} scoped identifier -> the instant it may be forgotten */ const spent = new Map(); /** * Spend an identifier, answering whether this is the first time it was seen. * * Expired entries are dropped on each call, so the map holds one token * lifetime's worth of traffic rather than growing without bound. * * @param {string} scope - what the identifier is unique within, e.g. an issuer * @param {string} id - the token's `jti` * @param {number} expiresAt - epoch ms after which a replay fails on its own * @returns {boolean} false when the identifier was already spent */ export function spend(scope, id, expiresAt) { const now = Date.now(); for (const [key, forgetAt] of spent) { if (forgetAt <= now) spent.delete(key); } const key = `${scope}:${id}`; if (spent.has(key)) return false; spent.set(key, expiresAt); return true; }