@ -263,6 +263,143 @@ after them):
bundle exec rake db:rollback STEP=3
bundle exec rake db:rollback STEP=3
```
```
#### Critical Security Note! Add before_action to your user registration controllers
You should have a file registrations_controller.rb in your controllers folder
to overwrite/customize user registrations. It should include the lines below, for 2FA protection of user model updates, meaning that users can only access the users/edit page after confirming 2FA fully, not simply by logging in. Otherwise the entire 2FA system can be bypassed!
```ruby
class RegistrationsController < Devise::RegistrationsController
before_action :confirm_two_factor_authenticated, except: [:new, :create, :cancel]
protected
def confirm_two_factor_authenticated
return if is_fully_authenticated?
flash[:error] = t('devise.errors.messages.user_not_authenticated')
redirect_to user_two_factor_authentication_url
end
end
```
#### Critical Security Note! Add 2FA validation to your custom user actions
Make sure you are passing the 2FA secret codes securely and checking for them upon critical user actions, such as API key updates, user email or pgp pubkey updates, or any other changess to private/secure account-related details. Validate the secret during the initial 2FA key/secret verification by the user also, of course.
For example, a simple account_controller.rb may look something like this:
```
require 'json'
class AccountController < ApplicationController
before_action :require_signed_in!
before_action :authenticate_user!
respond_to :html, :json
def account_API
resp = {}
begin
if(account_params["twoFAKey"] & & account_params["twoFASecret"])
current_user.otp_secret_key = account_params["twoFAKey"]
if(current_user.authenticate_totp(account_params["twoFASecret"]))
# user has validated their temporary 2FA code, save it to their account, enable 2FA on this account
current_user.save!
resp['success'] = "passed 2FA validation!"
else
resp['error'] = "failed 2FA validation!"
end
elsif(param[:userAccountStuff] & & param[:userAccountWidget])
#before updating important user account stuff and widgets,
#check to see that the 2FA secret has also been passed in, and verify it...
if(account_params["twoFASecret"] & & current_user.totp_enabled? & & current_user.authenticate_totp(account_params["twoFASecret"]))
# user has passed 2FA checks, do cool user account stuff here
...
else
# user failed 2FA check! No cool user stuff happens!
resp[error] = 'You failed 2FA validation!'
end
...
end
else
resp['error'] = 'unknown format error, not saved!'
end
rescue Exception => e
puts "WARNING: account api threw error : '#{e}' for user #{current_user.username}"
#print "error trace: #{e.backtrace}\n"
resp['error'] = "unanticipated server response"
end
render json: resp.to_json
end
def account_params
params.require(:twoFA).permit(:userAccountStuff, :userAcountWidget, :twoFAKey, :twoFASecret)
end
end
```
### Example App
### Example App
[TwoFactorAuthenticationExample ](https://github.com/Houdini/TwoFactorAuthenticationExample )
[TwoFactorAuthenticationExample ](https://github.com/Houdini/TwoFactorAuthenticationExample )
### Example user actions
to use an ENV VAR for the 2FA encryption key:
config.otp_secret_encryption_key = ENV['OTP_SECRET_ENCRYPTION_KEY']
to set up TOTP for Google Authenticator for user:
```
current_user.otp_secret_key = current_user.generate_totp_secret
current_user.save!
```
( encrypted db fields are set upon user model save action,
rails c access relies on setting env var: OTP_SECRET_ENCRYPTION_KEY )
to check if user has input the correct code (from the QR display page)
before saving the user model:
```
current_user.authenticate_totp('123456')
```
additional note:
```
current_user.otp_secret_key
```
This returns the OTP secret key in plaintext for the user (if you have set the env var) in the console
the string used for generating the QR given to the user for their Google Auth is something like:
otpauth://totp/LABEL?secret=p6wwetjnkjnrcmpd (example secret used here)
where LABEL should be something like "example.com (Username)", which shows up in their GA app to remind them the code is for example.com
this returns true or false with an allowed_otp_drift_seconds 'grace period'
to set TOTP to DISABLED for a user account:
```
current_user.second_factor_attempts_count=nil
current_user.encrypted_otp_secret_key=nil
current_user.encrypted_otp_secret_key_iv=nil
current_user.encrypted_otp_secret_key_salt=nil
current_user.direct_otp=nil
current_user.direct_otp_sent_at=nil
current_user.totp_timestamp=nil
current_user.direct_otp=nil
current_user.otp_secret_key=nil
current_user.otp_confirmed=nil
current_user.save! (if in ruby code instead of console)
current_user.direct_otp? => false
current_user.totp_enabled? => false
```