~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
35 lines · 1.4 KB Raw History
// @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<string, number>} 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;
}