summaryrefslogtreecommitdiffstats
path: root/lib/redmine/twofa
diff options
context:
space:
mode:
authorGo MAEDA <maeda@farend.jp>2020-08-29 06:51:21 +0000
committerGo MAEDA <maeda@farend.jp>2020-08-29 06:51:21 +0000
commit8900eb6eb5994310e3f957398cc21a512c5951ab (patch)
tree22c37392fef2ef6f750dbd9bda18c90c6cfd45b9 /lib/redmine/twofa
parentbe7f5e21faa05bdc483d1b58c8887ff499082073 (diff)
downloadredmine-8900eb6eb5994310e3f957398cc21a512c5951ab.tar.gz
redmine-8900eb6eb5994310e3f957398cc21a512c5951ab.zip
Backup codes for 2fa auth (#1237).
Patch by Felix Schäfer. git-svn-id: http://svn.redmine.org/redmine/trunk@19990 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'lib/redmine/twofa')
-rw-r--r--lib/redmine/twofa/base.rb50
1 files changed, 48 insertions, 2 deletions
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