Skip to content

Commit 4944adc

Browse files
committed
chore: wip
1 parent a5332f1 commit 4944adc

File tree

4 files changed

+121
-13
lines changed

4 files changed

+121
-13
lines changed

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@
9393
"Bash(pgrep:*)",
9494
"Bash(zig build:*)",
9595
"Bash(/usr/bin/grep:*)",
96-
"Bash(node -e:*)"
96+
"Bash(node -e:*)",
97+
"Bash(cd:*)"
9798
],
9899
"deny": [],
99100
"ask": []

storage/framework/core/error-handling/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ export type {
2525
ErrorPageData,
2626
HttpError as HttpErrorInfo,
2727
HttpStatusCode,
28+
JobContext,
2829
QueryInfo,
2930
RequestContext,
3031
RoutingContext,
3132
StackFrame,
33+
UserContext,
3234
} from 'ts-error-handling'
3335

3436
export {

storage/framework/core/router/src/error-handler.ts

Lines changed: 115 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,96 @@ function getErrorHandlerConfig(): ErrorPageConfig {
5252
}
5353
}
5454

55+
/**
56+
* Sensitive fields that should be hidden in error pages
57+
*/
58+
const SENSITIVE_FIELDS = [
59+
'password',
60+
'password_confirmation',
61+
'secret',
62+
'token',
63+
'api_key',
64+
'apiKey',
65+
'api_secret',
66+
'apiSecret',
67+
'access_token',
68+
'accessToken',
69+
'refresh_token',
70+
'refreshToken',
71+
'private_key',
72+
'privateKey',
73+
'credit_card',
74+
'creditCard',
75+
'card_number',
76+
'cardNumber',
77+
'cvv',
78+
'ssn',
79+
'authorization',
80+
]
81+
82+
/**
83+
* Sanitize object by hiding sensitive fields
84+
*/
85+
function sanitizeData(data: unknown): unknown {
86+
if (!data || typeof data !== 'object') {
87+
return data
88+
}
89+
90+
if (Array.isArray(data)) {
91+
return data.map(sanitizeData)
92+
}
93+
94+
const sanitized: Record<string, unknown> = {}
95+
for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
96+
const lowerKey = key.toLowerCase()
97+
if (SENSITIVE_FIELDS.some(field => lowerKey.includes(field.toLowerCase()))) {
98+
sanitized[key] = '********'
99+
}
100+
else if (typeof value === 'object' && value !== null) {
101+
sanitized[key] = sanitizeData(value)
102+
}
103+
else {
104+
sanitized[key] = value
105+
}
106+
}
107+
return sanitized
108+
}
109+
110+
/**
111+
* Get request body from enhanced request (already parsed)
112+
*/
113+
function getRequestBody(request: Request | EnhancedRequest): unknown {
114+
const req = request as any
115+
if (req.jsonBody) {
116+
return sanitizeData(req.jsonBody)
117+
}
118+
if (req.formBody) {
119+
return sanitizeData(req.formBody)
120+
}
121+
return undefined
122+
}
123+
124+
/**
125+
* Get user context from authenticated user on request
126+
*/
127+
async function getUserContext(request: Request | EnhancedRequest): Promise<{ id?: string | number; email?: string; name?: string } | undefined> {
128+
const req = request as any
129+
// Check if user was set by auth middleware
130+
if (req._authenticatedUser) {
131+
const user = req._authenticatedUser
132+
return {
133+
id: user.id,
134+
email: user.email,
135+
name: user.name || user.username,
136+
}
137+
}
138+
return undefined
139+
}
140+
55141
/**
56142
* Create an Ignition-style error response for development
57143
*/
58-
export function createErrorResponse(
144+
export async function createErrorResponse(
59145
error: Error,
60146
request: Request | EnhancedRequest,
61147
options?: {
@@ -67,7 +153,7 @@ export function createErrorResponse(
67153
middleware?: string[]
68154
}
69155
},
70-
): Response {
156+
): Promise<Response> {
71157
const status = options?.status || 500
72158
const isDevelopment = process.env.APP_ENV !== 'production' && process.env.NODE_ENV !== 'production'
73159

@@ -102,8 +188,27 @@ export function createErrorResponse(
102188
// Set framework info
103189
handler.setFramework('Stacks', '0.70.0')
104190

105-
// Set request context
106-
handler.setRequest(request)
191+
// Set request context with body
192+
const requestBody = getRequestBody(request)
193+
if (requestBody) {
194+
const url = new URL(request.url)
195+
handler.setRequest({
196+
method: request.method,
197+
url: request.url,
198+
headers: Object.fromEntries(request.headers.entries()),
199+
queryParams: Object.fromEntries(url.searchParams.entries()),
200+
body: requestBody,
201+
})
202+
}
203+
else {
204+
handler.setRequest(request)
205+
}
206+
207+
// Set user context if authenticated
208+
const userContext = await getUserContext(request)
209+
if (userContext) {
210+
handler.setUser(userContext)
211+
}
107212

108213
// Set routing context if available
109214
if (options?.routingContext) {
@@ -177,10 +282,10 @@ export function createErrorResponse(
177282
/**
178283
* Create a middleware error response (401, 403, etc.)
179284
*/
180-
export function createMiddlewareErrorResponse(
285+
export async function createMiddlewareErrorResponse(
181286
error: Error & { statusCode?: number },
182287
request: Request | EnhancedRequest,
183-
): Response {
288+
): Promise<Response> {
184289
const status = error.statusCode || 500
185290
const isDevelopment = process.env.APP_ENV !== 'production' && process.env.NODE_ENV !== 'production'
186291

@@ -200,7 +305,7 @@ export function createMiddlewareErrorResponse(
200305

201306
// For 5xx errors in development, show full error page
202307
if (isDevelopment) {
203-
return createErrorResponse(error, request, { status })
308+
return await createErrorResponse(error, request, { status })
204309
}
205310

206311
// Production 5xx
@@ -241,16 +346,16 @@ export function createValidationErrorResponse(
241346
/**
242347
* Create a 404 Not Found response
243348
*/
244-
export function createNotFoundResponse(
349+
export async function createNotFoundResponse(
245350
path: string,
246351
request: Request | EnhancedRequest,
247-
): Response {
352+
): Promise<Response> {
248353
const isDevelopment = process.env.APP_ENV !== 'production' && process.env.NODE_ENV !== 'production'
249354

250355
if (isDevelopment) {
251356
const error = new Error(`Route not found: ${path}`)
252357
error.name = 'NotFoundError'
253-
return createErrorResponse(error, request, { status: 404 })
358+
return await createErrorResponse(error, request, { status: 404 })
254359
}
255360

256361
return new Response(renderProductionErrorPage(404), {

storage/framework/core/router/src/stacks-router.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ function createMiddlewareHandler(routeKey: string, handler: StacksHandler): Acti
133133
catch (error) {
134134
// Middleware threw an error (e.g., 401 Unauthorized)
135135
if (error instanceof Error && 'statusCode' in error) {
136-
return createMiddlewareErrorResponse(
136+
return await createMiddlewareErrorResponse(
137137
error as Error & { statusCode: number },
138138
enhancedReq,
139139
)
@@ -615,7 +615,7 @@ function wrapHandler(handler: StacksHandler, skipParsing = false): ActionHandler
615615
catch (error) {
616616
log.error(`[Router] Error handling request for '${handlerPath}':`, error)
617617
// Return Ignition-style error page in development, JSON in production
618-
return createErrorResponse(
618+
return await createErrorResponse(
619619
error instanceof Error ? error : new Error(String(error)),
620620
req,
621621
{ handlerPath },

0 commit comments

Comments
 (0)