summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRenaud Chaput <renchap@gmail.com>2024-05-02 14:02:13 +0200
committerGitHub <noreply@github.com>2024-05-02 12:02:13 +0000
commit9e260014c72fa83740d70d2d603e836f86e64b55 (patch)
tree27b7fa5348ceb579a49cfe3c233ce8abb1801e04
parent616e2f26668d578ae81043a3836e881178d3e806 (diff)
Convert `entrypoints/two_factor_authentication` to Typescript (#30105)
-rw-r--r--app/javascript/entrypoints/two_factor_authentication.js119
-rw-r--r--app/javascript/entrypoints/two_factor_authentication.ts197
2 files changed, 197 insertions, 119 deletions
diff --git a/app/javascript/entrypoints/two_factor_authentication.js b/app/javascript/entrypoints/two_factor_authentication.js
deleted file mode 100644
index e77965c757f..00000000000
--- a/app/javascript/entrypoints/two_factor_authentication.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import * as WebAuthnJSON from '@github/webauthn-json';
-import axios from 'axios';
-
-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();
- });
- }
-});
diff --git a/app/javascript/entrypoints/two_factor_authentication.ts b/app/javascript/entrypoints/two_factor_authentication.ts
new file mode 100644
index 00000000000..981481694b8
--- /dev/null
+++ b/app/javascript/entrypoints/two_factor_authentication.ts
@@ -0,0 +1,197 @@
+import * as WebAuthnJSON from '@github/webauthn-json';
+import axios, { AxiosError } from 'axios';
+
+import ready from '../mastodon/ready';
+
+import 'regenerator-runtime/runtime';
+
+type PublicKeyCredentialCreationOptionsJSON =
+ WebAuthnJSON.CredentialCreationOptionsJSON['publicKey'];
+
+function exceptionHasAxiosError(
+ error: unknown,
+): error is AxiosError<{ error: unknown }> {
+ return (
+ error instanceof AxiosError &&
+ typeof error.response?.data === 'object' &&
+ 'error' in error.response.data
+ );
+}
+
+function logAxiosResponseError(error: unknown) {
+ if (exceptionHasAxiosError(error)) console.error(error);
+}
+
+function getCSRFToken() {
+ return document
+ .querySelector<HTMLMetaElement>('meta[name="csrf-token"]')
+ ?.getAttribute('content');
+}
+
+function hideFlashMessages() {
+ document.querySelectorAll('.flash-message').forEach((flashMessage) => {
+ flashMessage.classList.add('hidden');
+ });
+}
+
+async function callback(
+ url: string,
+ body:
+ | {
+ credential: WebAuthnJSON.PublicKeyCredentialWithAttestationJSON;
+ nickname: string;
+ }
+ | {
+ user: { credential: WebAuthnJSON.PublicKeyCredentialWithAssertionJSON };
+ },
+) {
+ try {
+ const response = await axios.post<{ redirect_path: string }>(
+ url,
+ JSON.stringify(body),
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ 'X-CSRF-Token': getCSRFToken(),
+ },
+ },
+ );
+
+ window.location.replace(response.data.redirect_path);
+ } catch (error) {
+ if (error instanceof AxiosError && error.response?.status === 422) {
+ const errorMessage = document.getElementById(
+ 'security-key-error-message',
+ );
+ errorMessage?.classList.remove('hidden');
+
+ logAxiosResponseError(error);
+ } else {
+ console.error(error);
+ }
+ }
+}
+
+async function handleWebauthnCredentialRegistration(nickname: string) {
+ try {
+ const response = await axios.get<PublicKeyCredentialCreationOptionsJSON>(
+ '/settings/security_keys/options',
+ );
+
+ const credentialOptions = response.data;
+
+ try {
+ const credential = await WebAuthnJSON.create({
+ publicKey: credentialOptions,
+ });
+
+ const params = {
+ credential: credential,
+ nickname: nickname,
+ };
+
+ await callback('/settings/security_keys', params);
+ } catch (error) {
+ const errorMessage = document.getElementById(
+ 'security-key-error-message',
+ );
+ errorMessage?.classList.remove('hidden');
+ console.error(error);
+ }
+ } catch (error) {
+ logAxiosResponseError(error);
+ }
+}
+
+async function handleWebauthnCredentialAuthentication() {
+ try {
+ const response = await axios.get<PublicKeyCredentialCreationOptionsJSON>(
+ 'sessions/security_key_options',
+ );
+
+ const credentialOptions = response.data;
+
+ try {
+ const credential = await WebAuthnJSON.get({
+ publicKey: credentialOptions,
+ });
+
+ const params = { user: { credential: credential } };
+ void callback('sign_in', params);
+ } catch (error) {
+ const errorMessage = document.getElementById(
+ 'security-key-error-message',
+ );
+ errorMessage?.classList.remove('hidden');
+ console.error(error);
+ }
+ } catch (error) {
+ logAxiosResponseError(error);
+ }
+}
+
+ready(() => {
+ if (!WebAuthnJSON.supported()) {
+ const unsupported_browser_message = document.getElementById(
+ 'unsupported-browser-message',
+ );
+ if (unsupported_browser_message) {
+ unsupported_browser_message.classList.remove('hidden');
+ const button = document.querySelector<HTMLButtonElement>(
+ 'button.btn.js-webauthn',
+ );
+ if (button) button.disabled = true;
+ }
+ }
+
+ const webAuthnCredentialRegistrationForm =
+ document.querySelector<HTMLFormElement>('form#new_webauthn_credential');
+ if (webAuthnCredentialRegistrationForm) {
+ webAuthnCredentialRegistrationForm.addEventListener('submit', (event) => {
+ event.preventDefault();
+
+ if (!(event.target instanceof HTMLFormElement)) return;
+
+ const nickname = event.target.querySelector<HTMLInputElement>(
+ 'input[name="new_webauthn_credential[nickname]"]',
+ );
+
+ if (nickname?.value) {
+ void handleWebauthnCredentialRegistration(nickname.value);
+ } else {
+ nickname?.focus();
+ }
+ });
+ }
+
+ const webAuthnCredentialAuthenticationForm =
+ document.getElementById('webauthn-form');
+ if (webAuthnCredentialAuthenticationForm) {
+ webAuthnCredentialAuthenticationForm.addEventListener('submit', (event) => {
+ event.preventDefault();
+ void handleWebauthnCredentialAuthentication();
+ });
+
+ 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();
+ });
+ }
+}).catch((e: unknown) => {
+ throw e;
+});