From 36713ba05f47a36e7b167610d236b60d72ae4288 Mon Sep 17 00:00:00 2001 From: kdh8219 Date: Fri, 6 Sep 2024 23:22:18 +0900 Subject: [PATCH 01/23] [refactor: frontend, note editing] merge both history sub content component --- .../src/components/MkNoteHistorySub.vue | 4 +- .../components/MkNoteHistorySubContent.vue | 20 ++++- .../src/components/MkNoteHistorySubRaw.vue | 76 ------------------- 3 files changed, 19 insertions(+), 81 deletions(-) delete mode 100644 packages/frontend/src/components/MkNoteHistorySubRaw.vue diff --git a/packages/frontend/src/components/MkNoteHistorySub.vue b/packages/frontend/src/components/MkNoteHistorySub.vue index 385b3254aca8..95411571f314 100644 --- a/packages/frontend/src/components/MkNoteHistorySub.vue +++ b/packages/frontend/src/components/MkNoteHistorySub.vue @@ -22,8 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only - - + @@ -34,7 +33,6 @@ import { ref } from 'vue'; import * as Misskey from 'misskey-js'; import MkNoteHeader from './MkNoteHeader.vue'; import MkNoteHistorySubContent from './MkNoteHistorySubContent.vue'; -import MkNoteHistorySubRaw from './MkNoteHistorySubRaw.vue'; import { userPage } from '@/filters/user.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/components/MkNoteHistorySubContent.vue b/packages/frontend/src/components/MkNoteHistorySubContent.vue index 7bb8d7a50db7..012bf72d2e38 100644 --- a/packages/frontend/src/components/MkNoteHistorySubContent.vue +++ b/packages/frontend/src/components/MkNoteHistorySubContent.vue @@ -5,8 +5,21 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -66,6 +72,7 @@ import { defineAsyncComponent, ref } from 'vue'; import { toUnicode } from 'punycode/'; import * as Misskey from 'misskey-js'; import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill'; +import { SigninWithPasskeyResponse } from 'misskey-js/entities.js'; import type { OpenOnRemoteOptions } from '@/scripts/please-login.js'; import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js'; import MkButton from '@/components/MkButton.vue'; @@ -150,6 +157,56 @@ async function queryKey(): Promise { }); } +const passkey_context = ref(''); + +function onPasskey(): void { + signing.value = true; + if (webAuthnSupported()) { + misskeyApi('signin-with-passkey', {}) + .then((res) => { + totpLogin.value = false; + signing.value = false; + queryingKey.value = true; + passkey_context.value = res.context; + credentialRequest = parseRequestOptionsFromJSON({ + publicKey: res.option, + }); + }) + .then(() => queryPasskey()) + .catch(loginFailed); + } +} + +async function queryPasskey(): Promise { + if (credentialRequest == null) return; + queryingKey.value = true; + console.log('Waiting passkey auth...'); + await webAuthnRequest(credentialRequest) + .catch((er) => { + console.warn('Fail!!', er); + queryingKey.value = false; + return Promise.reject(null); + }).then(credential => { + credentialRequest = null; + queryingKey.value = false; + signing.value = true; + return misskeyApi('signin-with-passkey', { + credential: credential.toJSON(), + context: passkey_context.value, + }); + }).then(res => { + emit('login', res); + return onLogin(res); + }).catch(err => { + if (err === null) return; + os.alert({ + type: 'error', + text: i18n.ts.signinFailed, + }); + signing.value = false; + }); +} + function onSubmit(): void { signing.value = true; if (!totpLogin.value && user.value && user.value.twoFactorEnabled) { diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 66dfa453daf5..c9f819f34dee 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1157,6 +1157,10 @@ export type Endpoints = Overwrite; res: AdminRolesCreateResponse; diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index 08d3dc5c6d37..a648aafdea71 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -271,6 +271,16 @@ export type SigninRequest = { token?: string; }; +export type SigninWithPasskeyRequest = { + credential?: object; + context?: string; +}; + +export type SigninWithPasskeyResponse = { + option?: object; + context?: string; +} | SigninResponse; + export type SigninResponse = { id: User['id'], i: string, From afe970944e1244bb60adb81fabd740a3904a2b2b Mon Sep 17 00:00:00 2001 From: Squarecat-meow Date: Thu, 12 Sep 2024 14:10:29 +0000 Subject: [PATCH 08/23] =?UTF-8?q?=F0=9F=92=84=20Added=20"Login=20with=20Pa?= =?UTF-8?q?sskey"=20Button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en-US.yml | 2 ++ locales/index.d.ts | 4 ++++ locales/ja-JP.yml | 1 + locales/ko-KR.yml | 1 + packages/frontend/src/components/MkSignin.vue | 11 +++++++---- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index 1b35b94d6bef..849dc2a1d16e 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1265,6 +1265,8 @@ createdLists: "Created lists" createdAntennas: "Created antennas" noteUpdatedAt: "Edited: {date} {time}" editHistory: "Edit history" +signinWithPasskey: "Login With Passkey" + _delivery: status: "Delivery status" stop: "Suspended" diff --git a/locales/index.d.ts b/locales/index.d.ts index 0be8325b4197..d0f54bc66189 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5084,6 +5084,10 @@ export interface Locale extends ILocale { * 閉じる */ "unfold": string; + /** + * パスキーでログイン + */ + "signinWithPasskey": string; "_delivery": { /** * 配信状態 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index d58413db447c..2fdd0ebb03d1 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1267,6 +1267,7 @@ noteUpdatedAt: "編集済み: {date} {time}" editHistory: "修正履歴" fold: "開く" unfold: "閉じる" +signinWithPasskey: "パスキーでログイン" _delivery: status: "配信状態" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index c130eece6d82..518c6d3cf8f4 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1253,6 +1253,7 @@ noteUpdatedAt: "편집됨: {date} {time}" editHistory: "편집 기록" fold: "펼치기" unfold: "접기" +signinWithPasskey: "패스키로 로그인" _delivery: status: "전송 상태" diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index d2bfe8fa51ca..99cb9b9cd0ad 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -58,10 +58,12 @@ SPDX-License-Identifier: AGPL-3.0-only
-

