chore(super-admin): update devise-two-factor v5 with otp_secret encrypted attribute
This commit is contained in:
parent
eb88c5e9de
commit
6616acb825
3 changed files with 80 additions and 1 deletions
|
@ -16,6 +16,7 @@
|
|||
# last_sign_in_ip :string
|
||||
# locked_at :datetime
|
||||
# otp_required_for_login :boolean
|
||||
# otp_secret :string
|
||||
# remember_created_at :datetime
|
||||
# reset_password_sent_at :datetime
|
||||
# reset_password_token :string
|
||||
|
@ -43,6 +44,7 @@ class SuperAdmin < ApplicationRecord
|
|||
def disable_otp!
|
||||
self.assign_attributes(
|
||||
{
|
||||
otp_secret: nil,
|
||||
encrypted_otp_secret: nil,
|
||||
encrypted_otp_secret_iv: nil,
|
||||
encrypted_otp_secret_salt: nil,
|
||||
|
@ -66,4 +68,75 @@ class SuperAdmin < ApplicationRecord
|
|||
def send_devise_notification(notification, *args)
|
||||
devise_mailer.send(notification, self, *args).deliver_later
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# From https://github.com/tinfoil/devise-two-factor/blob/main/UPGRADING.md
|
||||
# Remove me after super admin have been migrated to the new OTP system.
|
||||
# Decrypt and return the `encrypted_otp_secret` attribute which was used in
|
||||
# prior versions of devise-two-factor
|
||||
# @return [String] The decrypted OTP secret
|
||||
def legacy_otp_secret
|
||||
return nil unless self[:encrypted_otp_secret]
|
||||
return nil unless self.class.otp_secret_encryption_key
|
||||
|
||||
hmac_iterations = 2000 # a default set by the Encryptor gem
|
||||
key = self.class.otp_secret_encryption_key
|
||||
salt = Base64.decode64(encrypted_otp_secret_salt)
|
||||
iv = Base64.decode64(encrypted_otp_secret_iv)
|
||||
|
||||
raw_cipher_text = Base64.decode64(encrypted_otp_secret)
|
||||
# The last 16 bytes of the ciphertext are the authentication tag - we use
|
||||
# Galois Counter Mode which is an authenticated encryption mode
|
||||
cipher_text = raw_cipher_text[0..-17]
|
||||
auth_tag = raw_cipher_text[-16..-1]
|
||||
|
||||
# this alrorithm lifted from
|
||||
# https://github.com/attr-encrypted/encryptor/blob/master/lib/encryptor.rb#L54
|
||||
|
||||
# create an OpenSSL object which will decrypt the AES cipher with 256 bit
|
||||
# keys in Galois Counter Mode (GCM). See
|
||||
# https://ruby.github.io/openssl/OpenSSL/Cipher.html
|
||||
cipher = OpenSSL::Cipher.new('aes-256-gcm')
|
||||
|
||||
# tell the cipher we want to decrypt. Symmetric algorithms use a very
|
||||
# similar process for encryption and decryption, hence the same object can
|
||||
# do both.
|
||||
cipher.decrypt
|
||||
|
||||
# Use a Password-Based Key Derivation Function to generate the key actually
|
||||
# used for encryptoin from the key we got as input.
|
||||
cipher.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(key, salt, hmac_iterations, cipher.key_len)
|
||||
|
||||
# set the Initialization Vector (IV)
|
||||
cipher.iv = iv
|
||||
|
||||
# The tag must be set after calling Cipher#decrypt, Cipher#key= and
|
||||
# Cipher#iv=, but before calling Cipher#final. After all decryption is
|
||||
# performed, the tag is verified automatically in the call to Cipher#final.
|
||||
#
|
||||
# If the auth_tag does not verify, then #final will raise OpenSSL::Cipher::CipherError
|
||||
cipher.auth_tag = auth_tag
|
||||
|
||||
# auth_data must be set after auth_tag has been set when decrypting See
|
||||
# http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html#method-i-auth_data-3D
|
||||
# we are not adding any authenticated data but OpenSSL docs say this should
|
||||
# still be called.
|
||||
cipher.auth_data = ''
|
||||
|
||||
# #update is (somewhat confusingly named) the method which actually
|
||||
# performs the decryption on the given chunk of data. Our OTP secret is
|
||||
# short so we only need to call it once.
|
||||
#
|
||||
# It is very important that we call #final because:
|
||||
#
|
||||
# 1. The authentication tag is checked during the call to #final
|
||||
# 2. Block based cipher modes (e.g. CBC) work on fixed size chunks. We need
|
||||
# to call #final to get it to process the last chunk properly. The output
|
||||
# of #final should be appended to the decrypted value. This isn't
|
||||
# required for streaming cipher modes but including it is a best practice
|
||||
# so that your code will continue to function correctly even if you later
|
||||
# change to a block cipher mode.
|
||||
cipher.update(cipher_text) + cipher.final
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddOtpSecretToSuperAdmin < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :super_admins, :otp_secret, :string
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_03_31_125931) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_05_02_160046) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pgcrypto"
|
||||
enable_extension "plpgsql"
|
||||
|
@ -843,6 +843,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_31_125931) do
|
|||
t.boolean "otp_required_for_login"
|
||||
t.datetime "remember_created_at", precision: 6
|
||||
t.datetime "reset_password_sent_at", precision: 6
|
||||
t.string "otp_secret"
|
||||
t.string "reset_password_token"
|
||||
t.integer "sign_in_count", default: 0, null: false
|
||||
t.string "unlock_token"
|
||||
|
|
Loading…
Reference in a new issue