Refactor pleaseLogin to show a sign-in dialog (#8630)
* refactor(client): refactor pleaseLogin to show a sign-in dialog * Apply review suggestions from @Johann150 Co-authored-by: Johann150 <johann@qwertqwefsday.eu> Co-authored-by: Johann150 <johann@qwertqwefsday.eu> Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
This commit is contained in:
		@@ -2,12 +2,12 @@
 | 
			
		||||
<XModalWindow ref="dialog"
 | 
			
		||||
	:width="370"
 | 
			
		||||
	:height="400"
 | 
			
		||||
	@close="dialog.close()"
 | 
			
		||||
	@close="onClose"
 | 
			
		||||
	@closed="emit('closed')"
 | 
			
		||||
>
 | 
			
		||||
	<template #header>{{ $ts.login }}</template>
 | 
			
		||||
 | 
			
		||||
	<MkSignin :auto-set="autoSet" @login="onLogin"/>
 | 
			
		||||
	<MkSignin :auto-set="autoSet" :message="message" @login="onLogin"/>
 | 
			
		||||
</XModalWindow>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -18,17 +18,25 @@ import MkSignin from './signin.vue';
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<{
 | 
			
		||||
	autoSet?: boolean;
 | 
			
		||||
	message?: string,
 | 
			
		||||
}>(), {
 | 
			
		||||
	autoSet: false,
 | 
			
		||||
	message: ''
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
	(e: 'done'): void;
 | 
			
		||||
	(e: 'closed'): void;
 | 
			
		||||
	(ev: 'done'): void;
 | 
			
		||||
	(ev: 'closed'): void;
 | 
			
		||||
	(ev: 'cancelled'): void;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const dialog = $ref<InstanceType<typeof XModalWindow>>();
 | 
			
		||||
 | 
			
		||||
function onClose() {
 | 
			
		||||
	emit('cancelled');
 | 
			
		||||
	dialog.close();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onLogin(res) {
 | 
			
		||||
	emit('done', res);
 | 
			
		||||
	dialog.close();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,41 +1,44 @@
 | 
			
		||||
<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 }"></div>
 | 
			
		||||
		<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="$ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
 | 
			
		||||
			<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:modelValue="onUsernameChange">
 | 
			
		||||
				<template #prefix>@</template>
 | 
			
		||||
				<template #suffix>@{{ host }}</template>
 | 
			
		||||
			</MkInput>
 | 
			
		||||
			<MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="$ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password>
 | 
			
		||||
			<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="fas fa-lock"></i></template>
 | 
			
		||||
				<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ $ts.forgotPassword }}</button></template>
 | 
			
		||||
				<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
 | 
			
		||||
			</MkInput>
 | 
			
		||||
			<MkCaptcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/>
 | 
			
		||||
			<MkCaptcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/>
 | 
			
		||||
			<MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
 | 
			
		||||
			<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>{{ $ts.tapSecurityKey }}</p>
 | 
			
		||||
				<p>{{ i18n.ts.tapSecurityKey }}</p>
 | 
			
		||||
				<MkButton v-if="!queryingKey" @click="queryKey">
 | 
			
		||||
					{{ $ts.retry }}
 | 
			
		||||
					{{ i18n.ts.retry }}
 | 
			
		||||
				</MkButton>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div v-if="user && user.securityKeys" class="or-hr">
 | 
			
		||||
				<p class="or-msg">{{ $ts.or }}</p>
 | 
			
		||||
				<p class="or-msg">{{ i18n.ts.or }}</p>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="twofa-group totp-group">
 | 
			
		||||
				<p style="margin-bottom:0;">{{ $ts.twoStepAuthentication }}</p>
 | 
			
		||||
				<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>{{ $ts.password }}</template>
 | 
			
		||||
					<template #label>{{ i18n.ts.password }}</template>
 | 
			
		||||
					<template #prefix><i class="fas fa-lock"></i></template>
 | 
			
		||||
				</MkInput>
 | 
			
		||||
				<MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required>
 | 
			
		||||
					<template #label>{{ $ts.token }}</template>
 | 
			
		||||
					<template #label>{{ i18n.ts.token }}</template>
 | 
			
		||||
					<template #prefix><i class="fas fa-gavel"></i></template>
 | 
			
		||||
				</MkInput>
 | 
			
		||||
				<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
 | 
			
		||||
				<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
@@ -47,199 +50,192 @@
 | 
			
		||||
</form>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineAsyncComponent, defineComponent } from 'vue';
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { defineAsyncComponent } from 'vue';
 | 
			
		||||
import { toUnicode } from 'punycode/';
 | 
			
		||||
import MkButton from '@/components/ui/button.vue';
 | 
			
		||||
import MkInput from '@/components/form/input.vue';
 | 
			
		||||
