Skip to content

Commit

Permalink
Merge pull request #3859 from Shopify/eric/authn-recovery-codes
Browse files Browse the repository at this point in the history
Recovery code support for webauthn
  • Loading branch information
jenshenny authored Jun 26, 2023
2 parents 3571e32 + 42fe8ef commit 74e7294
Show file tree
Hide file tree
Showing 25 changed files with 379 additions and 68 deletions.
7 changes: 1 addition & 6 deletions app/controllers/email_confirmations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def create

def update
if @user.mfa_enabled?
setup_mfa_authentication
@form_mfa_url = mfa_update_email_confirmations_url(token: @user.confirmation_token)
setup_webauthn_authentication(form_url: webauthn_update_email_confirmations_url(token: @user.confirmation_token))

create_new_mfa_expiry
Expand Down Expand Up @@ -100,11 +100,6 @@ def mfa_update_conditions_met?
@user.mfa_enabled? && @user.ui_mfa_verified?(params[:otp]) && session_active?
end

def setup_mfa_authentication
return if @user.totp_disabled?
@form_mfa_url = mfa_update_email_confirmations_url(token: @user.confirmation_token)
end

def login_failure(message)
flash.now.alert = message
render template: "multifactor_auths/mfa_prompt", status: :unauthorized
Expand Down
11 changes: 3 additions & 8 deletions app/controllers/multifactor_auths_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ class MultifactorAuthsController < ApplicationController

before_action :redirect_to_signin, unless: :signed_in?
before_action :require_totp_disabled, only: %i[new create]
before_action :require_mfa_enabled, only: :update
before_action :require_totp_enabled, only: %i[mfa_update destroy]
before_action :require_mfa_enabled, only: %i[update mfa_update]
before_action :require_totp_enabled, only: :destroy
before_action :seed_and_expire, only: :create
before_action :verify_session_expiration, only: %i[mfa_update webauthn_update]
after_action :delete_mfa_level_update_session_variables, only: %i[mfa_update webauthn_update]
Expand Down Expand Up @@ -42,7 +42,7 @@ def update
session[:level] = level_param
@user = current_user

setup_mfa_authentication
@form_mfa_url = mfa_update_multifactor_auth_url(token: current_user.confirmation_token)
setup_webauthn_authentication

create_new_mfa_expiry
Expand Down Expand Up @@ -138,11 +138,6 @@ def seed_and_expire
end
end

def setup_mfa_authentication
return if current_user.totp_disabled?
@form_mfa_url = mfa_update_multifactor_auth_url(token: current_user.confirmation_token)
end

def setup_webauthn_authentication
return if current_user.webauthn_disabled?

Expand Down
7 changes: 1 addition & 6 deletions app/controllers/passwords_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class PasswordsController < Clearance::PasswordsController

def edit
if @user.mfa_enabled?
setup_mfa_authentication
@form_mfa_url = mfa_edit_user_password_url(@user, token: @user.confirmation_token)
setup_webauthn_authentication(form_url: webauthn_edit_user_password_url(token: @user.confirmation_token))

create_new_mfa_expiry
Expand Down Expand Up @@ -73,11 +73,6 @@ def deliver_email(user)
::ClearanceMailer.change_password(user).deliver_later
end

def setup_mfa_authentication
return if @user.totp_disabled?
@form_mfa_url = mfa_edit_user_password_url(@user, token: @user.confirmation_token)
end

def mfa_edit_conditions_met?
@user.mfa_enabled? && @user.ui_mfa_verified?(params[:otp]) && session_active?
end
Expand Down
7 changes: 1 addition & 6 deletions app/controllers/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def create

if @user&.mfa_enabled?
setup_webauthn_authentication(form_url: webauthn_create_session_path, session_options: { "user" => @user.id })
setup_mfa_authentication
session[:mfa_user] = @user.id

session[:mfa_login_started_at] = Time.now.utc.to_s
create_new_mfa_expiry
Expand Down Expand Up @@ -137,11 +137,6 @@ def ensure_not_blocked
render template: "sessions/new", status: :unauthorized
end

def setup_mfa_authentication
return if @user.totp_disabled?
session[:mfa_user] = @user.id
end

def login_conditions_met?
@user&.mfa_enabled? && @user&.ui_mfa_verified?(params[:otp]) && session_active?
end
Expand Down
4 changes: 4 additions & 0 deletions app/models/concerns/user_webauthn_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ def webauthn_disabled?
webauthn_credentials.none?
end

