Commit dddbd9b6 authored by mh's avatar mh
Browse files

add the activate your totp flow & make password mandotary when setting a recovery email

parent 740d28f8
...@@ -35,3 +35,11 @@ function mainOnLoad() { ...@@ -35,3 +35,11 @@ function mainOnLoad() {
} }
} }
window.addEventListener("load", mainOnLoad) window.addEventListener("load", mainOnLoad)
function toggle_visibility(id) {
var e = document.getElementById(id);
if(e.style.display == 'block')
e.style.display = 'none';
else
e.style.display = 'block';
}
...@@ -221,23 +221,27 @@ module ApiBackend ...@@ -221,23 +221,27 @@ module ApiBackend
post(['users', 'enable_mail_crypt'], {"password" => pw}) post(['users', 'enable_mail_crypt'], {"password" => pw})
end end
def enable_totp(name) def create_totp(name, password)
post(['users', 'enable_totp'], {'name' => name}) post(['users', 'create_totp'], {'name' => name, 'password' => password })
end end
def get_totp_names def get_totp_names
get(['users', 'totp_names']) get(['users', 'totp_names'])
end end
def delete_totp(name) def verify_totp(name, totp)
post(['users', 'delete_totp'], {'name' => name }) post(['users', 'verify_totp'], {'name' => name, 'totp' => totp })
end
def delete_totp(name, pw)
post(['users', 'delete_totp'], {'name' => name, 'password' => pw })
end end
def mail_crypt_token(pw) def mail_crypt_token(pw)
post(['users', 'generate_recovery_token'], {"password" => pw}) post(['users', 'generate_recovery_token'], {"password" => pw})
end end
def set_recovery_email(recovery, email_token, pass = nil) def set_recovery_email(recovery, email_token, pass)
post(['users', 'set_recovery_email'], {"recovery_email" => recovery, 'email_recovery_token' => email_token, 'password' => pass}) post(['users', 'set_recovery_email'], {"recovery_email" => recovery, 'email_recovery_token' => email_token, 'password' => pass})
end end
......
...@@ -6,15 +6,15 @@ class TfaController < ApplicationController ...@@ -6,15 +6,15 @@ class TfaController < ApplicationController
def show def show
fetch_existing_2fa fetch_existing_2fa
end end
def enable def create
if params[:name] if params[:name]
begin begin
res = api.enable_totp(params[:name]) res = api.create_totp(params[:name], params[:password])
totp = ROTP::TOTP.new(res['secret'], issuer: "immerda.ch") totp = ROTP::TOTP.new(res['secret'], issuer: "immerda.ch")
url = totp.provisioning_uri(current_user) url = totp.provisioning_uri(current_user)
@totp_qr = RQRCode::QRCode.new(url, :size => 12, :level => :h) @new_totp_qr = RQRCode::QRCode.new(url, :size => 12, :level => :h)
@totp_secret = res['secret'] @new_totp_secret = res['secret']
fetch_existing_2fa @new_totp_name = res['name']
flash[:notice] = nil flash[:notice] = nil
rescue ApiBackend::ApiError rescue ApiBackend::ApiError
flash[:notice] = :failed flash[:notice] = :failed
...@@ -22,13 +22,14 @@ class TfaController < ApplicationController ...@@ -22,13 +22,14 @@ class TfaController < ApplicationController
else else
flash[:notice] = t(:name_must_be_set) flash[:notice] = t(:name_must_be_set)
end end
fetch_existing_2fa
render 'show' render 'show'
end end
def delete_totp def delete_totp
if params['name'].present? if params['name'].present?
begin begin
api.delete_totp(params['name']) api.delete_totp(params['name'], params['password'])
flash[:notice] = :success flash[:notice] = :success
rescue ApiBackend::ApiError rescue ApiBackend::ApiError
flash[:notice] = :failed flash[:notice] = :failed
...@@ -39,6 +40,19 @@ class TfaController < ApplicationController ...@@ -39,6 +40,19 @@ class TfaController < ApplicationController
redirect_to tfa_path redirect_to tfa_path
end end
def verify_totp
if params['name'].present?
begin
api.verify_totp(params[:name], params[:totp])
rescue ApiBackend::ApiError
flash[:notice] = :failed
end
else
flash[:notice] = t(:name_must_be_set)
end
redirect_to tfa_path
end
private private
def fetch_existing_2fa def fetch_existing_2fa
res = api.get_totp_names res = api.get_totp_names
......
...@@ -17,20 +17,12 @@ ...@@ -17,20 +17,12 @@
type="checkbox" type="checkbox"
id="email_recovery_token" id="email_recovery_token"
name="email_recovery_token" <%= if @email_recovery_token then 'checked="checked"' else '' end %> name="email_recovery_token" <%= if @email_recovery_token then 'checked="checked"' else '' end %>
onclick= "document.getElementById('show-pass').style.display = (document.getElementById('email_recovery_token').checked ? 'block' : 'none');"
/> />
<%= t(:backup_recovery_token_email) %> <%= t(:backup_recovery_token_email) %>
<br />
<div id="show-pass"
<% unless @email_recovery_token %>
style="display:none"
<% end %>
>
<%= label_tag(:pass, (t :password_for_recovery_token)) %>
<%= password_field_tag(:pass) %>
</div>
<% end %> <% end %>
<br />
<%= label_tag(:pass, t(:your_main_pw)) %><%= password_field_tag(:password, nil, placeholder: t(:enter_your_current
_pw)) %>
<br /> <br />
<%= submit_tag(recovery_email_set? ? t(:overwrite) : t(:submit)) %> <%= submit_tag(recovery_email_set? ? t(:overwrite) : t(:submit)) %>
<%= link_to t(:back), root_path %> <%= link_to t(:back), root_path %>
......
<p> <p>
<h4><%= t(:your_new_totp_token) %></h4> <h4><%= t(:your_new_totp_token) %></h4>
<div class="qr-field"><%= raw(@totp_qr.as_html) %></div> <div class="qr-field"><%= raw(@new_totp_qr.as_html) %></div>
<br/> <br/>
Secret: <%= @totp_secret %> Secret: <%= @new_totp_secret %>
</p>
<p>
<h4><%= t(:activate_your_totp) %></h4>
<%= t(:activate_your_totp_long) %>
<br/>
<%= form_tag("/tfa/#{URI.encode(@new_totp_name)}/verify_totp", method: "post") do %>
<%= label_tag(:totp, t(:totp)) %><%= text_field_tag(:totp, nil, autofocus: true, autocomplete: :off) %>
<%= submit_tag t(:verify) %>
<% end -%>
</p> </p>
<h3><%= @page_title = t(:tfa_enable) %></h3> <h3><%= @page_title = t(:tfa_enable) %></h3>
<p> <p>
<%= t(:status) %>: <%= @existing_totps.present? ? "<b>#{t(:activated)}</b>".html_safe : t(:disabled) %> <%= t(:status) %>: <%= @existing_totps.values.any? ? "<b>#{t(:activated)}</b>".html_safe : t(:disabled) %>
</p> </p>
<% if @totp_qr -%> <% if @new_totp_qr -%>
<%= render 'new_totp' %> <%= render 'new_totp' %>
<%= link_to t(:back), tfa_path %> <%= link_to t(:back), tfa_path %>
...@@ -18,18 +18,38 @@ ...@@ -18,18 +18,38 @@
<%= t :totp_enable_help %> <%= link_to 'FreeOTP', 'https://freeotp.github.io/', target: '_new' %> <%= t :totp_enable_help %> <%= link_to 'FreeOTP', 'https://freeotp.github.io/', target: '_new' %>
</p> </p>
<% has_default = false <% has_default = false if @existing_totps.present? -%>
if @existing_totps.present? -%>
<hr /> <hr />
<h4><%= t(:existing_totps) %></h4> <h4><%= t(:existing_totps) %></h4>
<ul> <ul>
<% @existing_totps.each do |name, kind| <% @existing_totps.each do |name, verified|
has_default = true if name == 'default' has_default = true if name == 'default' -%>
-%> <li>
<li> <%= name %> <%= link_to t(:delete), "/tfa/#{URI.encode(name)}/delete_totp", method: :post %></li> <%= name %>&nbsp;
<% if verified -%>
<%= t(:active) %>
<% else -%>
<%= t(:not_active) %> - <a href="#" onclick="toggle_visibility('activate_form_<%= URI.encode(name) %>')"> <%= t(:verify_to_activate) %></a>&nbsp;
<div id="activate_form_<%= URI.encode(name) %>" style="display:none">
<%= t(:activate_your_totp_long) %>
<br/>
<%= form_tag("/tfa/#{URI.encode(name)}/verify_totp", method: "post") do %>
<%= label_tag(:totp, t(:totp)) %><%= text_field_tag(:totp, nil, autofocus: true, autocomplete: :off) %>
<%= submit_tag t(:verify) %>
<% end -%>
</div>
<% end -%>
<a href="#" onclick="toggle_visibility('delete_form_<%= URI.encode(name) %>')"/><%= t(:delete) %></a>
<div id="delete_form_<%= URI.encode(name) %>" style="display:none">
<br />
<%= form_tag("/tfa/#{URI.encode(name)}/delete_totp", method: "post") do %>
<%= label_tag(:pass, t(:your_main_pw)) %><%= password_field_tag(:password, nil, placeholder: t(:enter_your_current_pw)) %>
<%= submit_tag t(:delete) %>
<% end -%>
</div>
</li>
<% end -%> <% end -%>
</ul> </ul>
<% end -%>
<hr /> <hr />
<p> <p>
...@@ -42,6 +62,11 @@ ...@@ -42,6 +62,11 @@
</td><td> </td><td>
<%= text_field_tag(:name, '', value: (has_default ? "default-#{Time.now.strftime("%Y%m%d%H%M")}" : 'default'), maxlength: 255, autocomplete: 'off') %> <%= text_field_tag(:name, '', value: (has_default ? "default-#{Time.now.strftime("%Y%m%d%H%M")}" : 'default'), maxlength: 255, autocomplete: 'off') %>
</td></tr> </td></tr>
<tr><td>
<%= label_tag(:pass, t(:your_main_pw)) %>
</td><td>
<%= password_field_tag(:password, nil, placeholder: t(:enter_your_current_pw)) %>
</td></tr>
</table> </table>
<%= hidden_field('', :kind, :value => 'totp') %> <%= hidden_field('', :kind, :value => 'totp') %>
<%= submit_tag(t(:create)) %> <%= submit_tag(t(:create)) %>
......
...@@ -13,6 +13,7 @@ de: ...@@ -13,6 +13,7 @@ de:
present: "Gesetzt" present: "Gesetzt"
edit: "Bearbeiten" edit: "Bearbeiten"
deleted: "Gelöscht" deleted: "Gelöscht"
verify: "Verifizieren"
mail_crypt_settings: "Geschützte Mailablage" mail_crypt_settings: "Geschützte Mailablage"
mail_crypt_short_help: "Bestimme wie deine Mails auf unserem Server gespeichert werden." mail_crypt_short_help: "Bestimme wie deine Mails auf unserem Server gespeichert werden."
...@@ -113,6 +114,9 @@ de: ...@@ -113,6 +114,9 @@ de:
totp_enable_help: 'Zeitbasierte OneTimePassword (TOTP) sind ein zweiter Faktor, der auf der aktuellen Zeit und einem geteilten Passwort basiert. Üblicherweise kannst du eine Mobile App verwenden, welche dir das aktuell gültige Passwort anzeigt. Unsere TOTPs sind kompatibel mit RFC6263. Eine populäre und freie mobile App für TOTPs ist FreeOTP, welche in den Appstores von Android und iOS verfügbar ist. Wie auch auf der folgenden Webseite:' totp_enable_help: 'Zeitbasierte OneTimePassword (TOTP) sind ein zweiter Faktor, der auf der aktuellen Zeit und einem geteilten Passwort basiert. Üblicherweise kannst du eine Mobile App verwenden, welche dir das aktuell gültige Passwort anzeigt. Unsere TOTPs sind kompatibel mit RFC6263. Eine populäre und freie mobile App für TOTPs ist FreeOTP, welche in den Appstores von Android und iOS verfügbar ist. Wie auch auf der folgenden Webseite:'
your_new_totp_token: 'Dein neuer TOTP Token' your_new_totp_token: 'Dein neuer TOTP Token'
existing_totps: 'Bestehende TOTPs' existing_totps: 'Bestehende TOTPs'
activate_your_totp: 'Aktiviere dein TOTP Token'
activate_your_totp_long: 'Damit wir sicherstellen können, dass dein neuer TOTP auch richtig funktioniert, musst du ihn ein erstes mal verifizieren. Erst nach einer erfolgreichen Verifikation aktivieren wir diesen TOTP Token. Um die Verifikation durchzuführen musst du den den aktuellen Wert des TOTP Tokens eingeben'
auth_wait: "Bitte warte während wir deine Eingabe überprüfen..." auth_wait: "Bitte warte während wir deine Eingabe überprüfen..."
enter_captcha: "Bitte kopiere diese Buchstaben und Zahlen in das untere Feld." enter_captcha: "Bitte kopiere diese Buchstaben und Zahlen in das untere Feld."
admin_lock: "Sperre Account" admin_lock: "Sperre Account"
......
...@@ -13,6 +13,7 @@ en: ...@@ -13,6 +13,7 @@ en:
present: "present" present: "present"
edit: "Edit" edit: "Edit"
deleted: "Deleted" deleted: "Deleted"
verify: "verify"
mail_crypt_settings: "Secured Mailstorage" mail_crypt_settings: "Secured Mailstorage"
mail_crypt_short_help: "Decide how your mails are stored on our servers." mail_crypt_short_help: "Decide how your mails are stored on our servers."
...@@ -111,8 +112,11 @@ en: ...@@ -111,8 +112,11 @@ en:
tfa_enable_help: "You can define a second factor that is required to successfully authenticate your account. This provides an additional layer of protection for your account. NOTE: As two factor authentication only works when authenticating against our user portal, you will need to create application passwords e.g. for your mail programm like thunderbird. Once you enable TwoFactor Authentication your main password will only work on the portal login. We recommend you to set a recovery email address for your account, so in case of loosing your second factor, we can remove the two factor authentication. You can define multiple second factors, though to successfully login only one valid is required." tfa_enable_help: "You can define a second factor that is required to successfully authenticate your account. This provides an additional layer of protection for your account. NOTE: As two factor authentication only works when authenticating against our user portal, you will need to create application passwords e.g. for your mail programm like thunderbird. Once you enable TwoFactor Authentication your main password will only work on the portal login. We recommend you to set a recovery email address for your account, so in case of loosing your second factor, we can remove the two factor authentication. You can define multiple second factors, though to successfully login only one valid is required."
totp_enable: 'Timebased OneTimePasswords' totp_enable: 'Timebased OneTimePasswords'
totp_enable_help: "Timebased onetimepasswords (TOTP) are a second factor that is based on the current time and a shared secret. Usually you can use a mobile app, which will show you the current valid password. We are providing TOTPs which are compatible with RFC6263. A popular and free mobile application for TOTPs is FreeOTP, which is available on Android's and iOS' appstores or on the following website:" totp_enable_help: "Timebased onetimepasswords (TOTP) are a second factor that is based on the current time and a shared secret. Usually you can use a mobile app, which will show you the current valid password. We are providing TOTPs which are compatible with RFC6263. A popular and free mobile application for TOTPs is FreeOTP, which is available on Android's and iOS' appstores or on the following website:"
your_new_totp_token: 'Your new TOTP Token' your_new_totp_token: 'Your new TOTP token'
existing_totps: 'Existing TOTPs' existing_totps: 'Existing TOTPs'
activate_your_totp: 'Activate your TOTP token'
activate_your_totp_long: 'To be able to verify, that your new TOTP token also works correctly, you must verify it for a first time. Only after a successfull verification, we will activate this token. To verify your token, you need enter the current value of your TOTP for a first time.'
auth_wait: "Please wait while we validate your credentials..." auth_wait: "Please wait while we validate your credentials..."
enter_captcha: "Please copy the following letters into the field below." enter_captcha: "Please copy the following letters into the field below."
admin_lock: "Lock account" admin_lock: "Lock account"
......
...@@ -41,8 +41,13 @@ Rails.application.routes.draw do ...@@ -41,8 +41,13 @@ Rails.application.routes.draw do
post 'signup/:token', to: 'signup#create' post 'signup/:token', to: 'signup#create'
get 'tfa', to: 'tfa#show' get 'tfa', to: 'tfa#show'
post 'tfa', to: 'tfa#enable' post 'tfa', to: 'tfa#create'
post 'tfa/:name/delete_totp', to: 'tfa#delete_totp' constraints(:name => /[a-zA-Z0-9\-]+/) do
post 'tfa/:name/delete_totp', to: 'tfa#delete_totp'
get 'tfa/:name/delete_totp', to: redirect('tfa')
post 'tfa/:name/verify_totp', to: 'tfa#verify_totp'
post 'tfa/:name/verify_totp', to: redirect('tfa')
end
root to: 'welcome#index' root to: 'welcome#index'
get '/welcome', to: 'welcome#index' get '/welcome', to: 'welcome#index'
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment