AtAuth
1/**
2 * Error Handling Utilities
3 *
4 * Provides safe error responses that don't leak internal details.
5 * Express 5 automatically forwards async errors to the error handler.
6 */
7
8/**
9 * Standard error response format.
10 */
11export interface ErrorResponse {
12 error: string;
13 message: string;
14}
15
16/**
17 * HTTP error with status code and machine-readable error code.
18 * Thrown from route handlers and caught by the global error middleware.
19 */
20export class HttpError extends Error {
21 constructor(
22 public readonly statusCode: number,
23 public readonly code: string,
24 message: string
25 ) {
26 super(message);
27 this.name = 'HttpError';
28 }
29}
30
31/**
32 * Convenience factory functions for common HTTP errors.
33 */
34export const httpError = {
35 badRequest: (code: string, message: string) => new HttpError(400, code, message),
36 unauthorized: (code: string, message: string) => new HttpError(401, code, message),
37 forbidden: (code: string, message: string) => new HttpError(403, code, message),
38 notFound: (code: string, message: string) => new HttpError(404, code, message),
39 conflict: (code: string, message: string) => new HttpError(409, code, message),
40 internalServerError: (code: string, message: string) => new HttpError(500, code, message),
41};
42
43/**
44 * Sanitize an error for client response.
45 * Logs the full error server-side but returns a safe message to clients.
46 *
47 * @param error - The caught error
48 * @param context - Context for logging (e.g., "Token verify")
49 * @returns Safe error message for client response
50 */
51export function sanitizeError(error: unknown, context: string): string {
52 // Log full error details server-side for debugging
53 console.error(`${context} error:`, error);
54
55 // In development, return more details for debugging
56 // In production, return a generic message
57 if (process.env.NODE_ENV === 'development') {
58 if (error instanceof Error) {
59 // Even in dev, don't expose stack traces or sensitive paths
60 return error.message.replace(/\/[^\s:]+/g, '[path]');
61 }
62 }
63
64 // Generic message that doesn't leak implementation details
65 return 'An internal error occurred. Please try again later.';
66}
67
68/**
69 * Create a safe 500 error response.
70 *
71 * @param errorCode - Machine-readable error code
72 * @param error - The caught error
73 * @param context - Context for logging
74 * @returns Error response object
75 */
76export function internalError(
77 errorCode: string,
78 error: unknown,
79 context: string
80): ErrorResponse {
81 return {
82 error: errorCode,
83 message: sanitizeError(error, context),
84 };
85}