Commit 6118b4d
Changed files (7)
app
controllers
javascript
controllers
styles
views
my
config
locales
spec
requests
my
app/controllers/my/mfas_controller.rb
@@ -19,6 +19,13 @@ module My
def edit; end
+ def test
+ secure_params = params.require(:user).permit(:mfa_secret, :code)
+ current_user.mfa_secret = secure_params[:mfa_secret]
+ @valid = current_user.mfa.authenticate(secure_params[:code])
+ render status: :ok, layout: nil
+ end
+
def destroy
current_user.mfa.disable!
redirect_to my_dashboard_path, notice: 'MFA has been disabled'
app/javascript/controllers/mfa/test_controller.js
@@ -0,0 +1,10 @@
+import ApplicationController from '../application_controller';
+
+export default class extends ApplicationController {
+ static targets = ["message"];
+
+ onResponse(event) {
+ let [data, status, xhr] = event.detail;
+ this.messageTarget.innerHTML = xhr.response;
+ }
+}
app/javascript/styles/application.scss
@@ -22,6 +22,8 @@
height: 48px;
}
+.hide { display: none; }
+
.my-audits-index {
.attribute-truncate {
@include ellipsis(200px);
app/views/my/mfas/new.html.erb
@@ -12,33 +12,34 @@
<div class="box content">
<h2>Scan QR Code</h2>
<div class="notification is-warning">
- Warning: This QR code is your personal provisioning secret. This should be kept secure.
+ This QR code is your personal provisioning secret. This should be kept secure.
</div>
<div data-controller="mfa--setup">
<canvas id="canvas" data-target="mfa--setup.canvas"></canvas>
<%= form_with model: current_user, url: '#' do |form| %>
- <div class="field">
- <%= form.label :mfa_secret %>
- <div class="control">
- <%= form.text_field :mfa_secret, class: 'input', disabled: :disabled, data: { target: 'mfa--setup.secret' } %>
- </div>
+ <div class="field">
+ <%= form.label :mfa_secret %>
+ <div class="control">
+ <%= form.text_field :mfa_secret, class: 'input', disabled: :disabled, data: { target: 'mfa--setup.secret' } %>
</div>
- <div class="field">
- <%= form.label :provisioning_uri %>
- <div class="control">
- <%= form.text_field :provisioning_uri, value: current_user.mfa.provisioning_uri, class: 'input', disabled: :disabled %>
- </div>
+ </div>
+ <div class="field">
+ <%= form.label :provisioning_uri %>
+ <div class="control">
+ <%= form.text_field :provisioning_uri, value: current_user.mfa.provisioning_uri, class: 'input', disabled: :disabled %>
</div>
+ </div>
<% end %>
</div>
</div>
- <div class="box content">
+ <div class="box content" data-controller="mfa--test">
<h2>Test Verfication Code</h2>
+ <span data-target="mfa--test.message"></span>
<ol type="1">
<li>Open your Authenticator app</li>
<li>Enter the verification code displayed</li>
</ol>
- <%= form_with model: current_user, url: test_my_mfa_path, method: :post do |form| %>
+ <%= form_with model: current_user, url: test_my_mfa_path, method: :post, data: { action: 'ajax:success->mfa--test#onResponse' } do |form| %>
<div class="field has-addons">
<div class="control">
<%= form.number_field :code, value: '', placeholder: 'code', class: 'input', required: :required, data: { controller: 'input' } %>
@@ -50,20 +51,20 @@
</div>
<% end %>
</div>
- <div class="box content">
- <h2>Save Backup Codes</h2>
- <h3>Backup Codes</h3>
- <ul>
- <li>code1</li>
- <li>code2</li>
- <li>code3</li>
- </ul>
- </div>
+ <%#<div class="box content">%>
+ <%#<h2>Save Backup Codes</h2>%>
+ <%#<h3>Backup Codes</h3>%>
+ <%#<ul>%>
+ <%#<li>code1</li>%>
+ <%#<li>code2</li>%>
+ <%#<li>code3</li>%>
+ <%#</ul>%>
+ <%#</div>%>
<%= form_for current_user, url: my_mfa_path, method: :post do |form| %>
<%= form.hidden_field :mfa_secret, data: { target: 'mfa--setup.secret' } %>
<div class="field is-grouped is-grouped-right">
<div class="control">
- <%= form.submit t(".enable"), class: 'button is-primary', data: { disable_with: 'Saving…' } %>
+ <%= form.submit t(".enable"), id: 'enable-button', class: 'button is-primary', data: { disable_with: t('.saving') } %>
</div>
<div class="control">
<%= link_to t(".cancel"), my_dashboard_path, class: 'button' %>
app/views/my/mfas/test.html.erb
@@ -0,0 +1,11 @@
+<% if @valid %>
+ <div data-controller="notification" class="notification is-success">
+ <button data-action="click->notification#close" class="delete"></button>
+ <%= t('.success') %>
+ </div>
+<% else %>
+ <div data-controller="notification" class="notification is-danger">
+ <button data-action="click->notification#close" class="delete"></button>
+ <%= t('.invalid') %>
+ </div>
+<% end %>
config/locales/en.yml
@@ -37,6 +37,7 @@ en:
application:
title: Proof
loading: Loading…
+ saving: Saving…
mfas:
new:
login: Login
@@ -75,6 +76,9 @@ en:
enable: Enable
test: Test
title: Multi Factor Authentication - Setup
+ test:
+ invalid: Invalid
+ success: Success
sessions:
destroy:
success: Success
spec/requests/my/mfas_spec.rb
@@ -53,6 +53,25 @@ RSpec.describe '/my/mfa' do
end
end
+ describe "POST /my/mfa/test" do
+ context "when the code is correct" do
+ let(:mfa_secret) { ::ROTP::Base32.random_base32 }
+ let(:current_code) { ROTP::TOTP.new(mfa_secret).now }
+
+ before { post '/my/mfa/test', params: { user: { code: current_code, mfa_secret: mfa_secret } }, xhr: true }
+ specify { expect(response).to have_http_status(:ok) }
+ end
+
+ context "when the code is incorrect" do
+ let(:mfa_secret) { ::ROTP::Base32.random_base32 }
+ let(:current_code) { "12345" }
+
+ before { post '/my/mfa/test', params: { user: { code: current_code, mfa_secret: mfa_secret } }, xhr: true }
+ specify { expect(response).to have_http_status(:ok) }
+ specify { expect(response.body).to include(I18n.t('my.mfas.test.invalid')) }
+ end
+ end
+
describe "DELETE /my/mfa" do
context "when mfa is enabled" do
let(:current_user) { create(:user, :mfa_configured) }