fix: invalidate tokens on permission change (medium), optimize create_user query, fix ws reconnect race
- update_permissions now calls blacklist_active_refresh_token and rotate_user_secret to immediately invalidate stale tokens - create_user uses returned id from tx.run_one instead of redundant SELECT - websocket reconnect explicitly closes old connection after token refresh to prevent onclose handler race condition
This commit is contained in:
+8
-4
@@ -160,13 +160,13 @@ def create_user(
|
||||
|
||||
db = get_db()
|
||||
with db.in_transaction() as tx:
|
||||
tx.run_one(Q_INSERT_USER, (username, password_hash, jwt_secret))
|
||||
user_id = tx.run_one(Q_INSERT_USER, (username, password_hash, jwt_secret))
|
||||
if permissions:
|
||||
for subsystem, level in permissions.items():
|
||||
tx.run(Q_UPSERT_PERMISSION, (username, subsystem, level))
|
||||
|
||||
return {
|
||||
"id": db.query(Q_SELECT_USER_BY_NAME, (username,))[0]["id"],
|
||||
"id": user_id,
|
||||
"username": username,
|
||||
"permissions": _get_permissions(username),
|
||||
}
|
||||
@@ -202,9 +202,11 @@ def update_password(username: str, old_password: str, new_password: str) -> bool
|
||||
|
||||
|
||||
def update_permissions(username: str, permissions: dict[str, str]) -> None:
|
||||
"""Update a user's permissions.
|
||||
"""Update a user's permissions and invalidate all existing tokens.
|
||||
|
||||
Replaces all existing permissions with the provided mapping.
|
||||
Replaces all existing permissions with the provided mapping. Rotates the
|
||||
JWT secret so that permission changes take effect immediately — existing
|
||||
tokens with stale permissions are no longer valid.
|
||||
|
||||
Args:
|
||||
username: The username.
|
||||
@@ -219,6 +221,8 @@ def update_permissions(username: str, permissions: dict[str, str]) -> None:
|
||||
tx.run(Q_DELETE_PERMISSIONS, (username,))
|
||||
for subsystem, level in permissions.items():
|
||||
tx.run(Q_UPSERT_PERMISSION, (username, subsystem, level))
|
||||
blacklist_active_refresh_token(username)
|
||||
rotate_user_secret(username)
|
||||
|
||||
|
||||
def list_users() -> list[dict[str, Any]]:
|
||||
|
||||
@@ -59,12 +59,14 @@ function _wsConnect() {
|
||||
|
||||
if (_wsFailCount >= 3 && !_wsRefreshing) {
|
||||
_wsRefreshing = true;
|
||||
const oldConn = _wsConn;
|
||||
tryRefreshToken().then(ok => {
|
||||
_wsRefreshing = false;
|
||||
if (ok) {
|
||||
_wsFailCount = 0;
|
||||
_wsReconnectMs = 0;
|
||||
_wsConn = null;
|
||||
if (oldConn) oldConn.close();
|
||||
setTimeout(_wsConnect, 100);
|
||||
} else {
|
||||
redirectLogin();
|
||||
|
||||
Reference in New Issue
Block a user