@workos-inc/node 1.3.0 → 1.6.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 (39) hide show
  1. package/lib/common/exceptions/index.d.ts +7 -6
  2. package/lib/common/exceptions/index.js +17 -13
  3. package/lib/common/exceptions/signature-verification.exception.d.ts +4 -0
  4. package/lib/common/exceptions/signature-verification.exception.js +10 -0
  5. package/lib/common/exceptions/unprocessable-entity.exception.js +1 -1
  6. package/lib/index.d.ts +2 -0
  7. package/lib/index.js +2 -0
  8. package/lib/organizations/fixtures/create-organization.json +1 -0
  9. package/lib/organizations/fixtures/get-organization.json +1 -0
  10. package/lib/organizations/fixtures/list-organizations.json +7 -0
  11. package/lib/organizations/fixtures/update-organization.json +1 -0
  12. package/lib/organizations/interfaces/create-organization-options.interface.d.ts +5 -0
  13. package/lib/organizations/interfaces/create-organization-options.interface.js +2 -0
  14. package/lib/organizations/interfaces/index.d.ts +1 -0
  15. package/lib/organizations/interfaces/index.js +1 -0
  16. package/lib/organizations/interfaces/organization.interface.d.ts +1 -0
  17. package/lib/organizations/interfaces/update-organization-options.interface.d.ts +1 -0
  18. package/lib/organizations/organizations.d.ts +2 -5
  19. package/lib/organizations/organizations.js +2 -5
  20. package/lib/organizations/organizations.spec.js +5 -9
  21. package/lib/portal/portal.spec.js +5 -10
  22. package/lib/sso/interfaces/connection-type.enum.d.ts +1 -0
  23. package/lib/sso/interfaces/connection-type.enum.js +1 -0
  24. package/lib/sso/interfaces/index.d.ts +1 -0
  25. package/lib/sso/interfaces/index.js +1 -0
  26. package/lib/sso/interfaces/profile.interface.d.ts +1 -0
  27. package/lib/sso/sso.spec.js +2 -0
  28. package/lib/webhooks/fixtures/webhook.json +1 -0
  29. package/lib/webhooks/interfaces/index.d.ts +1 -0
  30. package/lib/webhooks/interfaces/index.js +13 -0
  31. package/lib/webhooks/interfaces/webhook.interface.d.ts +5 -0
  32. package/lib/webhooks/interfaces/webhook.interface.js +2 -0
  33. package/lib/webhooks/webhooks.d.ts +13 -0
  34. package/lib/webhooks/webhooks.js +67 -0
  35. package/lib/webhooks/webhooks.spec.d.ts +1 -0
  36. package/lib/webhooks/webhooks.spec.js +154 -0
  37. package/lib/workos.d.ts +2 -0
  38. package/lib/workos.js +3 -1
  39. package/package.json +11 -11
