온라인 인증 시도와 처리 비용 및 Kakao 응답 경계를 제한한다

This commit is contained in:
Hide_D committed 2026-10-09 03:43:59 +09:00
1 parent 9dceaf8e4e
commit 3d9f7392df
12 files changed
+874 -25

No files matched your search

+29 -2
View File
@@ -1,9 +1,36 @@
import { safeTrpcErrorFormatter } from '@sammo-ts/common';
import { initTRPC } from '@trpc/server';
import { AuthBudgetError, isBudgetedAuthAction } from './auth/attemptBudget.js';
import { initTRPC, TRPCError } from '@trpc/server';
import type { GatewayApiContext } from './context.js';
const t = initTRPC.context<GatewayApiContext>().create({ errorFormatter: safeTrpcErrorFormatter });
export const router = t.router;
export const procedure = t.procedure;
export const procedure = t.procedure.use(async ({ ctx, path, getRawInput, next }) => {
if (!isBudgetedAuthAction(path)) return next();
const raw: unknown = await getRawInput();
const input = raw && typeof raw === 'object' ? (raw as Record<string, unknown>) : {};
const resolveSubject = async (): Promise<string | undefined> => {
if (path === 'auth.login' && typeof input.username === 'string' && input.username.length <= 64) {
return `login:${input.username.trim().toLocaleLowerCase('en-US')}`;
}
if (path.startsWith('account.') && typeof input.sessionToken === 'string' && input.sessionToken.length <= 256) {
const session = await ctx.sessions.getSession(input.sessionToken);
if (session) return `account:${session.userId}`;
}
return undefined;
};
try {
return await ctx.authBudget.run(path, ctx.requestIp, resolveSubject, () => next());
} catch (error) {
if (!(error instanceof AuthBudgetError)) throw error;
throw new TRPCError({
code: error.reason === 'limited' ? 'TOO_MANY_REQUESTS' : 'SERVICE_UNAVAILABLE',
message:
error.reason === 'limited'
? `인증 요청이 많습니다. ${error.retryAfterSeconds}초 후 다시 시도해 주세요.`
: '인증 보호 서비스를 사용할 수 없습니다. 잠시 후 다시 시도해 주세요.',
});
}
});