@oneuptime/common 7.0.4850 → 7.0.4868

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/Models/DatabaseModels/Index.ts +3 -0
  2. package/Models/DatabaseModels/ProjectSCIM.ts +451 -0
  3. package/Server/Infrastructure/Postgres/SchemaMigrations/1754304193228-MigrationName.ts +67 -0
  4. package/Server/Infrastructure/Postgres/SchemaMigrations/1754315774827-MigrationName.ts +17 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  6. package/Server/Infrastructure/Queue.ts +1 -1
  7. package/Server/Middleware/SCIMAuthorization.ts +94 -0
  8. package/Server/Services/ProjectSCIMService.ts +27 -0
  9. package/Server/Utils/StartServer.ts +10 -0
  10. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  11. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  12. package/build/dist/Models/DatabaseModels/ProjectSCIM.js +467 -0
  13. package/build/dist/Models/DatabaseModels/ProjectSCIM.js.map +1 -0
  14. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1754304193228-MigrationName.js +30 -0
  15. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1754304193228-MigrationName.js.map +1 -0
  16. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1754315774827-MigrationName.js +12 -0
  17. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1754315774827-MigrationName.js.map +1 -0
  18. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  19. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  20. package/build/dist/Server/Infrastructure/Queue.js +1 -1
  21. package/build/dist/Server/Middleware/SCIMAuthorization.js +81 -0
  22. package/build/dist/Server/Middleware/SCIMAuthorization.js.map +1 -0
  23. package/build/dist/Server/Services/ProjectSCIMService.js +20 -0
  24. package/build/dist/Server/Services/ProjectSCIMService.js.map +1 -0
  25. package/build/dist/Server/Utils/StartServer.js +9 -0
  26. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  27. package/package.json +1 -1
@@ -179,6 +179,7 @@ import ProjectUser from "./ProjectUser";
179
179
  import OnCallDutyPolicyUserOverride from "./OnCallDutyPolicyUserOverride";
180
180
  import MonitorFeed from "./MonitorFeed";
181
181
  import MetricType from "./MetricType";
182
+ import ProjectSCIM from "./ProjectSCIM";
182
183
 
