Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
- Enhance: リモートノートクリーニングジョブの削除対象検索処理のパフォーマンス改善
- Fix: センシティブメディア自動検出周りの依存関係・ファイルの解決に失敗する問題を修正
- Fix: フォロワー限定投稿を指名投稿で引用した際に、引用した投稿の公開範囲が意図せず変更される問題を修正
- Fix: 誤ったワンタイムパスワードを入力した際に500エラーが返される問題を修正
- Enhance: 認証系エンドポイントのパスワード・TOTP検証エラーをINVALID_CREDENTIALに統合

## 2026.5.4

Expand Down
4 changes: 3 additions & 1 deletion packages/backend/src/core/UserAuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -38,7 +40,7 @@ export class UserAuthService {
});

if (delta === null) {
throw new Error('authentication failed');
throw new UserAuthService.AuthenticationFailedError();
}
}
}
Expand Down
19 changes: 17 additions & 2 deletions packages/backend/src/server/api/endpoints/i/2fa/done.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -53,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // 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({
Expand All @@ -64,7 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // 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);
Expand Down
18 changes: 15 additions & 3 deletions packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ export const meta = {
id: '0d7ec6d2-e652-443e-a7bf-9ee9a0cd77b0',
},

invalidCredential: {
message: 'Invalid credential.',
code: 'INVALID_CREDENTIAL',
id: '430da61f-f346-411b-8aa0-fdeb0736dbe4',
},

twoFactorNotEnabled: {
message: '2fa not enabled.',
code: 'TWO_FACTOR_NOT_ENABLED',
Expand Down Expand Up @@ -76,18 +82,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {

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');
} catch (e) {
if (e instanceof UserAuthService.AuthenticationFailedError) {
throw new ApiError(meta.errors.invalidCredential);
}
throw e;
}
}

const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? '');
if (!passwordMatched) {
if (profile.twoFactorEnabled) {
throw new ApiError(meta.errors.invalidCredential);
}
throw new ApiError(meta.errors.incorrectPassword);
}

Expand Down
18 changes: 15 additions & 3 deletions packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ export const meta = {
id: '38769596-efe2-4faf-9bec-abbb3f2cd9ba',
},

invalidCredential: {
message: 'Invalid credential.',
code: 'INVALID_CREDENTIAL',
id: 'a1189f08-e4a2-462e-b5de-647c312efadb',
},

twoFactorNotEnabled: {
message: '2fa not enabled.',
code: 'TWO_FACTOR_NOT_ENABLED',
Expand Down Expand Up @@ -76,18 +82,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {

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');
} catch (e) {
if (e instanceof UserAuthService.AuthenticationFailedError) {
throw new ApiError(meta.errors.invalidCredential);
}
throw e;
}
}

const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? '');
if (!passwordMatched) {
if (profile.twoFactorEnabled) {
throw new ApiError(meta.errors.invalidCredential);
}
throw new ApiError(meta.errors.incorrectPassword);
}

Expand Down
18 changes: 15 additions & 3 deletions packages/backend/src/server/api/endpoints/i/2fa/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export const meta = {
code: 'INCORRECT_PASSWORD',
id: '78d6c839-20c9-4c66-b90a-fc0542168b48',
},

invalidCredential: {
message: 'Invalid credential.',
code: 'INVALID_CREDENTIAL',
id: '4ccb213e-227e-4c18-8ddb-f2dddb31a9d0',
},
},

res: {
Expand Down Expand Up @@ -67,18 +73,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // 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');
} catch (e) {
if (e instanceof UserAuthService.AuthenticationFailedError) {
throw new ApiError(meta.errors.invalidCredential);
}
throw e;
}
}

const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? '');
if (!passwordMatched) {
if (profile.twoFactorEnabled) {
throw new ApiError(meta.errors.invalidCredential);
}
throw new ApiError(meta.errors.incorrectPassword);
}

Expand Down
18 changes: 15 additions & 3 deletions packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export const meta = {
code: 'INCORRECT_PASSWORD',
id: '141c598d-a825-44c8-9173-cfb9d92be493',
},

