Files
publisher-dashboard/apps/api-server/src/utils/crypto.ts
RevIQ 860d791125 Implement Workstream F1: me.get and me.setupProfile procedures
- Add me.get procedure returning user profile with needsSetup flag
- Add me.setupProfile procedure for initial profile setup after signup
- Add nonEmptyString/optionalString schema helpers with tests
- Use Web Crypto API (SubtleCrypto) for Cloudflare Workers compatibility
- Use @formatjs/intl-durationformat for duration formatting
- Remove node:crypto dependency from crypto utilities

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 16:29:41 +08:00

49 lines
1.3 KiB
TypeScript

/**
* Hash a token with SHA-256 for storage in database
* Never store raw tokens - always hash first
* Uses Web Crypto API for Cloudflare Workers compatibility
*/
export const hashToken = async (token: string): Promise<string> => {
const encoder = new TextEncoder();
const data = encoder.encode(token);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = new Uint8Array(hashBuffer);
return Array.from(hashArray)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
};
/**
* Generate a session token (UUID v4)
*/
export const generateSessionToken = (): string => {
return crypto.randomUUID();
};
/**
* Generate a device fingerprint (UUID v4)
*/
export const generateDeviceFingerprint = (): string => {
return crypto.randomUUID();
};
/**
* Generate a secure random token for email verification, password reset, etc.
* Uses 32 bytes (256 bits) of entropy
* Uses Web Crypto API for Cloudflare Workers compatibility
*/
export const generateSecureToken = (): string => {
const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);
return Array.from(bytes)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
};
/**
* Generate expiration date
*/
export const generateExpiry = (seconds: number): Date => {
return new Date(Date.now() + seconds * 1000);
};