18F/identity-idp

View on GitHub
app/services/encryption/uak_password_verifier.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
# frozen_string_literal: true

module Encryption
  class UakPasswordVerifier
    PasswordDigest = RedactedStruct.new(
      :encrypted_password,
      :encryption_key,
      :password_salt,
      :password_cost,
    ) do
      def self.parse_from_string(digest_string)
        data = JSON.parse(digest_string, symbolize_names: true)
        new(
          data[:encrypted_password],
          data[:encryption_key],
          data[:password_salt],
          data[:password_cost],
        )
      rescue JSON::ParserError, TypeError
        raise EncryptionError, 'digest contains invalid json'
      end

      def to_s
        {
          encrypted_password: encrypted_password,
          encryption_key: encryption_key,
          password_salt: password_salt,
          password_cost: password_cost,
        }.to_json
      end
    end.freeze

    def self.digest(password)
      salt = SecureRandom.hex(32)
      uak = UserAccessKey.new(password: password, salt: salt)
      uak.build
      PasswordDigest.new(
        uak.encrypted_password,
        uak.encryption_key,
        salt,
        uak.cost,
      ).to_s
    end

    def self.verify(password:, digest:, user_uuid:, log_context:)
      return false if password.blank?
      parsed_digest = PasswordDigest.parse_from_string(digest)
      uak = UserAccessKey.new(
        password: password,
        salt: parsed_digest.password_salt,
        cost: parsed_digest.password_cost,
        user_uuid: user_uuid,
        log_context: log_context,
      )
      uak.unlock(parsed_digest.encryption_key)
      Devise.secure_compare(uak.encrypted_password, parsed_digest.encrypted_password)
    rescue EncryptionError
      false
    end
  end
end