def webauthn_only_with_recovery?
webauthn_enabled? && totp_disabled? && mfa_recovery_codes.present?
end

def webauthn_options_for_get
WebAuthn::Credential.options_for_get(
allow: webauthn_credentials.pluck(:external_id),
Expand Down
38 changes: 29 additions & 9 deletions app/views/multifactor_auths/mfa_prompt.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,35 @@
</div>
<% end %>
<% if @user.totp_enabled? %>
<%= form_tag @form_mfa_url, method: :post do %>
<div class="text_field">
<%= label_tag :otp, t("multifactor_auths.otp_code"), class: "form__label" %>
<%= text_field_tag :otp, "", class: "form__input", autofocus: true, autocomplete: :off %>
</div>
<div class="form_bottom">
<%= submit_tag t("authenticate"), data: {disable_with: t("form_disable_with")}, class: "form__submit" %>
<% if @user.totp_enabled? || @user.webauthn_only_with_recovery? %>
<div class="mfa__option">
<% if @user.totp_enabled? %>
<h2 class="page__subheading--block"> <%= t("multifactor_auths.otp_code") %></h2>
<% elsif @user.webauthn_only_with_recovery? %>
<h2 class="page__subheading--block"> <%= t("multifactor_auths.recovery_code") %></h2>
<% end %>
<div class="t-body">
<p><%= t("multifactor_auths.recovery_code_html") %></p>
</div>
<% end %>
<%= form_tag @form_mfa_url, method: :post do %>
<div class="text_field">
<% if @user.totp_enabled? %>
<%= label_tag :otp, t('multifactor_auths.otp_or_recovery'), class: 'form__label' %>
<%= text_field_tag :otp, '', class: 'form__input', autofocus: true, autocomplete: :off %>
<% elsif @user.webauthn_only_with_recovery? %>
<%= text_field_tag :otp,
'',
class: 'form__input',
autofocus: true,
autocomplete: :off,
aria: { label: t("multifactor_auths.recovery_code") }
%>
<% end %>
</div>
<div class="form_bottom">
<%= submit_tag t("authenticate"), data: { disable_with: t("form_disable_with")}, class: "form__submit" %>
</div>
<% end %>
</div>
<% end %>
</div>
18 changes: 16 additions & 2 deletions app/views/sessions/prompt.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,31 @@
</div>
<% end %>
<% if @user.mfa_enabled? %>
<% if @user.totp_enabled? || @user.webauthn_only_with_recovery? %>
<div class="mfa__option">
<h2 class="page__subheading--block"> <%= t(".otp_code") %></h2>
<% if @user.totp_enabled? %>
<h2 class="page__subheading--block"> <%= t(".otp_code") %></h2>
<% elsif @user.webauthn_only_with_recovery? %>
<h2 class="page__subheading--block"> <%= t(".recovery_code") %></h2>
<% end %>
<div class="t-body">
<p><%= t("multifactor_auths.recovery_code_html") %></p>
</div>

<%= form_tag mfa_create_session_path, method: :post, class: "mfa-form" do %>
<div class="text_field">
<% if @user.totp_enabled? %>
<%= label_tag :otp, t('.otp_or_recovery'), class: 'form__label' %>
<%= text_field_tag :otp, '', class: 'form__input', autofocus: true, autocomplete: :off %>
<% elsif @user.webauthn_only_with_recovery? %>
<%= text_field_tag :otp,
'',
class: 'form__input',
autofocus: true,
autocomplete: :off,
aria: { label: t(".recovery_code") }
%>
<% end %>
</div>
<div class="form_bottom">
<%= submit_tag t('.verify_code'), data: { disable_with: t('form_disable_with') }, class: 'form__submit' %>
Expand Down
3 changes: 3 additions & 0 deletions config/locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@ de:
incorrect_otp:
session_expired:
otp_code:
otp_or_recovery:
recovery_code:
require_totp_disabled:
require_mfa_enabled:
require_totp_enabled:
Expand Down Expand Up @@ -603,6 +605,7 @@ de:
sign_in_with_webauthn_credential:
otp_code:
otp_or_recovery:
recovery_code:
security_device:
verify_code:
stats:
Expand Down
3 changes: 3 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ en:
incorrect_otp: Your OTP code is incorrect.
session_expired: Your login page session has expired.
otp_code: OTP code
otp_or_recovery: OTP or recovery code
recovery_code: Recovery code
require_totp_disabled: Your OTP based multi-factor authentication has already been enabled. To reconfigure your OTP based authentication, you'll have to remove it first.
require_mfa_enabled: Your multi-factor authentication has not been enabled. You have to enable it first.
require_totp_enabled: You don't have an authenticator app enabled. You have to enable it first.
Expand Down Expand Up @@ -601,6 +603,7 @@ en:
sign_in_with_webauthn_credential: Authenticate with security device
otp_code: One Time Passcode
otp_or_recovery: OTP or recovery code
recovery_code: Recovery code
security_device: Security Device
verify_code: Verify code
stats:
Expand Down
3 changes: 3 additions & 0 deletions config/locales/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ es:
incorrect_otp: Tu código OTP no es correcto.
session_expired:
otp_code: código OTP
otp_or_recovery:
recovery_code:
require_totp_disabled:
require_mfa_enabled: No se ha activado la autenticación de múltiples factores.
Primero tienes que activarla.
Expand Down Expand Up @@ -647,6 +649,7 @@ es:
sign_in_with_webauthn_credential:
otp_code:
otp_or_recovery:
recovery_code:
security_device:
verify_code:
stats:
Expand Down
3 changes: 3 additions & 0 deletions config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ fr:
incorrect_otp: Votre clé OTP est incorrecte.
session_expired:
otp_code: clé OTP
otp_or_recovery:
recovery_code:
require_totp_disabled:
require_mfa_enabled: Votre authentification multifacteur n'a pas été activée.
Vous devez d'abord l'activer.
Expand Down Expand Up @@ -653,6 +655,7 @@ fr:
sign_in_with_webauthn_credential:
otp_code:
otp_or_recovery:
recovery_code:
security_device:
verify_code:
stats:
Expand Down
3 changes: 3 additions & 0 deletions config/locales/ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ ja:
incorrect_otp:
session_expired:
otp_code:
otp_or_recovery:
recovery_code:
require_totp_disabled:
require_mfa_enabled:
require_totp_enabled:
Expand Down Expand Up @@ -592,6 +594,7 @@ ja:
sign_in_with_webauthn_credential:
otp_code:
otp_or_recovery:
recovery_code:
security_device:
verify_code:
stats:
Expand Down
3 changes: 3 additions & 0 deletions config/locales/nl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,8 @@ nl:
incorrect_otp:
session_expired:
otp_code:
otp_or_recovery:
recovery_code:
require_totp_disabled:
require_mfa_enabled:
require_totp_enabled:
Expand Down Expand Up @@ -607,6 +609,7 @@ nl:
sign_in_with_webauthn_credential:
otp_code:
otp_or_recovery:
recovery_code:
security_device:
verify_code:
stats:
Expand Down
3 changes: 3 additions & 0 deletions config/locales/pt-BR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,8 @@ pt-BR:
incorrect_otp:
session_expired:
otp_code:
otp_or_recovery:
recovery_code:
require_totp_disabled:
require_mfa_enabled:
require_totp_enabled:
Expand Down Expand Up @@ -630,6 +632,7 @@ pt-BR:
sign_in_with_webauthn_credential:
otp_code:
otp_or_recovery:
recovery_code:
security_device:
verify_code:
stats:
Expand Down
3 changes: 3 additions & 0 deletions config/locales/zh-CN.yml
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ zh-CN:
incorrect_otp: 你的 OTP 码 不正确。
session_expired:
otp_code: OTP 码
otp_or_recovery:
recovery_code:
require_totp_disabled:
require_mfa_enabled: 你的多重要素验证已停用,请先启用。
require_totp_enabled:
Expand Down Expand Up @@ -589,6 +591,7 @@ zh-CN:
sign_in_with_webauthn_credential:
otp_code:
otp_or_recovery:
recovery_code:
security_device:
verify_code:
stats:
Expand Down
3 changes: 3 additions & 0 deletions config/locales/zh-TW.yml
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ zh-TW:
incorrect_otp: 你的 OTP 碼 不正確。
session_expired:
otp_code: OTP 碼
otp_or_recovery:
recovery_code:
require_totp_disabled:
require_mfa_enabled: 你的多重要素驗證已停用,請先啟用。
require_totp_enabled:
Expand Down Expand Up @@ -590,6 +592,7 @@ zh-TW:
sign_in_with_webauthn_credential:
otp_code:
otp_or_recovery:
recovery_code:
security_device:
verify_code:
stats:
Expand Down
Loading

0 comments on commit 74e7294

Please sign in to comment.