summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-24 22:08:05 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-24 22:08:05 +0200
commit90207029de7474082310d0725801d60f7145fa98 (patch)
tree97b0c54fcd004ff221265e479f2602f8e3a5b093
parent215279dfb003163a7938ba948082363c105e888f (diff)
Extract admin strings to i18n: sessions, otp, users/edit
-rw-r--r--app/views/otp_challenges/new.html.erb8
-rw-r--r--app/views/otp_enrollments/show.html.erb11
-rw-r--r--app/views/sessions/new.html.erb6
-rw-r--r--app/views/users/edit.html.erb39
-rw-r--r--config/application.rb2
-rw-r--r--config/locales/de.yml41
-rw-r--r--config/locales/en.yml41
-rw-r--r--test/controllers/users_controller_test.rb7
8 files changed, 121 insertions, 34 deletions
diff --git a/app/views/otp_challenges/new.html.erb b/app/views/otp_challenges/new.html.erb
index d6e18c85..677244c7 100644
--- a/app/views/otp_challenges/new.html.erb
+++ b/app/views/otp_challenges/new.html.erb
@@ -1,13 +1,13 @@
1<h1>Second factor</h1> 1<h1><%= t(".title") %></h1>
2 2
3<div id="admin_layout"> 3<div id="admin_layout">
4 <div class="layout_row_label">Code</div> 4 <div class="layout_row_label"> <%= t(".code") %> </div>
5 <div class="layout_row_content"> 5 <div class="layout_row_content">
6 <%= form_tag otp_challenge_path, :method => :post, :class => "otp_form" do %> 6 <%= form_tag otp_challenge_path, :method => :post, :class => "otp_form" do %>
7 <%= text_field_tag :code, nil, :autofocus => true, 7 <%= text_field_tag :code, nil, :autofocus => true,
8 :autocomplete => "one-time-code", :inputmode => "numeric" %> 8 :autocomplete => "one-time-code", :inputmode => "numeric" %>
9 <%= submit_tag "Log in" %> 9 <%= submit_tag t(".log_in") %>
10 <% end %> 10 <% end %>
11 <span class="field_hint">Enter the six-digit code from your authenticator app.</span> 11 <span class="field_hint"> <%= t(".hint") %> </span>
12 </div> 12 </div>
13</div> 13</div>
diff --git a/app/views/otp_enrollments/show.html.erb b/app/views/otp_enrollments/show.html.erb
index 12067ae3..e18d7fd1 100644
--- a/app/views/otp_enrollments/show.html.erb
+++ b/app/views/otp_enrollments/show.html.erb
@@ -1,14 +1,14 @@
1<h1>Enable second factor</h1> 1<h1><%= t(".title") %></h1>
2 2
3<div id="admin_layout"> 3<div id="admin_layout">
4 <div class="layout_row_label">Scan</div> 4 <div class="layout_row_label"><%= t(".scan") %></div>
5 <div class="layout_row_content"> 5 <div class="layout_row_content">
6 <div class="otp_qr"> 6 <div class="otp_qr">
7 <%= raw RQRCode::QRCode.new(current_user.pending_otp_provisioning_uri) 7 <%= raw RQRCode::QRCode.new(current_user.pending_otp_provisioning_uri)
8 .as_svg(:module_size => 4, :viewbox => true, 8 .as_svg(:module_size => 4, :viewbox => true,
9 :color => "000", :fill => "fff") %> 9 :color => "000", :fill => "fff") %>
10 </div> 10 </div>
11 <span class="field_hint">Or enter the secret manually: <code><%= current_user.otp_pending_secret %></code></span> 11 <span class="field_hint"><%= t(".manual_hint") %> <code><%= current_user.otp_pending_secret %></code></span>
12 </div> 12 </div>
13 13
14 <div class="layout_row_label">Confirm</div> 14 <div class="layout_row_label">Confirm</div>
@@ -17,10 +17,9 @@
17 <%= text_field_tag :code, nil, :autofocus => true, 17 <%= text_field_tag :code, nil, :autofocus => true,
18 :autocomplete => "one-time-code", :inputmode => "numeric" %> 18 :autocomplete => "one-time-code", :inputmode => "numeric" %>
19 <%= button_tag :type => "submit", :class => "action_button" do %> 19 <%= button_tag :type => "submit", :class => "action_button" do %>
20 <%= icon("shield-check", library: "tabler", "aria-hidden": true) %> Confirm 20 <%= icon("shield-check", library: "tabler", "aria-hidden": true) %> <%= t(".confirm") %>
21 <% end %> 21 <% end %>
22 <% end %> 22 <% end %>
23 <span class="field_hint">Enter the six-digit code your app shows 23 <span class="field_hint"> <%= t(".app_hint", :issuer => OTP_ISSUER) %></span>
24 for “<%= OTP_ISSUER %>”.</span>
25 </div> 24 </div>
26</div> 25</div>
diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb
index 1c2c2368..3f115d58 100644
--- a/app/views/sessions/new.html.erb
+++ b/app/views/sessions/new.html.erb
@@ -4,12 +4,12 @@
4 <div class="error_messages"><%= flash[:error] %></div> 4 <div class="error_messages"><%= flash[:error] %></div>
5 <% end %> 5 <% end %>
6 6
7 <div class="layout_row_label">Login</div> 7 <div class="layout_row_label"> <%= t(".login") %> </div>
8 <div class="layout_row_content"><%= text_field_tag 'login', @login, :autocomplete => "username" %></div> 8 <div class="layout_row_content"><%= text_field_tag 'login', @login, :autocomplete => "username" %></div>
9 9
10 <div class="layout_row_label">Password</div> 10 <div class="layout_row_label"> <%= t(".password") %> </div>
11 <div class="layout_row_content"><%= password_field_tag 'password', nil, :autocomplete => "current-password" %></div> 11 <div class="layout_row_content"><%= password_field_tag 'password', nil, :autocomplete => "current-password" %></div>
12 12
13 <div class="layout_row_content"><%= submit_tag 'log in' %></div> 13 <div class="layout_row_content"><%= submit_tag t(".log_in") %></div>
14 <% end -%> 14 <% end -%>
15</div> 15</div>
diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb
index a8994701..ea5e7dec 100644
--- a/app/views/users/edit.html.erb
+++ b/app/views/users/edit.html.erb
@@ -1,4 +1,4 @@
1<h1><%= @user == current_user ? "My account" : "Edit user “#{@user.login}”" %></h1> 1<h1><%= @user == current_user ? t(".my_account") : t(".edit_user", :login => @user.login) %></h1>
2 2
3<% if @user.errors.any? %> 3<% if @user.errors.any? %>
4 <div class="error_messages"> 4 <div class="error_messages">
@@ -9,61 +9,60 @@
9<div id="admin_layout"> 9<div id="admin_layout">
10 <%= form_for @user do |f| %> 10 <%= form_for @user do |f| %>
11 <div id="content"> 11 <div id="content">
12 <div class="layout_row_label">Login</div> 12 <div class="layout_row_label"><%= t(".login") %></div>
13 <div class="layout_row_content"><%= f.text_field :login, :autocomplete => "username" %></div> 13 <div class="layout_row_content"><%= f.text_field :login, :autocomplete => "username" %></div>
14 14
15 <div class="layout_row_label">E-Mail</div> 15 <div class="layout_row_label"><%= t(".email") %></div>
16 <div class="layout_row_content"><%= f.text_field :email %></div> 16 <div class="layout_row_content"><%= f.text_field :email %></div>
17 17
18 <div class="layout_row_label">Password</div> 18 <div class="layout_row_label"><%= t(".password") %></div>
19 <div class="layout_row_content"><%= f.password_field :password, :autocomplete => "new-password" %></div> 19 <div class="layout_row_content"><%= f.password_field :password, :autocomplete => "new-password" %></div>
20 20
21 <div class="layout_row_label">Confirm</div> 21 <div class="layout_row_label"><%= t(".confirm") %></div>
22 <div class="layout_row_content"><%= f.password_field :password_confirmation, :autocomplete => "new-password" %></div> 22 <div class="layout_row_content"><%= f.password_field :password_confirmation, :autocomplete => "new-password" %></div>
23 23
24 <% if current_user.admin? %> 24 <% if current_user.admin? %>
25 <div class="layout_row_label">Admin?</div> 25 <div class="layout_row_label"><%= t(".admin") %></div>
26 <div class="layout_row_content"><%= f.check_box :admin %></div> 26 <div class="layout_row_content"><%= f.check_box :admin %></div>
27 <% end %> 27 <% end %>
28 28
29 <div class="layout_row_label"></div> 29 <div class="layout_row_label"></div>
30 <div class="layout_row_content"><%= f.submit "Update" %></div> 30 <div class="layout_row_content"><%= submit_tag t(".update") %></div>
31 <% end %> 31 <% end %>
32 </div> 32 </div>
33 33
34 <% if @user == current_user %> 34 <% if @user == current_user %>
35 <div class="layout_row_label">Second factor</div> 35 <div class="layout_row_label"><%= t(".second_factor") %></div>
36 <div class="layout_row_content"> 36 <div class="layout_row_content">
37 <% if current_user.otp_enrolled? %> 37 <% if current_user.otp_enrolled? %>
38 <p>Enabled.</p> 38 <p><%= t(".enabled") %></p>
39 <%= form_tag otp_enrollment_path, :method => :delete, :class => "otp_form" do %> 39 <%= form_tag otp_enrollment_path, :method => :delete, :class => "otp_form" do %>
40 <%= text_field_tag :login, current_user.login, :autocomplete => "username", :readonly => true, :tabindex => -1, :class => "visually_hidden" %> 40 <%= text_field_tag :login, current_user.login, :autocomplete => "username", :readonly => true, :tabindex => -1, :class => "visually_hidden" %>
41 <%= password_field_tag :current_password, nil, :placeholder => "Current password", :autocomplete => "current-password" %> 41 <%= password_field_tag :current_password, nil, :placeholder => t(".current_password"), :autocomplete => "current-password" %>
42 <%= text_field_tag :code, nil, :placeholder => "Current code", 42 <%= text_field_tag :code, nil, :placeholder => t(".current_code"),
43 :autocomplete => "one-time-code", :inputmode => "numeric" %> 43 :autocomplete => "one-time-code", :inputmode => "numeric" %>
44 <%= button_tag :type => "submit", :class => "action_button" do %> 44 <%= button_tag :type => "submit", :class => "action_button" do %>
45 <%= icon("shield-off", library: "tabler", "aria-hidden": true) %> Disable second factor 45 <%= icon("shield-off", library: "tabler", "aria-hidden": true) %> <%= t(".disable_second_factor") %>
46 <% end %> 46 <% end %>
47 <% end %> 47 <% end %>
48 <% else %> 48 <% else %>
49 <p>Not enrolled.</p> 49 <p><%= t(".not_enrolled") %></p>
50 <%= form_tag otp_enrollment_path, :method => :post, :class => "otp_form" do %> 50 <%= form_tag otp_enrollment_path, :method => :post, :class => "otp_form" do %>
51 <%= text_field_tag :login, current_user.login, :autocomplete => "username", :readonly => true, :tabindex => -1, :class => "visually_hidden" %> 51 <%= text_field_tag :login, current_user.login, :autocomplete => "username", :readonly => true, :tabindex => -1, :class => "visually_hidden" %>
52 <%= password_field_tag :current_password, nil, :placeholder => "Current password", :autocomplete => "current-password"%> 52 <%= password_field_tag :current_password, nil, :placeholder => t(".current_password"), :autocomplete => "current-password"%>
53 <%= button_tag :type => "submit", :class => "action_button" do %> 53 <%= button_tag :type => "submit", :class => "action_button" do %>
54 <%= icon("shield-lock", library: "tabler", "aria-hidden": true) %> Enable second factor 54 <%= icon("shield-lock", library: "tabler", "aria-hidden": true) %> <%= t(".enable_second_factor") %>
55 <% end %> 55 <% end %>
56 <% end %> 56 <% end %>
57 <% end %> 57 <% end %>
58 </div> 58 </div>
59 <% elsif current_user.admin? && @user.otp_enrolled? %> 59 <% elsif current_user.admin? && @user.otp_enrolled? %>
60 <div class="layout_row_label">Second factor</div> 60 <div class="layout_row_label"><%= t(".second_factor") %></div>
61 <div class="layout_row_content"> 61 <div class="layout_row_content">
62 Enabled. 62 <%= t(".enabled") %>
63 <%= button_to "Reset second factor", reset_otp_user_path(@user), :method => :put, 63 <%= button_to t(".reset_second_factor"), reset_otp_user_path(@user), :method => :put,
64 :form_class => "button_to destructive", 64 :form_class => "button_to destructive",
65 :form => { :data => { :confirm => 65 :form => { :data => { :confirm => t(".reset_confirm", :login => @user.login) } } %>
66 "Reset #{@user.login}'s second factor? They will log in with password only afterwards." } } %>
67 </div> 66 </div>
68 <% end %> 67 <% end %>
69</div> 68</div>
diff --git a/config/application.rb b/config/application.rb
index 5ec3e8fe..c8f53ac2 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -18,7 +18,7 @@ module Cccms
18 18
19 config.i18n.default_locale = :de 19 config.i18n.default_locale = :de
20 config.i18n.available_locales = [:de, :en] 20 config.i18n.available_locales = [:de, :en]
21 config.i18n.fallbacks = { en: [:en, :de] } 21 config.i18n.fallbacks = { en: [:en, :de], de: [:de, :en] }
22 22
23 config.filter_parameters += [:password, :password_confirmation] 23 config.filter_parameters += [:password, :password_confirmation]
24 config.active_storage.variant_processor = :disabled 24 config.active_storage.variant_processor = :disabled
diff --git a/config/locales/de.yml b/config/locales/de.yml
index ae955aa6..7d49a872 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -146,3 +146,44 @@ de:
146 licensed_under: "Lizenziert unter %{license}" 146 licensed_under: "Lizenziert unter %{license}"
147 147
148 related_documents: "Weitere Dokumente" 148 related_documents: "Weitere Dokumente"
149
150 sessions:
151 new:
152 login: "Anmeldename"
153 password: "Passwort"
154 log_in: "anmelden"
155
156 otp_challenges:
157 new:
158 title: "Zweiter Faktor"
159 code: "Code"
160 log_in: "Anmelden"
161 hint: "Gib den sechsstelligen Code aus deiner Authenticator-App ein."
162
163 otp_enrollments:
164 show:
165 title: "Zweiten Faktor einrichten"
166 scan: "Scannen"
167 manual_hint: "Oder Secret manuell eingeben:"
168 confirm: "Bestätigen"
169 app_hint: "Gib den sechsstelligen Code ein, den deine App für „%{issuer}“ anzeigt."
170
171 users:
172 edit:
173 my_account: "Mein Konto"
174 edit_user: "Benutzer „%{login}“ bearbeiten"
175 login: "Anmeldename"
176 email: "E-Mail"
177 password: "Passwort"
178 confirm: "bestätigen"
179 admin: "Admin?"
180 update: "Aktualisieren"
181 enabled: "Aktiviert."
182 not_enrolled: "Nicht eingerichtet."
183 current_password: "Aktuelles Passwort"
184 current_code: "Aktueller Code"
185 second_factor: "Zweiter Faktor"
186 enable_second_factor: "Zweiten Faktor aktivieren"
187 disable_second_factor: "Zweiten Faktor entfernen"
188 reset_second_factor: "Zweiten Faktor zurücksetzen"
189 reset_confirm: "Zweiten Faktor von %{login} zurücksetzen? Die Anmeldung ist danach nur mit Passwort möglich."
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 618f5164..ea94952a 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -98,3 +98,44 @@ en:
98 licensed_under: "Licensed under %{license}" 98 licensed_under: "Licensed under %{license}"
99 99
100 related_documents: "Related documents" 100 related_documents: "Related documents"
101
102 sessions:
103 new:
104 login: "Login"
105 password: "Password"
106 log_in: "log in"
107
108 otp_challenges:
109 new:
110 title: "Second factor"
111 code: "Code"
112 log_in: "Log in"
113 hint: "Enter the six-digit code from your authenticator app."
114
115 otp_enrollments:
116 show:
117 title: "Enable second factor"
118 scan: "Scan"
119 manual_hint: "Or enter the secret manually:"
120 confirm: "Confirm"
121 app_hint: "Enter the six-digit code your app shows for “%{issuer}”."
122
123 users:
124 edit:
125 my_account: "My account"
126 edit_user: "Edit user “%{login}”"
127 login: "login"
128 email: "e-mail"
129 password: "password"
130 confirm: "confirm"
131 admin: "admin?"
132 update: "Update"
133 enabled: "Enabled."
134 not_enrolled: "Not enrolled."
135 current_password: "Current password"
136 current_code: "Current code"
137 second_factor: "Second factor"
138 enable_second_factor: "Enable second factor"
139 disable_second_factor: "Disable second factor"
140 reset_second_factor: "Reset second factor"
141 reset_confirm: "Reset %{login}'s second factor? They will log in with password only afterwards."
diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb
index 946b5414..14f12b52 100644
--- a/test/controllers/users_controller_test.rb
+++ b/test/controllers/users_controller_test.rb
@@ -163,6 +163,13 @@ class UsersControllerTest < ActionController::TestCase
163 end 163 end
164 assert_redirected_to users_path 164 assert_redirected_to users_path
165 end 165 end
166
167 test "enrolled user gets a working my account" do
168 users(:quentin).update!(:otp_secret => ROTP::Base32.random)
169 login_as :quentin
170 get :edit, params: { :id => users(:quentin).id }
171 assert_response :success
172 end
166 173
167 test "admin user can promote regular users to admins" do 174 test "admin user can promote regular users to admins" do
168 login_as :aaron 175 login_as :aaron