From 69e4dcc3389018e71c9a4e5290e827566960008a Mon Sep 17 00:00:00 2001 From: kappa Date: Wed, 28 Jan 2026 20:40:41 +0900 Subject: [PATCH] fix: P2 medium priority issues - validation and logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P2-1: Tool selection fallback optimization - Return only utility tools when no patterns match - Reduces token usage by ~80% in fallback cases P2-2: Minimum deposit amount validation - Add MIN_DEPOSIT_AMOUNT = 1,000원 - Prevents spam with tiny deposits P2-3: Standardize logging - Replace console.log/error with structured logger - bank-sms-parser.ts and security.ts P2-4: Nameserver format validation - Add validateNameservers() function - Check minimum 2 NS, valid hostname format - Clear error messages in Korean P2-5: Optimistic lock error context - Return specific error for version conflicts - User-friendly message: "동시 요청으로 처리가 지연됨" Co-Authored-By: Claude Opus 4.5 --- src/deposit-agent.ts | 22 ++++++++++++++++++---- src/security.ts | 8 +++++--- src/services/bank-sms-parser.ts | 15 +++++++++------ src/tools/domain-tool.ts | 25 +++++++++++++++++++++++++ src/tools/index.ts | 10 ++++++---- 5 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/deposit-agent.ts b/src/deposit-agent.ts index efc14a5..475b423 100644 --- a/src/deposit-agent.ts +++ b/src/deposit-agent.ts @@ -18,6 +18,7 @@ import type { ManageDepositArgs, DepositFunctionResult } from './types'; const logger = createLogger('deposit-agent'); +const MIN_DEPOSIT_AMOUNT = 1000; // 1,000원 const MAX_DEPOSIT_AMOUNT = 100_000_000; // 1억원 export interface DepositContext { @@ -81,6 +82,9 @@ export async function executeDepositFunction( if (!amount || amount <= 0) { return { error: '충전 금액을 입력해주세요.' }; } + if (amount < MIN_DEPOSIT_AMOUNT) { + return { error: `최소 충전 금액은 ${MIN_DEPOSIT_AMOUNT.toLocaleString()}원입니다.` }; + } if (amount > MAX_DEPOSIT_AMOUNT) { return { error: `최대 충전 금액은 ${MAX_DEPOSIT_AMOUNT.toLocaleString()}원입니다.` }; } @@ -159,9 +163,14 @@ export async function executeDepositFunction( } catch (error) { if (error instanceof OptimisticLockError) { logger.warn('동시성 충돌 감지 (입금 자동 매칭)', { userId, amount, depositor_name }); - throw new Error('처리 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'); + return { + error: '⚠️ 동시 요청으로 처리가 지연되었습니다. 잠시 후 다시 시도해주세요.', + }; } - throw error; + logger.error('입금 자동 매칭 실패', error as Error, { userId, amount, depositor_name }); + return { + error: '처리 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.', + }; } } @@ -368,9 +377,14 @@ export async function executeDepositFunction( user_id: tx.user_id, amount: tx.amount, }); - throw new Error('처리 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'); + return { + error: '⚠️ 동시 요청으로 처리가 지연되었습니다. 잠시 후 다시 시도해주세요.', + }; } - throw error; + logger.error('관리자 입금 확인 실패', error as Error, { transaction_id, user_id: tx.user_id, amount: tx.amount }); + return { + error: '처리 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.', + }; } } diff --git a/src/security.ts b/src/security.ts index 65b5bed..606f4b2 100644 --- a/src/security.ts +++ b/src/security.ts @@ -1,6 +1,8 @@ import { Env, TelegramUpdate } from './types'; import { createLogger } from './utils/logger'; +const logger = createLogger('security'); + // Telegram 서버 IP 대역 (2024년 기준) // https://core.telegram.org/bots/webhooks#the-short-version const TELEGRAM_IP_RANGES = [ @@ -99,12 +101,12 @@ export async function validateWebhookRequest( // 3. Secret Token 검증 (필수) if (!env.WEBHOOK_SECRET) { - console.error('WEBHOOK_SECRET not configured - rejecting request'); + logger.error('WEBHOOK_SECRET not configured - rejecting request', new Error('Missing WEBHOOK_SECRET')); return { valid: false, error: 'Security configuration error' }; } if (!isValidSecretToken(request, env.WEBHOOK_SECRET)) { - console.error('Invalid webhook secret token'); + logger.warn('Invalid webhook secret token'); return { valid: false, error: 'Invalid secret token' }; } @@ -112,7 +114,7 @@ export async function validateWebhookRequest( const clientIP = request.headers.get('CF-Connecting-IP'); if (clientIP && !isValidTelegramIP(clientIP)) { // 경고만 로그 (Cloudflare 프록시 환경에서는 정확하지 않을 수 있음) - console.warn(`Request from non-Telegram IP: ${clientIP}`); + logger.warn('Request from non-Telegram IP', { clientIP }); } // 5. 요청 본문 파싱 및 검증 diff --git a/src/services/bank-sms-parser.ts b/src/services/bank-sms-parser.ts index a83a82e..e91ad78 100644 --- a/src/services/bank-sms-parser.ts +++ b/src/services/bank-sms-parser.ts @@ -1,5 +1,8 @@ import { Env, BankNotification, WorkersAITextGenerationOutput, WorkersAITextGenerationInput } from '../types'; import { parseQuotedPrintable } from '../utils/email-decoder'; +import { createLogger } from '../utils/logger'; + +const logger = createLogger('bank-sms-parser'); /** * 은행 SMS 파싱 함수 @@ -23,17 +26,17 @@ export async function parseBankSMS(content: string, env?: Env): Promise `• ${ns}`).join('\n')}`; diff --git a/src/tools/index.ts b/src/tools/index.ts index 3380803..a2f1d86 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -134,10 +134,12 @@ export function selectToolsForMessage(message: string): typeof tools { } } - // 패턴 매칭 없으면 전체 도구 사용 (폴백) - if (selectedCategories.size === 1) { // utility만 있으면 폴백 - logger.info('패턴 매칭 없음 → 전체 도구 사용'); - return tools; + // 패턴 매칭 없으면 유틸리티 도구만 사용 (토큰 절약) + if (selectedCategories.size === 1) { // utility만 있으면 + logger.info('패턴 매칭 없음 → 유틸리티 도구만 사용 (토큰 절약)'); + // 기본 도구만 반환 (시간, 계산) + const utilityNames = TOOL_CATEGORIES.utility || []; + return tools.filter(t => utilityNames.includes(t.function.name)); } const selectedNames = new Set(