74876b674c
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 확보.
25 lines
979 B
Python
25 lines
979 B
Python
"""users 테이블 ORM"""
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import BigInteger, Boolean, DateTime, String, Text
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from core.database import Base
|
|
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
|
username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
|
|
password_hash: Mapped[str] = mapped_column(Text, nullable=False)
|
|
totp_secret: Mapped[str | None] = mapped_column(String(64))
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
is_admin: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=datetime.now
|
|
)
|
|
last_login_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
password_changed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|