Migrating existing authentication to has_secure_password - ruby-on-rails

In my application, I have two different user accounts. The old account type was using a custom built authentication system. The newer one implements has_secure_password. Now I'm ready to move the old account type to the same system. In the database, that user type has a hashed_password column.
I have it working so that creating new users works, and they can login just fine on the new system. The problem is that I need existing users to be able to migrate their passwords from the hashed_password to password_digest. What is the best way to go about doing this?

It's possible, but you'll have to juggle a bit. Let's say you've used MD5 to hash passwords before you wanted to migrate. Make sure you have made a backup before you migrate :) Add a boolean column migrated_password to the database, default true. In your migration, do something like User.update_all(migrated_password: false) for existing users.
Use the current MD5 hashes as input for the has_secure_password function. Alter your login code to handle two paths, depending on the value of the migrated_password column. If the password is migrated (or the user registered after the migration), you use has_secure_password straight out of the box.
If it isn't, you hash the password with MD5 before feeding it to the authenticate method. If the login is successful, change the password of that user with the input password from the params and update the migrated_password column to false (wrap those two actions in a transaction).
After a certain amount of time you can delete the the migrated_password column and the migration code, and let users use your password reset functionality if they still need access but haven't migrated in time.

Related

Rails App With No Sign Up But Has Log In

I need some advice on a Rails app where I want to have signing up to be private (or not have one at all with users pre-registered in the seeds.rb file). In other words, I do not want random people off of the internet to be able to sign up. I know that I could easily not making a signup form and tweak my app accordingly, but the problem lies with the seeds.rb file containing sensitive information (i.e., passwords). The passwords need to somehow be encrypted. Any advice would help. Thanks.
You have multiple options:
Disable sign up and do it manually using an admin user (thinking about ActiveAdmin gem).
Register that users at DB (without rails' knowledge). I don't know what DB you're using, but imagine you're using postgreSQL (for instance). Go to your DB and manually insert the users on DB using some kind of user interface (command-line or graphic/visual)
Use environment variables at deployment that contain passwords.
Give them a default password and make them change password once they log in.
... there are more options, but you need to be explicit about what you want to achieve. Do you want to start with X users and don't add more? Do you want to start with X users and be able to expand to more users?

Password Blacklists

I've got a blacklist of passwords that I don't want Users to be able to select when they're creating their account or changing there password in my Rails app.
I want this to be in a database table rather than a YAML file.
How can I refer/check if the user's submitted password exists in this list? What's a way to do it?
It doesn't feel right that I'd need o create an ActiveRecord Model for it etc.
How can I refer/check if the user's submitted password exists in this list? What's a way to do it?
If you encrypt your user passwords (like I hope you do), it's a little bit challenging to perform this check (in fact, that's exactly one of the purposes of salt encryption passwords).
You will need, for each user in your database, loop each password, encrypt it using your current encryption strategy and match the result with the encrypted string stored in the database for the user. If they do match, it means the user is using one of those passwords.
And that's for already stored passwords.
I don't want Users to be able to select when they're creating their account or changing there password in my Rails app.
This is easier than previous step. When the user is signin up or updating the password, just compare the user-entered unencrypted password with the list you have. If there is a match, return a validation error.

Changing database structure and migrating password hashes

I currently have a site (Rails 4.1, ActiveRecord, Postgres) where a visitor can log in to one of multiple models — for example, a visitor can create an account or login as a User, Artist, etc. Each of these models have a password_digest column (using bcrypt and has_secure_password).
Now we want to move to the site to a unified login system — everyone creates and logs in as a User, and a User can belong to an Artist and the other models we have.
I think it makes sense to directly use the password_digest column in the User table, rather than looking across all the existing models. This means we'll have to create new entries in the User table and copy the password_digests into them.
Can this be safely done, and would everyone be able to login with the password they already have? I've tried playing around with password_digests in the Rails console (copying digests to known passwords and assigning them to other entries) and it appears to authenticate correctly … are there any downsides to doing this?
There's no uniqueness constraint on passwords (i assume) and so it doesn't matter if the passwords are the same between different User accounts (in the resulting table, with all the Artist etc records copied in). There's no safety issues with copying the data from one table/column to another: there's nothing magical about the password_digest value, it's just a text string. As long as you carry on using the same encryption method then the crypted password you generate to test on login should still match the saved value.
You may have a problem with usernames though, if they are required to be unique: what happens if you have an existing User and an existing Artist who have the same username? Is one of them going to have to change?

Migrate legacy database users to Devise

We have an old PHP app in MySQL that we're currently rewriting in Rails and PostgreSQL.
I'm looking for a way to migrate users one-by-one when they first sign in to the new system, so that we migrate only active users.
Is there a hook in Devise that I can use to catch a failed login towards the new database and check if the user exists in the old MySQL database and migrate the user if found.
BTW, the Rails app can access both databases and the migration code is already in place that accepts old username as input.
Or should I just simply make a separate _migration_assistant_ page that accepts old logins and initiates the migration?
PS. any user can possibly have thousands of database records to migrate so it can take a bit of time, but we already make use of PosgreSQL's COPY FROM to speed it up (about a few secs per account).
EDIT:
There are currently about 5000 users and we expect roughly 1000 of them to return to the new site.
You could migrate the required data from just your users table. Include the original primary key in a column 'old_id'.
Create a custom encryptor so that users can log in with their existing password.
Redirect users when they log in to a page that triggers the migration process per user making use of the saved 'old_id'.
After your migration period you could look at the Devise trackable columns to determine which users can be cleaned up.

Conditional password validation in Rails

In my application I have two ways of singing up users:
Sign up form
Facebook Connect
In both ways we store information into the database, but just in the first want we want to store the password.
I have some password related validations that I want to be performed for the first way of users signing up, but I don't want it to happen for the second one. What would be the appropriate and secure way of doing this in Rails?
My first approach was creating an attribute for the user object called password_optional and do a conditional in the validation with that, but I'm not sure how can I set that attribute by default to false or set it to false when the user is signing up using the form.
If you don't have a password when user signs up via facebook, but your validation requires it, set it to some random string then.
This is exactly what is recommended in Devise documentation.
Skip the validations when you don't need it. There are multiple ways of doing that
Maybe you could have two models User::Normal < User and User::Facebook < User. Most of the logic goes into User, and specificities go into the custom models.
Maybe you could just go validates_presence_of :password, :if => not_facebook? or something of the sort.

Resources