次の処理をsignin apiから読み取るように
This commit is contained in:
parent
65aac75a33
commit
adabff708d
|
@ -545,11 +545,6 @@ export class UserEntityService implements OnModuleInit {
|
|||
publicReactions: this.isLocalUser(user) ? profile!.publicReactions : false, // https://github.com/misskey-dev/misskey/issues/12964
|
||||
followersVisibility: profile!.followersVisibility,
|
||||
followingVisibility: profile!.followingVisibility,
|
||||
twoFactorEnabled: profile!.twoFactorEnabled,
|
||||
usePasswordLessLogin: profile!.usePasswordLessLogin,
|
||||
securityKeys: profile!.twoFactorEnabled
|
||||
? this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1)
|
||||
: false,
|
||||
roles: this.roleService.getUserRoles(user.id).then(roles => roles.filter(role => role.isPublic).sort((a, b) => b.displayOrder - a.displayOrder).map(role => ({
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
|
@ -564,6 +559,14 @@ export class UserEntityService implements OnModuleInit {
|
|||
moderationNote: iAmModerator ? (profile!.moderationNote ?? '') : undefined,
|
||||
} : {}),
|
||||
|
||||
...(isDetailed && (isMe || iAmModerator) ? {
|
||||
twoFactorEnabled: profile!.twoFactorEnabled,
|
||||
usePasswordLessLogin: profile!.usePasswordLessLogin,
|
||||
securityKeys: profile!.twoFactorEnabled
|
||||
? this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1)
|
||||
: false,
|
||||
} : {}),
|
||||
|
||||
...(isDetailed && isMe ? {
|
||||
avatarId: user.avatarId,
|
||||
bannerId: user.bannerId,
|
||||
|
|
|
@ -346,21 +346,6 @@ export const packedUserDetailedNotMeOnlySchema = {
|
|||
nullable: false, optional: false,
|
||||
enum: ['public', 'followers', 'private'],
|
||||
},
|
||||
twoFactorEnabled: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
default: false,
|
||||
},
|
||||
usePasswordLessLogin: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
default: false,
|
||||
},
|
||||
securityKeys: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
default: false,
|
||||
},
|
||||
roles: {
|
||||
type: 'array',
|
||||
nullable: false, optional: false,
|
||||
|
@ -382,6 +367,18 @@ export const packedUserDetailedNotMeOnlySchema = {
|
|||
type: 'string',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
twoFactorEnabled: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
usePasswordLessLogin: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
securityKeys: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
//#region relations
|
||||
isFollowing: {
|
||||
type: 'boolean',
|
||||
|
|
|
@ -12,6 +12,7 @@ import type {
|
|||
MiMeta,
|
||||
SigninsRepository,
|
||||
UserProfilesRepository,
|
||||
UserSecurityKeysRepository,
|
||||
UsersRepository,
|
||||
} from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
|
@ -25,9 +26,18 @@ import { CaptchaService } from '@/core/CaptchaService.js';
|
|||
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
|
||||
import { RateLimiterService } from './RateLimiterService.js';
|
||||
import { SigninService } from './SigninService.js';
|
||||
import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
|
||||
import type { AuthenticationResponseJSON, PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types';
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
|
||||
type SigninErrorResponse = {
|
||||
id: string;
|
||||
next?: 'captcha' | 'password' | 'totp';
|
||||
} | {
|
||||
id: string;
|
||||
next: 'passkey';
|
||||
authRequest: PublicKeyCredentialRequestOptionsJSON;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class SigninApiService {
|
||||
constructor(
|
||||
|
@ -43,6 +53,9 @@ export class SigninApiService {
|
|||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
@Inject(DI.userSecurityKeysRepository)
|
||||
private userSecurityKeysRepository: UserSecurityKeysRepository,
|
||||
|
||||
@Inject(DI.signinsRepository)
|
||||
private signinsRepository: SigninsRepository,
|
||||
|
||||
|
@ -60,7 +73,7 @@ export class SigninApiService {
|
|||
request: FastifyRequest<{
|
||||
Body: {
|
||||
username: string;
|
||||
password: string;
|
||||
password?: string;
|
||||
token?: string;
|
||||
credential?: AuthenticationResponseJSON;
|
||||
'hcaptcha-response'?: string;
|
||||
|
@ -79,7 +92,7 @@ export class SigninApiService {
|
|||
const password = body['password'];
|
||||
const token = body['token'];
|
||||
|
||||
function error(status: number, error: { id: string }) {
|
||||
function error(status: number, error: SigninErrorResponse) {
|
||||
reply.code(status);
|
||||
return { error };
|
||||
}
|
||||
|
@ -103,11 +116,6 @@ export class SigninApiService {
|
|||
return;
|
||||
}
|
||||
|
||||
if (typeof password !== 'string') {
|
||||
reply.code(400);
|
||||
return;
|
||||
}
|
||||
|
||||
if (token != null && typeof token !== 'string') {
|
||||
reply.code(400);
|
||||
return;
|
||||
|
@ -132,11 +140,36 @@ export class SigninApiService {
|
|||
}
|
||||
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
const securityKeysAvailable = await this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1);
|
||||
|
||||
if (password == null) {
|
||||
reply.code(403);
|
||||
if (profile.twoFactorEnabled) {
|
||||
return {
|
||||
error: {
|
||||
id: '144ff4f8-bd6c-41bc-82c3-b672eb09efbf',
|
||||
next: 'password',
|
||||
},
|
||||
} satisfies { error: SigninErrorResponse };
|
||||
} else {
|
||||
return {
|
||||
error: {
|
||||
id: '144ff4f8-bd6c-41bc-82c3-b672eb09efbf',
|
||||
next: 'captcha',
|
||||
},
|
||||
} satisfies { error: SigninErrorResponse };
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof password !== 'string') {
|
||||
reply.code(400);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compare password
|
||||
const same = await bcrypt.compare(password, profile.password!);
|
||||
|
||||
const fail = async (status?: number, failure?: { id: string }) => {
|
||||
const fail = async (status?: number, failure?: SigninErrorResponse) => {
|
||||
// Append signin history
|
||||
await this.signinsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
|
@ -217,7 +250,7 @@ export class SigninApiService {
|
|||
id: '93b86c4b-72f9-40eb-9815-798928603d1e',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
} else if (securityKeysAvailable) {
|
||||
if (!same && !profile.usePasswordLessLogin) {
|
||||
return await fail(403, {
|
||||
id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
|
||||
|
@ -226,8 +259,28 @@ export class SigninApiService {
|
|||
|
||||
const authRequest = await this.webAuthnService.initiateAuthentication(user.id);
|
||||
|
||||
reply.code(200);
|
||||
return authRequest;
|
||||
reply.code(403);
|
||||
return {
|
||||
error: {
|
||||
id: '06e661b9-8146-4ae3-bde5-47138c0ae0c4',
|
||||
next: 'passkey',
|
||||
authRequest,
|
||||
},
|
||||
} satisfies { error: SigninErrorResponse };
|
||||
} else {
|
||||
if (!same || !profile.twoFactorEnabled) {
|
||||
return await fail(403, {
|
||||
id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
|
||||
});
|
||||
} else {
|
||||
reply.code(403);
|
||||
return {
|
||||
error: {
|
||||
id: '144ff4f8-bd6c-41bc-82c3-b672eb09efbf',
|
||||
next: 'totp',
|
||||
},
|
||||
} satisfies { error: SigninErrorResponse };
|
||||
}
|
||||
}
|
||||
// never get here
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { get as webAuthnRequest } from '@github/webauthn-json/browser-ponyfill';
|
||||
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
@ -32,7 +31,6 @@ import MkButton from '@/components/MkButton.vue';
|
|||
import type { AuthenticationPublicKeyCredential } from '@github/webauthn-json/browser-ponyfill';
|
||||
|
||||
const props = defineProps<{
|
||||
user: Misskey.entities.UserDetailed;
|
||||
credentialRequest: CredentialRequestOptions;
|
||||
isPerformingPasswordlessLogin?: boolean;
|
||||
}>();
|
||||
|
|
|
@ -23,14 +23,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
|
||||
</MkInput>
|
||||
|
||||
<div v-if="!user.twoFactorEnabled">
|
||||
<div v-if="needCaptcha">
|
||||
<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :class="$style.captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
|
||||
<MkCaptcha v-if="instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
|
||||
<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
|
||||
<MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
|
||||
</div>
|
||||
|
||||
<MkButton type="submit" :disabled="!user.twoFactorEnabled && captchaFailed" large primary rounded style="margin: 0 auto;" data-cy-signin-page-password-continue>{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
|
||||
<MkButton type="submit" :disabled="needCaptcha && captchaFailed" large primary rounded style="margin: 0 auto;" data-cy-signin-page-password-continue>{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -62,6 +62,7 @@ import MkCaptcha from '@/components/MkCaptcha.vue';
|
|||
|
||||
const props = defineProps<{
|
||||
user: Misskey.entities.UserDetailed;
|
||||
needCaptcha: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
@ -82,10 +83,11 @@ const turnstileResponse = ref<string | null>(null);
|
|||
|
||||
const captchaFailed = computed((): boolean => {
|
||||
return (
|
||||
instance.enableHcaptcha && !hCaptchaResponse.value ||
|
||||
instance.enableMcaptcha && !mCaptchaResponse.value ||
|
||||
instance.enableRecaptcha && !reCaptchaResponse.value ||
|
||||
instance.enableTurnstile && !turnstileResponse.value);
|
||||
(instance.enableHcaptcha && !hCaptchaResponse.value) ||
|
||||
(instance.enableMcaptcha && !mCaptchaResponse.value) ||
|
||||
(instance.enableRecaptcha && !reCaptchaResponse.value) ||
|
||||
(instance.enableTurnstile && !turnstileResponse.value)
|
||||
);
|
||||
});
|
||||
|
||||
function resetPassword(): void {
|
||||
|
|
|
@ -32,6 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
ref="passwordPageEl"
|
||||
|
||||
:user="userInfo!"
|
||||
:needCaptcha="needCaptcha"
|
||||
|
||||
@passwordSubmitted="onPasswordSubmitted"
|
||||
/>
|
||||
|
@ -49,7 +50,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
v-else-if="page === 'passkey'"
|
||||
key="passkey"
|
||||
|
||||
:user="userInfo!"
|
||||
:credentialRequest="credentialRequest!"
|
||||
:isPerformingPasswordlessLogin="doingPasskeyFromInputPage"
|
||||
|
||||
|
@ -64,14 +64,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, shallowRef, useTemplateRef } from 'vue';
|
||||
import { nextTick, onBeforeUnmount, ref, shallowRef, useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { supported as webAuthnSupported, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
|
||||
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
|
||||
import { login } from '@/account.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
|
||||
|
@ -98,9 +97,11 @@ const props = withDefaults(defineProps<{
|
|||
});
|
||||
|
||||
const page = ref<'input' | 'password' | 'totp' | 'passkey'>('input');
|
||||
const passwordPageEl = useTemplateRef('passwordPageEl');
|
||||
const waiting = ref(false);
|
||||
|
||||
const passwordPageEl = useTemplateRef('passwordPageEl');
|
||||
const needCaptcha = ref(false);
|
||||
|
||||
const userInfo = ref<null | Misskey.entities.UserDetailed>(null);
|
||||
const password = ref('');
|
||||
|
||||
|
@ -142,14 +143,11 @@ function onPasskeyDone(credential: AuthenticationPublicKeyCredential): void {
|
|||
emit('login', res.signinResponse);
|
||||
}).catch(onLoginFailed);
|
||||
} else if (userInfo.value != null) {
|
||||
misskeyApi('signin', {
|
||||
tryLogin({
|
||||
username: userInfo.value.username,
|
||||
password: password.value,
|
||||
credential: credential.toJSON(),
|
||||
}).then(async (res) => {
|
||||
emit('login', res);
|
||||
await onLoginSucceeded(res);
|
||||
}).catch(onLoginFailed);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,17 +169,18 @@ async function onUsernameSubmitted(username: string) {
|
|||
title: i18n.ts.noSuchUser,
|
||||
text: i18n.ts.signinFailed,
|
||||
});
|
||||
} else if (userInfo.value.usePasswordLessLogin) {
|
||||
page.value = 'passkey';
|
||||
} else {
|
||||
page.value = 'password';
|
||||
waiting.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
waiting.value = false;
|
||||
await tryLogin({
|
||||
username,
|
||||
});
|
||||
}
|
||||
|
||||
async function onPasswordSubmitted(pw: PwResponse) {
|
||||
waiting.value = true;
|
||||
password.value = pw.password;
|
||||
|
||||
if (userInfo.value == null) {
|
||||
await os.alert({
|
||||
|
@ -189,48 +188,17 @@ async function onPasswordSubmitted(pw: PwResponse) {
|
|||
title: i18n.ts.noSuchUser,
|
||||
text: i18n.ts.signinFailed,
|
||||
});
|
||||
waiting.value = false;
|
||||
return;
|
||||
} else {
|
||||
if (!userInfo.value.twoFactorEnabled) {
|
||||
if (
|
||||
(instance.enableHcaptcha || instance.enableMcaptcha || instance.enableRecaptcha || instance.enableTurnstile) &&
|
||||
(pw.captcha.hCaptchaResponse == null && pw.captcha.mCaptchaResponse == null && pw.captcha.reCaptchaResponse == null && pw.captcha.turnstileResponse == null)
|
||||
) {
|
||||
// 2FAが無効で、CAPTCHAが有効で、かつCAPTCHAが未入力の場合
|
||||
onLoginFailed();
|
||||
waiting.value = false;
|
||||
return;
|
||||
} else {
|
||||
await misskeyApi('signin', {
|
||||
username: userInfo.value.username,
|
||||
password: pw.password,
|
||||
'h-captcha-response': pw.captcha.hCaptchaResponse,
|
||||
'm-captcha-response': pw.captcha.mCaptchaResponse,
|
||||
'g-recaptcha-response': pw.captcha.reCaptchaResponse,
|
||||
'turnstile-response': pw.captcha.turnstileResponse,
|
||||
}).then(async (res) => {
|
||||
emit('login', res);
|
||||
await onLoginSucceeded(res);
|
||||
}).catch(onLoginFailed);
|
||||
}
|
||||
} else if (userInfo.value.securityKeys) {
|
||||
password.value = pw.password;
|
||||
|
||||
await misskeyApi('signin', {
|
||||
username: userInfo.value.username,
|
||||
password: pw.password,
|
||||
}).then((res) => {
|
||||
credentialRequest.value = parseRequestOptionsFromJSON({
|
||||
publicKey: res,
|
||||
});
|
||||
page.value = 'passkey';
|
||||
waiting.value = false;
|
||||
}).catch(onLoginFailed);
|
||||
} else {
|
||||
password.value = pw.password;
|
||||
page.value = 'totp';
|
||||
waiting.value = false;
|
||||
}
|
||||
await tryLogin({
|
||||
username: userInfo.value.username,
|
||||
password: pw.password,
|
||||
'hcaptcha-response': pw.captcha.hCaptchaResponse,
|
||||
'm-captcha-response': pw.captcha.mCaptchaResponse,
|
||||
'g-recaptcha-response': pw.captcha.reCaptchaResponse,
|
||||
'turnstile-response': pw.captcha.turnstileResponse,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -243,19 +211,37 @@ async function onTotpSubmitted(token: string) {
|
|||
title: i18n.ts.noSuchUser,
|
||||
text: i18n.ts.signinFailed,
|
||||
});
|
||||
waiting.value = false;
|
||||
return;
|
||||
} else {
|
||||
await misskeyApi('signin', {
|
||||
await tryLogin({
|
||||
username: userInfo.value.username,
|
||||
password: password.value,
|
||||
token,
|
||||
}).then(async (res) => {
|
||||
emit('login', res);
|
||||
await onLoginSucceeded(res);
|
||||
}).catch(onLoginFailed);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function tryLogin(req: Partial<Misskey.entities.SigninRequest>): Promise<Misskey.entities.SigninResponse> {
|
||||
if (userInfo.value == null) {
|
||||
throw new Error('No such user');
|
||||
}
|
||||
|
||||
const _req = {
|
||||
username: userInfo.value.username,
|
||||
...req,
|
||||
};
|
||||
|
||||
return await misskeyApi('signin', _req).then(async (res) => {
|
||||
emit('login', res);
|
||||
await onLoginSucceeded(res);
|
||||
return res;
|
||||
}).catch((err) => {
|
||||
onLoginFailed(err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
async function onLoginSucceeded(res: Misskey.entities.SigninResponse) {
|
||||
if (props.autoSet) {
|
||||
await login(res.i);
|
||||
|
@ -265,92 +251,128 @@ async function onLoginSucceeded(res: Misskey.entities.SigninResponse) {
|
|||
function onLoginFailed(err?: any): void {
|
||||
const id = err?.id ?? null;
|
||||
|
||||
switch (id) {
|
||||
case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: i18n.ts.noSuchUser,
|
||||
});
|
||||
break;
|
||||
if (typeof err === 'object' && 'next' in err) {
|
||||
switch (err.next) {
|
||||
case 'captcha': {
|
||||
page.value = 'password';
|
||||
break;
|
||||
}
|
||||
case 'password': {
|
||||
page.value = 'password';
|
||||
break;
|
||||
}
|
||||
case 'totp': {
|
||||
page.value = 'totp';
|
||||
break;
|
||||
}
|
||||
case 'passkey': {
|
||||
if (webAuthnSupported() && 'authRequest' in err) {
|
||||
credentialRequest.value = parseRequestOptionsFromJSON({
|
||||
publicKey: err.authRequest,
|
||||
});
|
||||
page.value = 'passkey';
|
||||
} else {
|
||||
page.value = 'totp';
|
||||
}
|
||||
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;
|
||||
}
|
||||
case 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: i18n.ts.incorrectTotp,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '36b96a7d-b547-412d-aeed-2d611cdc8cdc': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: i18n.ts.unknownWebAuthnKey,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '93b86c4b-72f9-40eb-9815-798928603d1e': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: i18n.ts.passkeyVerificationFailed,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'b18c89a7-5b5e-4cec-bb5b-0419f332d430': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: i18n.ts.passkeyVerificationFailed,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '2d84773e-f7b7-4d0b-8f72-bb69b584c912': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: i18n.ts.passkeyVerificationSucceededButPasswordlessLoginDisabled,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.error(err);
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: JSON.stringify(err),
|
||||
});
|
||||
} else {
|
||||
switch (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;
|
||||
}
|
||||
case 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: i18n.ts.incorrectTotp,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '36b96a7d-b547-412d-aeed-2d611cdc8cdc': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: i18n.ts.unknownWebAuthnKey,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '93b86c4b-72f9-40eb-9815-798928603d1e': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: i18n.ts.passkeyVerificationFailed,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'b18c89a7-5b5e-4cec-bb5b-0419f332d430': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: i18n.ts.passkeyVerificationFailed,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '2d84773e-f7b7-4d0b-8f72-bb69b584c912': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: i18n.ts.passkeyVerificationSucceededButPasswordlessLoginDisabled,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.error(err);
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: JSON.stringify(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (doingPasskeyFromInputPage.value === true) {
|
||||
doingPasskeyFromInputPage.value = false;
|
||||
page.value = 'input';
|
||||
password.value = '';
|
||||
}
|
||||
passwordPageEl.value?.resetCaptcha();
|
||||
waiting.value = false;
|
||||
nextTick(() => {
|
||||
waiting.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
password.value = '';
|
||||
userInfo.value = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
|
|
@ -68,7 +68,6 @@ function onLogin(res) {
|
|||
height: 100%;
|
||||
max-height: 450px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
background: var(--panel);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
|
|
@ -3040,7 +3040,7 @@ type Signin = components['schemas']['Signin'];
|
|||
// @public (undocumented)
|
||||
type SigninRequest = {
|
||||
username: string;
|
||||
password: string;
|
||||
password?: string;
|
||||
token?: string;
|
||||
credential?: AuthenticationResponseJSON;
|
||||
'hcaptcha-response'?: string | null;
|
||||
|
|
|
@ -3782,16 +3782,13 @@ export type components = {
|
|||
followingVisibility: 'public' | 'followers' | 'private';
|
||||
/** @enum {string} */
|
||||
followersVisibility: 'public' | 'followers' | 'private';
|
||||
/** @default false */
|
||||
twoFactorEnabled: boolean;
|
||||
/** @default false */
|
||||
usePasswordLessLogin: boolean;
|
||||
/** @default false */
|
||||
securityKeys: boolean;
|
||||
roles: components['schemas']['RoleLite'][];
|
||||
followedMessage?: string | null;
|
||||
memo: string | null;
|
||||
moderationNote?: string;
|
||||
twoFactorEnabled?: boolean;
|
||||
usePasswordLessLogin?: boolean;
|
||||
securityKeys?: boolean;
|
||||
isFollowing?: boolean;
|
||||
isFollowed?: boolean;
|
||||
hasPendingFollowRequestFromYou?: boolean;
|
||||
|
|
|
@ -269,7 +269,7 @@ export type SignupPendingResponse = {
|
|||
|
||||
export type SigninRequest = {
|
||||
username: string;
|
||||
password: string;
|
||||
password?: string;
|
||||
token?: string;
|
||||
credential?: AuthenticationResponseJSON;
|
||||
'hcaptcha-response'?: string | null;
|
||||
|
|
Loading…
Reference in New Issue