@boxyhq/saml-jackson 1.1.4 → 1.2.0

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 (37) hide show
  1. package/dist/controller/error.d.ts +5 -0
  2. package/dist/controller/error.js +6 -1
  3. package/dist/controller/oauth.js +18 -4
  4. package/dist/controller/sp-config.d.ts +21 -0
  5. package/dist/controller/sp-config.js +74 -0
  6. package/dist/controller/utils.d.ts +13 -0
  7. package/dist/controller/utils.js +27 -1
  8. package/dist/db/mem.js +7 -3
  9. package/dist/directory-sync/Base.d.ts +15 -0
  10. package/dist/directory-sync/Base.js +39 -0
  11. package/dist/directory-sync/DirectoryConfig.d.ts +43 -0
  12. package/dist/directory-sync/DirectoryConfig.js +186 -0
  13. package/dist/directory-sync/DirectoryGroups.d.ts +26 -0
  14. package/dist/directory-sync/DirectoryGroups.js +254 -0
  15. package/dist/directory-sync/DirectoryUsers.d.ts +22 -0
  16. package/dist/directory-sync/DirectoryUsers.js +175 -0
  17. package/dist/directory-sync/Groups.d.ts +47 -0
  18. package/dist/directory-sync/Groups.js +195 -0
  19. package/dist/directory-sync/Users.d.ts +47 -0
  20. package/dist/directory-sync/Users.js +135 -0
  21. package/dist/directory-sync/WebhookEventsLogger.d.ts +13 -0
  22. package/dist/directory-sync/WebhookEventsLogger.js +57 -0
  23. package/dist/directory-sync/events.d.ts +7 -0
  24. package/dist/directory-sync/events.js +53 -0
  25. package/dist/directory-sync/index.d.ts +6 -0
  26. package/dist/directory-sync/index.js +42 -0
  27. package/dist/directory-sync/request.d.ts +7 -0
  28. package/dist/directory-sync/request.js +30 -0
  29. package/dist/directory-sync/transform.d.ts +7 -0
  30. package/dist/directory-sync/transform.js +26 -0
  31. package/dist/directory-sync/utils.d.ts +39 -0
  32. package/dist/directory-sync/utils.js +140 -0
  33. package/dist/index.d.ts +4 -1
  34. package/dist/index.js +10 -3
  35. package/dist/typings.d.ts +290 -9
  36. package/dist/typings.js +9 -0
  37. package/package.json +17 -14
package/dist/index.js CHANGED
@@ -27,15 +27,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.controllers = void 0;
30
+ const db_1 = __importDefault(require("./db/db"));
31
+ const defaultDb_1 = __importDefault(require("./db/defaultDb"));
32
+ const read_config_1 = __importDefault(require("./read-config"));
30
33
  const admin_1 = require("./controller/admin");
31
34
  const api_1 = require("./controller/api");
32
35
  const oauth_1 = require("./controller/oauth");
33
36
  const health_check_1 = require("./controller/health-check");
34
37
  const logout_1 = require("./controller/logout");
38
+ const directory_sync_1 = __importDefault(require("./directory-sync"));
35
39
  const oidc_discovery_1 = require("./controller/oidc-discovery");
36
- const db_1 = __importDefault(require("./db/db"));
37
- const defaultDb_1 = __importDefault(require("./db/defaultDb"));
38
- const read_config_1 = __importDefault(require("./read-config"));
40
+ const sp_config_1 = require("./controller/sp-config");
39
41
  const defaultOpts = (opts) => {
40
42
  const newOpts = Object.assign({}, opts);
41
43
  if (!newOpts.externalUrl) {
@@ -44,6 +46,7 @@ const defaultOpts = (opts) => {
44
46
  if (!newOpts.samlPath) {
45
47
  throw new Error('samlPath is required');
46
48
  }
49
+ newOpts.scimPath = newOpts.scimPath || '/api/scim/v2.0';
47
50
  newOpts.samlAudience = newOpts.samlAudience || 'https://saml.boxyhq.com';
48
51
  newOpts.preLoadedConfig = newOpts.preLoadedConfig || ''; // path to folder containing static SAML config that will be preloaded. This is useful for self-hosted deployments that only have to support a single tenant (or small number of known tenants).
49
52
  newOpts.idpEnabled = newOpts.idpEnabled === true;
@@ -78,7 +81,9 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
78
81
  sessionStore,
79
82
  opts,
80
83
  });
84
+ const directorySync = yield (0, directory_sync_1.default)({ db, opts });
81
85
  const oidcDiscoveryController = new oidc_discovery_1.OidcDiscoveryController({ opts });
86
+ const spConfig = new sp_config_1.SPSAMLConfig(opts);
82
87
  // write pre-loaded config if present
83
88
  if (opts.preLoadedConfig && opts.preLoadedConfig.length > 0) {
84
89
  const configs = yield (0, read_config_1.default)(opts.preLoadedConfig);
@@ -90,11 +95,13 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
90
95
  const type = opts.db.engine === 'sql' && opts.db.type ? ' Type: ' + opts.db.type : '';
91
96
  console.log(`Using engine: ${opts.db.engine}.${type}`);
92
97
  return {
98
+ spConfig,
93
99
  apiController,
94
100
  oauthController,
95
101
  adminController,
96
102
  logoutController,
97
103
  healthCheckController,
104
+ directorySync,
98
105
  oidcDiscoveryController,
99
106
  };
100
107
  });
