From 8900eb6eb5994310e3f957398cc21a512c5951ab Mon Sep 17 00:00:00 2001 From: Go MAEDA Date: Sat, 29 Aug 2020 06:51:21 +0000 Subject: Backup codes for 2fa auth (#1237). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch by Felix Schäfer. git-svn-id: http://svn.redmine.org/redmine/trunk@19990 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- lib/redmine/twofa/base.rb | 50 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) (limited to 'lib/redmine/twofa') diff --git a/lib/redmine/twofa/base.rb b/lib/redmine/twofa/base.rb index 8369c8f6a..e959aa930 100644 --- a/lib/redmine/twofa/base.rb +++ b/lib/redmine/twofa/base.rb @@ -42,7 +42,7 @@ module Redmine end def confirm_pairing!(code) - # make sure an otp is used + # make sure an otp and not a backup code is used if verify_otp!(code) @user.update!(twofa_scheme: scheme_name) deliver_twofa_paired @@ -77,6 +77,7 @@ module Redmine def destroy_pairing_without_verify! @user.update!(twofa_scheme: nil) + backup_codes.delete_all deliver_twofa_unpaired end @@ -98,13 +99,58 @@ module Redmine end def verify!(code) - verify_otp!(code) + verify_otp!(code) || verify_backup_code!(code) end def verify_otp!(code) raise 'not implemented' end + def verify_backup_code!(code) + # backup codes are case-insensitive and white-space-insensitive + code = code.to_s.remove(/[[:space:]]/).downcase + user_from_code = Token.find_active_user('twofa_backup_code', code) + # invalidate backup code after usage + Token.where(user_id: @user.id).find_token('twofa_backup_code', code).try(:delete) + # make sure the user using the backup code is the same it's been issued to + return false unless @user.present? && @user == user_from_code + Mailer.security_notification( + @user, + User.current, + { + originator: @user, + title: :label_my_account, + message: 'twofa_mail_body_backup_code_used', + url: { controller: 'my', action: 'account' } + } + ).deliver + return true + end + + def init_backup_codes! + backup_codes.delete_all + tokens = [] + 10.times do + token = Token.create(user_id: @user.id, action: 'twofa_backup_code') + token.update_columns value: Redmine::Utils.random_hex(6) + tokens << token + end + Mailer.security_notification( + @user, + User.current, + { + title: :label_my_account, + message: 'twofa_mail_body_backup_codes_generated', + url: { controller: 'my', action: 'account' } + } + ).deliver + tokens + end + + def backup_codes + Token.where(user_id: @user.id, action: 'twofa_backup_code') + end + # this will only be used on pairing initialization def init_pairing_view_variables otp_confirm_view_variables -- cgit v1.2.3