feat(auth): JWT iat + users.password_changed_at invalidation (PR-Docsrv-JWT-Invalidation-1)
PR-Infra-Sec-1H Phase 0 audit 에서 DS jwt invalidation 정책 부재 확정. password rotation 으로 구 365d JWT (voice-memo-bot 등) invalidate 안 되는 hard gate STOP 진입 → 선행 PR 분리. - migration 269: users.password_changed_at timestamptz NULL (legacy 호환) - create_access_token / create_refresh_token: payload 에 iat (int 초) 추가 - verify_password_changed_at helper: int(password_changed_at.timestamp()) > int(iat) 시 401 - get_current_user + refresh_token route: verify helper 호출 - change_password / setup signup / seed_admin INSERT+UPDATE: password_changed_at 갱신 NULL = 검증 skip (migration 직후 운영 영향 0). 첫 password 변경 후만 iat 검증 활성. Sec-1H 의 G-token-old hard gate 통과 path 확보.
This commit is contained in:
@@ -19,6 +19,7 @@ from core.auth import (
|
||||
create_voice_memo_bot_token,
|
||||
decode_token,
|
||||
get_current_user,
|
||||
verify_password_changed_at,
|
||||
hash_password,
|
||||
verify_password,
|
||||
verify_totp,
|
||||
@@ -161,6 +162,7 @@ async def refresh_token(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="유저를 찾을 수 없음",
|
||||
)
|
||||
verify_password_changed_at(payload, user)
|
||||
|
||||
# 새 refresh token → cookie
|
||||
_set_refresh_cookie(response, create_refresh_token(user.username))
|
||||
@@ -203,5 +205,6 @@ async def change_password(
|
||||
)
|
||||
|
||||
user.password_hash = hash_password(body.new_password)
|
||||
user.password_changed_at = datetime.now(timezone.utc)
|
||||
await session.commit()
|
||||
return {"message": "비밀번호가 변경되었습니다"}
|
||||
|
||||
Reference in New Issue
Block a user