package/dist/typings.d.ts CHANGED
@@ -4,8 +4,8 @@ export declare type IdPConfig = {
4
4
  redirectUrl: string[] | string;
5
5
  tenant: string;
6
6
  product: string;
7
- name: string;
8
- description: string;
7
+ name?: string;
8
+ description?: string;
9
9
  rawMetadata?: string;
10
10
  encodedRawMetadata?: string;
11
11
  };
@@ -45,6 +45,13 @@ export interface IHealthCheckController {
45
45
  }>;
46
46
  init(): Promise<void>;
47
47
  }
48
+ export interface ILogoutController {
49
+ createRequest(body: SLORequestParams): Promise<{
50
+ logoutUrl: string | null;
51
+ logoutForm: string | null;
52
+ }>;
53
+ handleResponse(body: SAMLResponsePayload): Promise<any>;
54
+ }
48
55
  export interface IOidcDiscoveryController {
49
56
  openidConfig(): {
50
57
  issuer: string;
@@ -89,6 +96,7 @@ export interface OAuthTokenReq {
89
96
  code_verifier: string;
90
97
  code: string;
91
98
  grant_type: 'authorization_code';
99
+ redirect_uri?: string;
92
100
  }
93
101
  export interface OAuthTokenRes {
94
102
  access_token: string;
@@ -122,6 +130,9 @@ export interface Storable {
122
130
  delete(key: string): Promise<any>;
123
131
  getByIndex(idx: Index): Promise<any>;
124
132
  }
133
+ export interface DatabaseStore {
134
+ store(namespace: string): Storable;
135
+ }
125
136
  export interface Encrypted {
126
137
  iv?: string;
127
138
  tag?: string;
@@ -149,6 +160,7 @@ export interface JacksonOption {
149
160
  db: DatabaseOption;
150
161
  clientSecretVerifier?: string;
151
162
  idpDiscoveryPath?: string;
163
+ scimPath?: string;
152
164
  openid: {
153
165
  jwsAlg?: string;
154
166
  jwtSigningKeys?: {
@@ -185,17 +197,286 @@ export interface SAMLConfig {
185
197
  };
186
198
  defaultRedirectUrl: string;
187
199
  }
188
- export interface ILogoutController {
189
- createRequest(body: SLORequestParams): Promise<{
190
- logoutUrl: string | null;
191
- logoutForm: string | null;
192
- }>;
193
- handleResponse(body: SAMLResponsePayload): Promise<any>;
194
- }
195
200
  export interface OAuthErrorHandlerParams {
196
201
  error: 'invalid_request' | 'access_denied' | 'unauthorized_client' | 'unsupported_response_type' | 'invalid_scope' | 'server_error' | 'temporarily_unavailable';
197
202
  error_description: string;
198
203
  redirect_uri: string;
199
204
  state?: string;
200
205
  }
206
+ export interface ISPSAMLConfig {
207
+ get(): {
208
+ acsUrl: string;
209
+ entityId: string;
210
+ response: string;
211
+ assertionSignature: string;
212
+ signatureAlgorithm: string;
213
+ assertionEncryption: string;
214
+ };
215
+ toMarkdown(): string;
216
+ toHTML(): string;
217
+ }
218
+ export declare type DirectorySyncEventType = 'user.created' | 'user.updated' | 'user.deleted' | 'group.created' | 'group.updated' | 'group.deleted' | 'group.user_added' | 'group.user_removed';
219
+ export interface Base {
220
+ store(type: 'groups' | 'members' | 'users'): Storable;
221
+ setTenant(tenant: string): this;
222
+ setProduct(product: string): this;
223
+ setTenantAndProduct(tenant: string, product: string): this;
224
+ with(tenant: string, product: string): this;
225
+ createId(): string;
226
+ }
227
+ export interface Users extends Base {
228
+ list({ pageOffset, pageLimit, }: {
229
+ pageOffset?: number;
230
+ pageLimit?: number;
231
+ }): Promise<{
232
+ data: User[] | null;
233
+ error: ApiError | null;
234
+ }>;
235
+ get(id: string): Promise<{
236
+ data: User | null;
237
+ error: ApiError | null;
238
+ }>;
239
+ search(userName: string): Promise<{
240
+ data: User[] | null;
241
+ error: ApiError | null;
242
+ }>;
243
+ delete(id: string): Promise<{
244
+ data: null;
245
+ error: ApiError | null;
246
+ }>;
247
+ clear(): Promise<void>;
248
+ create(param: {
249
+ first_name: string;
250
+ last_name: string;
251
+ email: string;
252
+ active: boolean;
253
+ raw: any;
254
+ }): Promise<{
255
+ data: User | null;
256
+ error: ApiError | null;
257
+ }>;
258
+ update(id: string, param: {
259
+ first_name: string;
260
+ last_name: string;
261
+ email: string;
262
+ active: boolean;
263
+ raw: object;
264
+ }): Promise<{
265
+ data: User | null;
266
+ error: ApiError | null;
267
+ }>;
268
+ }
269
+ export interface Groups extends Base {
270
+ create(param: {
271
+ name: string;
272
+ raw: any;
273
+ }): Promise<{
274
+ data: Group | null;
275
+ error: ApiError | null;
276
+ }>;
277
+ removeAllUsers(groupId: string): Promise<void>;
278
+ list({ pageOffset, pageLimit, }: {
279
+ pageOffset?: number;
280
+ pageLimit?: number;
281
+ }): Promise<{
282
+ data: Group[] | null;
283
+ error: ApiError | null;
284
+ }>;
285
+ get(id: string): Promise<{
286
+ data: Group | null;
287
+ error: ApiError | null;
288
+ }>;
289
+ getAllUsers(groupId: string): Promise<{
290
+ user_id: string;
291
+ }[]>;
292
+ delete(id: string): Promise<{
293
+ data: null;
294
+ error: ApiError | null;
295
+ }>;
296
+ addUserToGroup(groupId: string, userId: string): Promise<void>;
297
+ isUserInGroup(groupId: string, userId: string): Promise<boolean>;
298
+ removeUserFromGroup(groupId: string, userId: string): Promise<void>;
299
+ search(displayName: string): Promise<{
300
+ data: Group[] | null;
301
+ error: ApiError | null;
302
+ }>;
303
+ update(id: string, param: {
304
+ name: string;
305
+ raw: any;
306
+ }): Promise<{
307
+ data: Group | null;
308
+ error: ApiError | null;
309
+ }>;
310
+ }
311
+ export declare type User = {
312
+ id: string;
313
+ email: string;
314
+ first_name: string;
315
+ last_name: string;
316
+ active: boolean;
317
+ raw?: any;
318
+ };
319
+ export declare type Group = {
320
+ id: string;
321
+ name: string;
322
+ raw?: any;
323
+ };
324
+ export declare enum DirectorySyncProviders {
325
+ 'azure-scim-v2' = "Azure SCIM v2.0",
326
+ 'onelogin-scim-v2' = "OneLogin SCIM v2.0",
327
+ 'okta-scim-v2' = "Okta SCIM v2.0",
328
+ 'jumpcloud-scim-v2' = "JumpCloud v2.0",
329
+ 'generic-scim-v2' = "SCIM Generic v2.0"
330
+ }
331
+ export declare type DirectoryType = keyof typeof DirectorySyncProviders;
332
+ export declare type HTTPMethod = 'POST' | 'PUT' | 'DELETE' | 'GET' | 'PATCH';
333
+ export declare type Directory = {
334
+ id: string;
335
+ name: string;
336
+ tenant: string;
337
+ product: string;
338
+ type: DirectoryType;
339
+ log_webhook_events: boolean;
340
+ scim: {
341
+ path: string;
342
+ endpoint?: string;
343
+ secret: string;
344
+ };
345
+ webhook: {
346
+ endpoint: string;
347
+ secret: string;
348
+ };
349
+ };
350
+ export declare type DirectorySyncGroupMember = {
351
+ value: string;
352
+ email?: string;
353
+ };
354
+ export interface DirectoryConfig {
355
+ create({ name, tenant, product, webhook_url, webhook_secret, type, }: {
356
+ name?: string;
357
+ tenant: string;
358
+ product: string;
359
+ webhook_url?: string;
360
+ webhook_secret?: string;
361
+ type?: DirectoryType;
362
+ }): Promise<{
363
+ data: Directory | null;
364
+ error: ApiError | null;
365
+ }>;
366
+ update(id: string, param: Omit<Partial<Directory>, 'id' | 'tenant' | 'prodct' | 'scim'>): Promise<{
367
+ data: Directory | null;
368
+ error: ApiError | null;
369
+ }>;
370
+ get(id: string): Promise<{
371
+ data: Directory | null;
372
+ error: ApiError | null;
373
+ }>;
374
+ getByTenantAndProduct(tenant: string, product: string): Promise<{
375
+ data: Directory | null;
376
+ error: ApiError | null;
377
+ }>;
378
+ list({ pageOffset, pageLimit, }: {
379
+ pageOffset?: number;
380
+ pageLimit?: number;
381
+ }): Promise<{
382
+ data: Directory[] | null;
383
+ error: ApiError | null;
384
+ }>;
385
+ delete(id: string): Promise<void>;
386
+ }
387
+ export interface IDirectoryUsers {
388
+ create(directory: Directory, body: any): Promise<DirectorySyncResponse>;
389
+ get(user: User): Promise<DirectorySyncResponse>;
390
+ update(directory: Directory, user: User, body: any): Promise<DirectorySyncResponse>;
391
+ patch(directory: Directory, user: User, body: any): Promise<DirectorySyncResponse>;
392
+ delete(directory: Directory, user: User, active: boolean): Promise<DirectorySyncResponse>;
393
+ getAll(queryParams: {
394
+ count: number;
395
+ startIndex: number;
396
+ filter?: string;
397
+ }): Promise<DirectorySyncResponse>;
398
+ handleRequest(request: DirectorySyncRequest, eventCallback?: EventCallback): Promise<DirectorySyncResponse>;
399
+ }
400
+ export interface IDirectoryGroups {
401
+ create(directory: Directory, body: any): Promise<DirectorySyncResponse>;
402
+ get(group: Group): Promise<DirectorySyncResponse>;
403
+ updateDisplayName(directory: Directory, group: Group, body: any): Promise<Group>;
404
+ delete(directory: Directory, group: Group): Promise<DirectorySyncResponse>;
405
+ getAll(queryParams: {
406
+ filter?: string;
407
+ }): Promise<DirectorySyncResponse>;
408
+ addGroupMembers(directory: Directory, group: Group, members: DirectorySyncGroupMember[] | undefined, sendWebhookEvent: boolean): Promise<void>;
409
+ removeGroupMembers(directory: Directory, group: Group, members: DirectorySyncGroupMember[], sendWebhookEvent: boolean): Promise<void>;
410
+ addOrRemoveGroupMembers(directory: Directory, group: Group, members: DirectorySyncGroupMember[]): Promise<void>;
411
+ update(directory: Directory, group: Group, body: any): Promise<DirectorySyncResponse>;
412
+ patch(directory: Directory, group: Group, body: any): Promise<DirectorySyncResponse>;
413
+ handleRequest(request: DirectorySyncRequest, eventCallback?: EventCallback): Promise<DirectorySyncResponse>;
414
+ }
415
+ export interface IWebhookEventsLogger extends Base {
416
+ log(directory: Directory, event: DirectorySyncEvent): Promise<WebhookEventLog>;
417
+ getAll(): Promise<WebhookEventLog[]>;
418
+ get(id: string): Promise<WebhookEventLog>;
419
+ clear(): Promise<void>;
420
+ delete(id: string): Promise<void>;
421
+ updateStatus(log: WebhookEventLog, statusCode: number): Promise<WebhookEventLog>;
422
+ }
423
+ export declare type DirectorySyncResponse = {
424
+ status: number;
425
+ data?: any;
426
+ };
427
+ export interface DirectorySyncRequestHandler {
428
+ handle(request: DirectorySyncRequest, callback?: EventCallback): Promise<DirectorySyncResponse>;
429
+ }
430
+ export interface Events {
431
+ handle(event: DirectorySyncEvent): Promise<void>;
432
+ }
433
+ export interface DirectorySyncRequest {
434
+ method: HTTPMethod;
435
+ body: any | undefined;
436
+ directoryId: Directory['id'];
437
+ resourceType: 'users' | 'groups';
438
+ resourceId: string | undefined;
439
+ apiSecret: string | null;
440
+ query: {
441
+ count?: number;
442
+ startIndex?: number;
443
+ filter?: string;
444
+ };
445
+ }
446
+ export declare type DirectorySync = {
447
+ requests: DirectorySyncRequestHandler;
448
+ directories: DirectoryConfig;
449
+ groups: Groups;
450
+ users: Users;
451
+ events: {
452
+ callback: EventCallback;
453
+ };
454
+ webhookLogs: IWebhookEventsLogger;
455
+ providers: () => {
456
+ [K in string]: string;
457
+ };
458
+ };
459
+ export interface ApiError {
460
+ message: string;
461
+ code: number;
462
+ }
463
+ export interface DirectorySyncEvent {
464
+ directory_id: Directory['id'];
465
+ event: DirectorySyncEventType;
466
+ data: User | Group | (User & {
467
+ group: Group;
468
+ });
469
+ tenant: string;
470
+ product: string;
471
+ }
472
+ export interface EventCallback {
473
+ (event: DirectorySyncEvent): Promise<void>;
474
+ }
475
+ export interface WebhookEventLog extends DirectorySyncEvent {
476
+ id: string;
477
+ webhook_endpoint: string;
478
+ created_at: Date;
479
+ status_code?: number;
480
+ delivered?: boolean;
481
+ }
201
482
  export {};
package/dist/typings.js CHANGED
@@ -1,2 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DirectorySyncProviders = void 0;
4
+ var DirectorySyncProviders;
5
+ (function (DirectorySyncProviders) {
6
+ DirectorySyncProviders["azure-scim-v2"] = "Azure SCIM v2.0";
7
+ DirectorySyncProviders["onelogin-scim-v2"] = "OneLogin SCIM v2.0";
8
+ DirectorySyncProviders["okta-scim-v2"] = "Okta SCIM v2.0";
9
+ DirectorySyncProviders["jumpcloud-scim-v2"] = "JumpCloud v2.0";
10
+ DirectorySyncProviders["generic-scim-v2"] = "SCIM Generic v2.0";
11
+ })(DirectorySyncProviders = exports.DirectorySyncProviders || (exports.DirectorySyncProviders = {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxyhq/saml-jackson",
3
- "version": "1.1.4",
3
+ "version": "1.2.0",
4
4
  "description": "SAML Jackson library",
5
5
  "keywords": [
6
6
  "SAML 2.0"
@@ -39,36 +39,39 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@boxyhq/saml20": "1.0.6",
42
- "@opentelemetry/api-metrics": "0.27.0",
43
42
  "@opentelemetry/api": "1.0.4",
43
+ "@opentelemetry/api-metrics": "0.27.0",
44
44
  "@peculiar/webcrypto": "1.4.0",
45
- "@peculiar/x509": "1.8.1",
46
- "jose": "4.8.3",
47
- "mongodb": "4.8.1",
45
+ "axios": "^0.27.2",
46
+ "@peculiar/x509": "1.8.3",
47
+ "jose": "4.9.2",
48
+ "marked": "4.1.0",
49
+ "mongodb": "4.9.1",
48
50
  "mysql2": "2.3.3",
49
- "pg": "8.7.3",
50
- "redis": "4.0.6",
51
+ "pg": "8.8.0",
52
+ "redis": "4.3.1",
51
53
  "reflect-metadata": "0.1.13",
52
54
  "ripemd160": "2.0.2",
53
- "typeorm": "0.3.7",
55
+ "typeorm": "0.3.9",
54
56
  "xml2js": "0.4.23",
55
57
  "xmlbuilder": "15.1.1"
56
58
  },
57
59
  "devDependencies": {
58
- "@types/node": "18.6.3",
60
+ "@faker-js/faker": "7.2.0",
61
+ "@types/node": "18.7.16",
59
62
  "@types/sinon": "10.0.13",
60
63
  "@types/tap": "15.0.7",
61
- "@typescript-eslint/eslint-plugin": "5.31.0",
62
- "@typescript-eslint/parser": "5.31.0",
64
+ "@typescript-eslint/eslint-plugin": "5.36.2",
65
+ "@typescript-eslint/parser": "5.36.2",
63
66
  "cross-env": "7.0.3",
64
- "eslint": "8.21.0",
67
+ "eslint": "8.23.0",
65
68
  "eslint-config-prettier": "8.5.0",
66
69
  "prettier": "2.7.1",
67
70
  "sinon": "14.0.0",
68
71
  "tap": "16.3.0",
69
72
  "ts-node": "10.9.1",
70
- "tsconfig-paths": "4.0.0",
71
- "typescript": "4.7.4"
73
+ "tsconfig-paths": "4.1.0",
74
+ "typescript": "4.8.2"
72
75
  },
73
76
  "engines": {
74
77
  "node": ">=14.18.1 <=16.x"