SSE 헤더 인증과 연결 수명 검증 및 HTTP 오류 정보 보호
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
/** Preserve public validation/permission messages while hiding internal causes and stacks. */
|
||||
export const safeTrpcErrorFormatter = <T extends { message: string; data: { code: string; stack?: string } }>({
|
||||
shape,
|
||||
}: {
|
||||
shape: T;
|
||||
}): T => ({
|
||||
...shape,
|
||||
message: shape.data.code === 'INTERNAL_SERVER_ERROR' ? 'Internal server error' : shape.message,
|
||||
data: { ...shape.data, stack: undefined },
|
||||
});
|
||||
|
||||
export const safeHttpErrorHandler = (
|
||||
error: unknown,
|
||||
request: { log: { error: (context: object, message: string) => void } },
|
||||
reply: { code: (status: number) => { send: (body: object) => unknown } }
|
||||
): void => {
|
||||
const value = error && typeof error === 'object' ? (error as { statusCode?: number; message?: string }) : {};
|
||||
const status =
|
||||
typeof value.statusCode === 'number' &&
|
||||
Number.isInteger(value.statusCode) &&
|
||||
value.statusCode >= 400 &&
|
||||
value.statusCode < 500
|
||||
? value.statusCode
|
||||
: 500;
|
||||
request.log.error({ err: error }, 'Request handling failed');
|
||||
reply.code(status).send({
|
||||
statusCode: status,
|
||||
error: status >= 500 ? 'Internal Server Error' : 'Request Error',
|
||||
message: status >= 500 ? 'Internal server error' : (value.message ?? 'Request failed'),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
const errorCodes = /^(?:P\d{4}|FST_ERR_[A-Z_]+|ERR_[A-Z_]+|UNAUTHORIZED|FORBIDDEN|BAD_REQUEST|INTERNAL_SERVER_ERROR)$/;
|
||||
|
||||
const errorSummary = (
|
||||
error: unknown
|
||||
): { type: string; message: string; stack: string; code?: string; statusCode?: number } => {
|
||||
const value = error && typeof error === 'object' ? (error as Record<string, unknown>) : {};
|
||||
const code =
|
||||
typeof value.code === 'string' && value.code.length < 80 && errorCodes.test(value.code)
|
||||
? value.code
|
||||
: undefined;
|
||||
const statusCode =
|
||||
typeof value.statusCode === 'number' && value.statusCode >= 400 && value.statusCode <= 599
|
||||
? value.statusCode
|
||||
: undefined;
|
||||
return {
|
||||
type: 'RequestError',
|
||||
message: 'Request handling failed',
|
||||
stack: '',
|
||||
...(code ? { code } : {}),
|
||||
...(statusCode ? { statusCode } : {}),
|
||||
};
|
||||
};
|
||||
|
||||
/** HTTP diagnostics carry static route/status/code, never credentials or error payloads. */
|
||||
export const safeHttpLoggerOptions = {
|
||||
serializers: {
|
||||
req: (request: { method?: string; routeOptions?: { url?: string } }) => ({
|
||||
method: request.method,
|
||||
// URLs can carry OAuth/session/input values, including unmatched path segments.
|
||||
url: request.routeOptions?.url ?? '[unmatched]',
|
||||
}),
|
||||
res: (response: { statusCode?: number }) => ({ statusCode: response.statusCode }),
|
||||
err: errorSummary,
|
||||
},
|
||||
redact: {
|
||||
paths: [
|
||||
'authorization',
|
||||
'cookie',
|
||||
'password',
|
||||
'sessionToken',
|
||||
'accessToken',
|
||||
'token',
|
||||
'secret',
|
||||
'body',
|
||||
'headers',
|
||||
'req.headers',
|
||||
'req.body',
|
||||
'res.headers',
|
||||
],
|
||||
remove: true,
|
||||
},
|
||||
hooks: {
|
||||
logMethod(args: unknown[], method: (...values: unknown[]) => void): void {
|
||||
const first = args[0];
|
||||
const error =
|
||||
first instanceof Error
|
||||
? first
|
||||
: first && typeof first === 'object' && 'err' in first
|
||||
? first.err
|
||||
: undefined;
|
||||
if (error !== undefined) {
|
||||
// Fastify also copies err.message into msg; serializers alone do not protect it.
|
||||
method.apply(this, [{ err: errorSummary(error) }, 'Request handling failed']);
|
||||
} else {
|
||||
// Fastify's default 404 handler also embeds the raw URL in a message.
|
||||
method.apply(
|
||||
this,
|
||||
args.map((value) =>
|
||||
typeof value === 'string' && /^Route .+ not found$/.test(value) ? 'Route not found' : value
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -31,6 +31,8 @@ export * from './gateway/profileStatus.js';
|
||||
export * from './gateway/runtimeDiagnostics.js';
|
||||
export * from './game/accessPenalty.js';
|
||||
export * from './http/trpcTransport.js';
|
||||
export * from './http/safeLogger.js';
|
||||
export * from './http/errorPrivacy.js';
|
||||
export * from './webPush/types.js';
|
||||
export { canReadPlayAudit, canReadPlayAuditAccounts } from './auth/playAudit.js';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user