From fefec929c59c72dc93e4be30e8f23cd8c5258b0a Mon Sep 17 00:00:00 2001 From: erdgeist Date: Fri, 24 Jul 2026 13:52:56 +0200 Subject: Add self-service TOTP enrollment UI and witnessed admin reset --- .../controllers/otp_enrollments_controller_test.rb | 41 ++++++++++++++++++++++ test/controllers/users_controller_test.rb | 14 +++++++- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 test/controllers/otp_enrollments_controller_test.rb (limited to 'test') diff --git a/test/controllers/otp_enrollments_controller_test.rb b/test/controllers/otp_enrollments_controller_test.rb new file mode 100644 index 00000000..3bfab8e3 --- /dev/null +++ b/test/controllers/otp_enrollments_controller_test.rb @@ -0,0 +1,41 @@ +require "test_helper" + +class OtpEnrollmentsControllerTest < ActionController::TestCase + include AuthenticatedTestHelper + fixtures :users + + def setup + login_as :quentin + @user = users(:quentin) + User.authenticate("quentin", "monkey") # ensure digest is migrated + end + + test "create requires the current password" do + post :create, params: { :current_password => "wrong" } + assert_nil @user.reload.otp_pending_secret + end + + test "create with the password begins enrollment" do + post :create, params: { :current_password => "monkey" } + assert @user.reload.otp_pending_secret.present? + assert_redirected_to otp_enrollment_path + end + + test "update with the first code completes enrollment" do + @user.begin_otp_enrollment! + code = ROTP::TOTP.new(@user.reload.otp_pending_secret).now + put :update, params: { :code => code } + assert @user.reload.otp_enrolled? + end + + test "destroy needs password and a current code" do + @user.update!(:otp_secret => ROTP::Base32.random) + + delete :destroy, params: { :current_password => "monkey", :code => "000000" } + assert @user.reload.otp_enrolled? + + code = ROTP::TOTP.new(@user.otp_secret).now + delete :destroy, params: { :current_password => "monkey", :code => code } + assert_not @user.reload.otp_enrolled? + end +end diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index 58f8a86b..946b5414 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -182,5 +182,17 @@ class UsersControllerTest < ActionController::TestCase assert_equal false, user.is_admin? end - + test "reset_otp is admin-only and witnessed" do + user = users(:quentin) + user.update!(:otp_secret => ROTP::Base32.random) + + login_as :quentin + put :reset_otp, params: { :id => user.id } + assert user.reload.otp_enrolled?, "non-admin must be refused" + + login_as :aaron + put :reset_otp, params: { :id => user.id } + assert_not user.reload.otp_enrolled? + assert_equal "otp_reset", NodeAction.last.action + end end -- cgit v1.3