{{ i18n.ts.useSecurityKey }}

- - {{ i18n.ts.login }} + + + + {{ signing ? i18n.ts.loggingIn : i18n.ts.signinWithPasskey }} +

{{ i18n.ts.useSecurityKey }}

@@ -84,6 +86,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { query, extractDomain } from '@/scripts/url.js'; import { login } from '@/account.js'; import { i18n } from '@/i18n.js'; +import MkDivider from './MkDivider.vue'; const signing = ref(false); const user = ref(null); @@ -194,7 +197,7 @@ async function queryPasskey(): Promise { credential: credential.toJSON(), context: passkey_context.value, }); - }).then(res => { + }).then((res: SigninWithPasskeyResponse) => { emit('login', res); return onLogin(res); }).catch(err => { From 748c1393091d8436a3dabc2933bf3659a4685883 Mon Sep 17 00:00:00 2001 From: Yuno Date: Thu, 12 Sep 2024 14:24:06 +0000 Subject: [PATCH 09/23] =?UTF-8?q?WebAuthn=EC=B1=8C=EB=A6=B0=EC=A7=80?= =?UTF-8?q?=EC=97=90=20=EC=8B=A4=ED=8C=A8=ED=95=9C=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=EC=9D=98=20=EC=98=A4=EB=A5=98=20=EC=9D=91=EB=8B=B5=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/WebAuthnService.ts | 5 ++++ .../server/api/SigninWithPasskeyApiService.ts | 23 +++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index d1fd61d583b7..6ed2190245b7 100644 --- a/packages/backend/src/core/WebAuthnService.ts +++ b/packages/backend/src/core/WebAuthnService.ts @@ -180,6 +180,11 @@ export class WebAuthnService { return authenticationOptions; } + /** + * Verify Webauthn AuthenticationCredential + * @throws IdentifiableError + * @returns MiUser['id'] or null + */ @bindThis public async verifySignInWithPasskeyAuthentication(context: string, response: AuthenticationResponseJSON): Promise { const challenge = await this.redisClient.get(`webauthn:challenge:${context}`); diff --git a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts index 9ae52af8f488..6f21c45d595a 100644 --- a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts +++ b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts @@ -28,6 +28,7 @@ import type { FastifyReply, FastifyRequest } from 'fastify'; @Injectable() export class SigninWithPasskeyApiService { + private logger: Logger; constructor( @Inject(DI.config) private config: Config, @@ -45,7 +46,9 @@ export class SigninWithPasskeyApiService { private rateLimiterService: RateLimiterService, private signinService: SigninService, private webAuthnService: WebAuthnService, + private loggerService: LoggerService, ) { + this.logger = this.loggerService.getLogger('PasskeyAuth'); } @bindThis @@ -82,8 +85,8 @@ export class SigninWithPasskeyApiService { }; try { - // not more than 1 attempt per second and not more than 500 attempts per hour - await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 500, minInterval: 1000 }, getIpHash(request.ip)); + // not more than 1 attempt per second and not more than 100 attempts per hour + await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 100, minInterval: 1000 }, getIpHash(request.ip)); } catch (err) { reply.code(429); return { @@ -107,14 +110,24 @@ export class SigninWithPasskeyApiService { } const context = body.context; - console.log(`passkey auth context: ${context}`); if (!context || typeof context !== 'string') { reply.code(400); return; } - const authorizedUserId: MiUser['id'] | null = await this.webAuthnService.verifySignInWithPasskeyAuthentication(context, credential); - if (authorizedUserId == null) { + this.logger.debug(`VerifySignin Passkey auth: context: ${context}`); + let authorizedUserId : MiUser['id'] | null; + try { + authorizedUserId = await this.webAuthnService.verifySignInWithPasskeyAuthentication(context, credential); + } catch (err) { + this.logger.warn(`Verify error! : ${err}`); + const errorId = (err as IdentifiableError).id; + return error(403, { + id: errorId, + }); + } + + if (!authorizedUserId) { return error(403, { id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', }); From 58374b169eca965a1444b3aca392096baecc45d7 Mon Sep 17 00:00:00 2001 From: Yuno Date: Thu, 12 Sep 2024 14:32:10 +0000 Subject: [PATCH 10/23] =?UTF-8?q?signinResponse=20=EB=8A=94=20SigninWithPa?= =?UTF-8?q?sskeyResponse=20=EC=9D=98=20=EC=95=84=EB=9E=98=EC=97=90=20?= =?UTF-8?q?=EB=84=A3=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/server/api/SigninWithPasskeyApiService.ts | 9 ++++++--- packages/misskey-js/etc/misskey-js.api.md | 3 ++- packages/misskey-js/src/entities.ts | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts index 6f21c45d595a..933abe2c159f 100644 --- a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts +++ b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts @@ -85,7 +85,7 @@ export class SigninWithPasskeyApiService { }; try { - // not more than 1 attempt per second and not more than 100 attempts per hour + // not more than 1 attempt per second and not more than 100 attempts per hour await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 100, minInterval: 1000 }, getIpHash(request.ip)); } catch (err) { reply.code(429); @@ -116,7 +116,7 @@ export class SigninWithPasskeyApiService { } this.logger.debug(`VerifySignin Passkey auth: context: ${context}`); - let authorizedUserId : MiUser['id'] | null; + let authorizedUserId: MiUser['id'] | null; try { authorizedUserId = await this.webAuthnService.verifySignInWithPasskeyAuthentication(context, credential); } catch (err) { @@ -159,6 +159,9 @@ export class SigninWithPasskeyApiService { }); } - return this.signinService.signin(request, reply, user); + const signinResponse = this.signinService.signin(request, reply, user); + return { + signinResponse: signinResponse, + }; } } diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index c9f819f34dee..1f90d29a8b9e 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2998,7 +2998,8 @@ type SigninWithPasskeyRequest = { type SigninWithPasskeyResponse = { option?: object; context?: string; -} | SigninResponse; + signinResponse?: SigninResponse; +}; // @public (undocumented) type SignupPendingRequest = { diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index a648aafdea71..64ed90cbb119 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -279,7 +279,8 @@ export type SigninWithPasskeyRequest = { export type SigninWithPasskeyResponse = { option?: object; context?: string; -} | SigninResponse; + signinResponse?: SigninResponse; +}; export type SigninResponse = { id: User['id'], From e932673c29d527ddfda6341dce3009ff59525701 Mon Sep 17 00:00:00 2001 From: Yuno Date: Thu, 12 Sep 2024 15:11:07 +0000 Subject: [PATCH 11/23] =?UTF-8?q?=ED=94=84=EB=A1=A0=ED=8A=B8=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/MkSignin.vue | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index 99cb9b9cd0ad..e2b34e7407ce 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -75,6 +75,7 @@ import { toUnicode } from 'punycode/'; import * as Misskey from 'misskey-js'; import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill'; import { SigninWithPasskeyResponse } from 'misskey-js/entities.js'; +import MkDivider from './MkDivider.vue'; import type { OpenOnRemoteOptions } from '@/scripts/please-login.js'; import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js'; import MkButton from '@/components/MkButton.vue'; @@ -86,7 +87,6 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { query, extractDomain } from '@/scripts/url.js'; import { login } from '@/account.js'; import { i18n } from '@/i18n.js'; -import MkDivider from './MkDivider.vue'; const signing = ref(false); const user = ref(null); @@ -166,11 +166,11 @@ function onPasskey(): void { signing.value = true; if (webAuthnSupported()) { misskeyApi('signin-with-passkey', {}) - .then((res) => { + .then((res: SigninWithPasskeyResponse) => { totpLogin.value = false; signing.value = false; queryingKey.value = true; - passkey_context.value = res.context; + passkey_context.value = res.context ?? ''; credentialRequest = parseRequestOptionsFromJSON({ publicKey: res.option, }); @@ -198,15 +198,8 @@ async function queryPasskey(): Promise { context: passkey_context.value, }); }).then((res: SigninWithPasskeyResponse) => { - emit('login', res); - return onLogin(res); - }).catch(err => { - if (err === null) return; - os.alert({ - type: 'error', - text: i18n.ts.signinFailed, - }); - signing.value = false; + emit('login', res.signinResponse); + return onLogin(res.signinResponse); }); } From c6a957b07bbede9b8cb89279e4a7b59a8eb2562c Mon Sep 17 00:00:00 2001 From: Yunochi Date: Fri, 13 Sep 2024 00:22:35 +0000 Subject: [PATCH 12/23] Fix: Rate limiting key for passkey signin Use specific rate limiting key: 'signin-with-passkey' for passkey sign-in API to avoid collisions with signin rate-limit. --- packages/backend/src/server/api/SigninWithPasskeyApiService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts index 933abe2c159f..8de5ed96c631 100644 --- a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts +++ b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts @@ -86,7 +86,7 @@ export class SigninWithPasskeyApiService { try { // not more than 1 attempt per second and not more than 100 attempts per hour - await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 100, minInterval: 1000 }, getIpHash(request.ip)); + await this.rateLimiterService.limit({ key: 'signin-with-passkey', duration: 60 * 60 * 1000, max: 100, minInterval: 1000 }, getIpHash(request.ip)); } catch (err) { reply.code(429); return { From 5e38de3ff9e0f2d7c717097cb355feef0226775c Mon Sep 17 00:00:00 2001 From: Yunochi Date: Fri, 13 Sep 2024 02:50:12 +0000 Subject: [PATCH 13/23] feat(passkey): enhance Passkey sign-in flow and error handling - Increased the rate limit for Passkey sign-in attempts to accommodate the two API calls needed per sign-in. - Improved error messages and handling in both the `WebAuthnService` and the `SigninWithPasskeyApiService`, providing more context and better usability. - Updated error messages to provide more specific and helpful details to the user. These changes aim to enhance the Passkey sign-in experience by providing more robust error handling, improving security by limiting API calls, and delivering a more user-friendly interface. --- packages/backend/src/core/WebAuthnService.ts | 9 +++---- .../server/api/SigninWithPasskeyApiService.ts | 27 +++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index 6ed2190245b7..e925cc5f89ba 100644 --- a/packages/backend/src/core/WebAuthnService.ts +++ b/packages/backend/src/core/WebAuthnService.ts @@ -183,14 +183,14 @@ export class WebAuthnService { /** * Verify Webauthn AuthenticationCredential * @throws IdentifiableError - * @returns MiUser['id'] or null + * @returns If the challenge is successful, return the user ID. Otherwise, return null. */ @bindThis public async verifySignInWithPasskeyAuthentication(context: string, response: AuthenticationResponseJSON): Promise { const challenge = await this.redisClient.get(`webauthn:challenge:${context}`); if (!challenge) { - throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', 'challenge not found'); + throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', `challenge '${context}' not found`); } await this.redisClient.del(`webauthn:challenge:${context}`); @@ -200,7 +200,7 @@ export class WebAuthnService { }); if (!key) { - throw new IdentifiableError('36b96a7d-b547-412d-aeed-2d611cdc8cdc', 'unknown key'); + throw new IdentifiableError('36b96a7d-b547-412d-aeed-2d611cdc8cdc', 'Unknown Webauthn key'); } const relyingParty = await this.getRelyingParty(); @@ -221,8 +221,7 @@ export class WebAuthnService { requireUserVerification: true, }); } catch (error) { - console.error(error); - throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', 'verification failed'); + throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', `verification failed: ${error}`); } const { verified, authenticationInfo } = verification; diff --git a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts index 8de5ed96c631..4751f8b5a5b9 100644 --- a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts +++ b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts @@ -85,8 +85,9 @@ export class SigninWithPasskeyApiService { }; try { - // not more than 1 attempt per second and not more than 100 attempts per hour - await this.rateLimiterService.limit({ key: 'signin-with-passkey', duration: 60 * 60 * 1000, max: 100, minInterval: 1000 }, getIpHash(request.ip)); + // Not more than 1 API call per 250ms and not more than 100 attempts per 30min + // NOTE: 1 Sign-in require 2 API calls + await this.rateLimiterService.limit({ key: 'signin-with-passkey', duration: 60 * 30 * 1000, max: 200, minInterval: 250 }, getIpHash(request.ip)); } catch (err) { reply.code(429); return { @@ -98,29 +99,32 @@ export class SigninWithPasskeyApiService { }; } - // Initiate Passkey Auth with context + // Initiate Passkey Auth challenge with context if (!credential) { const context = randomUUID(); - const authRequest = { + this.logger.info(`Initiate Passkey challenge: context: ${context}`); + const authChallengeOptions = { option: await this.webAuthnService.initiateSignInWithPasskeyAuthentication(context), context: context, }; reply.code(200); - return authRequest; + return authChallengeOptions; } const context = body.context; if (!context || typeof context !== 'string') { - reply.code(400); - return; + return error(400, { + id: '1658cc2e-4495-461f-aee4-d403cdf073c1', + }); } - this.logger.debug(`VerifySignin Passkey auth: context: ${context}`); + this.logger.debug(`Try Sign-in with Passkey: context: ${context}`); + let authorizedUserId: MiUser['id'] | null; try { authorizedUserId = await this.webAuthnService.verifySignInWithPasskeyAuthentication(context, credential); } catch (err) { - this.logger.warn(`Verify error! : ${err}`); + this.logger.warn(`Passkey challenge Verify error! : ${err}`); const errorId = (err as IdentifiableError).id; return error(403, { id: errorId, @@ -141,7 +145,7 @@ export class SigninWithPasskeyApiService { if (user == null) { return error(403, { - id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', + id: '652f899f-66d4-490e-993e-6606c8ec04c3', }); } @@ -153,9 +157,10 @@ export class SigninWithPasskeyApiService { const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + // Authentication was successful, but passwordless login is not enabled if (!profile.usePasswordLessLogin) { return await fail(user.id, 403, { - id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', + id: '2d84773e-f7b7-4d0b-8f72-bb69b584c912', }); } From 30998ea32bd2c40ead0098084efa536cd45d1af6 Mon Sep 17 00:00:00 2001 From: Yunochi Date: Fri, 13 Sep 2024 04:04:00 +0000 Subject: [PATCH 14/23] Refactor: Streamline 2FA flow and remove redundant Passkey button. - Separate the flow of 1FA and 2FA. - Remove duplicate passkey buttons --- packages/frontend/src/components/MkSignin.vue | 16 +++++++--------- .../frontend/src/components/MkSigninDialog.vue | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index e2b34e7407ce..2c7b3384b48c 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -45,10 +45,6 @@ SPDX-License-Identifier: AGPL-3.0-only

{{ i18n.ts.or }}

- - - - @@ -57,13 +53,15 @@ SPDX-License-Identifier: AGPL-3.0-only {{ signing ? i18n.ts.loggingIn : i18n.ts.login }}
-
- - +
+

{{ i18n.ts.or }}

+
+
+ {{ signing ? i18n.ts.loggingIn : i18n.ts.signinWithPasskey }} -

{{ i18n.ts.useSecurityKey }}

+

{{ i18n.ts.useSecurityKey }}

diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue index 524c62b4d3aa..d48780e9de6d 100644 --- a/packages/frontend/src/components/MkSigninDialog.vue +++ b/packages/frontend/src/components/MkSigninDialog.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only From 3edee7c5cde57fecdd16ea3abad22f922eeea3b1 Mon Sep 17 00:00:00 2001 From: Squarecat-meow Date: Sun, 15 Sep 2024 16:49:59 +0000 Subject: [PATCH 15/23] =?UTF-8?q?=F0=9F=92=84=20added=20error=20messages?= =?UTF-8?q?=20to=20MkSignin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en-US.yml | 5 +++- locales/index.d.ts | 12 ++++++++++ locales/ja-JP.yml | 3 +++ locales/ko-KR.yml | 3 +++ packages/frontend/src/components/MkSignin.vue | 23 +++++++++++++++++++ 5 files changed, 45 insertions(+), 1 deletion(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index 849dc2a1d16e..f65ac47dee56 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1265,7 +1265,10 @@ createdLists: "Created lists" createdAntennas: "Created antennas" noteUpdatedAt: "Edited: {date} {time}" editHistory: "Edit history" -signinWithPasskey: "Login With Passkey" +signinWithPasskey: "Login with passkey" +unknownWebAuthnKey: "It is not authenticated passkey." +verificationFailed: "Failed to verificate passkey." +passwordlessLoginDisabled: "Passkey verification successful, but the passwordless login was not enabled." _delivery: status: "Delivery status" diff --git a/locales/index.d.ts b/locales/index.d.ts index d0f54bc66189..b856919b93b8 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5088,6 +5088,18 @@ export interface Locale extends ILocale { * パスキーでログイン */ "signinWithPasskey": string; + /** + * 登録していないパスキーです。 + */ + "unknownWebAuthnKey": string; + /** + * パスキー検証に失敗しました。 + */ + "verificationFailed": string; + /** + * パスキー検証には成功しましたが、パスワードレスログインが無効にしています。 + */ + "passwordlessLoginDisabled": string; "_delivery": { /** * 配信状態 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 2fdd0ebb03d1..1a0c3baea9e7 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1268,6 +1268,9 @@ editHistory: "修正履歴" fold: "開く" unfold: "閉じる" signinWithPasskey: "パスキーでログイン" +unknownWebAuthnKey: "登録していないパスキーです。" +verificationFailed: "パスキー検証に失敗しました。" +passwordlessLoginDisabled: "パスキー検証には成功しましたが、パスワードレスログインが無効にしています。" _delivery: status: "配信状態" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 518c6d3cf8f4..19a80f4e6a0e 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1254,6 +1254,9 @@ editHistory: "편집 기록" fold: "펼치기" unfold: "접기" signinWithPasskey: "패스키로 로그인" +unknownWebAuthnKey: "등록되지 않은 패스키 입니다." +verificationFailed: "패스키 검증이 실패했습니다." +passwordlessLoginDisabled: "인증에는 성공했지만, 비밀번호 없이 로그인 설정이 활성화되어있지 않습니다." _delivery: status: "전송 상태" diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index 2c7b3384b48c..7e288c478793 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -263,6 +263,29 @@ function loginFailed(err: any): void { }); break; } + case '36b96a7d-b547-412d-aeed-2d611cdc8cdc': { + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: i18n.ts.unknownWebAuthnKey, + }); + break; + } + case 'b18c89a7-5b5e-4cec-bb5b-0419f332d430': { + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: i18n.ts.verificationFailed, + }); + break; + } + case '2d84773e-f7b7-4d0b-8f72-bb69b584c912': { + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: i18n.ts.passwordlessLoginDisabled, + }) + } default: { console.error(err); os.alert({ From d9fc0001fe88107016c3554d0ef4c558ba0863c6 Mon Sep 17 00:00:00 2001 From: Squarecat-meow Date: Sun, 15 Sep 2024 17:44:30 +0000 Subject: [PATCH 16/23] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20added=20missing=20br?= =?UTF-8?q?eak=20in=20case=20statement=20-=20add=20missing=20spaces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ko-KR.yml | 2 +- packages/frontend/src/components/MkSignin.vue | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 19a80f4e6a0e..98932fa4ae46 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1256,7 +1256,7 @@ unfold: "접기" signinWithPasskey: "패스키로 로그인" unknownWebAuthnKey: "등록되지 않은 패스키 입니다." verificationFailed: "패스키 검증이 실패했습니다." -passwordlessLoginDisabled: "인증에는 성공했지만, 비밀번호 없이 로그인 설정이 활성화되어있지 않습니다." +passwordlessLoginDisabled: "인증에는 성공했지만, 비밀번호 없이 로그인 설정이 활성화 되어있지 않습니다." _delivery: status: "전송 상태" diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index 7e288c478793..c7fe06010e8e 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -285,6 +285,7 @@ function loginFailed(err: any): void { title: i18n.ts.loginFailed, text: i18n.ts.passwordlessLoginDisabled, }) + break; } default: { console.error(err); From 708a74737d2a9ac8bff594a81cc8e38281fe939e Mon Sep 17 00:00:00 2001 From: Squarecat-meow Date: Mon, 16 Sep 2024 06:37:26 +0000 Subject: [PATCH 17/23] =?UTF-8?q?=F0=9F=9A=A8=20fix=20semicolon=20warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/MkSignin.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index c7fe06010e8e..bcf507046f4a 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -284,7 +284,7 @@ function loginFailed(err: any): void { type: 'error', title: i18n.ts.loginFailed, text: i18n.ts.passwordlessLoginDisabled, - }) + }); break; } default: { From 6c085e459fb3246a64f82303b20d2a66d8670b6a Mon Sep 17 00:00:00 2001 From: Yuno Date: Mon, 16 Sep 2024 16:25:44 +0900 Subject: [PATCH 18/23] =?UTF-8?q?=EB=B9=84=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=9C=A0=EC=A0=80=EC=97=90=EA=B2=8C=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=8C=80=EC=8B=A0=20=EB=B9=88=20=EC=97=B0=ED=95=A9=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=EC=9D=84=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/server/api/endpoints/federation/instances.ts | 7 ++++--- .../src/server/api/endpoints/federation/show-instance.ts | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 7116f3d1e6d6..c91d33d96921 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -14,9 +14,7 @@ import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; export const meta = { tags: ['federation'], - requireCredential: true, - kind: 'read:federation', - + requireCredential: false, allowGet: true, cacheSec: 3600, @@ -79,6 +77,9 @@ export default class extends Endpoint { // eslint- private metaService: MetaService, ) { super(meta, paramDef, async (ps, me) => { + if (!me) { + return []; + } const query = this.instancesRepository.createQueryBuilder('instance'); switch (ps.sort) { diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts index 29bef372ec4a..4a9fe9062e44 100644 --- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts +++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts @@ -13,8 +13,7 @@ import { DI } from '@/di-symbols.js'; export const meta = { tags: ['federation'], - requireCredential: true, - kind: 'read:federation', + requireCredential: false, res: { type: 'object', @@ -44,6 +43,9 @@ export default class extends Endpoint { // eslint- const instance = await this.instancesRepository .findOneBy({ host: this.utilityService.toPuny(ps.host) }); + if (!me) { + return null; + } return instance ? await this.instanceEntityService.pack(instance, me) : null; }); } From 2b929a8b2002013865cad222a37838b4dc2158dd Mon Sep 17 00:00:00 2001 From: Yuno Date: Mon, 16 Sep 2024 07:40:44 +0000 Subject: [PATCH 19/23] =?UTF-8?q?inbox=20Queue=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=B0=9C=EC=83=9D=ED=95=98=EB=8A=94=20=EC=9E=90=EC=84=B8?= =?UTF-8?q?=ED=95=9C=20renderError=EB=A5=BC=20debug=EB=A0=88=EB=B2=A8?= =?UTF-8?q?=EB=A1=9C=20=EB=82=AE=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/queue/QueueProcessorService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 7bd74f3210f8..745e5dc54f25 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -306,7 +306,8 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('active', (job) => logger.debug(`active ${getJobInfo(job, true)}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)}`)) .on('failed', (job, err) => { - logger.error(`failed(${err.stack}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) }); + logger.error(`failed(${err.stack}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`); + logger.debug(`activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) }); if (config.sentryForBackend) { Sentry.captureMessage(`Queue: Inbox: ${err.message}`, { level: 'error', From f1f205135c42b02bcaaaff0f75ad7d1d69707837 Mon Sep 17 00:00:00 2001 From: Yuno Date: Mon, 16 Sep 2024 07:49:50 +0000 Subject: [PATCH 20/23] =?UTF-8?q?=ED=81=90=20failed=EC=8B=9C=20renderError?= =?UTF-8?q?=EB=8A=94=20debug=EB=A1=9C=20=EC=B6=9C=EB=A0=A5=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20(=EB=A1=9C=EA=B7=B8=EA=B0=80=20=EB=84=88?= =?UTF-8?q?=EB=AC=B4=20=EC=8B=9C=EB=81=84=EB=9F=AC=EC=9B=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/queue/QueueProcessorService.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 745e5dc54f25..5f0119bf7f79 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -169,7 +169,8 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('active', (job) => logger.debug(`active id=${job.id}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('failed', (job, err: Error) => { - logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }); + logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`); + logger.debug(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }); if (config.sentryForBackend) { Sentry.captureMessage(`Queue: System: ${job?.name ?? '?'}: ${err.message}`, { level: 'error', @@ -226,7 +227,8 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('active', (job) => logger.debug(`active id=${job.id}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('failed', (job, err) => { - logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }); + logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`); + logger.debug(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }); if (config.sentryForBackend) { Sentry.captureMessage(`Queue: DB: ${job?.name ?? '?'}: ${err.message}`, { level: 'error', From 80014f7973d5082777006901c4ce289c5c26e337 Mon Sep 17 00:00:00 2001 From: Yuno Date: Mon, 16 Sep 2024 17:00:01 +0900 Subject: [PATCH 21/23] use LIKE search instead ILIKE (to use pg_bigm index) --- packages/backend/src/core/SearchService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index edfc47037523..23d46003fc4d 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -215,7 +215,7 @@ export class SearchService { } query - .andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` }) + .andWhere('note.text LIKE :q', { q: `%${ sqlLikeEscape(q) }%` }) .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') From 66a261fe5ed8307953e91361e275f4622151e9fa Mon Sep 17 00:00:00 2001 From: Yuno Date: Tue, 17 Sep 2024 02:29:05 +0900 Subject: [PATCH 22/23] Add Dockerfile for pg_bigm --- Dockerfile_pg_bigm | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Dockerfile_pg_bigm diff --git a/Dockerfile_pg_bigm b/Dockerfile_pg_bigm new file mode 100644 index 000000000000..1e7c9af7450b --- /dev/null +++ b/Dockerfile_pg_bigm @@ -0,0 +1,15 @@ +FROM postgres:16-bullseye + +RUN apt update +RUN apt install -y postgresql-server-dev-16 make gcc wget libicu-dev + +RUN wget https://github.com/pgbigm/pg_bigm/archive/refs/tags/v1.2-20240606.tar.gz +RUN tar zxf v1.2-20240606.tar.gz +RUN cd pg_bigm-1.2-20240606 && make USE_PGXS=1 && make USE_PGXS=1 install + +RUN echo shared_preload_libraries='pg_bigm' >> /var/lib/postgresql/data/postgresql.conf + +ENTRYPOINT ["docker-entrypoint.sh"] +EXPOSE 5432 +CMD ["postgres"] + From 068661d0b474547a0af03f2c9521eab3dfae567e Mon Sep 17 00:00:00 2001 From: Yuno Date: Thu, 19 Sep 2024 00:05:12 +0900 Subject: [PATCH 23/23] 2024.8.0-munochi.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1371e8f079c4..5cd56f4d68fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2024.8.0+munochi.8", + "version": "2024.8.0+munochi.9", "codename": "nasubi", "repository": { "type": "git",