@@ -1,6 +1,7 @@
1
- export { GenericServerException } from './generic-server.exception';
2
- export { NoApiKeyProvidedException } from './no-api-key-provided.exception';
3
- export { NotFoundException } from './not-found.exception';
4
- export { UnauthorizedException } from './unauthorized.exception';
5
- export { UnprocessableEntityException } from './unprocessable-entity.exception';
6
- export { OauthException } from './oauth.exception';
1
+ export * from './generic-server.exception';
2
+ export * from './no-api-key-provided.exception';
3
+ export * from './not-found.exception';
4
+ export * from './oauth.exception';
5
+ export * from './signature-verification.exception';
6
+ export * from './unauthorized.exception';
7
+ export * from './unprocessable-entity.exception';
@@ -1,15 +1,19 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
+ };
2
12
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OauthException = exports.UnprocessableEntityException = exports.UnauthorizedException = exports.NotFoundException = exports.NoApiKeyProvidedException = exports.GenericServerException = void 0;
4
- var generic_server_exception_1 = require("./generic-server.exception");
5
- Object.defineProperty(exports, "GenericServerException", { enumerable: true, get: function () { return generic_server_exception_1.GenericServerException; } });
6
- var no_api_key_provided_exception_1 = require("./no-api-key-provided.exception");
7
- Object.defineProperty(exports, "NoApiKeyProvidedException", { enumerable: true, get: function () { return no_api_key_provided_exception_1.NoApiKeyProvidedException; } });
8
- var not_found_exception_1 = require("./not-found.exception");
9
- Object.defineProperty(exports, "NotFoundException", { enumerable: true, get: function () { return not_found_exception_1.NotFoundException; } });
10
- var unauthorized_exception_1 = require("./unauthorized.exception");
11
- Object.defineProperty(exports, "UnauthorizedException", { enumerable: true, get: function () { return unauthorized_exception_1.UnauthorizedException; } });
12
- var unprocessable_entity_exception_1 = require("./unprocessable-entity.exception");
13
- Object.defineProperty(exports, "UnprocessableEntityException", { enumerable: true, get: function () { return unprocessable_entity_exception_1.UnprocessableEntityException; } });
14
- var oauth_exception_1 = require("./oauth.exception");
15
- Object.defineProperty(exports, "OauthException", { enumerable: true, get: function () { return oauth_exception_1.OauthException; } });
13
+ __exportStar(require("./generic-server.exception"), exports);
14
+ __exportStar(require("./no-api-key-provided.exception"), exports);
15
+ __exportStar(require("./not-found.exception"), exports);
16
+ __exportStar(require("./oauth.exception"), exports);
17
+ __exportStar(require("./signature-verification.exception"), exports);
18
+ __exportStar(require("./unauthorized.exception"), exports);
19
+ __exportStar(require("./unprocessable-entity.exception"), exports);
@@ -0,0 +1,4 @@
1
+ export declare class SignatureVerificationException extends Error {
2
+ readonly name: string;
3
+ constructor(message: string);
4
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SignatureVerificationException = void 0;
4
+ class SignatureVerificationException extends Error {
5
+ constructor(message) {
6
+ super(message || 'Signature verification failed.');
7
+ this.name = 'SignatureVerificationException';
8
+ }
9
+ }
10
+ exports.SignatureVerificationException = SignatureVerificationException;
@@ -11,7 +11,7 @@ class UnprocessableEntityException extends Error {
11
11
  this.requestID = requestID;
12
12
  this.status = 422;
13
13
  this.name = 'UnprocessableEntityException';
14
- const requirement = pluralize_1.default('requirement', errors.length);
14
+ const requirement = (0, pluralize_1.default)('requirement', errors.length);
15
15
  this.message = `The following ${requirement} must be met:\n`;
16
16
  for (const { code } of errors) {
17
17
  this.message = this.message.concat(`\t${code}\n`);
package/lib/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { WorkOS } from './workos';
2
2
  export * from './audit-trail/interfaces';
3
+ export * from './common/exceptions';
3
4
  export * from './common/interfaces';
4
5
  export * from './directory-sync/interfaces';
5
6
  export * from './passwordless/interfaces';
6
7
  export * from './portal/interfaces';
7
8
  export * from './sso/interfaces';
9
+ export * from './webhooks/interfaces';
8
10
  export { WorkOS };
9
11
  export default WorkOS;
package/lib/index.js CHANGED
@@ -14,10 +14,12 @@ exports.WorkOS = void 0;
14
14
  const workos_1 = require("./workos");
15
15
  Object.defineProperty(exports, "WorkOS", { enumerable: true, get: function () { return workos_1.WorkOS; } });
16
16
  __exportStar(require("./audit-trail/interfaces"), exports);
17
+ __exportStar(require("./common/exceptions"), exports);
17
18
  __exportStar(require("./common/interfaces"), exports);
18
19
  __exportStar(require("./directory-sync/interfaces"), exports);
19
20
  __exportStar(require("./passwordless/interfaces"), exports);
20
21
  __exportStar(require("./portal/interfaces"), exports);
21
22
  __exportStar(require("./sso/interfaces"), exports);
23
+ __exportStar(require("./webhooks/interfaces"), exports);
22
24
  // tslint:disable-next-line:no-default-export
23
25
  exports.default = workos_1.WorkOS;
@@ -2,6 +2,7 @@
2
2
  "name": "Test Organization",
3
3
  "object": "organization",
4
4
  "id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
5
+ "allow_profiles_outside_organization": false,
5
6
  "domains": [
6
7
  {
7
8
  "domain": "example.com",
@@ -2,6 +2,7 @@
2
2
  "name": "Test Organization 3",
3
3
  "object": "organization",
4
4
  "id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
5
+ "allow_profiles_outside_organization": false,
5
6
  "domains": [
6
7
  {
7
8
  "domain": "example.com",
@@ -5,6 +5,7 @@
5
5
  "object": "organization",
6
6
  "id": "org_01EHQMYV6MBK39QC5PZXHY59C3",
7
7
  "name": "example.com",
8
+ "allow_profiles_outside_organization": false,
8
9
  "domains": [
9
10
  {
10
11
  "object": "organization_domain",
@@ -17,6 +18,7 @@
17
18
  "object": "organization",
18
19
  "id": "org_01EHQMVDTC2GRAHFCCRNTSKH46",
19
20
  "name": "example2.com",
21
+ "allow_profiles_outside_organization": false,
20
22
  "domains": [
21
23
  {
22
24
  "object": "organization_domain",
@@ -29,6 +31,7 @@
29
31
  "object": "organization",
30
32
  "id": "org_01EGS4P7QR31EZ4YWD1Z1XA176",
31
33
  "name": "foo-corp.com",
34
+ "allow_profiles_outside_organization": false,
32
35
  "domains": [
33
36
  {
34
37
  "object": "organization_domain",
@@ -41,6 +44,7 @@
41
44
  "object": "organization",
42
45
  "id": "org_01EGPYFYM0Y66AHNYQG9Z63DTN",
43
46
  "name": "example3.com",
47
+ "allow_profiles_outside_organization": false,
44
48
  "domains": [
45
49
  {
46
50
  "object": "organization_domain",
@@ -53,6 +57,7 @@
53
57
  "object": "organization",
54
58
  "id": "org_01EGPJWMT2EQMK7FMPR3TBC861",
55
59
  "name": "workos.com",
60
+ "allow_profiles_outside_organization": false,
56
61
  "domains": [
57
62
  {
58
63
  "object": "organization_domain",
@@ -65,6 +70,7 @@
65
70
  "object": "organization",
66
71
  "id": "org_01EGPJ5YY5P897573GJFGGY7ZF",
67
72
  "name": "example4.com",
73
+ "allow_profiles_outside_organization": false,
68
74
  "domains": [
69
75
  {
70
76
  "object": "organization_domain",
@@ -77,6 +83,7 @@
77
83
  "object": "organization",
78
84
  "id": "org_01EGP9Z6RY2J6YE0ZV57CGEXV2",
79
85
  "name": "example5.com",
86
+ "allow_profiles_outside_organization": false,
80
87
  "domains": [
81
88
  {
82
89
  "object": "organization_domain",
@@ -2,6 +2,7 @@
2
2
  "name": "Test Organization 2",
3
3
  "object": "organization",
4
4
  "id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
5
+ "allow_profiles_outside_organization": false,
5
6
  "domains": [
6
7
  {
7
8
  "domain": "example.com",
@@ -0,0 +1,5 @@
1
+ export interface CreateOrganizationOptions {
2
+ name: string;
3
+ allow_profiles_outside_organization?: boolean;
4
+ domains?: string[];
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,3 +1,4 @@
1
+ export * from './create-organization-options.interface';
1
2
  export * from './list-organizations-options.interface';
2
3
  export * from './organization-domain.interface';
3
4
  export * from './organization.interface';
@@ -10,6 +10,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
10
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
+ __exportStar(require("./create-organization-options.interface"), exports);
13
14
  __exportStar(require("./list-organizations-options.interface"), exports);
14
15
  __exportStar(require("./organization-domain.interface"), exports);
15
16
  __exportStar(require("./organization.interface"), exports);
@@ -3,6 +3,7 @@ export interface Organization {
3
3
  object: 'organization';
4
4
  id: string;
5
5
  name: string;
6
+ allow_profiles_outside_organization: boolean;
6
7
  domains: OrganizationDomain[];
7
8
  created_at: string;
8
9
  updated_at: string;
@@ -1,5 +1,6 @@
1
1
  export interface UpdateOrganizationOptions {
2
2
  organization: string;
3
3
  name: string;
4
+ allow_profiles_outside_organization?: boolean;
4
5
  domains?: string[];
5
6
  }
@@ -1,14 +1,11 @@
1
1
  import { List } from '../common/interfaces/list.interface';
2
2
  import { WorkOS } from '../workos';
3
- import { ListOrganizationsOptions, Organization, UpdateOrganizationOptions } from './interfaces';
3
+ import { CreateOrganizationOptions, ListOrganizationsOptions, Organization, UpdateOrganizationOptions } from './interfaces';
4
4
  export declare class Organizations {
5
5
  private readonly workos;
6
6
  constructor(workos: WorkOS);
7
7
  listOrganizations(options?: ListOrganizationsOptions): Promise<List<Organization>>;
8
- createOrganization({ domains, name, }: {
9
- domains?: string[];
10
- name: string;
11
- }): Promise<Organization>;
8
+ createOrganization(payload: CreateOrganizationOptions): Promise<Organization>;
12
9
  deleteOrganization(id: string): Promise<void>;
13
10
  getOrganization(id: string): Promise<Organization>;
14
11
  updateOrganization(options: UpdateOrganizationOptions): Promise<Organization>;
@@ -33,12 +33,9 @@ class Organizations {
33
33
  return data;
34
34
  });
35
35
  }
36
- createOrganization({ domains, name, }) {
36
+ createOrganization(payload) {
37
37
  return __awaiter(this, void 0, void 0, function* () {
38
- const { data } = yield this.workos.post('/organizations', {
39
- domains,
40
- name,
41
- });
38
+ const { data } = yield this.workos.post('/organizations', payload);
42
39
  return data;
43
40
  });
44
41
  }
@@ -109,15 +109,10 @@ describe('Organizations', () => {
109
109
  mock.onPost().reply(409, create_organization_invalid_json_1.default, {
110
110
  'X-Request-ID': 'a-request-id',
111
111
  });
112
- try {
113
- yield workos.organizations.createOrganization({
114
- domains: ['example.com'],
115
- name: 'Test Organization',
116
- });
117
- }
118
- catch (error) {
119
- expect(error.message).toEqual('An Organization with the domain example.com already exists.');
120
- }
112
+ yield expect(workos.organizations.createOrganization({
113
+ domains: ['example.com'],
114
+ name: 'Test Organization',
115
+ })).rejects.toThrowError('An Organization with the domain example.com already exists.');
121
116
  }));
122
117
  });
123
118
  });
@@ -130,6 +125,7 @@ describe('Organizations', () => {
130
125
  expect(mock.history.get[0].url).toEqual('/organizations/org_01EHT88Z8J8795GZNQ4ZP1J81T');
131
126
  expect(subject.id).toEqual('org_01EHT88Z8J8795GZNQ4ZP1J81T');
132
127
  expect(subject.name).toEqual('Test Organization 3');
128
+ expect(subject.allow_profiles_outside_organization).toEqual(false);
133
129
  expect(subject.domains).toHaveLength(1);
134
130
  }));
135
131
  });
@@ -52,16 +52,11 @@ describe('Portal', () => {
52
52
  mock.onPost().reply(400, generate_link_invalid_json_1.default, {
53
53
  'X-Request-ID': 'a-request-id',
54
54
  });
55
- try {
56
- yield workos.portal.generateLink({
57
- intent: generate_portal_link_intent_interface_1.GeneratePortalLinkIntent.SSO,
58
- organization: 'bogus-id',
59
- returnUrl: 'https://www.example.com',
60
- });
61
- }
62
- catch (error) {
63
- expect(error.message).toEqual('Could not find an organization with the id, bogus-id.');
64
- }
55
+ yield expect(workos.portal.generateLink({
56
+ intent: generate_portal_link_intent_interface_1.GeneratePortalLinkIntent.SSO,
57
+ organization: 'bogus-id',
58
+ returnUrl: 'https://www.example.com',
59
+ })).rejects.toThrowError('Could not find an organization with the id, bogus-id.');
65
60
  }));
66
61
  });
67
62
  });
@@ -1,5 +1,6 @@
1
1
  export declare enum ConnectionType {
2
2
  ADFSSAML = "ADFSSAML",
3
+ Auth0SAML = "Auth0SAML",
3
4
  AzureSAML = "AzureSAML",
4
5
  GenericOIDC = "GenericOIDC",
5
6
  GenericSAML = "GenericSAML",
@@ -4,6 +4,7 @@ exports.ConnectionType = void 0;
4
4
  var ConnectionType;
5
5
  (function (ConnectionType) {
6
6
  ConnectionType["ADFSSAML"] = "ADFSSAML";
7
+ ConnectionType["Auth0SAML"] = "Auth0SAML";
7
8
  ConnectionType["AzureSAML"] = "AzureSAML";
8
9
  ConnectionType["GenericOIDC"] = "GenericOIDC";
9
10
  ConnectionType["GenericSAML"] = "GenericSAML";
@@ -2,4 +2,5 @@ export * from './authorization-url-options.interface';
2
2
  export * from './connection-type.enum';
3
3
  export * from './connection.interface';
4
4
  export * from './get-profile-and-token-options.interface';
5
+ export * from './profile-and-token.interface';
5
6
  export * from './profile.interface';
@@ -14,4 +14,5 @@ __exportStar(require("./authorization-url-options.interface"), exports);
14
14
  __exportStar(require("./connection-type.enum"), exports);
15
15
  __exportStar(require("./connection.interface"), exports);
16
16
  __exportStar(require("./get-profile-and-token-options.interface"), exports);
17
+ __exportStar(require("./profile-and-token.interface"), exports);
17
18
  __exportStar(require("./profile.interface"), exports);
@@ -2,6 +2,7 @@ import { ConnectionType } from './connection-type.enum';
2
2
  export interface Profile {
3
3
  id: string;
4
4
  idp_id: string;
5
+ organization_id: string;
5
6
  connection_id: string;
6
7
  connection_type: ConnectionType;
7
8
  email: string;
@@ -100,6 +100,7 @@ describe('SSO', () => {
100
100
  profile: {
101
101
  id: 'prof_123',
102
102
  idp_id: '123',
103
+ organization_id: 'org_123',
103
104
  connection_id: 'conn_123',
104
105
  connection_type: 'OktaSAML',
105
106
  email: 'foo@test.com',
@@ -132,6 +133,7 @@ describe('SSO', () => {
132
133
  mock.onGet().reply(200, {
133
134
  id: 'prof_123',
134
135
  idp_id: '123',
136
+ organization_id: 'org_123',
135
137
  connection_id: 'conn_123',
136
138
  connection_type: 'OktaSAML',
137
139
  email: 'foo@test.com',
@@ -0,0 +1 @@
1
+ { "id": "wh_123", "data": { "id": "directory_user_01FAEAJCR3ZBZ30D8BD1924TVG", "state": "active", "emails": [{ "type": "work", "value": "blair@foo-corp.com", "primary": true }], "idp_id": "00u1e8mutl6wlH3lL4x7", "object": "directory_user", "username": "blair@foo-corp.com", "last_name": "Lunceford", "first_name": "Blair", "directory_id": "directory_01F9M7F68PZP8QXP8G7X5QRHS7", "raw_attributes": { "name": { "givenName": "Blair", "familyName": "Lunceford", "middleName": "Elizabeth", "honorificPrefix": "Ms." }, "title": "Developer Success Engineer", "active": true, "emails": [{ "type": "work", "value": "blair@foo-corp.com", "primary": true }], "groups": [], "locale": "en-US", "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User", "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"], "userName": "blair@foo-corp.com", "addresses": [{ "region": "CO", "primary": true, "locality": "Steamboat Springs", "postalCode": "80487" }], "externalId": "00u1e8mutl6wlH3lL4x7", "displayName": "Blair Lunceford", "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": { "manager": { "value": "2", "displayName": "Kathleen Chung" }, "division": "Engineering", "department": "Customer Success" } } }, "event": "dsync.user.created" }
@@ -0,0 +1 @@
1
+ export * from './webhook.interface';
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ __exportStar(require("./webhook.interface"), exports);
@@ -0,0 +1,5 @@
1
+ export interface Webhook {
2
+ id: string;
3
+ event: string;
4
+ data: Record<string, any>;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,13 @@
1
+ import { Webhook } from './interfaces/webhook.interface';
2
+ export declare class Webhooks {
3
+ constructEvent({ payload, sigHeader, secret, tolerance, }: {
4
+ payload: unknown;
5
+ sigHeader: string;
6
+ secret: string;
7
+ tolerance?: number;
8
+ }): Webhook;
9
+ private verifyHeader;
10
+ private getTimestampAndSignatureHash;
11
+ private computeSignature;
12
+ private secureCompare;
13
+ }
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Webhooks = void 0;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ const exceptions_1 = require("../common/exceptions");
9
+ class Webhooks {
10
+ constructEvent({ payload, sigHeader, secret, tolerance = 180, }) {
11
+ const options = { payload, sigHeader, secret, tolerance };
12
+ this.verifyHeader(options);
13
+ const webhookPayload = payload;
14
+ return webhookPayload;
15
+ }
16
+ verifyHeader({ payload, sigHeader, secret, tolerance = 180, }) {
17
+ const [timestamp, signatureHash] = this.getTimestampAndSignatureHash(sigHeader);
18
+ if (!signatureHash || Object.keys(signatureHash).length === 0) {
19
+ throw new exceptions_1.SignatureVerificationException('No signature hash found with expected scheme v1');
20
+ }
21
+ if (parseInt(timestamp, 10) < Date.now() - tolerance) {
22
+ throw new exceptions_1.SignatureVerificationException('Timestamp outside the tolerance zone');
23
+ }
24
+ const expectedSig = this.computeSignature(timestamp, payload, secret);
25
+ if (this.secureCompare(expectedSig, signatureHash) === false) {
26
+ throw new exceptions_1.SignatureVerificationException('Signature hash does not match the expected signature hash for payload');
27
+ }
28
+ return true;
29
+ }
30
+ getTimestampAndSignatureHash(sigHeader) {
31
+ const signature = sigHeader;
32
+ const [t, v1] = signature.split(',');
33
+ if (typeof t === 'undefined' || typeof v1 === 'undefined') {
34
+ throw new exceptions_1.SignatureVerificationException('Signature or timestamp missing');
35
+ }
36
+ const { 1: timestamp } = t.split('=');
37
+ const { 1: signatureHash } = v1.split('=');
38
+ return [timestamp, signatureHash];
39
+ }
40
+ computeSignature(timestamp, payload, secret) {
41
+ const signedPayload = `${timestamp}.${payload}`;
42
+ const expectedSignature = crypto_1.default
43
+ .createHmac('sha256', secret)
44
+ .update(signedPayload)
45
+ .digest()
46
+ .toString('hex');
47
+ return expectedSignature;
48
+ }
49
+ secureCompare(stringA, stringB) {
50
+ const strA = Buffer.from(stringA);
51
+ const strB = Buffer.from(stringB);
52
+ if (strA.length !== strB.length) {
53
+ return false;
54
+ }
55
+ if (crypto_1.default.timingSafeEqual) {
56
+ return crypto_1.default.timingSafeEqual(strA, strB);
57
+ }
58
+ const len = strA.length;
59
+ let result = 0;
60
+ for (let i = 0; i < len; ++i) {
61
+ // tslint:disable-next-line:no-bitwise
62
+ result |= strA[i] ^ strB[i];
63
+ }
64
+ return result === 0;
65
+ }
66
+ }
67
+ exports.Webhooks = Webhooks;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const crypto_1 = __importDefault(require("crypto"));
7
+ const workos_1 = require("../workos");
8
+ const webhook_json_1 = __importDefault(require("./fixtures/webhook.json"));
9
+ const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
10
+ const exceptions_1 = require("../common/exceptions");
11
+ describe('Webhooks', () => {
12
+ let payload;
13
+ let secret;
14
+ let timestamp;
15
+ let unhashedString;
16
+ let signatureHash;
17
+ let expectation;
18
+ beforeEach(() => {
19
+ payload = webhook_json_1.default;
20
+ secret = 'secret';
21
+ timestamp = Date.now() * 1000;
22
+ unhashedString = `${timestamp}.${payload}`;
23
+ signatureHash = crypto_1.default
24
+ .createHmac('sha256', secret)
25
+ .update(unhashedString)
26
+ .digest()
27
+ .toString('hex');
28
+ expectation = {
29
+ id: 'directory_user_01FAEAJCR3ZBZ30D8BD1924TVG',
30
+ state: 'active',
31
+ emails: [
32
+ {
33
+ type: 'work',
34
+ value: 'blair@foo-corp.com',
35
+ primary: true,
36
+ },
37
+ ],
38
+ idp_id: '00u1e8mutl6wlH3lL4x7',
39
+ object: 'directory_user',
40
+ username: 'blair@foo-corp.com',
41
+ last_name: 'Lunceford',
42
+ first_name: 'Blair',
43
+ directory_id: 'directory_01F9M7F68PZP8QXP8G7X5QRHS7',
44
+ raw_attributes: {
45
+ name: {
46
+ givenName: 'Blair',
47
+ familyName: 'Lunceford',
48
+ middleName: 'Elizabeth',
49
+ honorificPrefix: 'Ms.',
50
+ },
51
+ title: 'Developer Success Engineer',
52
+ active: true,
53
+ emails: [
54
+ {
55
+ type: 'work',
56
+ value: 'blair@foo-corp.com',
57
+ primary: true,
58
+ },
59
+ ],
60
+ groups: [],
61
+ locale: 'en-US',
62
+ schemas: [
63
+ 'urn:ietf:params:scim:schemas:core:2.0:User',
64
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
65
+ ],
66
+ userName: 'blair@foo-corp.com',
67
+ addresses: [
68
+ {
69
+ region: 'CO',
70
+ primary: true,
71
+ locality: 'Steamboat Springs',
72
+ postalCode: '80487',
73
+ },
74
+ ],
75
+ externalId: '00u1e8mutl6wlH3lL4x7',
76
+ displayName: 'Blair Lunceford',
77
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User': {
78
+ manager: {
79
+ value: '2',
80
+ displayName: 'Kathleen Chung',
81
+ },
82
+ division: 'Engineering',
83
+ department: 'Customer Success',
84
+ },
85
+ },
86
+ };
87
+ });
88
+ describe('constructEvent', () => {
89
+ describe('with the correct payload, sig_header, and secret', () => {
90
+ it('returns a webhook event', () => {
91
+ const sigHeader = `t=${timestamp}, v1=${signatureHash}`;
92
+ const options = { payload, sigHeader, secret };
93
+ const webhook = workos.webhooks.constructEvent(options);
94
+ expect(webhook.data).toEqual(expectation);
95
+ expect(webhook.event).toEqual('dsync.user.created');
96
+ expect(webhook.id).toEqual('wh_123');
97
+ });
98
+ });
99
+ describe('with the correct payload, sig_header, secret, and tolerance', () => {
100
+ it('returns a webhook event', () => {
101
+ const sigHeader = `t=${timestamp}, v1=${signatureHash}`;
102
+ const options = { payload, sigHeader, secret, tolerance: 200 };
103
+ const webhook = workos.webhooks.constructEvent(options);
104
+ expect(webhook.data).toEqual(expectation);
105
+ expect(webhook.event).toEqual('dsync.user.created');
106
+ expect(webhook.id).toEqual('wh_123');
107
+ });
108
+ });
109
+ describe('with an empty header', () => {
110
+ it('raises an error', () => {
111
+ const sigHeader = '';
112
+ const options = { payload, sigHeader, secret };
113
+ expect(() => workos.webhooks.constructEvent(options)).toThrowError(exceptions_1.SignatureVerificationException);
114
+ });
115
+ });
116
+ describe('with an empty signature hash', () => {
117
+ it('raises an error', () => {
118
+ const sigHeader = `t=${timestamp}, v1=`;
119
+ const options = { payload, sigHeader, secret };
120
+ expect(() => workos.webhooks.constructEvent(options)).toThrowError(exceptions_1.SignatureVerificationException);
121
+ });
122
+ });
123
+ describe('with an incorrect signature hash', () => {
124
+ it('raises an error', () => {
125
+ const sigHeader = `t=${timestamp}, v1=99999`;
126
+ const options = { payload, sigHeader, secret };
127
+ expect(() => workos.webhooks.constructEvent(options)).toThrowError(exceptions_1.SignatureVerificationException);
128
+ });
129
+ });
130
+ describe('with an incorrect payload', () => {
131
+ it('raises an error', () => {
132
+ const sigHeader = `t=${timestamp}, v1=${signatureHash}`;
133
+ payload = 'invalid';
134
+ const options = { payload, sigHeader, secret };
135
+ expect(() => workos.webhooks.constructEvent(options)).toThrowError(exceptions_1.SignatureVerificationException);
136
+ });
137
+ });
138
+ describe('with an incorrect webhook secret', () => {
139
+ it('raises an error', () => {
140
+ const sigHeader = `t=${timestamp}, v1=${signatureHash}`;
141
+ secret = 'invalid';
142
+ const options = { payload, sigHeader, secret };
143
+ expect(() => workos.webhooks.constructEvent(options)).toThrowError(exceptions_1.SignatureVerificationException);
144
+ });
145
+ });
146
+ describe('with a timestamp outside tolerance', () => {
147
+ it('raises an error', () => {
148
+ const sigHeader = `t=9999, v1=${signatureHash}`;
149
+ const options = { payload, sigHeader, secret };
150
+ expect(() => workos.webhooks.constructEvent(options)).toThrowError(exceptions_1.SignatureVerificationException);
151
+ });
152
+ });
153
+ });
154
+ });
package/lib/workos.d.ts CHANGED
@@ -6,6 +6,7 @@ import { Organizations } from './organizations/organizations';
6
6
  import { Passwordless } from './passwordless/passwordless';
7
7
  import { Portal } from './portal/portal';
8
8
  import { SSO } from './sso/sso';
9
+ import { Webhooks } from './webhooks/webhooks';
9
10
  export declare class WorkOS {
10
11
  readonly key?: string | undefined;
11
12
  readonly options: WorkOSOptions;
@@ -17,6 +18,7 @@ export declare class WorkOS {
17
18
  readonly passwordless: Passwordless;
18
19
  readonly portal: Portal;
19
20
  readonly sso: SSO;
21
+ readonly webhooks: Webhooks;
20
22
  constructor(key?: string | undefined, options?: WorkOSOptions);
21
23
  post(path: string, entity: any, options?: PostOptions): Promise<AxiosResponse>;
22
24
  get(path: string, options?: GetOptions): Promise<AxiosResponse>;
package/lib/workos.js CHANGED
@@ -21,7 +21,8 @@ const organizations_1 = require("./organizations/organizations");
21
21
  const passwordless_1 = require("./passwordless/passwordless");
22
22
  const portal_1 = require("./portal/portal");
23
23
  const sso_1 = require("./sso/sso");
24
- const VERSION = '1.3.0';
24
+ const webhooks_1 = require("./webhooks/webhooks");
25
+ const VERSION = '1.6.0';
25
26
  const DEFAULT_HOSTNAME = 'api.workos.com';
26
27
  class WorkOS {
27
28
  constructor(key, options = {}) {
@@ -33,6 +34,7 @@ class WorkOS {
33
34
  this.passwordless = new passwordless_1.Passwordless(this);
34
35
  this.portal = new portal_1.Portal(this);
35
36
  this.sso = new sso_1.SSO(this);
37
+ this.webhooks = new webhooks_1.Webhooks();
36
38
  if (!key) {
37
39
  this.key = process.env.WORKOS_API_KEY;
38
40
  if (!this.key) {
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.3.0",
2
+ "version": "1.6.0",
3
3
  "name": "@workos-inc/node",
4
4
  "author": "WorkOS",
5
5
  "description": "A Node wrapper for the WorkOS API",
@@ -9,8 +9,8 @@
9
9
  "workos"
10
10
  ],
11
11
  "volta": {
12
- "node": "14.17.5",
13
- "yarn": "1.22.11"
12
+ "node": "14.18.1",
13
+ "yarn": "1.22.17"
14
14
  },
15
15
  "main": "lib/index.js",
16
16
  "typings": "lib/index.d.ts",
@@ -34,20 +34,20 @@
34
34
  "prepublishOnly": "yarn run build"
35
35
  },
36
36
  "dependencies": {
37
- "axios": "0.21.1",
37
+ "axios": "0.21.4",
38
38
  "pluralize": "8.0.0",
39
39
  "query-string": "7.0.1"
40
40
  },
41
41
  "devDependencies": {
42
- "@types/jest": "26.0.24",
43
- "@types/node": "14.17.9",
42
+ "@types/jest": "27.0.2",
43
+ "@types/node": "14.17.32",
44
44
  "@types/pluralize": "0.0.29",
45
45
  "axios-mock-adapter": "1.20.0",
46
- "jest": "27.0.6",
47
- "prettier": "2.3.2",
48
- "supertest": "6.1.5",
49
- "ts-jest": "27.0.4",
46
+ "jest": "27.3.1",
47
+ "prettier": "2.4.1",
48
+ "supertest": "6.1.6",
49
+ "ts-jest": "27.0.7",
50
50
  "tslint": "6.1.3",
51
- "typescript": "4.3.5"
51
+ "typescript": "4.4.4"
52
52
  }
53
53
  }