fix(packs-lab): 일회성 토큰 jti 영속화 (SQLite) — 재시작 replay 방어 유지
인메모리 _used_jti set은 컨테이너 재시작 시 비워져 TTL 내 토큰 replay가 가능했음(webhook 배포가 잦아 실재 구멍). 영속 볼륨(PACK_BASE_DIR)의 jti_store.db에 사용 jti를 기록(PK 원자성), 만료 항목은 lazy 정리. verify_upload_token이 jti_store.consume 사용. TDD 3 + 기존 replay 테스트 보존. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,17 +14,14 @@ import hmac
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from threading import Lock
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
from . import jti_store
|
||||
|
||||
_SECRET = os.getenv("BACKEND_HMAC_SECRET", "")
|
||||
REQUEST_MAX_AGE_SEC = 300 # 5분
|
||||
|
||||
# JTI 단발성 set (in-memory, 단일 컨테이너 가정)
|
||||
_used_jti: set[str] = set()
|
||||
_jti_lock = Lock()
|
||||
|
||||
|
||||
def _sign(payload: bytes) -> str:
|
||||
if not _SECRET:
|
||||
@@ -83,10 +80,9 @@ def verify_upload_token(token: str) -> dict:
|
||||
payload = _decode_upload_token(token)
|
||||
jti = payload["jti"]
|
||||
|
||||
with _jti_lock:
|
||||
if jti in _used_jti:
|
||||
raise HTTPException(status_code=409, detail="이미 사용된 토큰")
|
||||
_used_jti.add(jti)
|
||||
# 영속 저장소에 사용 마킹 (재시작에도 단발성 유지). 이미 사용됐으면 False.
|
||||
if not jti_store.consume(jti, int(payload["expires_at"])):
|
||||
raise HTTPException(status_code=409, detail="이미 사용된 토큰")
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
Reference in New Issue
Block a user