From d23ad8b5117a46067464d1b693ae8898d127d5fc Mon Sep 17 00:00:00 2001
From: Kagami Sascha Rosylight <saschanaz@outlook.com>
Date: Mon, 26 Jun 2023 03:09:12 +0200
Subject: [PATCH] =?UTF-8?q?fix(backend):=20API=E3=82=A8=E3=83=A9=E3=83=BC?=
 =?UTF-8?q?=E3=81=AEHTTP=20status=20code=E5=A4=89=E6=9B=B4=20(#11047)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../backend/src/server/api/ApiCallService.ts  |  8 +++-
 packages/backend/test/e2e/api.ts              | 44 ++++++++++++++++++-
 2 files changed, 49 insertions(+), 3 deletions(-)

diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts
index dad1a4132a..45fb473763 100644
--- a/packages/backend/src/server/api/ApiCallService.ts
+++ b/packages/backend/src/server/api/ApiCallService.ts
@@ -83,7 +83,7 @@ export class ApiCallService implements OnApplicationShutdown {
 			}
 		}).catch(err => {
 			if (err instanceof AuthenticationError) {
-				this.send(reply, 403, new ApiError({
+				this.send(reply, 401, new ApiError({
 					message: 'Authentication failed. Please ensure your token is correct.',
 					code: 'AUTHENTICATION_FAILED',
 					id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
@@ -137,7 +137,7 @@ export class ApiCallService implements OnApplicationShutdown {
 			}
 		}).catch(err => {
 			if (err instanceof AuthenticationError) {
-				this.send(reply, 403, new ApiError({
+				this.send(reply, 401, new ApiError({
 					message: 'Authentication failed. Please ensure your token is correct.',
 					code: 'AUTHENTICATION_FAILED',
 					id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
@@ -278,6 +278,7 @@ export class ApiCallService implements OnApplicationShutdown {
 				throw new ApiError({
 					message: 'You are not assigned to a moderator role.',
 					code: 'ROLE_PERMISSION_DENIED',
+					kind: 'permission',
 					id: 'd33d5333-db36-423d-a8f9-1a2b9549da41',
 				});
 			}
@@ -285,6 +286,7 @@ export class ApiCallService implements OnApplicationShutdown {
 				throw new ApiError({
 					message: 'You are not assigned to an administrator role.',
 					code: 'ROLE_PERMISSION_DENIED',
+					kind: 'permission',
 					id: 'c3d38592-54c0-429d-be96-5636b0431a61',
 				});
 			}
@@ -296,6 +298,7 @@ export class ApiCallService implements OnApplicationShutdown {
 				throw new ApiError({
 					message: 'You are not assigned to a required role.',
 					code: 'ROLE_PERMISSION_DENIED',
+					kind: 'permission',
 					id: '7f86f06f-7e15-4057-8561-f4b6d4ac755a',
 				});
 			}
@@ -305,6 +308,7 @@ export class ApiCallService implements OnApplicationShutdown {
 			throw new ApiError({
 				message: 'Your app does not have the necessary permissions to use this endpoint.',
 				code: 'PERMISSION_DENIED',
+				kind: 'permission',
 				id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838',
 			});
 		}
diff --git a/packages/backend/test/e2e/api.ts b/packages/backend/test/e2e/api.ts
index 194ded7e8b..4b9167b6b1 100644
--- a/packages/backend/test/e2e/api.ts
+++ b/packages/backend/test/e2e/api.ts
@@ -1,7 +1,7 @@
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { signup, api, startServer } from '../utils.js';
+import { signup, api, startServer, successfulApiCall, failedApiCall } from '../utils.js';
 import type { INestApplicationContext } from '@nestjs/common';
 import type * as misskey from 'misskey-js';
 
@@ -81,4 +81,46 @@ describe('API', () => {
 			assert.strictEqual(res.body.nullableDefault, 'hello');
 		});
 	});
+
+	test('管理者専用のAPIのアクセス制限', async () => {
+		// aliceは管理者、APIを使える
+		await successfulApiCall({
+			endpoint: '/admin/get-index-stats',
+			parameters: {},
+			user: alice,
+		});
+
+		// bobは一般ユーザーだからダメ
+		await failedApiCall({
+			endpoint: '/admin/get-index-stats',
+			parameters: {},
+			user: bob,
+		}, {
+			status: 403,
+			code: 'ROLE_PERMISSION_DENIED',
+			id: 'c3d38592-54c0-429d-be96-5636b0431a61',
+		});
+
+		// publicアクセスももちろんダメ
+		await failedApiCall({
+			endpoint: '/admin/get-index-stats',
+			parameters: {},
+			user: undefined,
+		}, {
+			status: 401,
+			code: 'CREDENTIAL_REQUIRED',
+			id: '1384574d-a912-4b81-8601-c7b1c4085df1',
+		});
+
+		// ごまがしもダメ
+		await failedApiCall({
+			endpoint: '/admin/get-index-stats',
+			parameters: {},
+			user: { token: 'tsukawasete' },
+		}, {
+			status: 401,
+			code: 'AUTHENTICATION_FAILED',
+			id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
+		});
+	});
 });