Add session data to Call type

- AuthService.validateRequest now returns AuthResult with both user and session
- Call type includes session: SessionData | null
- Handlers can access session metadata (createdAt, authMethod, etc.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-04 09:50:05 -06:00
parent e9ccf6d757
commit ad6d405206
4 changed files with 19 additions and 11 deletions

View File

@@ -59,7 +59,7 @@ routes.forEach((route: Route, _idx: number, _allRoutes: Route[]) => {
console.log("request.originalUrl", request.originalUrl); console.log("request.originalUrl", request.originalUrl);
// Authenticate the request // Authenticate the request
const user = await services.auth.validateRequest(request); const auth = await services.auth.validateRequest(request);
const req: Call = { const req: Call = {
pattern: route.path, pattern: route.path,
@@ -67,7 +67,8 @@ routes.forEach((route: Route, _idx: number, _allRoutes: Route[]) => {
method, method,
parameters: { one: 1, two: 2 }, parameters: { one: 1, two: 2 },
request, request,
user, user: auth.user,
session: auth.session,
}; };
try { try {

View File

@@ -7,7 +7,7 @@
// Import authRoutes directly from "./auth/routes" instead. // Import authRoutes directly from "./auth/routes" instead.
export { hashPassword, verifyPassword } from "./password"; export { hashPassword, verifyPassword } from "./password";
export { AuthService } from "./service"; export { AuthService, type AuthResult } from "./service";
export { type AuthStore, InMemoryAuthStore } from "./store"; export { type AuthStore, InMemoryAuthStore } from "./store";
export { generateToken, hashToken, SESSION_COOKIE_NAME } from "./token"; export { generateToken, hashToken, SESSION_COOKIE_NAME } from "./token";
export * from "./types"; export * from "./types";

View File

@@ -12,7 +12,7 @@ import {
parseAuthorizationHeader, parseAuthorizationHeader,
SESSION_COOKIE_NAME, SESSION_COOKIE_NAME,
} from "./token"; } from "./token";
import { type TokenId, tokenLifetimes } from "./types"; import { type SessionData, type TokenId, tokenLifetimes } from "./types";
type LoginResult = type LoginResult =
| { success: true; token: string; user: User } | { success: true; token: string; user: User }
@@ -24,6 +24,11 @@ type RegisterResult =
type SimpleResult = { success: true } | { success: false; error: string }; type SimpleResult = { success: true } | { success: false; error: string };
// Result of validating a request/token - contains both user and session
export type AuthResult =
| { authenticated: true; user: User; session: SessionData }
| { authenticated: false; user: typeof AnonymousUser; session: null };
export class AuthService { export class AuthService {
constructor(private store: AuthStore) {} constructor(private store: AuthStore) {}
@@ -68,7 +73,7 @@ export class AuthService {
// === Session Validation === // === Session Validation ===
async validateRequest(request: ExpressRequest): Promise<MaybeUser> { async validateRequest(request: ExpressRequest): Promise<AuthResult> {
// Try cookie first (for web requests) // Try cookie first (for web requests)
let token = this.extractCookieToken(request); let token = this.extractCookieToken(request);
@@ -78,33 +83,33 @@ export class AuthService {
} }
if (!token) { if (!token) {
return AnonymousUser; return { authenticated: false, user: AnonymousUser, session: null };
} }
return this.validateToken(token); return this.validateToken(token);
} }
async validateToken(token: string): Promise<MaybeUser> { async validateToken(token: string): Promise<AuthResult> {
const tokenId = hashToken(token) as TokenId; const tokenId = hashToken(token) as TokenId;
const session = await this.store.getSession(tokenId); const session = await this.store.getSession(tokenId);
if (!session) { if (!session) {
return AnonymousUser; return { authenticated: false, user: AnonymousUser, session: null };
} }
if (session.tokenType !== "session") { if (session.tokenType !== "session") {
return AnonymousUser; return { authenticated: false, user: AnonymousUser, session: null };
} }
const user = await this.store.getUserById(session.userId as UserId); const user = await this.store.getUserById(session.userId as UserId);
if (!user || !user.isActive()) { if (!user || !user.isActive()) {
return AnonymousUser; return { authenticated: false, user: AnonymousUser, session: null };
} }
// Update last used (fire and forget) // Update last used (fire and forget)
this.store.updateLastUsed(tokenId).catch(() => {}); this.store.updateLastUsed(tokenId).catch(() => {});
return user; return { authenticated: true, user, session };
} }
private extractCookieToken(request: ExpressRequest): string | null { private extractCookieToken(request: ExpressRequest): string | null {

View File

@@ -8,6 +8,7 @@ import {
} from "express"; } from "express";
import type { MatchFunction } from "path-to-regexp"; import type { MatchFunction } from "path-to-regexp";
import { z } from "zod"; import { z } from "zod";
import type { SessionData } from "./auth/types";
import { type ContentType, contentTypes } from "./content-types"; import { type ContentType, contentTypes } from "./content-types";
import { type HttpCode, httpCodes } from "./http-codes"; import { type HttpCode, httpCodes } from "./http-codes";
import { import {
@@ -39,6 +40,7 @@ export type Call = {
parameters: object; parameters: object;
request: ExpressRequest; request: ExpressRequest;
user: MaybeUser; user: MaybeUser;
session: SessionData | null;
}; };
export type InternalHandler = (req: ExpressRequest) => Promise<Result>; export type InternalHandler = (req: ExpressRequest) => Promise<Result>;