# == Schema Information # # Table name: super_admins # # id :integer not null, primary key # consumed_timestep :integer # current_sign_in_at :datetime # current_sign_in_ip :string # email :string default(""), not null # encrypted_otp_secret :string # encrypted_otp_secret_iv :string # encrypted_otp_secret_salt :string # encrypted_password :string default(""), not null # failed_attempts :integer default(0), not null # last_sign_in_at :datetime # 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 # sign_in_count :integer default(0), not null # unlock_token :string # created_at :datetime # updated_at :datetime # class SuperAdmin < ApplicationRecord include PasswordComplexityConcern devise :rememberable, :trackable, :validatable, :lockable, :recoverable if SUPER_ADMIN_OTP_ENABLED devise :two_factor_authenticatable, :otp_secret_encryption_key => Rails.application.secrets.otp_secret_key else devise :database_authenticatable end def enable_otp! self.otp_secret = SuperAdmin.generate_otp_secret self.otp_required_for_login = true save! end def disable_otp! self.assign_attributes( { otp_secret: nil, encrypted_otp_secret: nil, encrypted_otp_secret_iv: nil, encrypted_otp_secret_salt: nil, consumed_timestep: nil, otp_required_for_login: false } ) save! end def invite_admin(email) user = User.create_or_promote_to_administrateur(email, SecureRandom.hex) if user.valid? user.invite_administrateur!(id) end user end 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