fix(files): ending every session and making a token lock the user without holding up rows that refer to it

This commit is contained in:
2026-10-15 06:05:51 +02:00
parent a248ca6c13
commit 27427eef32
4 changed files with 81 additions and 8 deletions
+4 -2
View File
@@ -1150,12 +1150,14 @@ def bump_session_epoch(db: Session, user: User) -> None:
outlive the answer to that theft. Not committed — the caller commits it with its change.
The user's row is locked before the tokens go, the lock `create_token` takes too: a token made at
the same moment either commits first and is deleted here, or waits and sees the new epoch."""
the same moment either commits first and is deleted here, or waits and sees the new epoch. FOR NO
KEY UPDATE (`key_share`): the two still exclude each other, but a row that only refers to the user
(a notice, an audit line) is not held up — a token's expiry notice would deadlock with it."""
if user.id is None:
user.session_epoch = (user.session_epoch or 0) + 1
return
epoch = db.execute(
select(User.session_epoch).where(User.id == user.id).with_for_update()
select(User.session_epoch).where(User.id == user.id).with_for_update(key_share=True)
).scalar_one()
user.session_epoch = (epoch or 0) + 1
apitokens.revoke_all(db, user.id)
+3 -2
View File
@@ -122,9 +122,10 @@ def create_token(
raise refused(400, "api_token_name_required")
# The user's row, locked: two requests at once cannot both slip under the ceiling, and a
# session ended since `current_user` read the epoch (unlocked) cannot mint a token that outlives
# it — `bump_session_epoch` takes this lock before it revokes them all.
# it — `bump_session_epoch` takes this lock before it revokes them all. No key update: a row that
# only refers to the user (a folder delete's audit line) must not wait on it, or the two deadlock.
epoch = db.execute(
select(User.session_epoch).where(User.id == user.id).with_for_update()
select(User.session_epoch).where(User.id == user.id).with_for_update(key_share=True)
).scalar_one()
if (epoch or 0) != (user.session_epoch or 0):
raise refused(401, "not_authenticated")