invalidCredential: {
message: 'Invalid credential.',
code: 'INVALID_CREDENTIAL',
id: 'c1c5fa72-f66a-4935-b36a-cb8259e4b03e',
},
},
} as const;

Expand Down Expand Up @@ -56,18 +62,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // 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');
} catch (e) {
if (e instanceof UserAuthService.AuthenticationFailedError) {
throw new ApiError(meta.errors.invalidCredential);
}
throw e;
}
}

const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? '');
if (!passwordMatched) {
if (profile.twoFactorEnabled) {
throw new ApiError(meta.errors.invalidCredential);
}
throw new ApiError(meta.errors.incorrectPassword);
}

Expand Down
18 changes: 15 additions & 3 deletions packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export const meta = {
code: 'INCORRECT_PASSWORD',
id: '7add0395-9901-4098-82f9-4f67af65f775',
},

invalidCredential: {
message: 'Invalid credential.',
code: 'INVALID_CREDENTIAL',
id: '5ce7354e-d1d0-4305-b37c-9825b766f03f',
},
},
} as const;

Expand Down Expand Up @@ -52,18 +58,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // 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');
} catch (e) {
if (e instanceof UserAuthService.AuthenticationFailedError) {
throw new ApiError(meta.errors.invalidCredential);
}
throw e;
}
}

const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? '');
if (!passwordMatched) {
if (profile.twoFactorEnabled) {
throw new ApiError(meta.errors.invalidCredential);
}
throw new ApiError(meta.errors.incorrectPassword);
}

Expand Down
29 changes: 25 additions & 4 deletions packages/backend/src/server/api/endpoints/i/change-password.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,27 @@ 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: {
incorrectPassword: {
message: 'Incorrect password.',
code: 'INCORRECT_PASSWORD',
id: 'd46ffe5c-200b-4471-aca2-4d0ef197368f',
},

invalidCredential: {
message: 'Invalid credential.',
code: 'INVALID_CREDENTIAL',
id: '1e5d4005-3eb0-43f8-b466-87ad864b9fd6',
},
},
} as const;

export const paramDef = {
Expand All @@ -40,20 +55,26 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // 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');
} catch (e) {
if (e instanceof UserAuthService.AuthenticationFailedError) {
throw new ApiError(meta.errors.invalidCredential);
}
throw e;
}
}

const passwordMatched = await bcrypt.compare(ps.currentPassword, profile.password!);

if (!passwordMatched) {
throw new Error('incorrect password');
if (profile.twoFactorEnabled) {
throw new ApiError(meta.errors.invalidCredential);
}
throw new ApiError(meta.errors.incorrectPassword);
}

// Generate hash of password
Expand Down
29 changes: 25 additions & 4 deletions packages/backend/src/server/api/endpoints/i/delete-account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,27 @@ 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: {
incorrectPassword: {
message: 'Incorrect password.',
code: 'INCORRECT_PASSWORD',
id: '9d72604c-9d55-4511-9b96-de11900925c7',
},

invalidCredential: {
message: 'Invalid credential.',
code: 'INVALID_CREDENTIAL',
id: 'd5af1163-2248-404f-a3d9-7b8c9e019723',
},
},
} as const;

export const paramDef = {
Expand Down Expand Up @@ -44,13 +59,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // 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');
} catch (e) {
if (e instanceof UserAuthService.AuthenticationFailedError) {
throw new ApiError(meta.errors.invalidCredential);
}
throw e;
}
}

Expand All @@ -61,7 +79,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-

const passwordMatched = await bcrypt.compare(ps.password, profile.password!);
if (!passwordMatched) {
throw new Error('incorrect password');
if (profile.twoFactorEnabled) {
throw new ApiError(meta.errors.invalidCredential);
}
throw new ApiError(meta.errors.incorrectPassword);
}

await this.deleteAccountService.deleteAccount(me);
Expand Down
Loading
Loading