Add authentication system with session-based auth
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>
This commit is contained in:
@@ -10,6 +10,12 @@ import type { MatchFunction } from "path-to-regexp";
|
||||
import { z } from "zod";
|
||||
import { type ContentType, contentTypes } from "./content-types";
|
||||
import { type HttpCode, httpCodes } from "./http-codes";
|
||||
import {
|
||||
AnonymousUser,
|
||||
type MaybeUser,
|
||||
type Permission,
|
||||
type User,
|
||||
} from "./user";
|
||||
|
||||
const methodParser = z.union([
|
||||
z.literal("GET"),
|
||||
@@ -32,6 +38,7 @@ export type Call = {
|
||||
method: Method;
|
||||
parameters: object;
|
||||
request: ExpressRequest;
|
||||
user: MaybeUser;
|
||||
};
|
||||
|
||||
export type InternalHandler = (req: ExpressRequest) => Promise<Result>;
|
||||
@@ -56,4 +63,36 @@ export type Route = {
|
||||
interruptable?: boolean;
|
||||
};
|
||||
|
||||
// Authentication error classes
|
||||
export class AuthenticationRequired extends Error {
|
||||
constructor() {
|
||||
super("Authentication required");
|
||||
this.name = "AuthenticationRequired";
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthorizationDenied extends Error {
|
||||
constructor() {
|
||||
super("Authorization denied");
|
||||
this.name = "AuthorizationDenied";
|
||||
}
|
||||
}
|
||||
|
||||
// Helper for handlers to require authentication
|
||||
export function requireAuth(call: Call): User {
|
||||
if (call.user === AnonymousUser) {
|
||||
throw new AuthenticationRequired();
|
||||
}
|
||||
return call.user;
|
||||
}
|
||||
|
||||
// Helper for handlers to require specific permission
|
||||
export function requirePermission(call: Call, permission: Permission): User {
|
||||
const user = requireAuth(call);
|
||||
if (!user.hasPermission(permission)) {
|
||||
throw new AuthorizationDenied();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
export { methodParser, massageMethod };
|
||||
|
||||
Reference in New Issue
Block a user