@workos-inc/node 1.4.0 → 2.1.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.
- package/lib/audit-trail/audit-trail.spec.js +2 -2
- package/lib/common/exceptions/index.d.ts +7 -6
- package/lib/common/exceptions/index.js +17 -13
- package/lib/common/exceptions/signature-verification.exception.d.ts +4 -0
- package/lib/common/exceptions/signature-verification.exception.js +10 -0
- package/lib/common/interfaces/list.interface.d.ts +2 -1
- package/lib/directory-sync/directory-sync.d.ts +1 -0
- package/lib/directory-sync/directory-sync.js +6 -0
- package/lib/directory-sync/directory-sync.spec.js +8 -0
- package/lib/directory-sync/interfaces/directory.interface.d.ts +1 -1
- package/lib/directory-sync/interfaces/user.interface.d.ts +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/organizations/fixtures/list-organizations.json +1 -1
- package/lib/organizations/organizations.spec.js +1 -1
- package/lib/sso/interfaces/connection-type.enum.d.ts +1 -0
- package/lib/sso/interfaces/connection-type.enum.js +1 -0
- package/lib/sso/interfaces/connection.interface.d.ts +1 -1
- package/lib/sso/interfaces/profile.interface.d.ts +1 -1
- package/lib/webhooks/fixtures/webhook.json +1 -0
- package/lib/webhooks/interfaces/index.d.ts +2 -0
- package/lib/webhooks/interfaces/index.js +14 -0
- package/lib/webhooks/interfaces/webhook-directory.interface.d.ts +4 -0
- package/lib/webhooks/interfaces/webhook-directory.interface.js +2 -0
- package/lib/webhooks/interfaces/webhook.interface.d.ts +78 -0
- package/lib/webhooks/interfaces/webhook.interface.js +2 -0
- package/lib/webhooks/webhooks.d.ts +13 -0
- package/lib/webhooks/webhooks.js +67 -0
- package/lib/webhooks/webhooks.spec.d.ts +1 -0
- package/lib/webhooks/webhooks.spec.js +154 -0
- package/lib/workos.d.ts +2 -0
- package/lib/workos.js +3 -1
- package/package.json +7 -7
|
@@ -127,7 +127,7 @@ describe('AuditTrail', () => {
|
|
|
127
127
|
},
|
|
128
128
|
},
|
|
129
129
|
],
|
|
130
|
-
|
|
130
|
+
list_metadata: { before: null, after: null },
|
|
131
131
|
});
|
|
132
132
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
133
133
|
const events = yield workos.auditTrail.listEvents();
|
|
@@ -159,7 +159,7 @@ describe('AuditTrail', () => {
|
|
|
159
159
|
},
|
|
160
160
|
},
|
|
161
161
|
],
|
|
162
|
-
|
|
162
|
+
list_metadata: { before: null, after: null },
|
|
163
163
|
});
|
|
164
164
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
165
165
|
const events = yield workos.auditTrail.listEvents({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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,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;
|
|
@@ -10,6 +10,7 @@ export declare class DirectorySync {
|
|
|
10
10
|
private readonly workos;
|
|
11
11
|
constructor(workos: WorkOS);
|
|
12
12
|
listDirectories(options?: ListDirectoriesOptions): Promise<List<Directory>>;
|
|
13
|
+
getDirectory(id: string): Promise<Directory>;
|
|
13
14
|
deleteDirectory(id: string): Promise<void>;
|
|
14
15
|
listGroups(options: ListGroupsOptions): Promise<List<Group>>;
|
|
15
16
|
listUsers<TCustomAttributes extends object = DefaultCustomAttributes>(options: ListUsersOptions): Promise<List<UserWithGroups<TCustomAttributes>>>;
|
|
@@ -22,6 +22,12 @@ class DirectorySync {
|
|
|
22
22
|
return data;
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
|
+
getDirectory(id) {
|
|
26
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
const { data } = yield this.workos.get(`/directories/${id}`);
|
|
28
|
+
return data;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
25
31
|
deleteDirectory(id) {
|
|
26
32
|
return __awaiter(this, void 0, void 0, function* () {
|
|
27
33
|
yield this.workos.delete(`/directories/${id}`);
|
|
@@ -33,6 +33,14 @@ describe('DirectorySync', () => {
|
|
|
33
33
|
}));
|
|
34
34
|
});
|
|
35
35
|
});
|
|
36
|
+
describe('getDirectory', () => {
|
|
37
|
+
it(`requests a Directory`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
|
+
mock.onGet().reply(200, {});
|
|
39
|
+
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
40
|
+
yield workos.directorySync.getDirectory('directory_123');
|
|
41
|
+
expect(mock.history.get[0].url).toEqual('/directories/directory_123');
|
|
42
|
+
}));
|
|
43
|
+
});
|
|
36
44
|
describe('deleteDirectory', () => {
|
|
37
45
|
it('sends a request to delete the directory', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
46
|
mock.onDelete().reply(202, {});
|
|
@@ -13,7 +13,7 @@ export interface User<TCustomAttributes extends object = DefaultCustomAttributes
|
|
|
13
13
|
}[];
|
|
14
14
|
username: string;
|
|
15
15
|
last_name: string;
|
|
16
|
-
state: 'active' | 'suspended';
|
|
16
|
+
state: 'active' | 'inactive' | 'suspended';
|
|
17
17
|
}
|
|
18
18
|
export interface UserWithGroups<TCustomAttributes extends object = DefaultCustomAttributes> extends User<TCustomAttributes> {
|
|
19
19
|
groups: Group[];
|
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;
|
|
@@ -28,7 +28,7 @@ describe('Organizations', () => {
|
|
|
28
28
|
describe('without any options', () => {
|
|
29
29
|
it('returns organizations and metadata', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
30
30
|
mock.onGet().reply(200, list_organizations_json_1.default);
|
|
31
|
-
const { data, listMetadata } = yield workos.organizations.listOrganizations();
|
|
31
|
+
const { data, list_metadata: listMetadata } = yield workos.organizations.listOrganizations();
|
|
32
32
|
expect(mock.history.get[0].params).toBeUndefined();
|
|
33
33
|
expect(mock.history.get[0].url).toEqual('/organizations');
|
|
34
34
|
expect(data).toHaveLength(7);
|
|
@@ -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";
|
|
@@ -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,14 @@
|
|
|
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);
|
|
14
|
+
__exportStar(require("./webhook-directory.interface"), exports);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Group, User } from '../../directory-sync/interfaces';
|
|
2
|
+
import { Connection } from '../../sso/interfaces';
|
|
3
|
+
import { WebhookDirectory as Directory } from './webhook-directory.interface';
|
|
4
|
+
interface WebhookBase {
|
|
5
|
+
id: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ConnectionActivatedWebhook extends WebhookBase {
|
|
8
|
+
event: 'connection.activated';
|
|
9
|
+
data: Connection;
|
|
10
|
+
}
|
|
11
|
+
export interface ConnectionDeactivatedWebhook extends WebhookBase {
|
|
12
|
+
event: 'connection.deactivated';
|
|
13
|
+
data: Connection;
|
|
14
|
+
}
|
|
15
|
+
export interface ConnectionDeletedWebhook extends WebhookBase {
|
|
16
|
+
event: 'connection.deleted';
|
|
17
|
+
data: Connection;
|
|
18
|
+
}
|
|
19
|
+
export interface DsyncActivatedWebhook extends WebhookBase {
|
|
20
|
+
event: 'dsync.activated';
|
|
21
|
+
data: Directory;
|
|
22
|
+
}
|
|
23
|
+
export interface DsyncDeactivatedWebhook extends WebhookBase {
|
|
24
|
+
event: 'dsync.deactivated';
|
|
25
|
+
data: Directory;
|
|
26
|
+
}
|
|
27
|
+
export interface DsyncDeletedWebhook extends WebhookBase {
|
|
28
|
+
event: 'dsync.deleted';
|
|
29
|
+
data: Directory;
|
|
30
|
+
}
|
|
31
|
+
export interface DsyncGroupCreatedWebhook extends WebhookBase {
|
|
32
|
+
event: 'dsync.group.created';
|
|
33
|
+
data: Group & {
|
|
34
|
+
directory_id: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export interface DsyncGroupDeletedWebhook extends WebhookBase {
|
|
38
|
+
event: 'dsync.group.deleted';
|
|
39
|
+
data: Group & {
|
|
40
|
+
directory_id: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface DsyncGroupUpdatedWebhook extends WebhookBase {
|
|
44
|
+
event: 'dsync.group.updated';
|
|
45
|
+
data: Group & {
|
|
46
|
+
directory_id: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export interface DsyncGroupUserAddedWebhook extends WebhookBase {
|
|
50
|
+
event: 'dsync.group.user_added';
|
|
51
|
+
data: {
|
|
52
|
+
directory_id: string;
|
|
53
|
+
user: User;
|
|
54
|
+
group: Group;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export interface DsyncGroupUserRemovedWebhook extends WebhookBase {
|
|
58
|
+
event: 'dsync.group.user_removed';
|
|
59
|
+
data: {
|
|
60
|
+
directory_id: string;
|
|
61
|
+
user: User;
|
|
62
|
+
group: Group;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export interface DsyncUserCreatedWebhook extends WebhookBase {
|
|
66
|
+
event: 'dsync.user.created';
|
|
67
|
+
data: User;
|
|
68
|
+
}
|
|
69
|
+
export interface DsyncUserDeletedWebhook extends WebhookBase {
|
|
70
|
+
event: 'dsync.user.deleted';
|
|
71
|
+
data: User;
|
|
72
|
+
}
|
|
73
|
+
export interface DsyncUserUpdatedWebhook extends WebhookBase {
|
|
74
|
+
event: 'dsync.user.updated';
|
|
75
|
+
data: User;
|
|
76
|
+
}
|
|
77
|
+
export declare type Webhook = ConnectionActivatedWebhook | ConnectionDeactivatedWebhook | ConnectionDeletedWebhook | DsyncActivatedWebhook | DsyncDeactivatedWebhook | DsyncDeletedWebhook | DsyncGroupCreatedWebhook | DsyncGroupUpdatedWebhook | DsyncGroupDeletedWebhook | DsyncGroupUserAddedWebhook | DsyncGroupUserRemovedWebhook | DsyncUserCreatedWebhook | DsyncUserUpdatedWebhook | DsyncUserDeletedWebhook;
|
|
78
|
+
export {};
|
|
@@ -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
|
|
24
|
+
const webhooks_1 = require("./webhooks/webhooks");
|
|
25
|
+
const VERSION = '2.1.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.
|
|
2
|
+
"version": "2.1.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.
|
|
13
|
-
"yarn": "1.22.
|
|
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",
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/jest": "27.0.2",
|
|
43
|
-
"@types/node": "14.17.
|
|
43
|
+
"@types/node": "14.17.32",
|
|
44
44
|
"@types/pluralize": "0.0.29",
|
|
45
45
|
"axios-mock-adapter": "1.20.0",
|
|
46
|
-
"jest": "27.
|
|
46
|
+
"jest": "27.3.1",
|
|
47
47
|
"prettier": "2.4.1",
|
|
48
48
|
"supertest": "6.1.6",
|
|
49
|
-
"ts-jest": "27.0.
|
|
49
|
+
"ts-jest": "27.0.7",
|
|
50
50
|
"tslint": "6.1.3",
|
|
51
|
-
"typescript": "4.4.
|
|
51
|
+
"typescript": "4.4.4"
|
|
52
52
|
}
|
|
53
53
|
}
|