Commit 10ab9e4
Changed files (7)
app
controllers
views
dashboards
spec
system
app/controllers/tfas_controller.rb
@@ -2,8 +2,8 @@
class TfasController < ApplicationController
def new
- return redirect_to edit_tfa_path if current_user.tfa_setup?
- current_user.tfa_secret = ::ROTP::Base32.random_base32
+ return redirect_to edit_tfa_path if current_user.tfa.setup?
+ current_user.tfa.build_secret
end
def create
@@ -14,7 +14,7 @@ class TfasController < ApplicationController
def edit; end
def destroy
- current_user.disable_tfa!
+ current_user.tfa.disable!
redirect_to dashboard_path
end
end
app/models/tfa.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class Tfa
+ attr_reader :user
+
+ def initialize(user)
+ @user = user
+ end
+
+ def setup?
+ secret.present?
+ end
+
+ def provisioning_uri
+ totp = ::ROTP::TOTP.new(secret, issuer: 'saml-kit')
+ totp.provisioning_uri(user.email)
+ end
+
+ def build_secret
+ user.tfa_secret = ::ROTP::Base32.random_base32
+ end
+
+ def disable!
+ user.update!(tfa_secret: nil)
+ end
+
+ def secret
+ user.tfa_secret
+ end
+
+ def current_totp
+ ROTP::TOTP.new(secret).now
+ end
+end
app/models/user.rb
@@ -16,17 +16,8 @@ class User < ApplicationRecord
request.trusted? ? trusted_attributes_for(request) : {}
end
- def tfa_provisioning_uri
- totp = ::ROTP::TOTP.new(tfa_secret, issuer: 'saml-kit')
- totp.provisioning_uri(email)
- end
-
- def tfa_setup?
- tfa_secret.present?
- end
-
- def disable_tfa!
- update!(tfa_secret: nil)
+ def tfa
+ Tfa.new(self)
end
def self.login(email, password)
app/views/dashboards/show.html.erb
@@ -2,7 +2,7 @@
<div class="row">
<div class="col">
<h1>Dashboard</h1>
- <% if current_user.tfa_setup? %>
+ <% if current_user.tfa.setup? %>
<%= link_to "TFA", edit_tfa_path %>
<% else %>
<%= link_to "Setup TFA", new_tfa_path %>
app/views/tfas/edit.html.erb
@@ -4,8 +4,8 @@
<h1>Two Factor Authentication (TFA)</h1>
<div data-controller="tfa--setup">
<canvas id="canvas" data-target="tfa--setup.canvas"></canvas>
- <p>Secret: <%= current_user.tfa_secret %></p>
- <p>Provisioning URI: <%= current_user.tfa_provisioning_uri %></p>
+ <p>Secret: <%= current_user.tfa.secret %></p>
+ <p>Provisioning URI: <%= current_user.tfa.provisioning_uri %></p>
<%= form_for current_user, url: tfa_path, method: :delete do |form| %>
<%= form.hidden_field :tfa_secret, data: { target: 'tfa--setup.secret' } %>
app/views/tfas/new.html.erb
@@ -4,8 +4,9 @@
<h1>Two Factor Authentication (TFA) - Setup</h1>
<div data-controller="tfa--setup">
<canvas id="canvas" data-target="tfa--setup.canvas"></canvas>
- <p>Secret: <%= current_user.tfa_secret %></p>
- <p>Provisioning URI: <%= current_user.tfa_provisioning_uri %></p>
+ <p>Secret: <%= current_user.tfa.secret %></p>
+ <p>Provisioning URI: <%= current_user.tfa.provisioning_uri %></p>
+
<%= form_for current_user, url: tfa_path, method: :post do |form| %>
<%= form.hidden_field :tfa_secret, data: { target: 'tfa--setup.secret' } %>
<%= form.submit "Save", class: 'btn btn-primary', data: { disable_with: 'Saving…' } %>
spec/system/direct_login_spec.rb
@@ -1,14 +1,32 @@
require 'rails_helper'
describe "when logging in directly in to the application", js: true do
- let(:user) { create(:user) }
+ describe "when tfa is disabled", js: true do
+ let(:user) { create(:user) }
- it 'redirects the user to the dashboard' do
- visit root_path
- fill_in "user_email", with: user.email
- fill_in "user_password", with: user.password
- click_button I18n.t('sessions.new.login')
+ it 'redirects the user to the dashboard' do
+ visit root_path
+ fill_in "user_email", with: user.email
+ fill_in "user_password", with: user.password
+ click_button I18n.t('sessions.new.login')
- expect(page).to have_content('Dashboard')
+ expect(page).to have_content('Dashboard')
+ end
+ end
+
+ describe "when TFA is enabled", js: true do
+ let(:user) { create(:user, tfa_secret: ::ROTP::Base32.random_base32) }
+
+ it 'prompts for a TOTP code then redirect to the dashboard' do
+ pending
+ visit root_path
+ fill_in "user_email", with: user.email
+ fill_in "user_password", with: user.password
+ click_button I18n.t('sessions.new.login')
+
+ fill_in "totp", with: user.tfa.current_totp
+
+ expect(page).to have_content('Dashboard')
+ end
end
end