feat(07.3-03): backend remember_me — TTL split + cookie Max-Age + 3 promoted tests
- services/auth.py: create_refresh_token gains remember_me=False param; selects 16h or 30d TTL (D-09, D-10, D-11) - api/auth.py: LoginRequest.remember_me bool field; _set_refresh_cookie remember_me param for conditional max_age; login handler threads remember_me through both calls (D-11, RM-03) - test_auth_es256.py: promote RM-01, RM-02, RM-03 stubs — all 9 phase tests now PASSED
This commit is contained in:
@@ -157,20 +157,30 @@ def decode_password_reset_token(token: str) -> str:
|
||||
|
||||
# ── Refresh token lifecycle ─────────────────────────────────────────────────────
|
||||
|
||||
async def create_refresh_token(session: AsyncSession, user_id: uuid.UUID) -> str:
|
||||
async def create_refresh_token(
|
||||
session: AsyncSession, user_id: uuid.UUID, remember_me: bool = False
|
||||
) -> str:
|
||||
"""Insert a new RefreshToken row and return the raw (unhashed) token string.
|
||||
|
||||
The raw token is returned to the caller and set as an httpOnly cookie.
|
||||
Only the SHA-256 hash is stored in the database.
|
||||
|
||||
remember_me=False (default): TTL = refresh_token_expire_hours (16h short session, D-09, D-10)
|
||||
remember_me=True: TTL = refresh_token_expire_days (30d extended session, D-11)
|
||||
"""
|
||||
raw = secrets.token_urlsafe(32)
|
||||
token_hash = hashlib.sha256(raw.encode()).hexdigest()
|
||||
now = datetime.now(timezone.utc)
|
||||
ttl = (
|
||||
timedelta(days=settings.refresh_token_expire_days)
|
||||
if remember_me
|
||||
else timedelta(hours=settings.refresh_token_expire_hours)
|
||||
)
|
||||
row = RefreshToken(
|
||||
id=uuid.uuid4(),
|
||||
user_id=user_id,
|
||||
token_hash=token_hash,
|
||||
expires_at=now + timedelta(days=settings.refresh_token_expire_days),
|
||||
expires_at=now + ttl,
|
||||
revoked=False,
|
||||
)
|
||||
session.add(row)
|
||||
|
||||
Reference in New Issue
Block a user