feat: implement project creation restriction for non-admin users

This commit is contained in:
Nawaz Dhandala
2026-02-16 14:54:14 +00:00
parent 462ad9d6ab
commit 3f4db5b7e0
6 changed files with 101 additions and 0 deletions

View File

@@ -73,6 +73,46 @@ const Settings: FunctionComponent = (): ReactElement => {
modelId: ObjectID.getZeroObjectID(),
}}
/>
<CardModelDetail
name="Project Creation Settings"
cardProps={{
title: "Project Creation",
description:
"Control who can create new projects on this OneUptime Server.",
}}
isEditable={true}
editButtonText="Edit Settings"
formFields={[
{
field: {
disableUserProjectCreation: true,
},
title: "Restrict Project Creation to Admins Only",
fieldType: FormFieldSchemaType.Toggle,
required: false,
description:
"When enabled, only master admin users can create new projects.",
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: "model-detail-project-creation",
fields: [
{
field: {
disableUserProjectCreation: true,
},
fieldType: FieldType.Boolean,
title: "Restrict Project Creation to Admins Only",
placeholder: "No",
description:
"When enabled, only master admin users can create new projects.",
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
</Page>
);
};

View File

@@ -58,6 +58,25 @@ export default class GlobalConfig extends GlobalConfigModel {
})
public disableSignup?: boolean = undefined;
@ColumnAccessControl({
create: [],
read: [],
update: [],
})
@TableColumn({
type: TableColumnType.Boolean,
title: "Disable User Project Creation",
description: "Only master admins can create projects when enabled.",
defaultValue: false,
})
@Column({
type: ColumnType.Boolean,
nullable: true,
default: false,
unique: true,
})
public disableUserProjectCreation?: boolean = undefined;
// SMTP Settings.
@ColumnAccessControl({

View File

@@ -80,4 +80,11 @@ export default class DatabaseConfig {
"disableSignup",
)) as boolean;
}
@CaptureSpan()
public static async shouldDisableUserProjectCreation(): Promise<boolean> {
return (await DatabaseConfig.getFromGlobalConfig(
"disableUserProjectCreation",
)) as boolean;
}
}

View File

@@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MigrationName1770834237091 implements MigrationInterface {
public name = "MigrationName1770834237091";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "GlobalConfig" ADD "disableUserProjectCreation" boolean DEFAULT false`,
);
await queryRunner.query(
`ALTER TABLE "GlobalConfig" ADD CONSTRAINT "UQ_disableUserProjectCreation" UNIQUE ("disableUserProjectCreation")`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "GlobalConfig" DROP CONSTRAINT "UQ_disableUserProjectCreation"`,
);
await queryRunner.query(
`ALTER TABLE "GlobalConfig" DROP COLUMN "disableUserProjectCreation"`,
);
}
}

View File

@@ -258,6 +258,7 @@ import { MigrationName1770728946893 } from "./1770728946893-MigrationName";
import { MigrationName1770732721195 } from "./1770732721195-MigrationName";
import { MigrationName1770833704656 } from "./1770833704656-MigrationName";
import { MigrationName1770834237090 } from "./1770834237090-MigrationName";
import { MigrationName1770834237091 } from "./1770834237091-MigrationName";
export default [
InitialMigration,
@@ -520,4 +521,5 @@ export default [
MigrationName1770732721195,
MigrationName1770833704656,
MigrationName1770834237090,
MigrationName1770834237091,
];

View File

@@ -124,6 +124,7 @@ export class ProjectService extends DatabaseService<Model> {
select: {
name: true,
email: true,
isMasterAdmin: true,
companyPhoneNumber: true,
companyName: true,
utmCampaign: true,
@@ -142,6 +143,15 @@ export class ProjectService extends DatabaseService<Model> {
throw new BadDataException("User not found.");
}
// Check if project creation is restricted to admins only
const shouldDisableProjectCreation: boolean =
await DatabaseConfig.shouldDisableUserProjectCreation();
if (shouldDisableProjectCreation && !user.isMasterAdmin) {
throw new NotAuthorizedException(
"Project creation is restricted to admin users only on this OneUptime Server. Please contact your server admin.",
);
}
if (IsBillingEnabled) {
if (!data.data.paymentProviderPlanId) {
throw new BadDataException("Plan required to create the project.");