summaryrefslogtreecommitdiff
path: root/app/views
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/views
parentcd36a46bbb5679ded1653f65bf4e8a74ae55100c (diff)
Add self-service TOTP enrollment UI and witnessed admin reset
Diffstat (limited to 'app/views')
-rw-r--r--app/views/otp_enrollments/show.html.erb23
-rw-r--r--app/views/users/edit.html.erb30
2 files changed, 53 insertions, 0 deletions
diff --git a/app/views/otp_enrollments/show.html.erb b/app/views/otp_enrollments/show.html.erb
new file mode 100644
index 00000000..9dfa3422
--- /dev/null
+++ b/app/views/otp_enrollments/show.html.erb
@@ -0,0 +1,23 @@
1<h1>Enable second factor</h1>
2
3<div id="page_editor">
4 <div class="node_description">Scan</div>
5 <div class="node_content">
6 <%= raw RQRCode::QRCode.new(current_user.pending_otp_provisioning_uri)
7 .as_svg(:module_size => 4, :viewbox => true,
8 :color => "000", :fill => "fff") %>
9 <span class="field_hint">Or enter the secret manually:
10 <code><%= current_user.otp_pending_secret %></code></span>
11 </div>
12
13 <div class="node_description">Confirm</div>
14 <div class="node_content">
15 <%= form_tag otp_enrollment_path, :method => :put do %>
16 <%= text_field_tag :code, nil, :autofocus => true,
17 :autocomplete => "one-time-code", :inputmode => "numeric" %>
18 <%= submit_tag "Confirm" %>
19 <% end %>
20 <span class="field_hint">Enter the six-digit code your app shows
21 for “<%= OTP_ISSUER %>”.</span>
22 </div>
23</div>
diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb
index 77b33c6a..8d14a058 100644
--- a/app/views/users/edit.html.erb
+++ b/app/views/users/edit.html.erb
@@ -30,4 +30,34 @@
30 <div class="node_content"><%= f.submit "Update" %></div> 30 <div class="node_content"><%= f.submit "Update" %></div>
31 <% end %> 31 <% end %>
32 </div> 32 </div>
33
34 <% if @user == current_user %>
35 <div class="node_description">Second factor</div>
36 <div class="node_content">
37 <% if current_user.otp_enrolled? %>
38 <p>Enabled.</p>
39 <%= form_tag otp_enrollment_path, :method => :delete do %>
40 <%= password_field_tag :current_password, nil, :placeholder => "Current password" %>
41 <%= text_field_tag :code, nil, :placeholder => "Current code",
42 :autocomplete => "one-time-code", :inputmode => "numeric" %>
43 <%= submit_tag "Disable second factor" %>
44 <% end %>
45 <% else %>
46 <p>Not enrolled.</p>
47 <%= form_tag otp_enrollment_path, :method => :post do %>
48 <%= password_field_tag :current_password, nil, :placeholder => "Current password" %>
49 <%= submit_tag "Enable second factor" %>
50 <% end %>
51 <% end %>
52 </div>
53 <% elsif current_user.admin? && @user.otp_enrolled? %>
54 <div class="node_description">Second factor</div>
55 <div class="node_content">
56 Enabled.
57 <%= button_to "Reset second factor", reset_otp_user_path(@user), :method => :put,
58 :form_class => "button_to destructive",
59 :form => { :data => { :confirm =>
60 "Reset #{@user.login}'s second factor? They will log in with password only afterwards." } } %>
61 </div>
62 <% end %>
33</div> 63</div>