From cd8b86170582528ce9edb91a9068b720aad2dc23 Mon Sep 17 00:00:00 2001 From: Kissa Ruokanen Date: Wed, 3 Jun 2026 09:38:46 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix(backend):=20TOTP=E8=AA=8D=E8=A8=BC?= =?UTF-8?q?=E5=A4=B1=E6=95=97=E6=99=82=E3=81=AB500=E3=82=A8=E3=83=A9?= =?UTF-8?q?=E3=83=BC=E3=81=8C=E8=BF=94=E3=81=95=E3=82=8C=E3=82=8B=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit パスワード・TOTP検証エラーをINVALID_CREDENTIALに統合し、 どちらが間違っているか判別できないようにした Closes misskey-dev/misskey#17522 --- CHANGELOG.md | 2 ++ .../src/server/api/endpoints/i/2fa/key-done.ts | 14 +++++++------- .../server/api/endpoints/i/2fa/register-key.ts | 14 +++++++------- .../src/server/api/endpoints/i/2fa/register.ts | 14 +++++++------- .../src/server/api/endpoints/i/2fa/remove-key.ts | 14 +++++++------- .../src/server/api/endpoints/i/2fa/unregister.ts | 14 +++++++------- .../src/server/api/endpoints/i/change-password.ts | 15 ++++++++++++--- .../src/server/api/endpoints/i/delete-account.ts | 15 ++++++++++++--- .../src/server/api/endpoints/i/update-email.ts | 14 +++++++------- 9 files changed, 68 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d084006562c..efcabfdcaf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ - Enhance: リモートノートクリーニングジョブの削除対象検索処理のパフォーマンス改善 - Fix: センシティブメディア自動検出周りの依存関係・ファイルの解決に失敗する問題を修正 - Fix: フォロワー限定投稿を指名投稿で引用した際に、引用した投稿の公開範囲が意図せず変更される問題を修正 +- Fix: 誤ったワンタイムパスワードを入力した際に500エラーが返される問題を修正 +- Enhance: 認証系エンドポイントのパスワード・TOTP検証エラーをINVALID_CREDENTIALに統合 ## 2026.5.4 diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index 8dc5cafb560..c2e8548aea0 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -20,10 +20,10 @@ export const meta = { secure: true, errors: { - incorrectPassword: { - message: 'Incorrect password.', - code: 'INCORRECT_PASSWORD', - id: '0d7ec6d2-e652-443e-a7bf-9ee9a0cd77b0', + invalidCredential: { + message: 'Invalid credential.', + code: 'INVALID_CREDENTIAL', + id: '430da61f-f346-411b-8aa0-fdeb0736dbe4', }, twoFactorNotEnabled: { @@ -76,19 +76,19 @@ export default class extends Endpoint { if (profile.twoFactorEnabled) { if (token == null) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } try { await this.userAuthService.twoFactorAuthenticate(profile, token); } catch (_) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } } const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); if (!passwordMatched) { - throw new ApiError(meta.errors.incorrectPassword); + throw new ApiError(meta.errors.invalidCredential); } if (!profile.twoFactorEnabled) { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index 9d1d595c900..61e677c47a8 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -24,10 +24,10 @@ export const meta = { id: '652f899f-66d4-490e-993e-6606c8ec04c3', }, - incorrectPassword: { - message: 'Incorrect password.', - code: 'INCORRECT_PASSWORD', - id: '38769596-efe2-4faf-9bec-abbb3f2cd9ba', + invalidCredential: { + message: 'Invalid credential.', + code: 'INVALID_CREDENTIAL', + id: 'a1189f08-e4a2-462e-b5de-647c312efadb', }, twoFactorNotEnabled: { @@ -76,19 +76,19 @@ export default class extends Endpoint { if (profile.twoFactorEnabled) { if (token == null) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } try { await this.userAuthService.twoFactorAuthenticate(profile, token); } catch (_) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } } const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); if (!passwordMatched) { - throw new ApiError(meta.errors.incorrectPassword); + throw new ApiError(meta.errors.invalidCredential); } if (!profile.twoFactorEnabled) { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index b6c837eda7d..767d8c8be71 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -20,10 +20,10 @@ export const meta = { secure: true, errors: { - incorrectPassword: { - message: 'Incorrect password.', - code: 'INCORRECT_PASSWORD', - id: '78d6c839-20c9-4c66-b90a-fc0542168b48', + invalidCredential: { + message: 'Invalid credential.', + code: 'INVALID_CREDENTIAL', + id: '4ccb213e-227e-4c18-8ddb-f2dddb31a9d0', }, }, @@ -67,19 +67,19 @@ export default class extends Endpoint { // eslint- if (profile.twoFactorEnabled) { if (token == null) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } try { await this.userAuthService.twoFactorAuthenticate(profile, token); } catch (_) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } } const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); if (!passwordMatched) { - throw new ApiError(meta.errors.incorrectPassword); + throw new ApiError(meta.errors.invalidCredential); } // Generate user's secret key diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index 6e5d9943def..41f8a91f8a2 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -19,10 +19,10 @@ export const meta = { secure: true, errors: { - incorrectPassword: { - message: 'Incorrect password.', - code: 'INCORRECT_PASSWORD', - id: '141c598d-a825-44c8-9173-cfb9d92be493', + invalidCredential: { + message: 'Invalid credential.', + code: 'INVALID_CREDENTIAL', + id: 'c1c5fa72-f66a-4935-b36a-cb8259e4b03e', }, }, } as const; @@ -56,19 +56,19 @@ export default class extends Endpoint { // eslint- if (profile.twoFactorEnabled) { if (token == null) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } try { await this.userAuthService.twoFactorAuthenticate(profile, token); } catch (_) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } } const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); if (!passwordMatched) { - throw new ApiError(meta.errors.incorrectPassword); + throw new ApiError(meta.errors.invalidCredential); } // Make sure we only delete the user's own creds diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index 23b577dc18b..32c3ebdc96d 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -19,10 +19,10 @@ export const meta = { secure: true, errors: { - incorrectPassword: { - message: 'Incorrect password.', - code: 'INCORRECT_PASSWORD', - id: '7add0395-9901-4098-82f9-4f67af65f775', + invalidCredential: { + message: 'Invalid credential.', + code: 'INVALID_CREDENTIAL', + id: '5ce7354e-d1d0-4305-b37c-9825b766f03f', }, }, } as const; @@ -52,19 +52,19 @@ export default class extends Endpoint { // eslint- if (profile.twoFactorEnabled) { if (token == null) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } try { await this.userAuthService.twoFactorAuthenticate(profile, token); } catch (_) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } } const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); if (!passwordMatched) { - throw new ApiError(meta.errors.incorrectPassword); + throw new ApiError(meta.errors.invalidCredential); } await this.userProfilesRepository.update(me.id, { diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index 19ea187ee86..fe0f126ab2f 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -8,12 +8,21 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; +import { ApiError } from '@/server/api/error.js'; import { UserAuthService } from '@/core/UserAuthService.js'; export const meta = { requireCredential: true, secure: true, + + errors: { + invalidCredential: { + message: 'Invalid credential.', + code: 'INVALID_CREDENTIAL', + id: '1e5d4005-3eb0-43f8-b466-87ad864b9fd6', + }, + }, } as const; export const paramDef = { @@ -40,20 +49,20 @@ export default class extends Endpoint { // eslint- if (profile.twoFactorEnabled) { if (token == null) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } try { await this.userAuthService.twoFactorAuthenticate(profile, token); } catch (_) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } } const passwordMatched = await bcrypt.compare(ps.currentPassword, profile.password!); if (!passwordMatched) { - throw new Error('incorrect password'); + throw new ApiError(meta.errors.invalidCredential); } // Generate hash of password diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index 42324c7778a..61d85561df6 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -9,12 +9,21 @@ import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DeleteAccountService } from '@/core/DeleteAccountService.js'; import { DI } from '@/di-symbols.js'; +import { ApiError } from '@/server/api/error.js'; import { UserAuthService } from '@/core/UserAuthService.js'; export const meta = { requireCredential: true, secure: true, + + errors: { + invalidCredential: { + message: 'Invalid credential.', + code: 'INVALID_CREDENTIAL', + id: 'd5af1163-2248-404f-a3d9-7b8c9e019723', + }, + }, } as const; export const paramDef = { @@ -44,13 +53,13 @@ export default class extends Endpoint { // eslint- if (profile.twoFactorEnabled) { if (token == null) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } try { await this.userAuthService.twoFactorAuthenticate(profile, token); } catch (_) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } } @@ -61,7 +70,7 @@ export default class extends Endpoint { // eslint- const passwordMatched = await bcrypt.compare(ps.password, profile.password!); if (!passwordMatched) { - throw new Error('incorrect password'); + throw new ApiError(meta.errors.invalidCredential); } await this.deleteAccountService.deleteAccount(me); diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index c2f4281f369..d7fa5e9fbc3 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -28,10 +28,10 @@ export const meta = { }, errors: { - incorrectPassword: { - message: 'Incorrect password.', - code: 'INCORRECT_PASSWORD', - id: 'e54c1d7e-e7d6-4103-86b6-0a95069b4ad3', + invalidCredential: { + message: 'Invalid credential.', + code: 'INVALID_CREDENTIAL', + id: 'b8231819-bd85-4c24-83f1-657a3e0efad8', }, unavailable: { @@ -86,19 +86,19 @@ export default class extends Endpoint { // eslint- if (profile.twoFactorEnabled) { if (token == null) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } try { await this.userAuthService.twoFactorAuthenticate(profile, token); } catch (_) { - throw new Error('authentication failed'); + throw new ApiError(meta.errors.invalidCredential); } } const passwordMatched = await bcrypt.compare(ps.password, profile.password!); if (!passwordMatched) { - throw new ApiError(meta.errors.incorrectPassword); + throw new ApiError(meta.errors.invalidCredential); } if (ps.email != null) { From b275f06c29ce62f8defcd27d66295e438eab19e3 Mon Sep 17 00:00:00 2001 From: Kissa Ruokanen Date: Wed, 3 Jun 2026 10:25:36 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix(frontend):=202FA=E8=A7=A3=E9=99=A4?= =?UTF-8?q?=E6=99=82=E3=81=AB=E3=82=A8=E3=83=A9=E3=83=BC=E3=82=AA=E3=83=96?= =?UTF-8?q?=E3=82=B8=E3=82=A7=E3=82=AF=E3=83=88=E3=81=8C=E3=83=80=E3=82=A4?= =?UTF-8?q?=E3=82=A2=E3=83=AD=E3=82=B0=E3=81=AB=E6=B8=A1=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit apiWithDialogが既にエラーダイアログを表示するため、 冗長な.catch()を削除 --- packages/frontend/src/pages/settings/2fa.vue | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue index a3b44134369..e50a45992e5 100644 --- a/packages/frontend/src/pages/settings/2fa.vue +++ b/packages/frontend/src/pages/settings/2fa.vue @@ -132,15 +132,10 @@ async function unregisterTOTP(): Promise { os.apiWithDialog('i/2fa/unregister', { password: auth.result.password, token: auth.result.token, - }).then(res => { + }).then(() => { updateCurrentAccountPartial({ twoFactorEnabled: false, }); - }).catch(error => { - os.alert({ - type: 'error', - text: error, - }); }); } From dd50a459986cb4247b28da0d612bc76fa12ff674 Mon Sep 17 00:00:00 2001 From: Kissa Ruokanen Date: Wed, 3 Jun 2026 10:37:50 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix(backend):=202FA=E3=82=BB=E3=83=83?= =?UTF-8?q?=E3=83=88=E3=82=A2=E3=83=83=E3=83=97=E6=99=82=E3=81=AETOTP?= =?UTF-8?q?=E6=A4=9C=E8=A8=BC=E5=A4=B1=E6=95=97=E3=81=A7500=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E3=81=8C=E8=BF=94=E3=81=95=E3=82=8C=E3=82=8B?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/server/api/endpoints/i/2fa/done.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts index 2a30e8b0c3e..a4b9d1e0874 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -10,12 +10,27 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { UserProfilesRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; +import { ApiError } from '@/server/api/error.js'; export const meta = { requireCredential: true, secure: true, + errors: { + notInitiated: { + message: '2fa setup has not been initiated.', + code: '2FA_SETUP_NOT_INITIATED', + id: '283f18c1-5b84-4699-a7a4-2beec808b74c', + }, + + verificationFailed: { + message: 'Verification failed. Please try again.', + code: 'VERIFICATION_FAILED', + id: '90a0971b-f73a-4993-b224-8307ba7421e7', + }, + }, + res: { type: 'object', properties: { @@ -53,7 +68,7 @@ export default class extends Endpoint { // eslint- const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); if (profile.twoFactorTempSecret == null) { - throw new Error('二段階認証の設定が開始されていません'); + throw new ApiError(meta.errors.notInitiated); } const delta = OTPAuth.TOTP.validate({ @@ -64,7 +79,7 @@ export default class extends Endpoint { // eslint- }); if (delta === null) { - throw new Error('not verified'); + throw new ApiError(meta.errors.verificationFailed); } const backupCodes = Array.from({ length: 5 }, () => new OTPAuth.Secret().base32); From 0b7cea5b620fc3ca245802af1b6545c5c460f0bd Mon Sep 17 00:00:00 2001 From: Kissa Ruokanen Date: Wed, 3 Jun 2026 11:02:07 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix(backend):=20TOTP=E6=9C=AA=E7=99=BB?= =?UTF-8?q?=E9=8C=B2=E6=99=82=E3=81=AFINCORRECT=5FPASSWORD=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E3=82=92=E8=BF=94=E3=81=99=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TOTP有効時のみINVALID_CREDENTIALに統合し、 パスワード単体認証時はINCORRECT_PASSWORDを返す --- .../src/server/api/endpoints/i/2fa/key-done.ts | 11 ++++++++++- .../src/server/api/endpoints/i/2fa/register-key.ts | 11 ++++++++++- .../src/server/api/endpoints/i/2fa/register.ts | 11 ++++++++++- .../src/server/api/endpoints/i/2fa/remove-key.ts | 11 ++++++++++- .../src/server/api/endpoints/i/2fa/unregister.ts | 11 ++++++++++- .../src/server/api/endpoints/i/change-password.ts | 11 ++++++++++- .../src/server/api/endpoints/i/delete-account.ts | 11 ++++++++++- .../src/server/api/endpoints/i/update-email.ts | 11 ++++++++++- 8 files changed, 80 insertions(+), 8 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index c2e8548aea0..ca19d0f6360 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -20,6 +20,12 @@ export const meta = { secure: true, errors: { + incorrectPassword: { + message: 'Incorrect password.', + code: 'INCORRECT_PASSWORD', + id: '0d7ec6d2-e652-443e-a7bf-9ee9a0cd77b0', + }, + invalidCredential: { message: 'Invalid credential.', code: 'INVALID_CREDENTIAL', @@ -88,7 +94,10 @@ export default class extends Endpoint { const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); if (!passwordMatched) { - throw new ApiError(meta.errors.invalidCredential); + if (profile.twoFactorEnabled) { + throw new ApiError(meta.errors.invalidCredential); + } + throw new ApiError(meta.errors.incorrectPassword); } if (!profile.twoFactorEnabled) { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index 61e677c47a8..aa665c3a62b 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -24,6 +24,12 @@ export const meta = { id: '652f899f-66d4-490e-993e-6606c8ec04c3', }, + incorrectPassword: { + message: 'Incorrect password.', + code: 'INCORRECT_PASSWORD', + id: '38769596-efe2-4faf-9bec-abbb3f2cd9ba', + }, + invalidCredential: { message: 'Invalid credential.', code: 'INVALID_CREDENTIAL', @@ -88,7 +94,10 @@ export default class extends Endpoint { const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); if (!passwordMatched) { - throw new ApiError(meta.errors.invalidCredential); + if (profile.twoFactorEnabled) { + throw new ApiError(meta.errors.invalidCredential); + } + throw new ApiError(meta.errors.incorrectPassword); } if (!profile.twoFactorEnabled) { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index 767d8c8be71..a4676f78a2a 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -20,6 +20,12 @@ export const meta = { secure: true, errors: { + incorrectPassword: { + message: 'Incorrect password.', + code: 'INCORRECT_PASSWORD', + id: '78d6c839-20c9-4c66-b90a-fc0542168b48', + }, + invalidCredential: { message: 'Invalid credential.', code: 'INVALID_CREDENTIAL', @@ -79,7 +85,10 @@ export default class extends Endpoint { // eslint- const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); if (!passwordMatched) { - throw new ApiError(meta.errors.invalidCredential); + if (profile.twoFactorEnabled) { + throw new ApiError(meta.errors.invalidCredential); + } + throw new ApiError(meta.errors.incorrectPassword); } // Generate user's secret key diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index 41f8a91f8a2..384b87a3112 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -19,6 +19,12 @@ export const meta = { secure: true, errors: { + incorrectPassword: { + message: 'Incorrect password.', + code: 'INCORRECT_PASSWORD', + id: '141c598d-a825-44c8-9173-cfb9d92be493', + }, + invalidCredential: { message: 'Invalid credential.', code: 'INVALID_CREDENTIAL', @@ -68,7 +74,10 @@ export default class extends Endpoint { // eslint- const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); if (!passwordMatched) { - throw new ApiError(meta.errors.invalidCredential); + if (profile.twoFactorEnabled) { + throw new ApiError(meta.errors.invalidCredential); + } + throw new ApiError(meta.errors.incorrectPassword); } // Make sure we only delete the user's own creds diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index 32c3ebdc96d..25afefced35 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -19,6 +19,12 @@ export const meta = { secure: true, errors: { + incorrectPassword: { + message: 'Incorrect password.', + code: 'INCORRECT_PASSWORD', + id: '7add0395-9901-4098-82f9-4f67af65f775', + }, + invalidCredential: { message: 'Invalid credential.', code: 'INVALID_CREDENTIAL', @@ -64,7 +70,10 @@ export default class extends Endpoint { // eslint- const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); if (!passwordMatched) { - throw new ApiError(meta.errors.invalidCredential); + if (profile.twoFactorEnabled) { + throw new ApiError(meta.errors.invalidCredential); + } + throw new ApiError(meta.errors.incorrectPassword); } await this.userProfilesRepository.update(me.id, { diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index fe0f126ab2f..5ce3a592b4e 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -17,6 +17,12 @@ export const meta = { secure: true, errors: { + incorrectPassword: { + message: 'Incorrect password.', + code: 'INCORRECT_PASSWORD', + id: 'd46ffe5c-200b-4471-aca2-4d0ef197368f', + }, + invalidCredential: { message: 'Invalid credential.', code: 'INVALID_CREDENTIAL', @@ -62,7 +68,10 @@ export default class extends Endpoint { // eslint- const passwordMatched = await bcrypt.compare(ps.currentPassword, profile.password!); if (!passwordMatched) { - throw new ApiError(meta.errors.invalidCredential); + if (profile.twoFactorEnabled) { + throw new ApiError(meta.errors.invalidCredential); + } + throw new ApiError(meta.errors.incorrectPassword); } // Generate hash of password diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index 61d85561df6..11cfd5a0c0a 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -18,6 +18,12 @@ export const meta = { secure: true, errors: { + incorrectPassword: { + message: 'Incorrect password.', + code: 'INCORRECT_PASSWORD', + id: '9d72604c-9d55-4511-9b96-de11900925c7', + }, + invalidCredential: { message: 'Invalid credential.', code: 'INVALID_CREDENTIAL', @@ -70,7 +76,10 @@ export default class extends Endpoint { // eslint- const passwordMatched = await bcrypt.compare(ps.password, profile.password!); if (!passwordMatched) { - throw new ApiError(meta.errors.invalidCredential); + if (profile.twoFactorEnabled) { + throw new ApiError(meta.errors.invalidCredential); + } + throw new ApiError(meta.errors.incorrectPassword); } await this.deleteAccountService.deleteAccount(me); diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index d7fa5e9fbc3..08e2e6cc22a 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -28,6 +28,12 @@ export const meta = { }, errors: { + incorrectPassword: { + message: 'Incorrect password.', + code: 'INCORRECT_PASSWORD', + id: 'e54c1d7e-e7d6-4103-86b6-0a95069b4ad3', + }, + invalidCredential: { message: 'Invalid credential.', code: 'INVALID_CREDENTIAL', @@ -98,7 +104,10 @@ export default class extends Endpoint { // eslint- const passwordMatched = await bcrypt.compare(ps.password, profile.password!); if (!passwordMatched) { - throw new ApiError(meta.errors.invalidCredential); + if (profile.twoFactorEnabled) { + throw new ApiError(meta.errors.invalidCredential); + } + throw new ApiError(meta.errors.incorrectPassword); } if (ps.email != null) { From 09581bbb14b31b0934bba731d0702aee68a732b1 Mon Sep 17 00:00:00 2001 From: Kissa Ruokanen Date: Wed, 3 Jun 2026 21:21:31 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refactor(backend):=20TOTP=E8=AA=8D=E8=A8=BC?= =?UTF-8?q?=E5=A4=B1=E6=95=97=E6=99=82=E3=81=AE=E4=BE=8B=E5=A4=96=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E5=9E=8B=E5=AE=89=E5=85=A8=E3=81=AB=E6=94=B9?= =?UTF-8?q?=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/UserAuthService.ts | 4 +++- .../backend/src/server/api/endpoints/i/2fa/key-done.ts | 7 +++++-- .../backend/src/server/api/endpoints/i/2fa/register-key.ts | 7 +++++-- .../backend/src/server/api/endpoints/i/2fa/register.ts | 7 +++++-- .../backend/src/server/api/endpoints/i/2fa/remove-key.ts | 7 +++++-- .../backend/src/server/api/endpoints/i/2fa/unregister.ts | 7 +++++-- .../backend/src/server/api/endpoints/i/change-password.ts | 7 +++++-- .../backend/src/server/api/endpoints/i/delete-account.ts | 7 +++++-- .../backend/src/server/api/endpoints/i/update-email.ts | 7 +++++-- 9 files changed, 43 insertions(+), 17 deletions(-) diff --git a/packages/backend/src/core/UserAuthService.ts b/packages/backend/src/core/UserAuthService.ts index bdc27cbe8e5..5a12c57c583 100644 --- a/packages/backend/src/core/UserAuthService.ts +++ b/packages/backend/src/core/UserAuthService.ts @@ -14,6 +14,8 @@ import type { MiLocalUser } from '@/models/User.js'; @Injectable() export class UserAuthService { + public static AuthenticationFailedError = class extends Error {}; + constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -38,7 +40,7 @@ export class UserAuthService { }); if (delta === null) { - throw new Error('authentication failed'); + throw new UserAuthService.AuthenticationFailedError(); } } } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index ca19d0f6360..d9f4c7b0193 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -87,8 +87,11 @@ export default class extends Endpoint { try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (_) { - throw new ApiError(meta.errors.invalidCredential); + } catch (e) { + if (e instanceof UserAuthService.AuthenticationFailedError) { + throw new ApiError(meta.errors.invalidCredential); + } + throw e; } } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index aa665c3a62b..98655a10bd3 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -87,8 +87,11 @@ export default class extends Endpoint { try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (_) { - throw new ApiError(meta.errors.invalidCredential); + } catch (e) { + if (e instanceof UserAuthService.AuthenticationFailedError) { + throw new ApiError(meta.errors.invalidCredential); + } + throw e; } } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index a4676f78a2a..b95ff4f4e29 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -78,8 +78,11 @@ export default class extends Endpoint { // eslint- try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (_) { - throw new ApiError(meta.errors.invalidCredential); + } catch (e) { + if (e instanceof UserAuthService.AuthenticationFailedError) { + throw new ApiError(meta.errors.invalidCredential); + } + throw e; } } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index 384b87a3112..8ec99f21491 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -67,8 +67,11 @@ export default class extends Endpoint { // eslint- try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (_) { - throw new ApiError(meta.errors.invalidCredential); + } catch (e) { + if (e instanceof UserAuthService.AuthenticationFailedError) { + throw new ApiError(meta.errors.invalidCredential); + } + throw e; } } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index 25afefced35..b757d742939 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -63,8 +63,11 @@ export default class extends Endpoint { // eslint- try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (_) { - throw new ApiError(meta.errors.invalidCredential); + } catch (e) { + if (e instanceof UserAuthService.AuthenticationFailedError) { + throw new ApiError(meta.errors.invalidCredential); + } + throw e; } } diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index 5ce3a592b4e..6ff4878fbde 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -60,8 +60,11 @@ export default class extends Endpoint { // eslint- try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (_) { - throw new ApiError(meta.errors.invalidCredential); + } catch (e) { + if (e instanceof UserAuthService.AuthenticationFailedError) { + throw new ApiError(meta.errors.invalidCredential); + } + throw e; } } diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index 11cfd5a0c0a..92d89d360b6 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -64,8 +64,11 @@ export default class extends Endpoint { // eslint- try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (_) { - throw new ApiError(meta.errors.invalidCredential); + } catch (e) { + if (e instanceof UserAuthService.AuthenticationFailedError) { + throw new ApiError(meta.errors.invalidCredential); + } + throw e; } } diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 08e2e6cc22a..8f244f1faa2 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -97,8 +97,11 @@ export default class extends Endpoint { // eslint- try { await this.userAuthService.twoFactorAuthenticate(profile, token); - } catch (_) { - throw new ApiError(meta.errors.invalidCredential); + } catch (e) { + if (e instanceof UserAuthService.AuthenticationFailedError) { + throw new ApiError(meta.errors.invalidCredential); + } + throw e; } }