Convert entrypoints/two_factor_authentication to Typescript (#30105)
				
					
				
			This commit is contained in:
		@@ -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();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										197
									
								
								app/javascript/entrypoints/two_factor_authentication.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								app/javascript/entrypoints/two_factor_authentication.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
});
 | 
			
		||||
		Reference in New Issue
	
	Block a user