diff --git a/AdminDashboard/src/Pages/Settings/Authentication/Index.tsx b/AdminDashboard/src/Pages/Settings/Authentication/Index.tsx
index 5a0b99f468..a20ab39155 100644
--- a/AdminDashboard/src/Pages/Settings/Authentication/Index.tsx
+++ b/AdminDashboard/src/Pages/Settings/Authentication/Index.tsx
@@ -73,6 +73,46 @@ const Settings: FunctionComponent = (): ReactElement => {
modelId: ObjectID.getZeroObjectID(),
}}
/>
+
+
);
};
diff --git a/Common/Models/DatabaseModels/GlobalConfig.ts b/Common/Models/DatabaseModels/GlobalConfig.ts
index 672c968e42..cfb63a14f4 100644
--- a/Common/Models/DatabaseModels/GlobalConfig.ts
+++ b/Common/Models/DatabaseModels/GlobalConfig.ts
@@ -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({
diff --git a/Common/Server/DatabaseConfig.ts b/Common/Server/DatabaseConfig.ts
index dac451e8e9..bbb6e792ef 100644
--- a/Common/Server/DatabaseConfig.ts
+++ b/Common/Server/DatabaseConfig.ts
@@ -80,4 +80,11 @@ export default class DatabaseConfig {
"disableSignup",
)) as boolean;
}
+
+ @CaptureSpan()
+ public static async shouldDisableUserProjectCreation(): Promise {
+ return (await DatabaseConfig.getFromGlobalConfig(
+ "disableUserProjectCreation",
+ )) as boolean;
+ }
}
diff --git a/Common/Server/Infrastructure/Postgres/SchemaMigrations/1770834237091-MigrationName.ts b/Common/Server/Infrastructure/Postgres/SchemaMigrations/1770834237091-MigrationName.ts
new file mode 100644
index 0000000000..cc3eb4d381
--- /dev/null
+++ b/Common/Server/Infrastructure/Postgres/SchemaMigrations/1770834237091-MigrationName.ts
@@ -0,0 +1,23 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class MigrationName1770834237091 implements MigrationInterface {
+ public name = "MigrationName1770834237091";
+
+ public async up(queryRunner: QueryRunner): Promise {
+ 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 {
+ await queryRunner.query(
+ `ALTER TABLE "GlobalConfig" DROP CONSTRAINT "UQ_disableUserProjectCreation"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "GlobalConfig" DROP COLUMN "disableUserProjectCreation"`,
+ );
+ }
+}
diff --git a/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts b/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts
index c24bbea036..7c4b602b9e 100644
--- a/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts
+++ b/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts
@@ -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,
];
diff --git a/Common/Server/Services/ProjectService.ts b/Common/Server/Services/ProjectService.ts
index 52d4b80486..4838090727 100755
--- a/Common/Server/Services/ProjectService.ts
+++ b/Common/Server/Services/ProjectService.ts
@@ -124,6 +124,7 @@ export class ProjectService extends DatabaseService {
select: {
name: true,
email: true,
+ isMasterAdmin: true,
companyPhoneNumber: true,
companyName: true,
utmCampaign: true,
@@ -142,6 +143,15 @@ export class ProjectService extends DatabaseService {
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.");