@workos-inc/node 1.4.0 → 1.5.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.
@@ -4,3 +4,4 @@ export { NotFoundException } from './not-found.exception';
4
4
  export { UnauthorizedException } from './unauthorized.exception';
5
5
  export { UnprocessableEntityException } from './unprocessable-entity.exception';
6
6
  export { OauthException } from './oauth.exception';
7
+ export { SignatureVerificationException } from './signature-verification.exception';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OauthException = exports.UnprocessableEntityException = exports.UnauthorizedException = exports.NotFoundException = exports.NoApiKeyProvidedException = exports.GenericServerException = void 0;
3
+ exports.SignatureVerificationException = exports.OauthException = exports.UnprocessableEntityException = exports.UnauthorizedException = exports.NotFoundException = exports.NoApiKeyProvidedException = exports.GenericServerException = void 0;
4
4
  var generic_server_exception_1 = require("./generic-server.exception");
5
5
  Object.defineProperty(exports, "GenericServerException", { enumerable: true, get: function () { return generic_server_exception_1.GenericServerException; } });
6
6
  var no_api_key_provided_exception_1 = require("./no-api-key-provided.exception");
@@ -13,3 +13,5 @@ var unprocessable_entity_exception_1 = require("./unprocessable-entity.exception
13
13
  Object.defineProperty(exports, "UnprocessableEntityException", { enumerable: true, get: function () { return unprocessable_entity_exception_1.UnprocessableEntityException; } });
14
14
  var oauth_exception_1 = require("./oauth.exception");
15
15
  Object.defineProperty(exports, "OauthException", { enumerable: true, get: function () { return oauth_exception_1.OauthException; } });
16
+ var signature_verification_exception_1 = require("./signature-verification.exception");
17
+ Object.defineProperty(exports, "SignatureVerificationException", { enumerable: true, get: function () { return signature_verification_exception_1.SignatureVerificationException; } });
@@ -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;
package/lib/index.d.ts CHANGED
@@ -5,5 +5,6 @@ export * from './directory-sync/interfaces';
5
5
  export * from './passwordless/interfaces';
6
6
  export * from './portal/interfaces';
7
7
  export * from './sso/interfaces';
8
+ export * from './webhooks/interfaces';
8
9
  export { WorkOS };
9
10
  export default WorkOS;
package/lib/index.js CHANGED
@@ -19,5 +19,6 @@ __exportStar(require("./directory-sync/interfaces"), exports);
19
19
  __exportStar(require("./passwordless/interfaces"), exports);
20
20
  __exportStar(require("./portal/interfaces"), exports);
21
21
  __exportStar(require("./sso/interfaces"), exports);
22
+ __exportStar(require("./webhooks/interfaces"), exports);
22
23
  // tslint:disable-next-line:no-default-export
23
24
  exports.default = workos_1.WorkOS;
@@ -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.4.0';
24
+ const webhooks_1 = require("./webhooks/webhooks");
25
+ const VERSION = '1.5.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.4.0",
2
+ "version": "1.5.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.6",
13
- "yarn": "1.22.11"
12
+ "node": "14.18.1",
13
+ "yarn": "1.22.15"
14
14
  },
15
15
  "main": "lib/index.js",
16
16
  "typings": "lib/index.d.ts",
@@ -40,14 +40,14 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/jest": "27.0.2",
43
- "@types/node": "14.17.17",
43
+ "@types/node": "14.17.22",
44
44
  "@types/pluralize": "0.0.29",
45
45
  "axios-mock-adapter": "1.20.0",
46
- "jest": "27.2.1",
46
+ "jest": "27.2.5",
47
47
  "prettier": "2.4.1",
48
48
  "supertest": "6.1.6",
49
49
  "ts-jest": "27.0.5",
50
50
  "tslint": "6.1.3",
51
- "typescript": "4.4.3"
51
+ "typescript": "4.4.4"
52
52
  }
53
53
  }