Commit 6118b4d

mo <mo@mokhan.ca>
2018-12-17 22:50:07
add ability to test mfa code before enabling it.
1 parent 722e5b3
Changed files (7)
app
controllers
javascript
views
config
locales
spec
requests
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) }