TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import { customAlphabet } from "nanoid";2import { createHash, randomBytes, timingSafeEqual } from "node:crypto";34const alphabet = "0123456789abcdefghijklmnopqrstuvwxyz";5const nano = customAlphabet(alphabet, 16);6const nanoLong = customAlphabet(7 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",8 32,9);1011export type IdPrefix =12 | "req"13 | "att"14 | "sess"15 | "proj"16 | "org"17 | "key"18 | "usage"19 | "evt"20 | "wh"21 | "aud"22 | "abuse"23 | "crawl"24 | "cpg";2526export function newId(prefix: IdPrefix): string {27 return `${prefix}_${nano()}`;28}2930export type ApiKeyMode = "live" | "test";3132export interface GeneratedApiKey {33 /** Full plaintext key — shown once, never persisted. */34 plaintext: string;35 /** SHA-256 hash persisted in the database. */36 hash: string;37 /** Short non-secret prefix for display: `fch_live_ab12…` */38 prefix: string;39 /** Last 4 characters for display. */40 last4: string;41 mode: ApiKeyMode;42}4344export function generateApiKey(mode: ApiKeyMode = "live"): GeneratedApiKey {45 const secret = nanoLong();46 const plaintext = `fch_${mode}_${secret}`;47 return {48 plaintext,49 hash: hashApiKey(plaintext),50 prefix: plaintext.slice(0, 13),51 last4: plaintext.slice(-4),52 mode,53 };54}5556export function hashApiKey(plaintext: string): string {57 return createHash("sha256").update(plaintext, "utf8").digest("hex");58}5960export function parseApiKeyMode(plaintext: string): ApiKeyMode | null {61 if (plaintext.startsWith("fch_live_")) return "live";62 if (plaintext.startsWith("fch_test_")) return "test";63 return null;64}6566export function safeEqual(a: string, b: string): boolean {67 const ab = Buffer.from(a);68 const bb = Buffer.from(b);69 if (ab.length !== bb.length) return false;70 return timingSafeEqual(ab, bb);71}7273export function randomToken(bytes = 32): string {74 return randomBytes(bytes).toString("base64url");75}76