183
184
  const AllModelTypes: Array<{
184
185
  new (): BaseModel;
@@ -380,6 +381,8 @@ const AllModelTypes: Array<{
380
381
  MetricType,
381
382
 
382
383
  OnCallDutyPolicyTimeLog,
384
+
385
+ ProjectSCIM,
383
386
  ];
384
387
 
385
388
  const modelTypeMap: { [key: string]: { new (): BaseModel } } = {};
@@ -0,0 +1,451 @@
1
+ import Project from "./Project";
2
+ import Team from "./Team";
3
+ import User from "./User";
4
+ import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
5
+ import Route from "../../Types/API/Route";
6
+ import { PlanType } from "../../Types/Billing/SubscriptionPlan";
7
+ import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
8
+ import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
9
+ import TableBillingAccessControl from "../../Types/Database/AccessControl/TableBillingAccessControl";
10
+ import ColumnLength from "../../Types/Database/ColumnLength";
11
+ import ColumnType from "../../Types/Database/ColumnType";
12
+ import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
13
+ import TableColumn from "../../Types/Database/TableColumn";
14
+ import TableColumnType from "../../Types/Database/TableColumnType";
15
+ import TableMetadata from "../../Types/Database/TableMetadata";
16
+ import TenantColumn from "../../Types/Database/TenantColumn";
17
+ import UniqueColumnBy from "../../Types/Database/UniqueColumnBy";
18
+ import IconProp from "../../Types/Icon/IconProp";
19
+ import ObjectID from "../../Types/ObjectID";
20
+ import Permission from "../../Types/Permission";
21
+ import {
22
+ Column,
23
+ Entity,
24
+ Index,
25
+ JoinColumn,
26
+ JoinTable,
27
+ ManyToMany,
28
+ ManyToOne,
29
+ } from "typeorm";
30
+
31
+ @TableBillingAccessControl({
32
+ create: PlanType.Scale,
33
+ read: PlanType.Scale,
34
+ update: PlanType.Scale,
35
+ delete: PlanType.Scale,
36
+ })
37
+ @TenantColumn("projectId")
38
+ @TableAccessControl({
39
+ create: [
40
+ Permission.ProjectOwner,
41
+ Permission.ProjectAdmin,
42
+ Permission.CreateProjectSSO,
43
+ ],
44
+ read: [
45
+ Permission.ProjectOwner,
46
+ Permission.ProjectAdmin,
47
+ Permission.ProjectMember,
48
+ Permission.ReadProjectSSO,
49
+ ],
50
+ delete: [
51
+ Permission.ProjectOwner,
52
+ Permission.ProjectAdmin,
53
+ Permission.DeleteProjectSSO,
54
+ ],
55
+ update: [
56
+ Permission.ProjectOwner,
57
+ Permission.ProjectAdmin,
58
+ Permission.EditProjectSSO,
59
+ ],
60
+ })
61
+ @CrudApiEndpoint(new Route("/project-scim"))
62
+ @TableMetadata({
63
+ tableName: "ProjectSCIM",
64
+ singularName: "SCIM",
65
+ pluralName: "SCIM",
66
+ icon: IconProp.Lock,
67
+ tableDescription: "Manage SCIM auto-provisioning for your project",
68
+ })
69
+ @Entity({
70
+ name: "ProjectSCIM",
71
+ })
72
+ export default class ProjectSCIM extends BaseModel {
73
+ @ColumnAccessControl({
74
+ create: [
75
+ Permission.ProjectOwner,
76
+ Permission.ProjectAdmin,
77
+ Permission.CreateProjectSSO,
78
+ ],
79
+ read: [
80
+ Permission.ProjectOwner,
81
+ Permission.ProjectAdmin,
82
+ Permission.ProjectMember,
83
+ Permission.ReadProjectSSO,
84
+ ],
85
+ update: [],
86
+ })
87
+ @TableColumn({
88
+ manyToOneRelationColumn: "projectId",
89
+ type: TableColumnType.Entity,
90
+ modelType: Project,
91
+ title: "Project",
92
+ description: "Relation to Project Resource in which this object belongs",
93
+ })
94
+ @ManyToOne(
95
+ () => {
96
+ return Project;
97
+ },
98
+ {
99
+ eager: false,
100
+ nullable: true,
101
+ onDelete: "CASCADE",
102
+ orphanedRowAction: "nullify",
103
+ },
104
+ )
105
+ @JoinColumn({ name: "projectId" })
106
+ public project?: Project = undefined;
107
+
108
+ @ColumnAccessControl({
109
+ create: [
110
+ Permission.ProjectOwner,
111
+ Permission.ProjectAdmin,
112
+ Permission.CreateProjectSSO,
113
+ ],
114
+ read: [
115
+ Permission.ProjectOwner,
116
+ Permission.ProjectAdmin,
117
+ Permission.ProjectMember,
118
+ Permission.ReadProjectSSO,
119
+ ],
120
+ update: [],
121
+ })
122
+ @Index()
123
+ @TableColumn({
124
+ type: TableColumnType.ObjectID,
125
+ required: true,
126
+ canReadOnRelationQuery: true,
127
+ title: "Project ID",
128
+ description: "ID of your OneUptime Project in which this object belongs",
129
+ })
130
+ @Column({
131
+ type: ColumnType.ObjectID,
132
+ nullable: false,
133
+ transformer: ObjectID.getDatabaseTransformer(),
134
+ })
135
+ public projectId?: ObjectID = undefined;
136
+
137
+ @ColumnAccessControl({
138
+ create: [
139
+ Permission.ProjectOwner,
140
+ Permission.ProjectAdmin,
141
+ Permission.CreateProjectSSO,
142
+ ],
143
+ read: [
144
+ Permission.ProjectOwner,
145
+ Permission.ProjectAdmin,
146
+ Permission.ProjectMember,
147
+ Permission.ReadProjectSSO,
148
+ ],
149
+ update: [
150
+ Permission.ProjectOwner,
151
+ Permission.ProjectAdmin,
152
+ Permission.EditProjectSSO,
153
+ ],
154
+ })
155
+ @TableColumn({
156
+ required: true,
157
+ type: TableColumnType.ShortText,
158
+ canReadOnRelationQuery: true,
159
+ title: "Name",
160
+ description: "Any friendly name for this SCIM configuration",
161
+ })
162
+ @Column({
163
+ nullable: false,
164
+ type: ColumnType.ShortText,
165
+ length: ColumnLength.ShortText,
166
+ })
167
+ @UniqueColumnBy("projectId")
168
+ public name?: string = undefined;
169
+
170
+ @ColumnAccessControl({
171
+ create: [
172
+ Permission.ProjectOwner,
173
+ Permission.ProjectAdmin,
174
+ Permission.CreateProjectSSO,
175
+ ],
176
+ read: [
177
+ Permission.ProjectOwner,
178
+ Permission.ProjectAdmin,
179
+ Permission.ProjectMember,
180
+ Permission.ReadProjectSSO,
181
+ ],
182
+ update: [
183
+ Permission.ProjectOwner,
184
+ Permission.ProjectAdmin,
185
+ Permission.EditProjectSSO,
186
+ ],
187
+ })
188
+ @TableColumn({
189
+ required: false,
190
+ type: TableColumnType.LongText,
191
+ title: "Description",
192
+ description: "Friendly description to help you remember",
193
+ })
194
+ @Column({
195
+ nullable: true,
196
+ type: ColumnType.LongText,
197
+ length: ColumnLength.LongText,
198
+ })
199
+ public description?: string = undefined;
200
+
201
+ @ColumnAccessControl({
202
+ create: [
203
+ Permission.ProjectOwner,
204
+ Permission.ProjectAdmin,
205
+ Permission.CreateProjectSSO,
206
+ ],
207
+ read: [
208
+ Permission.ProjectOwner,
209
+ Permission.ProjectAdmin,
210
+ Permission.ReadProjectSSO,
211
+ ],
212
+ update: [
213
+ Permission.ProjectOwner,
214
+ Permission.ProjectAdmin,
215
+ Permission.EditProjectSSO,
216
+ ],
217
+ })
218
+ @TableColumn({
219
+ required: true,
220
+ type: TableColumnType.LongText,
221
+ title: "Bearer Token",
222
+ description: "Bearer token for SCIM authentication. Keep this secure.",
223
+ })
224
+ @Column({
225
+ nullable: false,
226
+ type: ColumnType.LongText,
227
+ length: ColumnLength.LongText,
228
+ })
229
+ public bearerToken?: string = undefined;
230
+
231
+ @ColumnAccessControl({
232
+ create: [
233
+ Permission.ProjectOwner,
234
+ Permission.ProjectAdmin,
235
+ Permission.CreateProjectSSO,
236
+ ],
237
+ read: [
238
+ Permission.ProjectOwner,
239
+ Permission.ProjectAdmin,
240
+ Permission.ProjectMember,
241
+ Permission.ReadProjectSSO,
242
+ ],
243
+ update: [
244
+ Permission.ProjectOwner,
245
+ Permission.ProjectAdmin,
246
+ Permission.EditProjectSSO,
247
+ ],
248
+ })
249
+ @TableColumn({
250
+ required: false,
251
+ type: TableColumnType.EntityArray,
252
+ modelType: Team,
253
+ title: "Default Teams",
254
+ description: "Default teams that new users will be added to via SCIM",
255
+ })
256
+ @ManyToMany(
257
+ () => {
258
+ return Team;
259
+ },
260
+ { eager: false },
261
+ )
262
+ @JoinTable({
263
+ name: "ProjectScimTeam",
264
+ inverseJoinColumn: {
265
+ name: "teamId",
266
+ referencedColumnName: "_id",
267
+ },
268
+ joinColumn: {
269
+ name: "projectScimId",
270
+ referencedColumnName: "_id",
271
+ },
272
+ })
273
+ public teams?: Array<Team> = undefined;
274
+
275
+ @ColumnAccessControl({
276
+ create: [
277
+ Permission.ProjectOwner,
278
+ Permission.ProjectAdmin,
279
+ Permission.CreateProjectSSO,
280
+ ],
281
+ read: [
282
+ Permission.ProjectOwner,
283
+ Permission.ProjectAdmin,
284
+ Permission.ProjectMember,
285
+ Permission.ReadProjectSSO,
286
+ ],
287
+ update: [
288
+ Permission.ProjectOwner,
289
+ Permission.ProjectAdmin,
290
+ Permission.EditProjectSSO,
291
+ ],
292
+ })
293
+ @TableColumn({
294
+ isDefaultValueColumn: true,
295
+ type: TableColumnType.Boolean,
296
+ title: "Auto Provision Users",
297
+ description: "Automatically create users when they are added via SCIM",
298
+ defaultValue: true,
299
+ })
300
+ @Column({
301
+ type: ColumnType.Boolean,
302
+ default: true,
303
+ })
304
+ public autoProvisionUsers?: boolean = undefined;
305
+
306
+ @ColumnAccessControl({
307
+ create: [
308
+ Permission.ProjectOwner,
309
+ Permission.ProjectAdmin,
310
+ Permission.CreateProjectSSO,
311
+ ],
312
+ read: [
313
+ Permission.ProjectOwner,
314
+ Permission.ProjectAdmin,
315
+ Permission.ProjectMember,
316
+ Permission.ReadProjectSSO,
317
+ ],
318
+ update: [
319
+ Permission.ProjectOwner,
320
+ Permission.ProjectAdmin,
321
+ Permission.EditProjectSSO,
322
+ ],
323
+ })
324
+ @TableColumn({
325
+ isDefaultValueColumn: true,
326
+ type: TableColumnType.Boolean,
327
+ title: "Auto Deprovision Users",
328
+ description: "Automatically remove users when they are removed via SCIM",
329
+ defaultValue: true,
330
+ })
331
+ @Column({
332
+ type: ColumnType.Boolean,
333
+ default: true,
334
+ })
335
+ public autoDeprovisionUsers?: boolean = undefined;
336
+
337
+ @ColumnAccessControl({
338
+ create: [],
339
+ read: [
340
+ Permission.ProjectOwner,
341
+ Permission.ProjectAdmin,
342
+ Permission.ProjectMember,
343
+ Permission.ReadProjectSSO,
344
+ ],
345
+ update: [],
346
+ })
347
+ @TableColumn({
348
+ manyToOneRelationColumn: "createdByUserId",
349
+ type: TableColumnType.Entity,
350
+ modelType: User,
351
+ title: "Created by User",
352
+ description:
353
+ "Relation to User who created this object (if this object was created by a User)",
354
+ })
355
+ @ManyToOne(
356
+ () => {
357
+ return User;
358
+ },
359
+ {
360
+ eager: false,
361
+ nullable: true,
362
+ onDelete: "SET NULL",
363
+ orphanedRowAction: "nullify",
364
+ },
365
+ )
366
+ @JoinColumn({ name: "createdByUserId" })
367
+ public createdByUser?: User = undefined;
368
+
369
+ @ColumnAccessControl({
370
+ create: [
371
+ Permission.ProjectOwner,
372
+ Permission.ProjectAdmin,
373
+ Permission.CreateProjectSSO,
374
+ ],
375
+ read: [
376
+ Permission.ProjectOwner,
377
+ Permission.ProjectAdmin,
378
+ Permission.ProjectMember,
379
+ Permission.ReadProjectSSO,
380
+ ],
381
+ update: [],
382
+ })
383
+ @TableColumn({
384
+ type: TableColumnType.ObjectID,
385
+ title: "Created by User ID",
386
+ description:
387
+ "User ID who created this object (if this object was created by a User)",
388
+ })
389
+ @Column({
390
+ type: ColumnType.ObjectID,
391
+ nullable: true,
392
+ transformer: ObjectID.getDatabaseTransformer(),
393
+ })
394
+ public createdByUserId?: ObjectID = undefined;
395
+
396
+ @ColumnAccessControl({
397
+ create: [],
398
+ read: [
399
+ Permission.ProjectOwner,
400
+ Permission.ProjectAdmin,
401
+ Permission.ProjectMember,
402
+ Permission.ReadProjectSSO,
403
+ ],
404
+ update: [],
405
+ })
406
+ @TableColumn({
407
+ manyToOneRelationColumn: "deletedByUserId",
408
+ type: TableColumnType.Entity,
409
+ modelType: User,
410
+ title: "Deleted by User",
411
+ description:
412
+ "Relation to User who deleted this object (if this object was deleted by a User)",
413
+ })
414
+ @ManyToOne(
415
+ () => {
416
+ return User;
417
+ },
418
+ {
419
+ cascade: false,
420
+ eager: false,
421
+ nullable: true,
422
+ onDelete: "SET NULL",
423
+ orphanedRowAction: "nullify",
424
+ },
425
+ )
426
+ @JoinColumn({ name: "deletedByUserId" })
427
+ public deletedByUser?: User = undefined;
428
+
429
+ @ColumnAccessControl({
430
+ create: [],
431
+ read: [
432
+ Permission.ProjectOwner,
433
+ Permission.ProjectAdmin,
434
+ Permission.ProjectMember,
435
+ Permission.ReadProjectSSO,
436
+ ],
437
+ update: [],
438
+ })
439
+ @TableColumn({
440
+ type: TableColumnType.ObjectID,
441
+ title: "Deleted by User ID",
442
+ description:
443
+ "User ID who deleted this object (if this object was deleted by a User)",
444
+ })
445
+ @Column({
446
+ type: ColumnType.ObjectID,
447
+ nullable: true,
448
+ transformer: ObjectID.getDatabaseTransformer(),
449
+ })
450
+ public deletedByUserId?: ObjectID = undefined;
451
+ }
@@ -0,0 +1,67 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1754304193228 implements MigrationInterface {
4
+ public name = "MigrationName1754304193228";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `CREATE TABLE "ProjectSCIM" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "bearerToken" character varying(500) NOT NULL, "autoProvisionUsers" boolean NOT NULL DEFAULT true, "autoDeprovisionUsers" boolean NOT NULL DEFAULT true, "isEnabled" boolean NOT NULL DEFAULT false, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_51e71d70211675a5c918aee4e68" PRIMARY KEY ("_id"))`,
9
+ );
10
+ await queryRunner.query(
11
+ `CREATE INDEX "IDX_f916360335859c26c4d7051239" ON "ProjectSCIM" ("projectId") `,
12
+ );
13
+ await queryRunner.query(
14
+ `CREATE TABLE "ProjectScimTeam" ("projectScimId" uuid NOT NULL, "teamId" uuid NOT NULL, CONSTRAINT "PK_db724b66b4fa8c880ce5ccf820b" PRIMARY KEY ("projectScimId", "teamId"))`,
15
+ );
16
+ await queryRunner.query(
17
+ `CREATE INDEX "IDX_b9a28efd66600267f0e9de0731" ON "ProjectScimTeam" ("projectScimId") `,
18
+ );
19
+ await queryRunner.query(
20
+ `CREATE INDEX "IDX_bb0eda2ef0c773f975e9ad8448" ON "ProjectScimTeam" ("teamId") `,
21
+ );
22
+ await queryRunner.query(
23
+ `ALTER TABLE "ProjectSCIM" ADD CONSTRAINT "FK_f916360335859c26c4d7051239b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
24
+ );
25
+ await queryRunner.query(
26
+ `ALTER TABLE "ProjectSCIM" ADD CONSTRAINT "FK_5d5d587984f156e5215d51daff7" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
27
+ );
28
+ await queryRunner.query(
29
+ `ALTER TABLE "ProjectSCIM" ADD CONSTRAINT "FK_9cadda4fc2af268b5670d02bf76" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
30
+ );
31
+ await queryRunner.query(
32
+ `ALTER TABLE "ProjectScimTeam" ADD CONSTRAINT "FK_b9a28efd66600267f0e9de0731b" FOREIGN KEY ("projectScimId") REFERENCES "ProjectSCIM"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "ProjectScimTeam" ADD CONSTRAINT "FK_bb0eda2ef0c773f975e9ad8448a" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
36
+ );
37
+ }
38
+
39
+ public async down(queryRunner: QueryRunner): Promise<void> {
40
+ await queryRunner.query(
41
+ `ALTER TABLE "ProjectScimTeam" DROP CONSTRAINT "FK_bb0eda2ef0c773f975e9ad8448a"`,
42
+ );
43
+ await queryRunner.query(
44
+ `ALTER TABLE "ProjectScimTeam" DROP CONSTRAINT "FK_b9a28efd66600267f0e9de0731b"`,
45
+ );
46
+ await queryRunner.query(
47
+ `ALTER TABLE "ProjectSCIM" DROP CONSTRAINT "FK_9cadda4fc2af268b5670d02bf76"`,
48
+ );
49
+ await queryRunner.query(
50
+ `ALTER TABLE "ProjectSCIM" DROP CONSTRAINT "FK_5d5d587984f156e5215d51daff7"`,
51
+ );
52
+ await queryRunner.query(
53
+ `ALTER TABLE "ProjectSCIM" DROP CONSTRAINT "FK_f916360335859c26c4d7051239b"`,
54
+ );
55
+ await queryRunner.query(
56
+ `DROP INDEX "public"."IDX_bb0eda2ef0c773f975e9ad8448"`,
57
+ );
58
+ await queryRunner.query(
59
+ `DROP INDEX "public"."IDX_b9a28efd66600267f0e9de0731"`,
60
+ );
61
+ await queryRunner.query(`DROP TABLE "ProjectScimTeam"`);
62
+ await queryRunner.query(
63
+ `DROP INDEX "public"."IDX_f916360335859c26c4d7051239"`,
64
+ );
65
+ await queryRunner.query(`DROP TABLE "ProjectSCIM"`);
66
+ }
67
+ }
@@ -0,0 +1,17 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1754315774827 implements MigrationInterface {
4
+ public name = "MigrationName1754315774827";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "ProjectSCIM" DROP COLUMN "isEnabled"`,
9
+ );
10
+ }
11
+
12
+ public async down(queryRunner: QueryRunner): Promise<void> {
13
+ await queryRunner.query(
14
+ `ALTER TABLE "ProjectSCIM" ADD "isEnabled" boolean NOT NULL DEFAULT false`,
15
+ );
16
+ }
17
+ }
@@ -146,6 +146,8 @@ import { MigrationName1753343522987 } from "./1753343522987-MigrationName";
146
146
  import { MigrationName1753377161288 } from "./1753377161288-MigrationName";
147
147
  import { AddPerformanceIndexes1753378524062 } from "./1753378524062-AddPerformanceIndexes";
148
148
  import { MigrationName1753383711511 } from "./1753383711511-MigrationName";
149
+ import { MigrationName1754304193228 } from "./1754304193228-MigrationName";
150
+ import { MigrationName1754315774827 } from "./1754315774827-MigrationName";
149
151
 
150
152
  export default [
151
153
  InitialMigration,
@@ -296,4 +298,6 @@ export default [
296
298
  MigrationName1753377161288,
297
299
  AddPerformanceIndexes1753378524062,
298
300
  MigrationName1753383711511,
301
+ MigrationName1754304193228,
302
+ MigrationName1754315774827,
299
303
  ];
@@ -225,7 +225,7 @@ export default class Queue {
225
225
  };
226
226
 
227
227
  if (job.stacktrace && job.stacktrace.length > 0) {
228
- result.stackTrace = job.stacktrace.join('\n');
228
+ result.stackTrace = job.stacktrace.join("\n");
229
229
  }
230
230
 
231
231
  return result;
@@ -0,0 +1,94 @@
1
+ import ProjectSCIMService from "../Services/ProjectSCIMService";
2
+ import {
3
+ ExpressRequest,
4
+ ExpressResponse,
5
+ NextFunction,
6
+ OneUptimeRequest,
7
+ } from "../Utils/Express";
8
+ import ObjectID from "../../Types/ObjectID";
9
+ import ProjectSCIM from "../../Models/DatabaseModels/ProjectSCIM";
10
+ import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
11
+ import BadRequestException from "../../Types/Exception/BadRequestException";
12
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
13
+ import logger from "../Utils/Logger";
14
+
15
+ export default class SCIMMiddleware {
16
+ @CaptureSpan()
17
+ public static async isAuthorizedSCIMRequest(
18
+ req: ExpressRequest,
19
+ _res: ExpressResponse,
20
+ next: NextFunction,
21
+ ): Promise<void> {
22
+ try {
23
+ const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
24
+
25
+ // Extract project SCIM ID from URL path
26
+ const projectScimId: string | undefined = req.params["projectScimId"];
27
+ if (!projectScimId) {
28
+ throw new BadRequestException("Project SCIM ID is required");
29
+ }
30
+
31
+ // Extract bearer token from Authorization header
32
+ let bearerToken: string | undefined;
33
+ if (req.headers?.["authorization"]) {
34
+ const authHeader: string = req.headers["authorization"] as string;
35
+ if (authHeader.startsWith("Bearer ")) {
36
+ bearerToken = authHeader.substring(7);
37
+ }
38
+ }
39
+
40
+ logger.debug(
41
+ `SCIM Authorization: projectScimId=${projectScimId}, bearerToken=${
42
+ bearerToken
43
+ }`,
44
+ );
45
+
46
+ if (!bearerToken) {
47
+ throw new NotAuthorizedException(
48
+ "Bearer token is required for SCIM authentication",
49
+ );
50
+ }
51
+
52
+ // Find SCIM configuration by SCIM ID and bearer token
53
+ const scimConfig: ProjectSCIM | null = await ProjectSCIMService.findOneBy(
54
+ {
55
+ query: {
56
+ _id: new ObjectID(projectScimId),
57
+ bearerToken: bearerToken,
58
+ },
59
+ select: {
60
+ _id: true,
61
+ projectId: true,
62
+ autoProvisionUsers: true,
63
+ autoDeprovisionUsers: true,
64
+ teams: {
65
+ _id: true,
66
+ name: true,
67
+ },
68
+ },
69
+ props: {
70
+ isRoot: true,
71
+ },
72
+ },
73
+ );
74
+
75
+ if (!scimConfig) {
76
+ throw new NotAuthorizedException(
77
+ "Invalid bearer token or SCIM configuration not found",
78
+ );
79
+ }
80
+
81
+ // Store SCIM configuration and project ID in bearerTokenData for use in handlers
82
+ oneuptimeRequest.bearerTokenData = {
83
+ scimConfig: scimConfig,
84
+ projectId: scimConfig.projectId,
85
+ projectScimId: new ObjectID(projectScimId),
86
+ type: "scim",
87
+ };
88
+
89
+ return next();
90
+ } catch (err) {
91
+ return next(err);
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,27 @@
1
+ import CreateBy from "../Types/Database/CreateBy";
2
+ import { OnCreate } from "../Types/Database/Hooks";
3
+ import DatabaseService from "./DatabaseService";
4
+ import Model from "../../Models/DatabaseModels/ProjectSCIM";
5
+ import ObjectID from "../../Types/ObjectID";
6
+
7
+ export class Service extends DatabaseService<Model> {
8
+ public constructor() {
9
+ super(Model);
10
+ }
11
+
12
+ protected override async onBeforeCreate(
13
+ createBy: CreateBy<Model>,
14
+ ): Promise<OnCreate<Model>> {
15
+ if (!createBy.data.bearerToken) {
16
+ // Generate a secure bearer token if not provided
17
+ createBy.data.bearerToken = ObjectID.generate().toString();
18
+ }
19
+
20
+ return {
21
+ createBy: createBy,
22
+ carryForward: {},
23
+ };
24
+ }
25
+ }
26
+
27
+ export default new Service();