Commit 5495e6c6 authored by mh's avatar mh
Browse files

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

parent d3918547
......@@ -35,3 +35,11 @@ function 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
post(['users', 'enable_mail_crypt'], {"password" => pw})
end
def enable_totp(name)
post(['users', 'enable_totp'], {'name' => name})
def create_totp(name, password)
post(['users', 'create_totp'], {'name' => name, 'password' => password })
end
def get_totp_names
get(['users', 'totp_names'])
end
def delete_totp(name)
post(['users', 'delete_totp'], {'name' => name })
def verify_totp(name, totp)
post(['users', 'verify_totp'], {'name' => name, 'totp' => totp })
end
def delete_totp(name, pw)
post(['users', 'delete_totp'], {'name' => name, 'password' => pw })
end
def mail_crypt_token(pw)
post(['users', 'generate_recovery_token'], {"password" => pw})
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})
end
......
......@@ -6,15 +6,15 @@ class TfaController < ApplicationController
def show
fetch_existing_2fa
end
def enable
def create
if params[:name]
begin
res = api.enable_totp(params[:name])
res = api.create_totp(params[:name], params[:password])
totp = ROTP::TOTP.new(res['secret'], issuer: "immerda.ch")
url = totp.provisioning_uri(current_user)
@totp_qr = RQRCode::QRCode.new(url, :size => 12, :level => :h)
@totp_secret = res['secret']
fetch_existing_2fa
@new_totp_qr = RQRCode::QRCode.new(url, :size => 12, :level => :h)
@new_totp_secret = res['secret']
@new_totp_name = res['name']
flash[:notice] = nil
rescue ApiBackend::ApiError
flash[:notice] = :failed
......@@ -22,13 +22,14 @@ class TfaController < ApplicationController
else
flash[:notice] = t(:name_must_be_set)
end
fetch_existing_2fa
render 'show'
end
def delete_totp
if params['name'].present?
begin
api.delete_totp(params['name'])
api.delete_totp(params['name'], params['password'])
flash[:notice] = :success
rescue ApiBackend::ApiError
flash[:notice] = :failed
......@@ -39,6 +40,19 @@ class TfaController < ApplicationController
redirect_to tfa_path
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
def fetch_existing_2fa
res = api.get_totp_names
......
......@@ -17,20 +17,12 @@
type="checkbox"
id="email_recovery_token"
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) %>
<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 %>
<br />
<%= label_tag(:pass, t(:your_main_pw)) %><%= password_field_tag(:password, nil, placeholder: t(:enter_your_current
_pw)) %>
<br />
<%= submit_tag(recovery_email_set? ? t(:overwrite) : t(:submit)) %>
<%= link_to t(:back), root_path %>
......
<p>
<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/>
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>
<h3><%= @page_title = t(:tfa_enable) %></h3>
<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>
<% if @totp_qr -%>
<% if @new_totp_qr -%>
<%= render 'new_totp' %>
<%= link_to t(:back), tfa_path %>
......@@ -18,18 +18,38 @@
<%= t :totp_enable_help %> <%= link_to 'FreeOTP', 'https://freeotp.github.io/', target: '_new' %>
</p>
<% has_default = false
if @existing_totps.present? -%>
<% has_default = false if @existing_totps.present? -%>
<hr />
<h4><%= t(:existing_totps) %></h4>
<ul>
<% @existing_totps.each do |name, kind|
has_default = true if name == 'default'
-%>
<li> <%= name %> <%= link_to t(:delete), "/tfa/#{URI.encode(name)}/delete_totp", method: :post %></li>
<% @existing_totps.each do |name, verified|
has_default = true if name == 'default' -%>
<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 -%>
</ul>
<% end -%>
<hr />
<p>
......@@ -42,6 +62,11 @@
</td><td>
<%= text_field_tag(:name, '', value: (has_default ? "default-#{Time.now.strftime("%Y%m%d%H%M")}" : 'default'), maxlength: 255, autocomplete: 'off') %>
</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>
<%= hidden_field('', :kind, :value => 'totp') %>
<%= submit_tag(t(:create)) %>
......
......@@ -13,6 +13,7 @@ de:
present: "Gesetzt"
edit: "Bearbeiten"
deleted: "Gelöscht"
verify: "Verifizieren"
mail_crypt_settings: "Geschützte Mailablage"
mail_crypt_short_help: "Bestimme wie deine Mails auf unserem Server gespeichert werden."
......@@ -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:'
your_new_totp_token: 'Dein neuer TOTP Token'
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..."
enter_captcha: "Bitte kopiere diese Buchstaben und Zahlen in das untere Feld."
admin_lock: "Sperre Account"
......
......@@ -13,6 +13,7 @@ en:
present: "present"
edit: "Edit"
deleted: "Deleted"
verify: "verify"
mail_crypt_settings: "Secured Mailstorage"
mail_crypt_short_help: "Decide how your mails are stored on our servers."
......@@ -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."
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:"
your_new_totp_token: 'Your new TOTP Token'
your_new_totp_token: 'Your new TOTP token'
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..."
enter_captcha: "Please copy the following letters into the field below."
admin_lock: "Lock account"
......
......@@ -41,8 +41,13 @@ Rails.application.routes.draw do
post 'signup/:token', to: 'signup#create'
get 'tfa', to: 'tfa#show'
post 'tfa', to: 'tfa#enable'
post 'tfa/:name/delete_totp', to: 'tfa#delete_totp'
post 'tfa', to: 'tfa#create'
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'
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