refactor: improve OpenAI service and tools
- Enhance OpenAI message types with tool_calls support - Improve security validation and rate limiting - Update utility tools and weather tool - Minor fixes in deposit-agent and domain-register Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
import { Env, TelegramUpdate } from './types';
|
||||
|
||||
// KV 오류 시 인메모리 폴백 (Worker 인스턴스 내)
|
||||
const fallbackRateLimits = new Map<string, { count: number; resetAt: number }>();
|
||||
import { createLogger } from './utils/logger';
|
||||
|
||||
// Telegram 서버 IP 대역 (2024년 기준)
|
||||
// https://core.telegram.org/bots/webhooks#the-short-version
|
||||
@@ -65,9 +63,16 @@ function isValidRequestBody(body: unknown): body is TelegramUpdate {
|
||||
);
|
||||
}
|
||||
|
||||
// 타임스탬프 검증 (비활성화 - WEBHOOK_SECRET으로 충분)
|
||||
function isRecentUpdate(_message: TelegramUpdate['message']): boolean {
|
||||
return true;
|
||||
// 타임스탬프 검증 (5분 이내 메시지만 허용 - 리플레이 공격 방지)
|
||||
function isRecentUpdate(message: TelegramUpdate['message']): boolean {
|
||||
// message가 없으면 callback_query 등일 수 있음 - 허용
|
||||
if (!message?.date) return true;
|
||||
|
||||
const messageTime = message.date * 1000; // Telegram uses Unix timestamp in seconds
|
||||
const now = Date.now();
|
||||
const MAX_AGE_MS = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
return (now - messageTime) < MAX_AGE_MS;
|
||||
}
|
||||
|
||||
export interface SecurityCheckResult {
|
||||
@@ -144,6 +149,7 @@ export async function checkRateLimit(
|
||||
): Promise<boolean> {
|
||||
const key = `ratelimit:${userId}`;
|
||||
const now = Date.now();
|
||||
const logger = createLogger('rate-limit');
|
||||
|
||||
try {
|
||||
// KV에서 기존 데이터 조회
|
||||
@@ -159,11 +165,24 @@ export async function checkRateLimit(
|
||||
await kv.put(key, JSON.stringify(newData), {
|
||||
expirationTtl: Math.ceil(windowMs / 1000), // 초 단위
|
||||
});
|
||||
logger.info('Rate limit 윈도우 시작', {
|
||||
userId,
|
||||
resetAt: new Date(newData.resetAt).toISOString(),
|
||||
maxRequests,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Rate limit 초과
|
||||
if (data.count >= maxRequests) {
|
||||
const resetInSeconds = Math.ceil((data.resetAt - now) / 1000);
|
||||
logger.warn('Rate limit 초과', {
|
||||
userId,
|
||||
currentCount: data.count,
|
||||
maxRequests,
|
||||
resetInSeconds,
|
||||
resetAt: new Date(data.resetAt).toISOString(),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -178,25 +197,11 @@ export async function checkRateLimit(
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[RateLimit] KV 오류:', error);
|
||||
// KV 오류 시 요청 허용 (fail-open)
|
||||
// Rate limiting은 abuse 방지 목적이므로 가용성 우선
|
||||
// 심각한 abuse는 Cloudflare WAF/Firewall Rules로 별도 대응
|
||||
logger.warn('KV 오류 - 요청 허용 (fail-open)', { userId, error: (error as Error).message });
|
||||
|
||||
// 인메모리 폴백으로 기본 보호
|
||||
const fallbackKey = `fallback:${userId}`;
|
||||
const existing = fallbackRateLimits.get(fallbackKey);
|
||||
|
||||
// 윈도우 만료 시 리셋
|
||||
if (!existing || existing.resetAt < now) {
|
||||
fallbackRateLimits.set(fallbackKey, { count: 1, resetAt: now + 60000 }); // 1분 윈도우
|
||||
return true;
|
||||
}
|
||||
|
||||
// 제한 초과 체크 (인메모리에서는 더 보수적으로 10회)
|
||||
if (existing.count >= 10) {
|
||||
console.warn('[RateLimit] Fallback limit exceeded', { userId });
|
||||
return false;
|
||||
}
|
||||
|
||||
existing.count++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user