o2o-site-AEO/postgres-init/migrations/0015_users_token_version.sql
Mina Choi b4085a0e0f [fix] solution: 온보딩 생성·크롤링 진단·장애 알림 묶음
운영 번들 자동 로그인 자격증명 유출, 온보딩 COPY 잡이 Gemini 429 로 죽던 것,
크롤링 실패가 로그에만 남던 것을 한 번에 정리한다. 실측(2026-09-15 밤, 킹서버):
사진분석 배치가 Gemini 분당 쿼터를 다 써서 같은 키를 쓰는 온보딩 COPY 잡도 같이
429 를 맞고 DEAD 로 갔다 — 확인된 fact 만으로도 편집·발행이 되는데 잡을 죽일
이유가 없었다.

- solution/frontend: `VITE_AUTO_LOGIN_ID`·`PW` 를 운영 진입점에 안 넘긴다(자동 로그인은
  dev 서버 전용) + `Step5Generating` 겉모습을 이전 카드 스타일로, 데이터는 실제 잡
  진행(useGenerationJob) 그대로
- solution/backend: copy_service — Gemini 호출 실패해도 잡을 안 죽이고 fact 만으로 계속.
  db_session_manager — 유니크 제약 충돌(정상 경로) 로그를 ERROR → WARN.
  worker/runner + alert_service + teams_webhook — 잡 dead-letter·발행 실패·큐 정체를
  Teams 로 알림(영구 저장 + 재시도 + dedupe). `/readyz` 추가.
  collect_diagnostics(신규) — 크롤링 채널별 실패를 jobs.result 에 구조화해서 싣는다.
- postgres-init: 0015(users token_version) · 0016(alert_outbox) 마이그레이션

검증: 백엔드 pytest 759 passed. tsc(solution/frontend) 통과. Teams 알림 실채널 수신 확인.
2026-09-16 16:25:02 +09:00

19 lines
1.4 KiB
SQL

-- 0015 · users.token_version — refresh 토큰 무효화 키
--
-- ★ 왜 필요한가 (보안 점검, 2026-09-15)
-- auth_service.refresh_token() 은 지금까지 refresh 토큰을 서명만 검증하고 그 안의 sub
-- (user_id·id·role)를 그대로 새 access 토큰에 옮겨 찍었다 — DB 를 한 번도 보지 않았다.
-- 비밀번호를 바꾸거나(다른 기기의 세션을 끊고 싶을 때) 계정을 차단해도, 이미 발급된
-- refresh 토큰(7일)을 쥔 클라이언트는 만료 전까지 계속 새 access 토큰을 받을 수 있었다.
-- token_version 을 JWT 의 sub 에 같이 싣고 refresh 할 때 DB 의 지금 값과 대조하면,
-- bump_token_version() 을 부른 시점 이후의 refresh 시도는 전부 거절된다.
--
-- ★ 옛 토큰(token_version 없이 발급된 것)도 읽힌다 — UserInfo 가 기본값 1 을 먼저 깔고
-- 그 위에 없는 키는 안 덮으므로(common/models/gmodel.py UserInfo.__init__), 새 컬럼의
-- DEFAULT 1 과 맞아떨어진다. 배포 순간 전원 강제 로그아웃이 되지 않는다.
ALTER TABLE public.users ADD COLUMN IF NOT EXISTS token_version SMALLINT NOT NULL DEFAULT 1;
COMMENT ON COLUMN public.users.token_version IS
'refresh 토큰 무효화 키. JWT(access·refresh)의 sub 에 실려 나간다 — 이 값을 올리면(bump_token_version) 그 전에 발급된 refresh 토큰은 다음 재발급에서 전부 거절된다.';