feat(intake): token generator and shape check
Phase 8a Task 2. 24 bytes of CSPRNG entropy, base64url-encoded to exactly 32 URL-safe characters. isIntakeToken matches that exact shape so the public route can fail fast on bogus input without hitting the database or charging the rate limiter.
This commit is contained in:
parent
2937a305c3
commit
36572d99f3
1 changed files with 24 additions and 0 deletions
24
src/lib/intake/token.ts
Normal file
24
src/lib/intake/token.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { randomBytes } from "node:crypto";
|
||||
|
||||
/**
|
||||
* 24 bytes of entropy, base64url encoded → 32 URL-safe characters.
|
||||
*
|
||||
* The token is the link's whole authentication. It must be unguessable and
|
||||
* must not be derived from anything the public can see (no project id, no
|
||||
* timestamp, no hash of either). `crypto.randomBytes` satisfies both: it is
|
||||
* CSPRNG output, and the rest of the system treats the result as opaque.
|
||||
*
|
||||
* 24 bytes is 192 bits of entropy. Brute-forcing one token against the
|
||||
* `(token)` unique index is wasted effort at any realistic rate limit.
|
||||
*/
|
||||
export function generateIntakeToken(): string {
|
||||
return randomBytes(24).toString("base64url");
|
||||
}
|
||||
|
||||
const TOKEN_PATTERN = /^[A-Za-z0-9_-]{32}$/;
|
||||
|
||||
/** True when `value` is the shape the generator produces. Used by the
|
||||
* public route to reject obviously-bogus input early. */
|
||||
export function isIntakeToken(value: unknown): value is string {
|
||||
return typeof value === "string" && TOKEN_PATTERN.test(value);
|
||||
}
|
||||
Loading…
Reference in a new issue