fix: prevent data loss in update_permissions and token_refresh
- update_permissions: swap to upsert-first-then-delete-stale so a failed upsert mid-loop rolls back cleanly, leaving the user's permissions intact. Adds Q_DELETE_PERMISSION_SUBSYSTEM for targeted removal. - auth_refresh: generate and persist the new refresh token before blacklisting/clearing the old one, so a failure in generate_tokens doesn't leave the user locked out with no valid refresh token.
This commit is contained in:
+12
-2
@@ -16,7 +16,7 @@ from lib.auth import (
|
||||
rotate_user_secret,
|
||||
)
|
||||
from lib.db import (
|
||||
Q_DELETE_PERMISSIONS,
|
||||
Q_DELETE_PERMISSION_SUBSYSTEM,
|
||||
Q_DELETE_USER,
|
||||
Q_INSERT_USER,
|
||||
Q_SELECT_PERMISSIONS,
|
||||
@@ -230,9 +230,19 @@ def update_permissions(username: str, permissions: dict[str, str]) -> None:
|
||||
|
||||
db = get_db()
|
||||
with db.in_transaction() as tx:
|
||||
tx.run(Q_DELETE_PERMISSIONS, (username,))
|
||||
# Upsert all new permissions first, then remove stale ones.
|
||||
# This order ensures that if an upsert fails mid-loop, the user's
|
||||
# permissions remain intact (transaction rolls back) rather than
|
||||
# being permanently wiped.
|
||||
existing = {
|
||||
row["subsystem"]: row["level"]
|
||||
for row in db.query(Q_SELECT_PERMISSIONS, (username,))
|
||||
}
|
||||
for subsystem, level in permissions.items():
|
||||
tx.run(Q_UPSERT_PERMISSION, (username, subsystem, level))
|
||||
for subsystem in existing:
|
||||
if subsystem not in permissions:
|
||||
tx.run(Q_DELETE_PERMISSION_SUBSYSTEM, (username, subsystem))
|
||||
blacklist_active_refresh_token(username)
|
||||
rotate_user_secret(username)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user