Session invalidation on credential change
Add session_version to users table. Sessions now carry session_version alongside user_id; @login_required rejects any session where the version doesn't match the DB value. - migration 0024: session_version INTEGER NOT NULL DEFAULT 1 - models/user.py: session_version Mapped[int] column - auth.py: version mismatch → session.clear() + 401 - services/auth.py: change_password and reset_password_with_token both increment session_version, evicting all other live sessions - routes/auth.py: login/register/oauth_callback store session_version; update_password rehydrates current session with new version so the user who changed their password stays logged in Existing sessions will require one re-login after the migration runs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -109,18 +109,19 @@ async def get_user_by_username(username: str) -> User | None:
|
||||
return result.scalars().first()
|
||||
|
||||
|
||||
async def change_password(user_id: int, current_password: str, new_password: str) -> bool:
|
||||
"""Change a user's password. Returns True on success, False if current password is wrong."""
|
||||
async def change_password(user_id: int, current_password: str, new_password: str) -> int | None:
|
||||
"""Change a user's password. Returns new session_version on success, None if current password is wrong."""
|
||||
async with async_session() as session:
|
||||
user = await session.get(User, user_id)
|
||||
if not user:
|
||||
return False
|
||||
return None
|
||||
if not verify_password(current_password, user.password_hash):
|
||||
return False
|
||||
return None
|
||||
user.password_hash = hash_password(new_password)
|
||||
user.session_version += 1
|
||||
await session.commit()
|
||||
logger.info("Password changed for user %d (%s)", user_id, user.username)
|
||||
return True
|
||||
return user.session_version
|
||||
|
||||
|
||||
async def is_registration_open() -> bool:
|
||||
@@ -249,6 +250,7 @@ async def reset_password_with_token(raw_token: str, new_password: str) -> int |
|
||||
return None
|
||||
|
||||
user.password_hash = hash_password(new_password)
|
||||
user.session_version += 1
|
||||
reset_token.used = True
|
||||
await session.commit()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user