import { browserSupportsWebAuthn, platformAuthenticatorIsAvailable, startRegistration } from '@simplewebauthn/browser'; import $ from 'jquery'; import QRCode from 'qrcode'; import { ActionDialog } from 'vj/components/dialog'; import Notification from 'vj/components/notification'; import { NamedPage } from 'vj/misc/Page'; import { delay, i18n, request, secureRandomString, tpl, } from 'vj/utils'; async function changeMail() { const changeMailDialog = new ActionDialog({ $body: tpl(
, ), onDispatch(action) { if (action === 'ok') { const $password = $('#change-mail-dialog [name="password"]'); const $mail = $('#change-mail-dialog [name="mail"]'); if (!$password.val() || !$mail.val()) { if (!$password.val()) $password.focus(); else $mail.focus(); return false; } } return true; }, }).open(); const action = await changeMailDialog; if (action !== 'ok') return; try { await request.post('', { operation: 'change_mail', password: $('[name="password"]').val(), mail: $('[name="mail"]').val(), }); } catch (err) { Notification.error(err.message); console.error(err); return; } Notification.success(i18n('Successfully changed.')); await delay(2000); window.location.reload(); } async function enableTfa() { const enableTFA = new ActionDialog({ $body: tpl`

${i18n('Please use your two factor authentication app to scan the qrcode below:')}

${i18n('Click to show secret')}

`, onDispatch(action) { if (action === 'ok' && $('[name="tfa_code"]').val() === null) { $('[name="tfa_code"]').focus(); return false; } return true; }, }).open(); const secret = secureRandomString(13, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'); $('#secret').on('click', () => $('#secret').html(secret)); const uri = `otpauth://totp/Hydro:${UserContext.uname}?secret=${secret}&issuer=Hydro`; const canvas = document.getElementById('qrcode'); await QRCode.toCanvas(canvas, uri); const tfaAction = await enableTFA; if (tfaAction !== 'ok') return; try { await request.post('', { operation: 'enable_tfa', code: $('[name="tfa_code"]').val(), secret, }); } catch (err) { Notification.error(err.message); console.error(err); return; } Notification.success(i18n('Successfully enabled.')); await delay(2000); window.location.reload(); } async function enableAuthn(type: string) { const authnInfo = await request.post('', { operation: 'register', type }); if (!authnInfo.authOptions) { Notification.error(i18n('Failed to fetch registration data.')); return; } Notification.info(i18n('Please follow the instructions on your device to complete the verification.')); let credential; try { console.log(authnInfo); credential = await startRegistration({ optionsJSON: authnInfo.authOptions }); } catch (err) { Notification.error(i18n('Failed to get credential: {0}', err)); return; } const op = await new ActionDialog({ $body: tpl`
`, onDispatch(action) { if (action === 'ok' && $('[name="webauthn_name"]').val() === null) { $('[name="webauthn_name"]').focus(); return false; } return true; }, }).open(); if (op !== 'ok') return; try { await request.post('', { operation: 'enable_authn', name: $('[name="webauthn_name"]').val(), result: credential, }); } catch (err) { Notification.error(err.message); console.error(err); return; } Notification.success(i18n('Successfully enabled.')); await delay(2000); window.location.reload(); } export default new NamedPage('home_security', () => { const MenuLink = ({ children, action, icon }: { children: React.ReactNode, action?: string, icon?: string }) => (
  • {icon && } {children}
  • ); $(document).on('click', '[name="auth_enable"]', async () => { const platformAvailable = browserSupportsWebAuthn() && await platformAuthenticatorIsAvailable(); const $body = tpl(

    {i18n('Choose Authenticator Type')}

      {i18n('Two Factor Authentication')}
    1. {(!window.isSecureContext || !browserSupportsWebAuthn()) ? ( {window.isSecureContext ? i18n("Your browser doesn't support WebAuthn.") : i18n('Webauthn is not available in insecure context.')} ) : (<> {platformAvailable ? ( {i18n('Your Device')} ) : ( {i18n("Your browser doesn't support platform authenticator.")} )} {i18n('Multi Platform Authenticator')} )}
    , ); const action = await new ActionDialog({ $body, $action: [] }).open(); if (!action || action === 'cancel') return; if (action === 'tfa') enableTfa(); else enableAuthn(action); }); $(document).on('click', '[data-operation="change_mail"]', async (ev) => { ev.preventDefault(); await changeMail(); }); });