@oneuptime/common 7.0.4849 → 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 (28) 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 +17 -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 +5 -1
  21. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  22. package/build/dist/Server/Middleware/SCIMAuthorization.js +81 -0
  23. package/build/dist/Server/Middleware/SCIMAuthorization.js.map +1 -0
  24. package/build/dist/Server/Services/ProjectSCIMService.js +20 -0
  25. package/build/dist/Server/Services/ProjectSCIMService.js.map +1 -0
  26. package/build/dist/Server/Utils/StartServer.js +9 -0
  27. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  28. 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
  ];
@@ -193,6 +193,7 @@ export default class Queue {
193
193
  name: string;
194
194
  data: JSONObject;
195
195
  failedReason: string;
196
+ stackTrace?: string;
196
197
  processedOn: Date | null;
197
198
  finishedOn: Date | null;
198
199
  attemptsMade: number;
@@ -204,7 +205,16 @@ export default class Queue {
204
205
  const failed: Job[] = await queue.getFailed(start, end);
205
206
 
206
207
  return failed.map((job: Job) => {
207
- return {
208
+ const result: {
209
+ id: string;
210
+ name: string;
211
+ data: JSONObject;
212
+ failedReason: string;
213
+ stackTrace?: string;
214
+ processedOn: Date | null;
215
+ finishedOn: Date | null;
216
+ attemptsMade: number;
217
+ } = {
208
218
  id: job.id || "unknown",
209
219
  name: job.name || "unknown",
210
220
  data: job.data as JSONObject,
@@ -213,6 +223,12 @@ export default class Queue {
213
223
  finishedOn: job.finishedOn ? new Date(job.finishedOn) : null,
214
224
  attemptsMade: job.attemptsMade || 0,
215
225
  };
226
+
227
+ if (job.stacktrace && job.stacktrace.length > 0) {
228
+ result.stackTrace = job.stacktrace.join("\n");
229
+ }
230
+
231
+ return result;
216
232
  });
217
233
  }
218
234
  }
@@ -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
+ }