Add utils package with Web Crypto password hashing

- Create @reviq/utils package with PBKDF2-SHA256 password hashing
  compatible with Cloudflare Workers (uses crypto.subtle)
- Update api-server and CLI to use new utils package for consistent
  password hashing format across the codebase
- Add pino logging to api-server for better request debugging
- Make login request tokens cryptographically secure base58 strings
  instead of database IDs
- Add migration to make login_requests.token non-nullable with unique
  constraint
- Fix RPCLink URL construction for client-side API calls
- Add db:codegen script to root package.json

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
RevIQ
2026-01-09 18:12:33 +08:00
parent cee700f063
commit c1afc39062
25 changed files with 512 additions and 142 deletions

View File

@@ -11,6 +11,7 @@ import {
setCookie,
} from "../../utils/cookies.js";
import {
generateBase58Token,
generateDeviceFingerprint,
generateExpiry,
} from "../../utils/crypto.js";
@@ -59,9 +60,9 @@ export const createLoginRequest = os.auth.createLoginRequest.handler(
// User doesn't exist - return fake response for anti-enumeration
if (!user) {
// Generate placeholder token (UUID) for anti-enumeration
// Generate placeholder token (base58) for anti-enumeration
// This prevents attackers from knowing if an email exists based on response
const placeholderToken = crypto.randomUUID();
const placeholderToken = generateBase58Token();
// Set placeholder login request token cookie
setCookie(
@@ -104,14 +105,16 @@ export const createLoginRequest = os.auth.createLoginRequest.handler(
const geo = getGeoInfo(context.reqHeaders);
const userAgent = getUserAgent(context.reqHeaders);
// Create login request
// Create login request with secure token
const expiresAt = generateExpiry(COOKIE_DURATIONS.LOGIN_REQUEST);
const token = generateBase58Token();
const loginRequest = await context.db
await context.db
.insertInto("login_requests")
.values({
user_id: userId,
email,
token,
device_fingerprint: deviceFingerprint,
ip_address: geo.ip,
city: geo.city,
@@ -120,16 +123,13 @@ export const createLoginRequest = os.auth.createLoginRequest.handler(
user_agent: userAgent,
expires_at: expiresAt,
})
.returning(["id"])
.executeTakeFirstOrThrow();
.execute();
const loginRequestId = loginRequest.id;
// Set login request token cookie with the real login request ID
// Set login request token cookie with the secure token
setCookie(
context.resHeaders,
COOKIE_NAMES.LOGIN_REQUEST_TOKEN,
loginRequestId,
token,
COOKIE_OPTIONS.loginRequest,
);