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
This commit is contained in:
Go MAEDA
2020-08-29 06:51:21 +00:00
parent be7f5e21fa
commit 8900eb6eb5
15 changed files with 230 additions and 19 deletions

View File

@@ -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