fix: log disabled users out and keep them out #34

Merged
jfro merged 2 commits from jtk/deactivated-users into main 2026-10-18 17:19:03 +00:00
Owner

Closes #10 (medium).

Bug: disabling a user only blocked new password and magic-link logins. Their existing sessions and remember-me cookies kept working, because fetch_current_scope_for_user and the LiveViews' session lookups never looked at status. The OIDC callback logged disabled users in too.

Fix

  • UserToken.verify_session_token_query/1 only matches active users. Every session lookup goes through Accounts.get_user_by_session_token/1: the auth plug, remember-me cookies, and the admin LiveViews that read session["user_token"]. So a disabled user is logged out on their next request.
  • Accounts.update_user_by_admin/2 deletes all of a user's tokens when their status changes to disabled (same transaction helper as password changes), and logs the count.
  • The OIDC callback checks Accounts.active?/1 before log_in_user and shows the same "account has been disabled" flash as the other login paths, with a Logger.warning (user id).

Tests: disabled users aren't returned for a session token; disabling deletes tokens, while role changes and invalid updates keep them; the plug refuses a disabled user from both the session and a remember-me cookie. The OIDC branch has no controller test (it needs a signed id_token mock, same gap as #21), but the session filter blocks it on the next request anyway.

Not covered: a LiveView that's already connected stays up until it reconnects, since sessions don't set a live_socket_id to broadcast a disconnect to.

mix precommit: 475 passed, 4 skipped.

Closes #10 (medium). **Bug:** disabling a user only blocked new password and magic-link logins. Their existing sessions and remember-me cookies kept working, because `fetch_current_scope_for_user` and the LiveViews' session lookups never looked at `status`. The OIDC callback logged disabled users in too. **Fix** - `UserToken.verify_session_token_query/1` only matches active users. Every session lookup goes through `Accounts.get_user_by_session_token/1`: the auth plug, remember-me cookies, and the admin LiveViews that read `session["user_token"]`. So a disabled user is logged out on their next request. - `Accounts.update_user_by_admin/2` deletes all of a user's tokens when their status changes to disabled (same transaction helper as password changes), and logs the count. - The OIDC callback checks `Accounts.active?/1` before `log_in_user` and shows the same "account has been disabled" flash as the other login paths, with a `Logger.warning` (user id). **Tests:** disabled users aren't returned for a session token; disabling deletes tokens, while role changes and invalid updates keep them; the plug refuses a disabled user from both the session and a remember-me cookie. The OIDC branch has no controller test (it needs a signed id_token mock, same gap as #21), but the session filter blocks it on the next request anyway. **Not covered:** a LiveView that's already connected stays up until it reconnects, since sessions don't set a `live_socket_id` to broadcast a disconnect to. `mix precommit`: 475 passed, 4 skipped.
fix: log disabled users out and keep them out
All checks were successful
CI / test (pull_request) Successful in 56s
5731e34844
A disabled user's existing sessions and remember-me cookies kept working,
and OIDC logins never checked the account status.

- Session token lookup only returns active users, which covers the plug
  and every LiveView that reads the session token.
- Disabling a user in admin deletes all their tokens.
- The OIDC callback refuses disabled users like password and magic-link
  logins do.

Closes #10
jfro left a comment

Review from Nye: APPROVE (posted by Upper Wolfjaw, since Forgejo won't let jfro approve its own PR)

The verify_session_token_query filter on user.status == "active" is safe: status is NOT NULL DEFAULT 'active' (migration 20250816192636), so no existing user gets logged out. update_user_by_admin deletes tokens only when status changes to disabled, inside a transaction. Password, magic-link and OIDC logins all check active?.

Non-blocking follow-up (filed separately as low): connected LiveViews aren't disconnected on disable, because log_in_user never puts :live_socket_id in the session, so the disconnect broadcast in user_auth.ex never fires.

**Review from Nye: APPROVE** (posted by Upper Wolfjaw, since Forgejo won't let jfro approve its own PR) The `verify_session_token_query` filter on `user.status == "active"` is safe: status is `NOT NULL DEFAULT 'active'` (migration 20250816192636), so no existing user gets logged out. `update_user_by_admin` deletes tokens only when status changes to disabled, inside a transaction. Password, magic-link and OIDC logins all check `active?`. Non-blocking follow-up (filed separately as low): connected LiveViews aren't disconnected on disable, because log_in_user never puts `:live_socket_id` in the session, so the disconnect broadcast in user_auth.ex never fires.
Merge remote-tracking branch 'origin/main' into jtk/deactivated-users
All checks were successful
CI / test (pull_request) Successful in 1m0s
fd234bafff
jfro merged commit 4104ba439a into main 2026-10-18 17:19:03 +00:00
jfro deleted branch jtk/deactivated-users 2026-10-18 17:19:03 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jfro/fuzzy_catalog!34
No description provided.