Files
zpan/server/lib/password.ts
Jasper Van de4938e75c feat: add shares schema, service layer, and lifecycle integration (#308)
- Add `shares` and `share_recipients` tables to Drizzle schema with indices
- Add migration 0010_shares.sql for shares/share_recipients tables
- Add `server/lib/password.ts` extracting scrypt hash/verify from auth.ts
  to eliminate duplicate crypto params across services
- Add `server/services/share.ts` implementing full CRUD + atomic counters:
  createShare, getShareByToken, incrementViews, incrementDownloadsAtomic
  (atomic SQL UPDATE), listSharesByCreator, revokeShare, cascadeDeleteByMatter
- Add `shared/schemas/share.ts` Zod validation schemas
- Export Share, ShareKind, ShareRecipient from shared/types
- Extend `purge.ts` to cascade-delete shares on matter hard-delete
- Add 38 integration tests and CF Workers atomic counter race tests

Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f

Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
2026-04-19 23:23:48 -04:00

19 lines
866 B
TypeScript

import crypto from 'node:crypto'
// node:crypto.scryptSync is native OpenSSL — safe on Cloudflare Workers (nodejs_compat)
// and avoids the JS-CPU budget issue that the pure-JS @noble/hashes scrypt triggers.
const SCRYPT_PARAMS = { N: 16384, r: 16, p: 1, maxmem: 128 * 16384 * 16 * 2 }
export function hashPassword(password: string): string {
const salt = crypto.randomBytes(16)
const key = crypto.scryptSync(password.normalize('NFKC'), salt, 64, SCRYPT_PARAMS)
return `${salt.toString('hex')}:${key.toString('hex')}`
}
export function verifyPassword(hash: string, plaintext: string): boolean {
const [saltHex, keyHex] = hash.split(':')
if (!saltHex || !keyHex) return false
const key = crypto.scryptSync(plaintext.normalize('NFKC'), Buffer.from(saltHex, 'hex'), 64, SCRYPT_PARAMS)
return crypto.timingSafeEqual(key, Buffer.from(keyHex, 'hex'))
}