Skip to content

Commit 3f4db5b

Browse files
committed
feat: implement project creation restriction for non-admin users
1 parent 462ad9d commit 3f4db5b

File tree

6 files changed

+101
-0
lines changed

6 files changed

+101
-0
lines changed

AdminDashboard/src/Pages/Settings/Authentication/Index.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,46 @@ const Settings: FunctionComponent = (): ReactElement => {
7373
modelId: ObjectID.getZeroObjectID(),
7474
}}
7575
/>
76+
77+
<CardModelDetail
78+
name="Project Creation Settings"
79+
cardProps={{
80+
title: "Project Creation",
81+
description:
82+
"Control who can create new projects on this OneUptime Server.",
83+
}}
84+
isEditable={true}
85+
editButtonText="Edit Settings"
86+
formFields={[
87+
{
88+
field: {
89+
disableUserProjectCreation: true,
90+
},
91+
title: "Restrict Project Creation to Admins Only",
92+
fieldType: FormFieldSchemaType.Toggle,
93+
required: false,
94+
description:
95+
"When enabled, only master admin users can create new projects.",
96+
},
97+
]}
98+
modelDetailProps={{
99+
modelType: GlobalConfig,
100+
id: "model-detail-project-creation",
101+
fields: [
102+
{
103+
field: {
104+
disableUserProjectCreation: true,
105+
},
106+
fieldType: FieldType.Boolean,
107+
title: "Restrict Project Creation to Admins Only",
108+
placeholder: "No",
109+
description:
110+
"When enabled, only master admin users can create new projects.",
111+
},
112+
],
113+
modelId: ObjectID.getZeroObjectID(),
114+
}}
115+
/>
76116
</Page>
77117
);
78118
};

Common/Models/DatabaseModels/GlobalConfig.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,25 @@ export default class GlobalConfig extends GlobalConfigModel {
5858
})
5959
public disableSignup?: boolean = undefined;
6060

61+
@ColumnAccessControl({
62+
create: [],
63+
read: [],
64+
update: [],
65+
})
66+
@TableColumn({
67+
type: TableColumnType.Boolean,
68+
title: "Disable User Project Creation",
69+
description: "Only master admins can create projects when enabled.",
70+
defaultValue: false,
71+
})
72+
@Column({
73+
type: ColumnType.Boolean,
74+
nullable: true,
75+
default: false,
76+
unique: true,
77+
})
78+
public disableUserProjectCreation?: boolean = undefined;
79+
6180
// SMTP Settings.
6281

6382
@ColumnAccessControl({

Common/Server/DatabaseConfig.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,11 @@ export default class DatabaseConfig {
8080
"disableSignup",
8181
)) as boolean;
8282
}
83+
84+
@CaptureSpan()
85+
public static async shouldDisableUserProjectCreation(): Promise<boolean> {
86+
return (await DatabaseConfig.getFromGlobalConfig(
87+
"disableUserProjectCreation",
88+
)) as boolean;
89+
}
8390
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
3+
export class MigrationName1770834237091 implements MigrationInterface {
4+
public name = "MigrationName1770834237091";
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(
8+
`ALTER TABLE "GlobalConfig" ADD "disableUserProjectCreation" boolean DEFAULT false`,
9+
);
10+
await queryRunner.query(
11+
`ALTER TABLE "GlobalConfig" ADD CONSTRAINT "UQ_disableUserProjectCreation" UNIQUE ("disableUserProjectCreation")`,
12+
);
13+
}
14+
15+
public async down(queryRunner: QueryRunner): Promise<void> {
16+
await queryRunner.query(
17+
`ALTER TABLE "GlobalConfig" DROP CONSTRAINT "UQ_disableUserProjectCreation"`,
18+
);
19+
await queryRunner.query(
20+
`ALTER TABLE "GlobalConfig" DROP COLUMN "disableUserProjectCreation"`,
21+
);
22+
}
23+
}

Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ import { MigrationName1770728946893 } from "./1770728946893-MigrationName";
258258
import { MigrationName1770732721195 } from "./1770732721195-MigrationName";
259259
import { MigrationName1770833704656 } from "./1770833704656-MigrationName";
260260
import { MigrationName1770834237090 } from "./1770834237090-MigrationName";
261+
import { MigrationName1770834237091 } from "./1770834237091-MigrationName";
261262

262263
export default [
263264
InitialMigration,
@@ -520,4 +521,5 @@ export default [
520521
MigrationName1770732721195,
521522
MigrationName1770833704656,
522523
MigrationName1770834237090,
524+
MigrationName1770834237091,
523525
];

Common/Server/Services/ProjectService.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export class ProjectService extends DatabaseService<Model> {
124124
select: {
125125
name: true,
126126
email: true,
127+
isMasterAdmin: true,
127128
companyPhoneNumber: true,
128129
companyName: true,
129130
utmCampaign: true,
@@ -142,6 +143,15 @@ export class ProjectService extends DatabaseService<Model> {
142143
throw new BadDataException("User not found.");
143144
}
144145

146+
// Check if project creation is restricted to admins only
147+
const shouldDisableProjectCreation: boolean =
148+
await DatabaseConfig.shouldDisableUserProjectCreation();
149+
if (shouldDisableProjectCreation && !user.isMasterAdmin) {
150+
throw new NotAuthorizedException(
151+
"Project creation is restricted to admin users only on this OneUptime Server. Please contact your server admin.",
152+
);
153+
}
154+
145155
if (IsBillingEnabled) {
146156
if (!data.data.paymentProviderPlanId) {
147157
throw new BadDataException("Plan required to create the project.");

0 commit comments

Comments
 (0)