summaryrefslogtreecommitdiff
path: root/app/controllers/otp_enrollments_controller.rb
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-24 13:52:56 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-24 13:52:56 +0200
commitfefec929c59c72dc93e4be30e8f23cd8c5258b0a (patch)
treee35d2e050a9052384e52a5ccb623d8dd193c2370 /app/controllers/otp_enrollments_controller.rb
parentcd36a46bbb5679ded1653f65bf4e8a74ae55100c (diff)
Add self-service TOTP enrollment UI and witnessed admin reset
Diffstat (limited to 'app/controllers/otp_enrollments_controller.rb')
-rw-r--r--app/controllers/otp_enrollments_controller.rb48
1 files changed, 48 insertions, 0 deletions
diff --git a/app/controllers/otp_enrollments_controller.rb b/app/controllers/otp_enrollments_controller.rb
new file mode 100644
index 00000000..7a31d1e1
--- /dev/null
+++ b/app/controllers/otp_enrollments_controller.rb
@@ -0,0 +1,48 @@
1# Self-service TOTP enrollment, deliberately scoped to current_user only:
2# an administrator must never hold another account's secret -- admins get
3# the witnessed reset on the user page instead.
4class OtpEnrollmentsController < ApplicationController
5 before_action :login_required
6
7 layout 'admin'
8
9 # QR plus confirmation form; only meaningful while a pending secret exists.
10 def show
11 redirect_to edit_user_path(current_user) if current_user.otp_pending_secret.blank?
12 end
13
14 # Begins (or restarts) enrollment. Requires the current password so an
15 # unattended logged-in session cannot be enrolled onto a stranger's phone.
16 def create
17 unless User.authenticate(current_user.login, params[:current_password].to_s)
18 flash[:error] = "Wrong password."
19 return redirect_to edit_user_path(current_user)
20 end
21 current_user.begin_otp_enrollment!
22 redirect_to otp_enrollment_path
23 end
24
25 # Confirms with the first generated code.
26 def update
27 if current_user.confirm_otp_enrollment!(params[:code])
28 flash[:notice] = "Second factor enabled. The code you just entered is " \
29 "spent -- wait for the next one before logging in with it."
30 redirect_to edit_user_path(current_user)
31 else
32 flash.now[:error] = "That code did not match. Rescan or wait for the next code."
33 render :show
34 end
35 end
36
37 # Self-service disable: password AND a current code.
38 def destroy
39 unless User.authenticate(current_user.login, params[:current_password].to_s) &&
40 current_user.verify_otp!(params[:code])
41 flash[:error] = "Password or code wrong -- second factor unchanged."
42 return redirect_to edit_user_path(current_user)
43 end
44 current_user.disable_otp!(:actor => current_user)
45 flash[:notice] = "Second factor disabled."
46 redirect_to edit_user_path(current_user)
47 end
48end