Merge 373d2aeb04
into 00cbf9fe80
This commit is contained in:
commit
7f9bec8b7b
|
@ -5174,6 +5174,10 @@ export interface Locale extends ILocale {
|
|||
* パスキーの検証に成功しましたが、パスワードレスログインが無効になっています。
|
||||
*/
|
||||
"passkeyVerificationSucceededButPasswordlessLoginDisabled": string;
|
||||
/**
|
||||
* お使いのブラウザはパスキーをサポートしていません。
|
||||
*/
|
||||
"yourBrowserDoesNotSupportPasskey": string;
|
||||
/**
|
||||
* フォロワーへのメッセージ
|
||||
*/
|
||||
|
|
|
@ -1289,6 +1289,7 @@ signinWithPasskey: "パスキーでログイン"
|
|||
unknownWebAuthnKey: "登録されていないパスキーです。"
|
||||
passkeyVerificationFailed: "パスキーの検証に失敗しました。"
|
||||
passkeyVerificationSucceededButPasswordlessLoginDisabled: "パスキーの検証に成功しましたが、パスワードレスログインが無効になっています。"
|
||||
yourBrowserDoesNotSupportPasskey: "お使いのブラウザはパスキーをサポートしていません。"
|
||||
messageToFollower: "フォロワーへのメッセージ"
|
||||
target: "対象"
|
||||
testCaptchaWarning: "CAPTCHAのテストを目的とした機能です。<strong>本番環境で使用しないでください。</strong>"
|
||||
|
|
|
@ -137,6 +137,17 @@ export class SigninApiService {
|
|||
if (password == null) {
|
||||
reply.code(200);
|
||||
if (profile.twoFactorEnabled) {
|
||||
if (profile.usePasswordLessLogin && securityKeysAvailable) {
|
||||
const authRequest = await this.webAuthnService.initiateAuthentication(user.id);
|
||||
|
||||
return {
|
||||
finished: false,
|
||||
next: 'passkey',
|
||||
force: true,
|
||||
authRequest,
|
||||
} satisfies Misskey.entities.SigninFlowResponse;
|
||||
}
|
||||
|
||||
return {
|
||||
finished: false,
|
||||
next: 'password',
|
||||
|
|
|
@ -40,14 +40,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</form>
|
||||
|
||||
<!-- パスワードレスログイン -->
|
||||
<div :class="$style.orHr">
|
||||
<p :class="$style.orMsg">{{ i18n.ts.or }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<MkButton type="submit" style="margin: auto auto;" large rounded primary gradate @click="emit('passkeyClick', $event)">
|
||||
<i class="ti ti-device-usb" style="font-size: medium;"></i>{{ i18n.ts.signinWithPasskey }}
|
||||
</MkButton>
|
||||
</div>
|
||||
<template v-if="webAuthnSupported()">
|
||||
<div :class="$style.orHr">
|
||||
<p :class="$style.orMsg">{{ i18n.ts.or }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<MkButton type="submit" style="margin: auto auto;" large rounded primary gradate @click="emit('passkeyClick', $event)">
|
||||
<i class="ti ti-device-usb" style="font-size: medium;"></i>{{ i18n.ts.signinWithPasskey }}
|
||||
</MkButton>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -55,6 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { toUnicode } from 'punycode/';
|
||||
import { supported as webAuthnSupported } from '@github/webauthn-json/browser-ponyfill';
|
||||
|
||||
import { query, extractDomain } from '@@/js/url.js';
|
||||
import { host as configHost } from '@@/js/config.js';
|
||||
|
|
|
@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
key="passkey"
|
||||
|
||||
:credentialRequest="credentialRequest!"
|
||||
:isPerformingPasswordlessLogin="doingPasskeyFromInputPage"
|
||||
:isPerformingPasswordlessLogin="doingPasskeyFromInputPage || needForcedPasskey"
|
||||
|
||||
@done="onPasskeyDone"
|
||||
@useTotp="onUseTotp"
|
||||
|
@ -100,6 +100,7 @@ const waiting = ref(false);
|
|||
|
||||
const passwordPageEl = useTemplateRef('passwordPageEl');
|
||||
const needCaptcha = ref(false);
|
||||
const needForcedPasskey = ref(false);
|
||||
|
||||
const userInfo = ref<null | Misskey.entities.UserDetailed>(null);
|
||||
const password = ref('');
|
||||
|
@ -247,7 +248,22 @@ async function tryLogin(req: Partial<Misskey.entities.SigninFlowRequest>): Promi
|
|||
break;
|
||||
}
|
||||
case 'passkey': {
|
||||
if (webAuthnSupported()) {
|
||||
if (res.force === true) {
|
||||
if (webAuthnSupported()) {
|
||||
needForcedPasskey.value = true;
|
||||
credentialRequest.value = parseRequestOptionsFromJSON({
|
||||
publicKey: res.authRequest,
|
||||
});
|
||||
page.value = 'passkey';
|
||||
} else {
|
||||
const err = {
|
||||
id: '8b12bdf5-d5ed-4429-b5da-e3370cfcb869',
|
||||
};
|
||||
|
||||
onSigninApiError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
} else if (webAuthnSupported()) {
|
||||
credentialRequest.value = parseRequestOptionsFromJSON({
|
||||
publicKey: res.authRequest,
|
||||
});
|
||||
|
@ -264,6 +280,9 @@ async function tryLogin(req: Partial<Misskey.entities.SigninFlowRequest>): Promi
|
|||
page.value = 'input';
|
||||
password.value = '';
|
||||
}
|
||||
if (!('force' in res)) {
|
||||
needForcedPasskey.value = false;
|
||||
}
|
||||
passwordPageEl.value?.resetCaptcha();
|
||||
nextTick(() => {
|
||||
waiting.value = false;
|
||||
|
@ -286,6 +305,7 @@ function onSigninApiError(err?: any): void {
|
|||
const id = err?.id ?? null;
|
||||
|
||||
switch (id) {
|
||||
// signin-flow api
|
||||
case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
|
@ -338,6 +358,8 @@ function onSigninApiError(err?: any): void {
|
|||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// signin-with-passkey api
|
||||
case 'b18c89a7-5b5e-4cec-bb5b-0419f332d430': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
|
@ -354,6 +376,18 @@ function onSigninApiError(err?: any): void {
|
|||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// client-produced error
|
||||
case '8b12bdf5-d5ed-4429-b5da-e3370cfcb869': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: i18n.ts.yourBrowserDoesNotSupportPasskey,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// default
|
||||
default: {
|
||||
console.error(err);
|
||||
os.alert({
|
||||
|
@ -369,6 +403,7 @@ function onSigninApiError(err?: any): void {
|
|||
page.value = 'input';
|
||||
password.value = '';
|
||||
}
|
||||
needForcedPasskey.value = false;
|
||||
passwordPageEl.value?.resetCaptcha();
|
||||
nextTick(() => {
|
||||
waiting.value = false;
|
||||
|
|
|
@ -3083,6 +3083,7 @@ type SigninFlowResponse = {
|
|||
} | {
|
||||
finished: false;
|
||||
next: 'passkey';
|
||||
force?: boolean;
|
||||
authRequest: PublicKeyCredentialRequestOptionsJSON;
|
||||
};
|
||||
|
||||
|
|
|
@ -294,6 +294,7 @@ export type SigninFlowResponse = {
|
|||
} | {
|
||||
finished: false;
|
||||
next: 'passkey';
|
||||
force?: boolean;
|
||||
authRequest: PublicKeyCredentialRequestOptionsJSON;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue