Enforce account-active status across the auth lifecycle
active? was only checked at the password step of sign-in. A user disabled afterwards could (a) still complete the 2FA step and mint a valid session, and (b) keep using any existing session until natural expiry, because per-request auth only checked session expiry, not user status. Three enforcement points: - Mid-flow guard: verify_totp and webauthn_verify re-check active? before start_new_session_for, clearing the pending session and rejecting if disabled. - Request-time guard: find_session_by_cookie now uses Session.for_active_user, so a session whose user is disabled no longer authenticates (authoritative, catches any disable path including direct DB changes). - Immediate cleanup: User#revoke_sessions_when_deactivated destroys a user's sessions when status changes away from active, so access is revoked everywhere at once rather than on the next request. Tests cover the mid-flow TOTP rejection, request-time rejection of an existing session after disable, session destruction on disable, and that unrelated updates leave sessions intact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,49 @@
|
||||
require "test_helper"
|
||||
|
||||
class SessionSecurityTest < ActionDispatch::IntegrationTest
|
||||
# ====================
|
||||
# ACCOUNT DEACTIVATION TESTS
|
||||
# ====================
|
||||
|
||||
test "TOTP verification rejects a user disabled mid-flow" do
|
||||
user = User.create!(email_address: "midflow_totp@example.com", password: "password123")
|
||||
user.enable_totp!
|
||||
code = ROTP::TOTP.new(user.totp_secret).now
|
||||
|
||||
# Phase A: password step stashes the pending 2FA user
|
||||
post signin_path, params: {email_address: "midflow_totp@example.com", password: "password123"}
|
||||
assert_redirected_to totp_verification_path
|
||||
|
||||
# Admin disables the account while the user is on the 2FA screen
|
||||
user.update!(status: :disabled)
|
||||
|
||||
# Phase B: completing TOTP must NOT create a session
|
||||
post totp_verification_path, params: {code: code}
|
||||
assert_redirected_to signin_path
|
||||
assert_equal 0, user.reload.sessions.count
|
||||
|
||||
user.destroy
|
||||
end
|
||||
|
||||
test "an existing session stops authenticating once the user is disabled" do
|
||||
user = User.create!(email_address: "disabled_session@example.com", password: "password123")
|
||||
sign_in_as(user)
|
||||
|
||||
get root_path
|
||||
assert_response :success
|
||||
|
||||
# Disable bypassing the destroy callback to isolate the request-time lookup
|
||||
# guard (find_session_by_cookie filtering on active users).
|
||||
user.update_column(:status, User.statuses[:disabled])
|
||||
|
||||
get root_path
|
||||
assert_response :redirect
|
||||
assert_match %r{/signin}, response.location
|
||||
|
||||
user.sessions.delete_all
|
||||
user.destroy
|
||||
end
|
||||
|
||||
# ====================
|
||||
# SESSION TIMEOUT TESTS
|
||||
# ====================
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
require "test_helper"
|
||||
|
||||
class UserTest < ActiveSupport::TestCase
|
||||
test "disabling a user destroys their active sessions" do
|
||||
user = User.create!(email_address: "disable_sessions@example.com", password: "password123")
|
||||
user.sessions.create!
|
||||
user.sessions.create!
|
||||
assert_equal 2, user.sessions.count
|
||||
|
||||
user.update!(status: :disabled)
|
||||
|
||||
assert_equal 0, user.reload.sessions.count
|
||||
end
|
||||
|
||||
test "reactivating or other updates do not destroy sessions" do
|
||||
user = User.create!(email_address: "keep_sessions@example.com", password: "password123")
|
||||
user.sessions.create!
|
||||
|
||||
# An update that does not change status must leave sessions intact.
|
||||
user.update!(username: "keepsessions")
|
||||
|
||||
assert_equal 1, user.reload.sessions.count
|
||||
end
|
||||
|
||||
test "downcases and strips email_address" do
|
||||
user = User.new(email_address: " DOWNCASED@EXAMPLE.COM ")
|
||||
assert_equal("downcased@example.com", user.email_address)
|
||||
|
||||
Reference in New Issue
Block a user