Implements full auth flows with opaque tokens (not JWT) for easy revocation: - Login/logout with cookie or bearer token support - Registration with email verification - Password reset with one-time tokens - scrypt password hashing (no external deps) New files in express/auth/: - token.ts: 256-bit token generation, SHA-256 hashing - password.ts: scrypt hashing with timing-safe verification - types.ts: Session schemas, token types, input validation - store.ts: AuthStore interface + InMemoryAuthStore - service.ts: AuthService with all auth operations - routes.ts: 6 auth endpoints Modified: - types.ts: Added user field to Call, requireAuth/requirePermission helpers - app.ts: JSON body parsing, populates call.user, handles auth errors - services.ts: Added services.auth - routes.ts: Includes auth routes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
65 lines
1.8 KiB
TypeScript
65 lines
1.8 KiB
TypeScript
// types.ts
|
|
//
|
|
// Authentication types and Zod schemas.
|
|
|
|
import { z } from "zod";
|
|
|
|
// Branded type for token IDs (the hash, not the raw token)
|
|
export type TokenId = string & { readonly __brand: "TokenId" };
|
|
|
|
// Token types for different purposes
|
|
export const tokenTypeParser = z.enum([
|
|
"session",
|
|
"password_reset",
|
|
"email_verify",
|
|
]);
|
|
export type TokenType = z.infer<typeof tokenTypeParser>;
|
|
|
|
// Authentication method - how the token was delivered
|
|
export const authMethodParser = z.enum(["cookie", "bearer"]);
|
|
export type AuthMethod = z.infer<typeof authMethodParser>;
|
|
|
|
// Session data schema - what gets stored
|
|
export const sessionDataParser = z.object({
|
|
tokenId: z.string().min(1),
|
|
userId: z.string().min(1),
|
|
tokenType: tokenTypeParser,
|
|
authMethod: authMethodParser,
|
|
createdAt: z.coerce.date(),
|
|
expiresAt: z.coerce.date(),
|
|
lastUsedAt: z.coerce.date().optional(),
|
|
userAgent: z.string().optional(),
|
|
ipAddress: z.string().optional(),
|
|
isUsed: z.boolean().optional(), // For one-time tokens
|
|
});
|
|
|
|
export type SessionData = z.infer<typeof sessionDataParser>;
|
|
|
|
// Input validation schemas for auth endpoints
|
|
export const loginInputParser = z.object({
|
|
email: z.string().email(),
|
|
password: z.string().min(1),
|
|
});
|
|
|
|
export const registerInputParser = z.object({
|
|
email: z.string().email(),
|
|
password: z.string().min(8),
|
|
displayName: z.string().optional(),
|
|
});
|
|
|
|
export const forgotPasswordInputParser = z.object({
|
|
email: z.string().email(),
|
|
});
|
|
|
|
export const resetPasswordInputParser = z.object({
|
|
token: z.string().min(1),
|
|
password: z.string().min(8),
|
|
});
|
|
|
|
// Token lifetimes in milliseconds
|
|
export const tokenLifetimes: Record<TokenType, number> = {
|
|
session: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
password_reset: 1 * 60 * 60 * 1000, // 1 hour
|
|
email_verify: 24 * 60 * 60 * 1000, // 24 hours
|
|
};
|