import { apiUrl, host } from '@/config';
 | 
			
		||||
import MkInfo from '@/components/ui/info.vue';
 | 
			
		||||
import { apiUrl, host as configHost } from '@/config';
 | 
			
		||||
import { byteify, hexify } from '@/scripts/2fa';
 | 
			
		||||
import * as os from '@/os';
 | 
			
		||||
import { login } from '@/account';
 | 
			
		||||
import { showSuspendedDialog } from '../scripts/show-suspended-dialog';
 | 
			
		||||
import { instance } from '@/instance';
 | 
			
		||||
import { i18n } from '@/i18n';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
	components: {
 | 
			
		||||
		MkButton,
 | 
			
		||||
		MkInput,
 | 
			
		||||
		MkCaptcha: defineAsyncComponent(() => import('./captcha.vue')),
 | 
			
		||||
const MkCaptcha = defineAsyncComponent(() => import('./captcha.vue'));
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		withAvatar: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: true
 | 
			
		||||
		},
 | 
			
		||||
		autoSet: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false,
 | 
			
		||||
		}
 | 
			
		||||
	autoSet: {
 | 
			
		||||
		type: Boolean,
 | 
			
		||||
		required: false,
 | 
			
		||||
		default: false,
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	emits: ['login'],
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			signing: false,
 | 
			
		||||
			user: null,
 | 
			
		||||
			username: '',
 | 
			
		||||
			password: '',
 | 
			
		||||
			token: '',
 | 
			
		||||
			apiUrl,
 | 
			
		||||
			host: toUnicode(host),
 | 
			
		||||
			totpLogin: false,
 | 
			
		||||
			credential: null,
 | 
			
		||||
			challengeData: null,
 | 
			
		||||
			queryingKey: false,
 | 
			
		||||
			hCaptchaResponse: null,
 | 
			
		||||
			reCaptchaResponse: null,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		meta() {
 | 
			
		||||
			return this.$instance;
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		onUsernameChange() {
 | 
			
		||||
			os.api('users/show', {
 | 
			
		||||
				username: this.username
 | 
			
		||||
			}).then(user => {
 | 
			
		||||
				this.user = user;
 | 
			
		||||
			}, () => {
 | 
			
		||||
				this.user = null;
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onLogin(res) {
 | 
			
		||||
			if (this.autoSet) {
 | 
			
		||||
				return login(res.i);
 | 
			
		||||
			} else {
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		queryKey() {
 | 
			
		||||
			this.queryingKey = true;
 | 
			
		||||
			return navigator.credentials.get({
 | 
			
		||||
				publicKey: {
 | 
			
		||||
					challenge: byteify(this.challengeData.challenge, 'base64'),
 | 
			
		||||
					allowCredentials: this.challengeData.securityKeys.map(key => ({
 | 
			
		||||
						id: byteify(key.id, 'hex'),
 | 
			
		||||
						type: 'public-key',
 | 
			
		||||
						transports: ['usb', 'nfc', 'ble', 'internal']
 | 
			
		||||
					})),
 | 
			
		||||
					timeout: 60 * 1000
 | 
			
		||||
				}
 | 
			
		||||
			}).catch(() => {
 | 
			
		||||
				this.queryingKey = false;
 | 
			
		||||
				return Promise.reject(null);
 | 
			
		||||
			}).then(credential => {
 | 
			
		||||
				this.queryingKey = false;
 | 
			
		||||
				this.signing = true;
 | 
			
		||||
				return os.api('signin', {
 | 
			
		||||
					username: this.username,
 | 
			
		||||
					password: this.password,
 | 
			
		||||
					'hcaptcha-response': this.hCaptchaResponse,
 | 
			
		||||
					'g-recaptcha-response': this.reCaptchaResponse,
 | 
			
		||||
					signature: hexify(credential.response.signature),
 | 
			
		||||
					authenticatorData: hexify(credential.response.authenticatorData),
 | 
			
		||||
					clientDataJSON: hexify(credential.response.clientDataJSON),
 | 
			
		||||
					credentialId: credential.id,
 | 
			
		||||
					challengeId: this.challengeData.challengeId,
 | 
			
		||||
				});
 | 
			
		||||
			}).then(res => {
 | 
			
		||||
				this.$emit('login', res);
 | 
			
		||||
				return this.onLogin(res);
 | 
			
		||||
			}).catch(err => {
 | 
			
		||||
				if (err === null) return;
 | 
			
		||||
				os.alert({
 | 
			
		||||
					type: 'error',
 | 
			
		||||
					text: this.$ts.signinFailed
 | 
			
		||||
				});
 | 
			
		||||
				this.signing = false;
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onSubmit() {
 | 
			
		||||
			this.signing = true;
 | 
			
		||||
			if (!this.totpLogin && this.user && this.user.twoFactorEnabled) {
 | 
			
		||||
				if (window.PublicKeyCredential && this.user.securityKeys) {
 | 
			
		||||
					os.api('signin', {
 | 
			
		||||
						username: this.username,
 | 
			
		||||
						password: this.password,
 | 
			
		||||
						'hcaptcha-response': this.hCaptchaResponse,
 | 
			
		||||
						'g-recaptcha-response': this.reCaptchaResponse,
 | 
			
		||||
					}).then(res => {
 | 
			
		||||
						this.totpLogin = true;
 | 
			
		||||
						this.signing = false;
 | 
			
		||||
						this.challengeData = res;
 | 
			
		||||
						return this.queryKey();
 | 
			
		||||
					}).catch(this.loginFailed);
 | 
			
		||||
				} else {
 | 
			
		||||
					this.totpLogin = true;
 | 
			
		||||
					this.signing = false;
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				os.api('signin', {
 | 
			
		||||
					username: this.username,
 | 
			
		||||
					password: this.password,
 | 
			
		||||
					'hcaptcha-response': this.hCaptchaResponse,
 | 
			
		||||
					'g-recaptcha-response': this.reCaptchaResponse,
 | 
			
		||||
					token: this.user && this.user.twoFactorEnabled ? this.token : undefined,
 | 
			
		||||
				}).then(res => {
 | 
			
		||||
					this.$emit('login', res);
 | 
			
		||||
					this.onLogin(res);
 | 
			
		||||
				}).catch(this.loginFailed);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		loginFailed(err) {
 | 
			
		||||
			switch (err.id) {
 | 
			
		||||
				case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
 | 
			
		||||
					os.alert({
 | 
			
		||||
						type: 'error',
 | 
			
		||||
						title: this.$ts.loginFailed,
 | 
			
		||||
						text: this.$ts.noSuchUser
 | 
			
		||||
					});
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
				case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': {
 | 
			
		||||
					os.alert({
 | 
			
		||||
						type: 'error',
 | 
			
		||||
						title: this.$ts.loginFailed,
 | 
			
		||||
						text: this.$ts.incorrectPassword,
 | 
			
		||||
					});
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
				case 'e03a5f46-d309-4865-9b69-56282d94e1eb': {
 | 
			
		||||
					showSuspendedDialog();
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
				default: {
 | 
			
		||||
					os.alert({
 | 
			
		||||
						type: 'error',
 | 
			
		||||
						title: this.$ts.loginFailed,
 | 
			
		||||
						text: JSON.stringify(err)
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			this.challengeData = null;
 | 
			
		||||
			this.totpLogin = false;
 | 
			
		||||
			this.signing = false;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		resetPassword() {
 | 
			
		||||
			os.popup(defineAsyncComponent(() => import('@/components/forgot-password.vue')), {}, {
 | 
			
		||||
			}, 'closed');
 | 
			
		||||
		}
 | 
			
		||||
	message: {
 | 
			
		||||
		type: String,
 | 
			
		||||
		required: false,
 | 
			
		||||
		default: ''
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function onUsernameChange() {
 | 
			
		||||
	os.api('users/show', {
 | 
			
		||||
		username: username
 | 
			
		||||
	}).then(user => {
 | 
			
		||||
		user = user;
 | 
			
		||||
	}, () => {
 | 
			
		||||
		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;
 | 
			
		||||
		}
 | 
			
		||||
		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/forgot-password.vue')), {}, {
 | 
			
		||||
	}, 'closed');
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,21 @@
 | 
			
		||||
import { defineAsyncComponent } from 'vue';
 | 
			
		||||
import { $i } from '@/account';
 | 
			
		||||
import { i18n } from '@/i18n';
 | 
			
		||||
import { alert } from '@/os';
 | 
			
		||||
import { popup } from '@/os';
 | 
			
		||||
 | 
			
		||||
export function pleaseLogin() {
 | 
			
		||||
export function pleaseLogin(path?: string) {
 | 
			
		||||
	if ($i) return;
 | 
			
		||||
 | 
			
		||||
	alert({
 | 
			
		||||
		title: i18n.ts.signinRequired,
 | 
			
		||||
		text: null
 | 
			
		||||
	});
 | 
			
		||||
	popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {
 | 
			
		||||
		autoSet: true,
 | 
			
		||||
		message: i18n.ts.signinRequired
 | 
			
		||||
	}, {
 | 
			
		||||
		cancelled: () => {
 | 
			
		||||
			if (path) {
 | 
			
		||||
				window.location.href = path;
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
	}, 'closed');
 | 
			
		||||
 | 
			
		||||
	throw new Error('signin required');
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user