about summary refs log tree commit diff
path: root/app/javascript/core
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/core')
-rw-r--r--app/javascript/core/auth.js2
-rw-r--r--app/javascript/core/theme.yml2
-rw-r--r--app/javascript/core/two_factor_authentication.js118
3 files changed, 121 insertions, 1 deletions
diff --git a/app/javascript/core/auth.js b/app/javascript/core/auth.js
new file mode 100644
index 000000000..ca04730a3
--- /dev/null
+++ b/app/javascript/core/auth.js
@@ -0,0 +1,2 @@
+import './settings';
+import './two_factor_authentication';
diff --git a/app/javascript/core/theme.yml b/app/javascript/core/theme.yml
index dc641772c..b9144e43a 100644
--- a/app/javascript/core/theme.yml
+++ b/app/javascript/core/theme.yml
@@ -3,7 +3,7 @@
 pack:
   about:
   admin: admin.js
-  auth: settings.js
+  auth: auth.js
   common:
     filename: common.js
     stylesheet: true
diff --git a/app/javascript/core/two_factor_authentication.js b/app/javascript/core/two_factor_authentication.js
new file mode 100644
index 000000000..dde06be8c
--- /dev/null
+++ b/app/javascript/core/two_factor_authentication.js
@@ -0,0 +1,118 @@
+import axios from 'axios';
+import * as WebAuthnJSON from '@github/webauthn-json';
+import ready from '../mastodon/ready';
+import 'regenerator-runtime/runtime';
+
+function getCSRFToken() {
+  var CSRFSelector = document.querySelector('meta[name="csrf-token"]');
+  if (CSRFSelector) {
+    return CSRFSelector.getAttribute('content');
+  } else {
+    return null;
+  }
+}
+
+function hideFlashMessages() {
+  Array.from(document.getElementsByClassName('flash-message')).forEach(function(flashMessage) {
+    flashMessage.classList.add('hidden');
+  });
+}
+
+function callback(url, body) {
+  axios.post(url, JSON.stringify(body), {
+    headers: {
+      'Content-Type': 'application/json',
+      'Accept': 'application/json',
+      'X-CSRF-Token': getCSRFToken(),
+    },
+    credentials: 'same-origin',
+  }).then(function(response) {
+    window.location.replace(response.data.redirect_path);
+  }).catch(function(error) {
+    if (error.response.status === 422) {
+      const errorMessage = document.getElementById('security-key-error-message');
+      errorMessage.classList.remove('hidden');
+      console.error(error.response.data.error);
+    } else {
+      console.error(error);
+    }
+  });
+}
+
+ready(() => {
+  if (!WebAuthnJSON.supported()) {
+    const unsupported_browser_message = document.getElementById('unsupported-browser-message');
+    if (unsupported_browser_message) {
+      unsupported_browser_message.classList.remove('hidden');
+      document.querySelector('.btn.js-webauthn').disabled = true;
+    }
+  }
+
+
+  const webAuthnCredentialRegistrationForm = document.getElementById('new_webauthn_credential');
+  if (webAuthnCredentialRegistrationForm) {
+    webAuthnCredentialRegistrationForm.addEventListener('submit', (event) => {
+      event.preventDefault();
+
+      var nickname = event.target.querySelector('input[name="new_webauthn_credential[nickname]"]');
+      if (nickname.value) {
+        axios.get('/settings/security_keys/options')
+          .then((response) => {
+            const credentialOptions = response.data;
+
+            WebAuthnJSON.create({ 'publicKey': credentialOptions }).then((credential) => {
+              var params = { 'credential': credential, 'nickname': nickname.value };
+              callback('/settings/security_keys', params);
+            }).catch((error) => {
+              const errorMessage = document.getElementById('security-key-error-message');
+              errorMessage.classList.remove('hidden');
+              console.error(error);
+            });
+          }).catch((error) => {
+            console.error(error.response.data.error);
+          });
+      } else {
+        nickname.focus();
+      }
+    });
+  }
+
+  const webAuthnCredentialAuthenticationForm = document.getElementById('webauthn-form');
+  if (webAuthnCredentialAuthenticationForm) {
+    webAuthnCredentialAuthenticationForm.addEventListener('submit', (event) => {
+      event.preventDefault();
+
+      axios.get('sessions/security_key_options')
+        .then((response) => {
+          const credentialOptions = response.data;
+
+          WebAuthnJSON.get({ 'publicKey': credentialOptions }).then((credential) => {
+            var params = { 'user': { 'credential': credential } };
+            callback('sign_in', params);
+          }).catch((error) => {
+            const errorMessage = document.getElementById('security-key-error-message');
+            errorMessage.classList.remove('hidden');
+            console.error(error);
+          });
+        }).catch((error) => {
+          console.error(error.response.data.error);
+        });
+    });
+
+    const otpAuthenticationForm = document.getElementById('otp-authentication-form');
+
+    const linkToOtp = document.getElementById('link-to-otp');
+    linkToOtp.addEventListener('click', () => {
+      webAuthnCredentialAuthenticationForm.classList.add('hidden');
+      otpAuthenticationForm.classList.remove('hidden');
+      hideFlashMessages();
+    });
+
+    const linkToWebAuthn = document.getElementById('link-to-webauthn');
+    linkToWebAuthn.addEventListener('click', () => {
+      otpAuthenticationForm.classList.add('hidden');
+      webAuthnCredentialAuthenticationForm.classList.remove('hidden');
+      hideFlashMessages();
+    });
+  }
+});