File size: 1,765 Bytes
6c3af4e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
765119d
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
 * Tokens that have already failed argon2 verification.
 *
 * A process verifies against one search token for its whole life, so a token
 * that failed once cannot become valid again in this process: the lookup is
 * exact rather than a heuristic. A replay of a dead token then costs a Set
 * check instead of a full argon2id verification, which is what a stream of
 * dead search URLs from earlier deployments used to pay.
 */
const rejectedTokens = new Set<string>();

/**
 * Bounds the set against a flood of distinct junk tokens: once full, the
 * oldest entry is evicted and that token pays for one verification again if
 * it comes back.
 */
export const MAX_REJECTED_TOKENS = 1024;

/**
 * Rejections served from this set without a second argon2 verification, so
 * `/status` can show how much of the dead-token traffic stopped costing a
 * verification.
 */
let rejectedTokenCacheHits = 0;

export function isRejectedToken(token: string) {
  return rejectedTokens.has(token);
}

export function addRejectedToken(token: string) {
  if (rejectedTokens.has(token)) return;
  if (rejectedTokens.size >= MAX_REJECTED_TOKENS) {
    const oldest = rejectedTokens.values().next().value;
    if (oldest !== undefined) rejectedTokens.delete(oldest);
  }
  rejectedTokens.add(token);
}

export function recordRejectedTokenCacheHit(): void {
  rejectedTokenCacheHits++;
}

export function getRejectedTokenCacheHits(): number {
  return rejectedTokenCacheHits;
}

/**
 * How many distinct dead tokens the set holds, so `/status` can tell one stuck
 * client replaying a single token from many stale links, which the hit count
 * alone cannot. Never exceeds `MAX_REJECTED_TOKENS`.
 */
export function getRejectedTokenCount(): number {
  return rejectedTokens.size;
}