From ade801ec58f1fe589d011b1bbb1731a7faddbd7d Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Fri, 1 Nov 2024 10:12:28 -0400
Subject: [PATCH] check token permissions in admin/accounts/create.ts

---
 .../api/endpoints/admin/accounts/create.ts    | 57 +++++++++++++++----
 1 file changed, 46 insertions(+), 11 deletions(-)

diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
index 3cb4029780..7754899b95 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
@@ -5,13 +5,14 @@
 
 import { Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { MiUser } from '@/models/_.js';
+import { MiAccessToken, MiUser } from '@/models/_.js';
 import { SignupService } from '@/core/SignupService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { InstanceActorService } from '@/core/InstanceActorService.js';
 import { localUsernameSchema, passwordSchema } from '@/models/User.js';
 import { Packed } from '@/misc/json-schema.js';
 import { RoleService } from '@/core/RoleService.js';
+import { ApiError } from '@/server/api/error.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -27,6 +28,35 @@ export const meta = {
 			},
 		},
 	},
+
+	errors: {
+		// From ApiCallService.ts
+		noCredential: {
+			message: 'Credential required.',
+			code: 'CREDENTIAL_REQUIRED',
+			id: '1384574d-a912-4b81-8601-c7b1c4085df1',
+			httpStatusCode: 401,
+		},
+		noAdmin: {
+			message: 'You are not assigned to an administrator role.',
+			code: 'ROLE_PERMISSION_DENIED',
+			kind: 'permission',
+			id: 'c3d38592-54c0-429d-be96-5636b0431a61',
+		},
+		noPermission: {
+			message: 'Your app does not have the necessary permissions to use this endpoint.',
+			code: 'PERMISSION_DENIED',
+			kind: 'permission',
+			id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838',
+		},
+	},
+
+	// Required token permissions, but we need to check them manually.
+	// ApiCallService checks access in a way that would prevent creating the first account.
+	softPermissions: [
+		'write:admin:account',
+		'write:admin:approve-user',
+	],
 } as const;
 
 export const paramDef = {
@@ -46,10 +76,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private signupService: SignupService,
 		private instanceActorService: InstanceActorService,
 	) {
-		super(meta, paramDef, async (ps, _me) => {
-			if (!await this.canCreate(_me)) {
-				throw new Error('access denied');
-			}
+		super(meta, paramDef, async (ps, _me, token) => {
+			await this.ensurePermissions(_me, token);
 
 			const { account, secret } = await this.signupService.signup({
 				username: ps.username,
@@ -69,13 +97,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		});
 	}
 
-	private async canCreate(me: MiUser | null): Promise<boolean> {
-		// Allow the first user to be created without authentication, as part of normal setup flow
-		if (!me) {
-			return !await this.instanceActorService.realLocalUsersPresent();
+	private async ensurePermissions(me: MiUser | null, token: MiAccessToken | null): Promise<void> {
+		// Tokens have scoped permissions which may be *less* than the user's official role, so we need to check.
+		if (token && !meta.softPermissions.every(p => token.permission.includes(p))) {
+			throw new ApiError(meta.errors.noPermission);
+		}
+
+		// Only administrators (including root) can create users.
+		if (me && !await this.roleService.isAdministrator(me)) {
+			throw new ApiError(meta.errors.noAdmin);
 		}
 
-		// Administrators (including root) can always create users
-		return await this.roleService.isAdministrator(me);
+		// Anonymous access is only allowed for initial instance setup.
+		if (!me && await this.instanceActorService.realLocalUsersPresent()) {
+			throw new ApiError(meta.errors.noCredential);
+		}
 	}
 }
-- 
GitLab