devise-two-factor icon indicating copy to clipboard operation
devise-two-factor copied to clipboard

Support NoSQL databases in generator

Open jessicard opened this issue 11 years ago • 12 comments

It would be awesome if, in the generator, you could specify an optional arg that let you generate for a NoSQL database (like Mongoid). So instead of creating a database migration, it would just add the appropriate fields to the model file.

jessicard avatar Oct 15 '14 20:10 jessicard

That's a great idea. I don't know how well attr_encrypted plays with Mongoid, but I'll investigate when I have some free-time later.

QuinnWilton avatar Oct 15 '14 21:10 QuinnWilton

We have a fork at http://github.com/tinfoil/attr_encrypted that patches in Mongoid support. So long as you have the proper fields declared, it should work with something like:

field :encrypted_otp_secret, :type => String
field :encrypted_otp_secret_iv, :type => String
field :encrypted_otp_secret_salt, :type => String
attr_encrypted :otp_secret, :key => ENV['OTP_SECRET_DB_KEY']

bsedat avatar Oct 15 '14 21:10 bsedat

@bsedat I'm using your fork of attr_encrypted for my rails 3 app using mongoid. I'm trying to get this set up, but nothing is getting saved to the encrypted fields when I set the otp_secret and save the user.

Here's an example from the console:

1.9.3-head :003 > u.otp_secret = User.generate_otp_secret
 => "u7n3t3fmn3q2rwnduvcwrpzplkp6nhxgv7fnerytmzym46rk2him74l2hhti4kfijhp7w6fmwqvhnqxzbcparznop62edmivcyiy7q5hdsak3v62smptmvahhipb25sc" 
1.9.3-head :004 > u.save!
 => true 
1.9.3-head :006 > u.inspect
 => "#<User _id: 531a3cdcc8eb1cd895000008, created_at: 2014-03-07 21:40:44 UTC, updated_at: 2014-10-24 21:50:51 UTC, invited_by_type: nil, invited_by_field: nil, invited_by_id: nil, email: \"FAKEFAKEFAKE\", encrypted_password: \"BLAHBLAHBLAH\", authentication_token: \"WAHWAHWAH\", [...bunch of stuff], encrypted_otp_secret: nil, encrypted_otp_secret_iv: nil, encrypted_otp_secret_salt: nil, active: false, [...bunch of other stuff]>" 

Any ideas why the otp fields aren't saving?

I've tried all the combinations of fields that I can think of on my user model after reading several other attr_encrypted issues

Here's the last thing I tried, which is maximum fields:

  ## Two-Factor
  attr_accessible :otp_attempt
  field :otp_required_for_login,      type:Boolean, default:true ## TODO remove this--should not be on by default
  field :otp_secret,                  type:String
  field :otp_secret_iv,               type:String
  field :otp_secret_salt,             type:String
  field :encrypted_otp_secret,        type:String
  field :encrypted_otp_secret_iv,     type:String
  field :encrypted_otp_secret_salt,   type:String
  attr_encrypted :otp_secret, :key => 'super_secret_two_factor_key', :encode => true, :mode => :per_attribute_iv_and_salt
  attr_encrypted :otp_secret_iv, :key => 'super_secret_two_factor_key', :encode => true, :mode => :per_attribute_iv_and_salt
  attr_encrypted :otp_secret_salt, :key => 'super_secret_two_factor_key', :encode => true, :mode => :per_attribute_iv_and_salt

atroutt avatar Oct 29 '14 15:10 atroutt

I got things to save by explicitly calling update_attribute, but now when I try to access the unencrypted fields I get "OpenSSL::Cipher::CipherError: bad decrypt"

atroutt avatar Oct 31 '14 14:10 atroutt

I couldn't get this working with rails 3 & mongoid. Interestingly, I did get attr_encrypted to work on an arbitrary other field, but I could never get the otp_secret to be decrypted correctly. I decided to use https://github.com/Houdini/two_factor_authentication instead.

atroutt avatar Nov 03 '14 14:11 atroutt

I override some methods in my model and this work with mongoid. BTW: i use standard attr_encrypted(not fork).

def encrypted_otp_secret=(value)
    self.write_attribute(:encrypted_otp_secret, value.force_encoding("ISO-8859-1").encode("UTF-8"))
end
def encrypted_otp_secret
    if self.read_attribute(:encrypted_otp_secret).nil?
        super
    else
        self.read_attribute(:encrypted_otp_secret).encode("ISO-8859-1").force_encoding('ASCII-8BIT')
    end
end

P.S. But this not clear way, i think.

JIucToyxuu avatar Mar 25 '16 10:03 JIucToyxuu

Is there a solution for this? It does not work with mongoid (5.1.6).... I tried everything from here, still receiving nil...

pabloq avatar Jun 28 '17 16:06 pabloq

@JIucToyxuu can you explain it bit more. probably with gist ?

abhipatnala avatar Jul 14 '20 14:07 abhipatnala

@QuinnWilton any update on this issue ?

abhipatnala avatar Jul 14 '20 14:07 abhipatnala

@JIucToyxuu is right, but I'll expound. attr_encrypted converts the otp_secret into raw byte data that Mongoid refuses to write into a String field:

user = User.find(id)
user.otp_secret = User.generate_otp_secret
user.save!

user.encrypted_otp_secret
=> "z\xD5\xCEK\x05?Q\x8C\aY\xBB\xF1\x01\x92\x12u\xC24~F\x04\x1C=\x8D\x11\b\xCB\x11,B\x94\xCBj6(\xE4\x8B\x85\x0E\xD0"

User.find(id).encrypted_otp_secret
=> nil

My solution, instead of playing with force_encoding is to write Base64 to the database instead:

# app/models/concerns/base64_encoded.rb
module Base64Encoded
  def base64_encoded(*attributes)
    attributes.each do |attribute|
      define_method :"#{attribute}=" do |value|
        value = Base64.encode64(value) if value
        self.write_attribute(attribute, value)
      end

      define_method attribute do
        value = self.read_attribute(attribute)
        value = Base64.decode64(value) if value
        value
      end
    end
  end
end
  extend Base64Encoded

  field :encrypted_otp_secret, type: String
  field :encrypted_otp_secret_iv, type: String
  field :encrypted_otp_secret_salt, type: String
  field :consumed_timestep, type: Integer
  field :otp_required_for_login, type: Boolean, default: false

  # ensure all encrypted attributes are base64 encoded 
  # so Mongoid can write them into a String field
  base64_encoded :encrypted_otp_secret
  base64_encoded :encrypted_otp_secret_iv
  base64_encoded :encrypted_otp_secret_salt

dcunning avatar Aug 10 '21 21:08 dcunning

I have an error coming from devise_two_factor/models/two_factor_authenticatable.rb:10: undefined method encrypts

Do you know how to get this right?

paul-mesnilgrente avatar Jul 31 '24 15:07 paul-mesnilgrente

I've run into the same problem but cannot offer you a solution other than suggesting you stick to v4.1.1.

The upgrade to v5.0.0 made devise-two-factor rely on active record's encrypts method. This was intentional as it's explicitly mentioned in the repo's README ("Devise-Two-Factor uses ActiveRecord encrypted attributes")

tl;dr v5.0.0 adds activerecord as an undeclared dependency, making it incompatible with any app that uses a NoSQL solution like mongoid over activerecord.

dcunning avatar Aug 02 '24 14:08 dcunning