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