SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
1.8 KB · 76 lines typescript
Raw Blame History
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