summaryrefslogtreecommitdiff
path: root/packages/client/src/components/MkSignin.vue
diff options
context:
space:
mode:
Diffstat (limited to 'packages/client/src/components/MkSignin.vue')
-rw-r--r--packages/client/src/components/MkSignin.vue259
1 files changed, 0 insertions, 259 deletions
diff --git a/packages/client/src/components/MkSignin.vue b/packages/client/src/components/MkSignin.vue
deleted file mode 100644
index 96f18f8d61..0000000000
--- a/packages/client/src/components/MkSignin.vue
+++ /dev/null
@@ -1,259 +0,0 @@
-<template>
-<form class="eppvobhk _monolithic_" :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
- <div class="auth _section _formRoot">
- <div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null, marginBottom: message ? '1.5em' : null }"></div>
- <MkInfo v-if="message">
- {{ message }}
- </MkInfo>
- <div v-if="!totpLogin" class="normal-signin">
- <MkInput v-model="username" class="_formBlock" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autofocus required data-cy-signin-username @update:model-value="onUsernameChange">
- <template #prefix>@</template>
- <template #suffix>@{{ host }}</template>
- </MkInput>
- <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="i18n.ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password>
- <template #prefix><i class="ti ti-lock"></i></template>
- <template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
- </MkInput>
- <MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
- </div>
- <div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }">
- <div v-if="user && user.securityKeys" class="twofa-group tap-group">
- <p>{{ i18n.ts.tapSecurityKey }}</p>
- <MkButton v-if="!queryingKey" @click="queryKey">
- {{ i18n.ts.retry }}
- </MkButton>
- </div>
- <div v-if="user && user.securityKeys" class="or-hr">
- <p class="or-msg">{{ i18n.ts.or }}</p>
- </div>
- <div class="twofa-group totp-group">
- <p style="margin-bottom:0;">{{ i18n.ts.twoStepAuthentication }}</p>
- <MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" :with-password-toggle="true" required>
- <template #label>{{ i18n.ts.password }}</template>
- <template #prefix><i class="ti ti-lock"></i></template>
- </MkInput>
- <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" :spellcheck="false" required>
- <template #label>{{ i18n.ts.token }}</template>
- <template #prefix><i class="ti ti-123"></i></template>
- </MkInput>
- <MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
- </div>
- </div>
- </div>
- <div class="social _section">
- <a v-if="meta && meta.enableTwitterIntegration" class="_borderButton _gap" :href="`${apiUrl}/signin/twitter`"><i class="ti ti-brand-twitter" style="margin-right: 4px;"></i>{{ $t('signinWith', { x: 'Twitter' }) }}</a>
- <a v-if="meta && meta.enableGithubIntegration" class="_borderButton _gap" :href="`${apiUrl}/signin/github`"><i class="ti ti-brand-github" style="margin-right: 4px;"></i>{{ $t('signinWith', { x: 'GitHub' }) }}</a>
- <a v-if="meta && meta.enableDiscordIntegration" class="_borderButton _gap" :href="`${apiUrl}/signin/discord`"><i class="ti ti-brand-discord" style="margin-right: 4px;"></i>{{ $t('signinWith', { x: 'Discord' }) }}</a>
- </div>
-</form>
-</template>
-
-<script lang="ts" setup>
-import { defineAsyncComponent } from 'vue';
-import { toUnicode } from 'punycode/';
-import { showSuspendedDialog } from '../scripts/show-suspended-dialog';
-import MkButton from '@/components/MkButton.vue';
-import MkInput from '@/components/form/input.vue';
-import MkInfo from '@/components/MkInfo.vue';
-import { apiUrl, host as configHost } from '@/config';
-import { byteify, hexify } from '@/scripts/2fa';
-import * as os from '@/os';
-import { login } from '@/account';
-import { instance } from '@/instance';
-import { i18n } from '@/i18n';
-
-let signing = $ref(false);
-let user = $ref(null);
-let username = $ref('');
-let password = $ref('');
-let token = $ref('');
-let host = $ref(toUnicode(configHost));
-let totpLogin = $ref(false);
-let credential = $ref(null);
-let challengeData = $ref(null);
-let queryingKey = $ref(false);
-let hCaptchaResponse = $ref(null);
-let reCaptchaResponse = $ref(null);
-
-const meta = $computed(() => instance);
-
-const emit = defineEmits<{
- (ev: 'login', v: any): void;
-}>();
-
-const props = defineProps({
- withAvatar: {
- type: Boolean,
- required: false,
- default: true,
- },
- autoSet: {
- type: Boolean,
- required: false,
- default: false,
- },
- message: {
- type: String,
- required: false,
- default: '',
- },
-});
-
-function onUsernameChange() {
- os.api('users/show', {
- username: username,
- }).then(userResponse => {
- user = userResponse;
- }, () => {
- user = null;
- });
-}
-
-function onLogin(res) {
- if (props.autoSet) {
- return login(res.i);
- }
-}
-
-function queryKey() {
- queryingKey = true;
- return navigator.credentials.get({
- publicKey: {
- challenge: byteify(challengeData.challenge, 'base64'),
- allowCredentials: challengeData.securityKeys.map(key => ({
- id: byteify(key.id, 'hex'),
- type: 'public-key',
- transports: ['usb', 'nfc', 'ble', 'internal'],
- })),
- timeout: 60 * 1000,
- },
- }).catch(() => {
- queryingKey = false;
- return Promise.reject(null);
- }).then(credential => {
- queryingKey = false;
- signing = true;
- return os.api('signin', {
- username,
- password,
- signature: hexify(credential.response.signature),
- authenticatorData: hexify(credential.response.authenticatorData),
- clientDataJSON: hexify(credential.response.clientDataJSON),
- credentialId: credential.id,
- challengeId: challengeData.challengeId,
- 'hcaptcha-response': hCaptchaResponse,
- 'g-recaptcha-response': reCaptchaResponse,
- });
- }).then(res => {
- emit('login', res);
- return onLogin(res);
- }).catch(err => {
- if (err === null) return;
- os.alert({
- type: 'error',
- text: i18n.ts.signinFailed,
- });
- signing = false;
- });
-}
-
-function onSubmit() {
- signing = true;
- console.log('submit');
- if (!totpLogin && user && user.twoFactorEnabled) {
- if (window.PublicKeyCredential && user.securityKeys) {
- os.api('signin', {
- username,
- password,
- 'hcaptcha-response': hCaptchaResponse,
- 'g-recaptcha-response': reCaptchaResponse,
- }).then(res => {
- totpLogin = true;
- signing = false;
- challengeData = res;
- return queryKey();
- }).catch(loginFailed);
- } else {
- totpLogin = true;
- signing = false;
- }
- } else {
- os.api('signin', {
- username,
- password,
- 'hcaptcha-response': hCaptchaResponse,
- 'g-recaptcha-response': reCaptchaResponse,
- token: user && user.twoFactorEnabled ? token : undefined,
- }).then(res => {
- emit('login', res);
- onLogin(res);
- }).catch(loginFailed);
- }
-}
-
-function loginFailed(err) {
- switch (err.id) {
- case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
- os.alert({
- type: 'error',
- title: i18n.ts.loginFailed,
- text: i18n.ts.noSuchUser,
- });
- break;
- }
- case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': {
- os.alert({
- type: 'error',
- title: i18n.ts.loginFailed,
- text: i18n.ts.incorrectPassword,
- });
- break;
- }
- case 'e03a5f46-d309-4865-9b69-56282d94e1eb': {
- showSuspendedDialog();
- break;
- }
- case '22d05606-fbcf-421a-a2db-b32610dcfd1b': {
- os.alert({
- type: 'error',
- title: i18n.ts.loginFailed,
- text: i18n.ts.rateLimitExceeded,
- });
- break;
- }
- default: {
- console.log(err);
- os.alert({
- type: 'error',
- title: i18n.ts.loginFailed,
- text: JSON.stringify(err),
- });
- }
- }
-
- challengeData = null;
- totpLogin = false;
- signing = false;
-}
-
-function resetPassword() {
- os.popup(defineAsyncComponent(() => import('@/components/MkForgotPassword.vue')), {}, {
- }, 'closed');
-}
-</script>
-
-<style lang="scss" scoped>
-.eppvobhk {
- > .auth {
- > .avatar {
- margin: 0 auto 0 auto;
- width: 64px;
- height: 64px;
- background: #ddd;
- background-position: center;
- background-size: cover;
- border-radius: 100%;
- }
- }
-}
-</style>