SSE 헤더 인증과 연결 수명 검증 및 HTTP 오류 정보 보호

This commit is contained in:
2026-10-09 02:35:45 +09:00
parent 87f810a878
commit 9dceaf8e4e
22 changed files with 1023 additions and 170 deletions
+3 -1
View File
@@ -1,3 +1,4 @@
import { safeHttpLoggerOptions, safeHttpErrorHandler } from '@sammo-ts/common';
import fastify, { type FastifyRequest } from 'fastify';
import cors from '@fastify/cors';
import fastifyStatic from '@fastify/static';
@@ -89,9 +90,10 @@ export const createGatewayApiServer = async () => {
config.defaultNavigationConfigFile
);
const app = fastify({
logger: true,
logger: safeHttpLoggerOptions,
routerOptions: gatewayFastifyRouterOptions,
});
app.setErrorHandler(safeHttpErrorHandler);
const webPush = new WebPushCoordinator(
postgres.prisma as GatewayPrismaClient,
{
+2 -1
View File
@@ -1,8 +1,9 @@
import { safeTrpcErrorFormatter } from '@sammo-ts/common';
import { initTRPC } from '@trpc/server';
import type { GatewayApiContext } from './context.js';
const t = initTRPC.context<GatewayApiContext>().create();
const t = initTRPC.context<GatewayApiContext>().create({ errorFormatter: safeTrpcErrorFormatter });
export const router = t.router;
export const procedure = t.procedure;
@@ -0,0 +1,51 @@
import fastify from 'fastify';
import { TRPCError } from '@trpc/server';
import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify';
import { describe, expect, it } from 'vitest';
import { router as gatewayRouter, procedure as gatewayProcedure } from '../src/trpc.js';
const internalFailure = () => {
throw new Error('synthetic-db-secret', { cause: new Error('synthetic-cause-secret') });
};
const badInput = () => {
throw new TRPCError({
code: 'BAD_REQUEST',
message: '잘못된 입력입니다.',
cause: new Error('synthetic-private-cause'),
});
};
describe('public HTTP error privacy', () => {
it('the real router factory hides internal errors and all stacks while preserving validation messages', async () => {
const app = fastify();
await app.register(fastifyTRPCPlugin, {
prefix: '/gateway',
trpcOptions: {
router: gatewayRouter({
internal: gatewayProcedure.query(internalFailure),
invalid: gatewayProcedure.query(badInput),
}),
createContext: () => ({}),
},
});
try {
const address = await app.listen({ host: '127.0.0.1', port: 0 });
for (const prefix of ['gateway']) {
for (const procedure of ['internal', 'invalid']) {
const response = await fetch(`${address}/${prefix}/${procedure}`);
const body = (await response.json()) as {
error: { message: string; data: { code: string; stack?: string } };
};
expect(response.status).toBe(procedure === 'internal' ? 500 : 400);
expect(body.error.message).toBe(
procedure === 'internal' ? 'Internal server error' : '잘못된 입력입니다.'
);
expect(body.error.data.stack).toBeUndefined();
expect(JSON.stringify(body)).not.toContain('synthetic-');
}
}
} finally {
await app.close();
}
});
});