Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Two Factor Authentication #147

Merged
merged 27 commits into from Oct 23, 2020
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
141ff59
Implement Two Factor Authentication
raccube Oct 18, 2020
25410e1
Fix OTP auth triggering for users who haven't set it up
raccube Oct 18, 2020
4ce5dfc
Fix detaching, improve UI for attaching 2FA
raccube Oct 18, 2020
d550e6d
Add help text and confirmation for disabling 2FA
raccube Oct 18, 2020
5447e90
Shorten 2FA setup OTP validation field
raccube Oct 18, 2020
00da21a
Redirect away from two factor entry page if no target user is set in …
raccube Oct 19, 2020
433f1d4
Use controller for setting up QR Code
raccube Oct 19, 2020
70b8053
Add F-Droid & GitHub links for Android TOTP App
raccube Oct 19, 2020
dc88ac3
haml-lint fixes
raccube Oct 19, 2020
ea99805
Fix remaining lint warnings
raccube Oct 19, 2020
3211f8f
Make OTP secret longer
raccube Oct 19, 2020
febcf34
Add basic login form tests
raccube Oct 20, 2020
556050a
Add tests for security settings page
raccube Oct 21, 2020
d3cc421
Rename settings partials to match naming conventions
raccube Oct 21, 2020
68b1bbb
Fix bad refactor
raccube Oct 21, 2020
d89d7a0
Add trailing new line to settings partials
raccube Oct 21, 2020
55de0e4
Add test for #update_2fa endpoint
raccube Oct 21, 2020
482b799
Add test for #destroy_2fa endpoint
raccube Oct 21, 2020
d7a1750
Implement @nilsding's review changes
raccube Oct 23, 2020
66cccbb
Use the same string for 2FA failures
raccube Oct 23, 2020
7021562
Remove user/sessions#two_factor_entry
raccube Oct 23, 2020
0f80bce
Remove I18n. prefix
raccube Oct 23, 2020
75c7827
Add string for views.auth.2fa.errors.invalid_code
raccube Oct 23, 2020
7f4d6cd
Remove #two_factor_entry test
raccube Oct 23, 2020
37d2b43
Apply styling to OTP attempt field
raccube Oct 23, 2020
ee4b7e2
Auto focus OTP validation field on setup page
raccube Oct 23, 2020
d20f527
Add drift period
raccube Oct 23, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Expand Up @@ -24,6 +24,8 @@ gem 'sweetalert-rails'
gem 'devise', '~> 4.0'
gem 'devise-i18n'
gem 'devise-async'
gem 'active_model_otp'
gem 'rqrcode'
gem 'bootstrap_form'
gem 'font-kit-rails'
gem 'nprogress-rails'
Expand Down
89 changes: 51 additions & 38 deletions Gemfile.lock
Expand Up @@ -59,6 +59,9 @@ GEM
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
active_model_otp (2.0.1)
activemodel
rotp (~> 5.0.0)
activejob (5.2.4.3)
activesupport (= 5.2.4.3)
globalid (>= 0.3.6)
Expand All @@ -84,8 +87,8 @@ GEM
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
arel (9.0.0)
ast (2.4.0)
autoprefixer-rails (9.7.6)
ast (2.4.1)
autoprefixer-rails (9.8.5)
execjs
bcrypt (3.1.13)
better_errors (2.7.1)
Expand All @@ -110,7 +113,7 @@ GEM
buftok (0.2.0)
builder (3.2.4)
byebug (11.1.3)
capybara (3.32.2)
capybara (3.33.0)
addressable
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
Expand All @@ -125,8 +128,9 @@ GEM
image_processing (~> 1.1)
mimemagic (>= 0.3.0)
mini_mime (>= 0.1.3)
chunky_png (1.3.12)
cliver (0.3.2)
coderay (1.1.2)
coderay (1.1.3)
coffee-rails (4.2.2)
coffee-script (>= 2.2.0)
railties (>= 4.0.0)
Expand All @@ -140,7 +144,7 @@ GEM
crass (1.0.6)
database_cleaner (1.8.5)
debug_inspector (0.0.3)
devise (4.7.1)
devise (4.7.2)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
Expand All @@ -151,19 +155,19 @@ GEM
devise (>= 4.0)
devise-i18n (1.9.1)
devise (>= 4.7.1)
diff-lcs (1.3)
diff-lcs (1.4.4)
docile (1.3.2)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
equalizer (0.0.11)
erubi (1.9.0)
excon (0.73.0)
excon (0.75.0)
execjs (2.7.0)
factory_bot (5.2.0)
activesupport (>= 4.2.0)
factory_bot_rails (5.2.0)
factory_bot (~> 5.2.0)
railties (>= 4.2.0)
factory_bot (6.1.0)
activesupport (>= 5.0.0)
factory_bot_rails (6.1.0)
factory_bot (~> 6.1.0)
railties (>= 5.0.0)
fake_email_validator (1.0.11)
activemodel
mail
Expand All @@ -173,11 +177,11 @@ GEM
multipart-post (>= 1.2, < 3)
faraday_middleware (1.0.0)
faraday (~> 1.0)
ffi (1.12.2)
ffi (1.13.1)
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
fog-aws (3.6.5)
fog-aws (3.6.6)
fog-core (~> 2.1)
fog-json (~> 1.1)
fog-xml (~> 0.1)
Expand Down Expand Up @@ -236,7 +240,7 @@ GEM
http-parser (1.2.1)
ffi-compiler (>= 1.0, < 2.0)
http_parser.rb (0.6.0)
httparty (0.18.0)
httparty (0.18.1)
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
i18n (0.9.5)
Expand All @@ -261,7 +265,7 @@ GEM
turbolinks
jquery-ui-rails (6.0.1)
railties (>= 3.2.16)
json (2.3.0)
json (2.3.1)
kaminari (1.2.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.1)
Expand All @@ -281,10 +285,10 @@ GEM
listen (3.2.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.5.0)
loofah (2.6.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
lumberjack (1.2.4)
lumberjack (1.2.6)
mail (2.7.1)
mini_mime (>= 0.1.1)
marcel (0.3.3)
Expand All @@ -304,15 +308,15 @@ GEM
momentjs-rails (>= 2.10.5, <= 3.0.0)
momentjs-rails (2.20.1)
railties (>= 3.1)
multi_json (1.14.1)
multi_json (1.15.0)
multi_xml (0.6.0)
multipart-post (2.1.1)
naught (1.1.0)
nenv (0.3.0)
nested_form (0.3.2)
newrelic_rpm (6.10.0.364)
newrelic_rpm (6.11.0.365)
nio4r (2.5.2)
nokogiri (1.10.9)
nokogiri (1.10.10)
mini_portile2 (~> 2.4.0)
nokogumbo (2.0.2)
nokogiri (~> 1.8, >= 1.8.4)
Expand All @@ -334,9 +338,9 @@ GEM
omniauth-oauth (~> 1.1)
rack
orm_adapter (0.5.0)
parallel (1.19.1)
parser (2.7.1.2)
ast (~> 2.4.0)
parallel (1.19.2)
parser (2.7.1.4)
ast (~> 2.4.1)
pg (1.2.3)
pghero (2.7.0)
activerecord (>= 5)
Expand Down Expand Up @@ -375,10 +379,10 @@ GEM
rails-assets-growl (1.3.5)
rails-assets-jquery
rails-assets-jquery (2.2.4)
rails-controller-testing (1.0.4)
actionpack (>= 5.0.1.x)
actionview (>= 5.0.1.x)
activesupport (>= 5.0.1.x)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
Expand Down Expand Up @@ -412,13 +416,19 @@ GEM
ffi (~> 1.0)
redcarpet (3.5.0)
redis (4.1.4)
regexp_parser (1.7.0)
regexp_parser (1.7.1)
remotipart (1.4.4)
responders (3.0.0)
responders (3.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
rexml (3.2.4)
rolify (5.2.0)
rolify (5.3.0)
rotp (5.0.0)
addressable (~> 2.5)
rqrcode (1.1.2)
chunky_png (~> 1.0)
rqrcode_core (~> 0.1)
rqrcode_core (0.1.2)
rspec-core (3.9.2)
rspec-support (~> 3.9.3)
rspec-expectations (3.9.2)
Expand All @@ -438,19 +448,20 @@ GEM
rspec-expectations (~> 3.9.0)
rspec-mocks (~> 3.9.0)
rspec-support (~> 3.9.0)
rspec-sidekiq (3.0.3)
rspec-sidekiq (3.1.0)
rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0)
rspec-support (3.9.3)
rubocop (0.84.0)
rubocop (0.88.0)
parallel (~> 1.10)
parser (>= 2.7.0.1)
parser (>= 2.7.1.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.7)
rexml
rubocop-ast (>= 0.0.3)
rubocop-ast (>= 0.1.0, < 1.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (0.0.3)
rubocop-ast (0.1.0)
parser (>= 2.7.0.1)
ruby-progressbar (1.10.1)
ruby-vips (2.0.17)
Expand All @@ -470,7 +481,7 @@ GEM
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
sassc (2.3.0)
sassc (2.4.0)
ffi (~> 1.9)
sassc-rails (2.1.2)
railties (>= 4.0.0)
Expand Down Expand Up @@ -540,7 +551,7 @@ GEM
activemodel (>= 5.0)
bindex (>= 0.4.0)
railties (>= 5.0)
websocket-driver (0.7.2)
websocket-driver (0.7.3)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
xpath (3.2.0)
Expand All @@ -550,6 +561,7 @@ PLATFORMS
ruby

DEPENDENCIES
active_model_otp
bcrypt (~> 3.1.7)
better_errors
binding_of_caller
Expand Down Expand Up @@ -609,6 +621,7 @@ DEPENDENCIES
redcarpet
redis
rolify (~> 5.2)
rqrcode
rspec-its (~> 1.3)
rspec-mocks
rspec-rails (~> 3.9)
Expand Down
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.scss
Expand Up @@ -91,6 +91,7 @@
"components/profile",
"components/question",
"components/smiles",
"components/totp-setup",
"components/userbox";

/**
Expand Down
36 changes: 36 additions & 0 deletions app/assets/stylesheets/components/_totp-setup.scss
@@ -0,0 +1,36 @@
.totp-setup {
display: flex;

&__card {
background: var(--primary);
padding: 10px;
border-radius: 5px;
width: 256px;
}

&__qr {
background: white;
border-radius: 5px;
}

&__text {
background: #000;
color: #fff;
margin: 10px 0 0 0;
padding: 5px;
border-radius: 5px;

code {
color: var(--warning);
}
}

&__right {
margin-left: 20px;
}

&__code-field {
font-family: "Monaco", "Inconsolata", "Cascadia Code", "Consolas", monospace;
width: 86px;
}
}
47 changes: 47 additions & 0 deletions app/controllers/user/sessions_controller.rb
@@ -0,0 +1,47 @@
class User::SessionsController < Devise::SessionsController
def create
raccube marked this conversation as resolved.
Show resolved Hide resolved
if session.has_key?(:user_sign_in_uid)
self.resource = User.find(session[:user_sign_in_uid])
session.delete(:user_sign_in_uid)
raccube marked this conversation as resolved.
Show resolved Hide resolved
else
self.resource = warden.authenticate!(auth_options)
end

if resource.active_for_authentication? && resource.otp_module_enabled?
if params[:user][:otp_attempt].blank?
session[:user_sign_in_uid] = resource.id
sign_out(resource)
redirect_to user_two_factor_entry_url
raccube marked this conversation as resolved.
Show resolved Hide resolved
else
if resource.authenticate_otp(params[:user][:otp_attempt])
continue_sign_in(resource, resource_name)
else
sign_out(resource)
flash[:error] = t('devise.failure.invalid')
raccube marked this conversation as resolved.
Show resolved Hide resolved
redirect_to root_url
raccube marked this conversation as resolved.
Show resolved Hide resolved
end
end
else
continue_sign_in(resource, resource_name)
end
end

def two_factor_entry
unless session.has_key? :user_sign_in_uid
redirect_to root_url
return
end

self.resource = User.find(session[:user_sign_in_uid])
render 'auth/two_factor_authentication'
end
raccube marked this conversation as resolved.
Show resolved Hide resolved

private

def continue_sign_in(resource, resource_name)
set_flash_message!(:notice, :signed_in)
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource)
end
end
33 changes: 33 additions & 0 deletions app/controllers/user_controller.rb
Expand Up @@ -172,4 +172,37 @@ def begin_export

redirect_to user_export_path
end

def edit_security
if current_user.otp_module_disabled?
current_user.otp_secret_key = User.otp_random_secret(26)

@provisioning_uri = current_user.provisioning_uri(nil, issuer: APP_CONFIG[:hostname])
qr_code = RQRCode::QRCode.new(current_user.provisioning_uri("Retrospring:#{current_user.screen_name}", issuer: "Retrospring"))

@qr_svg = qr_code.as_svg({offset: 4, module_size: 4, color: '000;fill:var(--primary)'}).html_safe
end
end

def update_2fa
req_params = params.require(:user).permit(:otp_secret_key, :otp_validation)
current_user.otp_secret_key = req_params[:otp_secret_key]
current_user.otp_module = :enabled

if current_user.authenticate_otp(req_params[:otp_validation])
flash[:success] = 'Two factor authentication has been enabled for your account.'
current_user.save!
else
flash[:error] = 'The code you entered was invalid.'
end

redirect_to edit_user_security_path
end

def destroy_2fa
current_user.otp_module = :disabled
current_user.save!
flash[:success] = 'Two factor authentication has been disabled for your account.'
redirect_to edit_user_security_path
end
end