@workos-inc/node 7.3.0 → 7.5.0-beta.node-compatibility
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/README.md +4 -0
- package/lib/common/crypto/CryptoProvider.d.ts +32 -0
- package/lib/common/crypto/CryptoProvider.js +13 -0
- package/lib/common/crypto/NodeCryptoProvider.d.ts +12 -0
- package/lib/common/crypto/NodeCryptoProvider.js +73 -0
- package/lib/common/crypto/SubtleCryptoProvider.d.ts +15 -0
- package/lib/common/crypto/SubtleCryptoProvider.js +75 -0
- package/lib/common/crypto/index.d.ts +3 -0
- package/lib/common/crypto/index.js +19 -0
- package/lib/common/exceptions/bad-request.exception.d.ts +4 -3
- package/lib/common/exceptions/generic-server.exception.d.ts +2 -1
- package/lib/common/exceptions/index.d.ts +1 -0
- package/lib/common/exceptions/index.js +1 -0
- package/lib/common/exceptions/no-api-key-provided.exception.d.ts +2 -2
- package/lib/common/exceptions/not-found.exception.d.ts +4 -3
- package/lib/common/exceptions/oauth.exception.d.ts +3 -2
- package/lib/common/exceptions/rate-limit-exceeded.exception.d.ts +13 -0
- package/lib/common/exceptions/rate-limit-exceeded.exception.js +20 -0
- package/lib/common/exceptions/signature-verification.exception.d.ts +1 -1
- package/lib/common/exceptions/unauthorized.exception.d.ts +4 -3
- package/lib/common/exceptions/unprocessable-entity.exception.d.ts +4 -3
- package/lib/common/interfaces/event.interface.d.ts +1 -1
- package/lib/common/interfaces/http-client.interface.d.ts +20 -0
- package/lib/common/interfaces/index.d.ts +1 -0
- package/lib/common/interfaces/index.js +1 -0
- package/lib/common/interfaces/request-exception.interface.d.ts +5 -0
- package/lib/common/interfaces/request-exception.interface.js +2 -0
- package/lib/common/interfaces/workos-options.interface.d.ts +1 -0
- package/lib/common/net/fetch-client.d.ts +22 -0
- package/lib/common/net/fetch-client.js +112 -0
- package/lib/common/net/http-client.d.ts +39 -0
- package/lib/common/net/http-client.js +76 -0
- package/lib/common/net/index.d.ts +5 -0
- package/lib/common/net/index.js +31 -0
- package/lib/common/net/node-client.d.ts +23 -0
- package/lib/common/net/node-client.js +155 -0
- package/lib/directory-sync/directory-sync.spec.js +61 -0
- package/lib/directory-sync/interfaces/directory-user.interface.d.ts +3 -0
- package/lib/directory-sync/serializers/directory-user.serializer.js +2 -0
- package/lib/events/events.spec.js +88 -0
- package/lib/roles/interfaces/index.d.ts +1 -0
- package/lib/roles/interfaces/index.js +17 -0
- package/lib/roles/interfaces/role.interface.js +2 -0
- package/lib/user-management/fixtures/deactivate-organization-membership.json +12 -0
- package/lib/user-management/fixtures/invitation.json +1 -0
- package/lib/user-management/fixtures/list-invitations.json +1 -0
- package/lib/user-management/interfaces/index.d.ts +0 -2
- package/lib/user-management/interfaces/index.js +0 -2
- package/lib/user-management/interfaces/invitation.interface.d.ts +4 -0
- package/lib/user-management/interfaces/list-organization-memberships-options.interface.d.ts +3 -0
- package/lib/user-management/interfaces/organization-membership.interface.d.ts +4 -3
- package/lib/user-management/serializers/invitation.serializer.js +2 -0
- package/lib/user-management/serializers/list-organization-memberships-options.serializer.js +12 -8
- package/lib/user-management/serializers/role.serializer.d.ts +1 -1
- package/lib/user-management/user-management.d.ts +2 -0
- package/lib/user-management/user-management.js +12 -0
- package/lib/user-management/user-management.spec.js +40 -5
- package/lib/webhooks/webhooks.d.ts +2 -2
- package/lib/webhooks/webhooks.js +11 -37
- package/lib/webhooks/webhooks.spec.js +29 -0
- package/lib/workos.d.ts +1 -1
- package/lib/workos.js +22 -12
- package/lib/workos.spec.js +69 -3
- package/package.json +2 -3
- package/lib/common/utils/fetch-client.d.ts +0 -31
- package/lib/common/utils/fetch-client.js +0 -108
- /package/lib/{user-management/interfaces/role.interface.js → common/interfaces/http-client.interface.js} +0 -0
- /package/lib/{user-management → roles}/interfaces/role.interface.d.ts +0 -0
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { PaginationOptions } from '../../common/interfaces';
|
|
2
|
+
import { OrganizationMembershipStatus } from './organization-membership.interface';
|
|
2
3
|
export interface ListOrganizationMembershipsOptions extends PaginationOptions {
|
|
3
4
|
organizationId?: string;
|
|
4
5
|
userId?: string;
|
|
6
|
+
statuses?: OrganizationMembershipStatus[];
|
|
5
7
|
}
|
|
6
8
|
export interface SerializedListOrganizationMembershipsOptions extends PaginationOptions {
|
|
7
9
|
organization_id?: string;
|
|
8
10
|
user_id?: string;
|
|
11
|
+
statuses?: string;
|
|
9
12
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { RoleResponse } from '
|
|
1
|
+
import { RoleResponse } from '../../roles/interfaces/';
|
|
2
|
+
export type OrganizationMembershipStatus = 'active' | 'inactive' | 'pending';
|
|
2
3
|
export interface OrganizationMembership {
|
|
3
4
|
object: 'organization_membership';
|
|
4
5
|
id: string;
|
|
5
6
|
organizationId: string;
|
|
6
|
-
status:
|
|
7
|
+
status: OrganizationMembershipStatus;
|
|
7
8
|
userId: string;
|
|
8
9
|
createdAt: string;
|
|
9
10
|
updatedAt: string;
|
|
@@ -13,7 +14,7 @@ export interface OrganizationMembershipResponse {
|
|
|
13
14
|
object: 'organization_membership';
|
|
14
15
|
id: string;
|
|
15
16
|
organization_id: string;
|
|
16
|
-
status:
|
|
17
|
+
status: OrganizationMembershipStatus;
|
|
17
18
|
user_id: string;
|
|
18
19
|
created_at: string;
|
|
19
20
|
updated_at: string;
|
|
@@ -10,6 +10,7 @@ const deserializeInvitation = (invitation) => ({
|
|
|
10
10
|
revokedAt: invitation.revoked_at,
|
|
11
11
|
expiresAt: invitation.expires_at,
|
|
12
12
|
organizationId: invitation.organization_id,
|
|
13
|
+
inviterUserId: invitation.inviter_user_id,
|
|
13
14
|
token: invitation.token,
|
|
14
15
|
acceptInvitationUrl: invitation.accept_invitation_url,
|
|
15
16
|
createdAt: invitation.created_at,
|
|
@@ -25,6 +26,7 @@ const deserializeInvitationEvent = (invitation) => ({
|
|
|
25
26
|
revokedAt: invitation.revoked_at,
|
|
26
27
|
expiresAt: invitation.expires_at,
|
|
27
28
|
organizationId: invitation.organization_id,
|
|
29
|
+
inviterUserId: invitation.inviter_user_id,
|
|
28
30
|
createdAt: invitation.created_at,
|
|
29
31
|
updatedAt: invitation.updated_at,
|
|
30
32
|
});
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.serializeListOrganizationMembershipsOptions = void 0;
|
|
4
|
-
const serializeListOrganizationMembershipsOptions = (options) =>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
const serializeListOrganizationMembershipsOptions = (options) => {
|
|
5
|
+
var _a;
|
|
6
|
+
return ({
|
|
7
|
+
user_id: options.userId,
|
|
8
|
+
organization_id: options.organizationId,
|
|
9
|
+
statuses: (_a = options.statuses) === null || _a === void 0 ? void 0 : _a.join(','),
|
|
10
|
+
limit: options.limit,
|
|
11
|
+
before: options.before,
|
|
12
|
+
after: options.after,
|
|
13
|
+
order: options.order,
|
|
14
|
+
});
|
|
15
|
+
};
|
|
12
16
|
exports.serializeListOrganizationMembershipsOptions = serializeListOrganizationMembershipsOptions;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { RoleEvent, RoleEventResponse } from '
|
|
1
|
+
import { RoleEvent, RoleEventResponse } from '../../roles/interfaces';
|
|
2
2
|
export declare const deserializeRole: (role: RoleEventResponse) => RoleEvent;
|
|
@@ -55,6 +55,8 @@ export declare class UserManagement {
|
|
|
55
55
|
createOrganizationMembership(options: CreateOrganizationMembershipOptions): Promise<OrganizationMembership>;
|
|
56
56
|
updateOrganizationMembership(organizationMembershipId: string, options: UpdateOrganizationMembershipOptions): Promise<OrganizationMembership>;
|
|
57
57
|
deleteOrganizationMembership(organizationMembershipId: string): Promise<void>;
|
|
58
|
+
deactivateOrganizationMembership(organizationMembershipId: string): Promise<OrganizationMembership>;
|
|
59
|
+
reactivateOrganizationMembership(organizationMembershipId: string): Promise<OrganizationMembership>;
|
|
58
60
|
getInvitation(invitationId: string): Promise<Invitation>;
|
|
59
61
|
listInvitations(options: ListInvitationsOptions): Promise<AutoPaginatable<Invitation>>;
|
|
60
62
|
sendInvitation(payload: SendInvitationOptions): Promise<Invitation>;
|
|
@@ -202,6 +202,18 @@ class UserManagement {
|
|
|
202
202
|
yield this.workos.delete(`/user_management/organization_memberships/${organizationMembershipId}`);
|
|
203
203
|
});
|
|
204
204
|
}
|
|
205
|
+
deactivateOrganizationMembership(organizationMembershipId) {
|
|
206
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
207
|
+
const { data } = yield this.workos.put(`/user_management/organization_memberships/${organizationMembershipId}/deactivate`, {});
|
|
208
|
+
return (0, organization_membership_serializer_1.deserializeOrganizationMembership)(data);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
reactivateOrganizationMembership(organizationMembershipId) {
|
|
212
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
213
|
+
const { data } = yield this.workos.put(`/user_management/organization_memberships/${organizationMembershipId}/reactivate`, {});
|
|
214
|
+
return (0, organization_membership_serializer_1.deserializeOrganizationMembership)(data);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
205
217
|
getInvitation(invitationId) {
|
|
206
218
|
return __awaiter(this, void 0, void 0, function* () {
|
|
207
219
|
const { data } = yield this.workos.get(`/user_management/invitations/${invitationId}`);
|
|
@@ -15,14 +15,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
const jest_fetch_mock_1 = __importDefault(require("jest-fetch-mock"));
|
|
16
16
|
const test_utils_1 = require("../common/utils/test-utils");
|
|
17
17
|
const workos_1 = require("../workos");
|
|
18
|
-
const
|
|
19
|
-
const list_users_json_1 = __importDefault(require("./fixtures/list-users.json"));
|
|
20
|
-
const list_factors_json_1 = __importDefault(require("./fixtures/list-factors.json"));
|
|
21
|
-
const organization_membership_json_1 = __importDefault(require("./fixtures/organization-membership.json"));
|
|
22
|
-
const list_organization_memberships_json_1 = __importDefault(require("./fixtures/list-organization-memberships.json"));
|
|
18
|
+
const deactivate_organization_membership_json_1 = __importDefault(require("./fixtures/deactivate-organization-membership.json"));
|
|
23
19
|
const invitation_json_1 = __importDefault(require("./fixtures/invitation.json"));
|
|
20
|
+
const list_factors_json_1 = __importDefault(require("./fixtures/list-factors.json"));
|
|
24
21
|
const list_invitations_json_1 = __importDefault(require("./fixtures/list-invitations.json"));
|
|
22
|
+
const list_organization_memberships_json_1 = __importDefault(require("./fixtures/list-organization-memberships.json"));
|
|
23
|
+
const list_users_json_1 = __importDefault(require("./fixtures/list-users.json"));
|
|
25
24
|
const magic_auth_json_1 = __importDefault(require("./fixtures/magic_auth.json"));
|
|
25
|
+
const organization_membership_json_1 = __importDefault(require("./fixtures/organization-membership.json"));
|
|
26
|
+
const user_json_1 = __importDefault(require("./fixtures/user.json"));
|
|
26
27
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
27
28
|
const userId = 'user_01H5JQDV7R7ATEYZDEG0W5PRYS';
|
|
28
29
|
const organizationMembershipId = 'om_01H5JQDV7R7ATEYZDEG0W5PRYS';
|
|
@@ -583,12 +584,14 @@ describe('UserManagement', () => {
|
|
|
583
584
|
yield workos.userManagement.listOrganizationMemberships({
|
|
584
585
|
userId: 'user_someuser',
|
|
585
586
|
organizationId: 'org_someorg',
|
|
587
|
+
statuses: ['active', 'inactive'],
|
|
586
588
|
after: 'user_01H5JQDV7R7ATEYZDEG0W5PRYS',
|
|
587
589
|
limit: 10,
|
|
588
590
|
});
|
|
589
591
|
expect((0, test_utils_1.fetchSearchParams)()).toEqual({
|
|
590
592
|
user_id: 'user_someuser',
|
|
591
593
|
organization_id: 'org_someorg',
|
|
594
|
+
statuses: 'active,inactive',
|
|
592
595
|
after: 'user_01H5JQDV7R7ATEYZDEG0W5PRYS',
|
|
593
596
|
limit: '10',
|
|
594
597
|
order: 'desc',
|
|
@@ -644,6 +647,38 @@ describe('UserManagement', () => {
|
|
|
644
647
|
expect(resp).toBeUndefined();
|
|
645
648
|
}));
|
|
646
649
|
});
|
|
650
|
+
describe('deactivateOrganizationMembership', () => {
|
|
651
|
+
it('sends a deactivateOrganizationMembership request', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
652
|
+
(0, test_utils_1.fetchOnce)(deactivate_organization_membership_json_1.default);
|
|
653
|
+
const organizationMembership = yield workos.userManagement.deactivateOrganizationMembership(organizationMembershipId);
|
|
654
|
+
expect((0, test_utils_1.fetchURL)()).toContain(`/user_management/organization_memberships/${organizationMembershipId}/deactivate`);
|
|
655
|
+
expect(organizationMembership).toMatchObject({
|
|
656
|
+
object: 'organization_membership',
|
|
657
|
+
organizationId: 'organization_01H5JQDV7R7ATEYZDEG0W5PRYS',
|
|
658
|
+
userId: 'user_01H5JQDV7R7ATEYZDEG0W5PRYS',
|
|
659
|
+
status: 'inactive',
|
|
660
|
+
role: {
|
|
661
|
+
slug: 'member',
|
|
662
|
+
},
|
|
663
|
+
});
|
|
664
|
+
}));
|
|
665
|
+
});
|
|
666
|
+
describe('reactivateOrganizationMembership', () => {
|
|
667
|
+
it('sends a reactivateOrganizationMembership request', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
668
|
+
(0, test_utils_1.fetchOnce)(organization_membership_json_1.default);
|
|
669
|
+
const organizationMembership = yield workos.userManagement.reactivateOrganizationMembership(organizationMembershipId);
|
|
670
|
+
expect((0, test_utils_1.fetchURL)()).toContain(`/user_management/organization_memberships/${organizationMembershipId}/reactivate`);
|
|
671
|
+
expect(organizationMembership).toMatchObject({
|
|
672
|
+
object: 'organization_membership',
|
|
673
|
+
organizationId: 'organization_01H5JQDV7R7ATEYZDEG0W5PRYS',
|
|
674
|
+
userId: 'user_01H5JQDV7R7ATEYZDEG0W5PRYS',
|
|
675
|
+
status: 'active',
|
|
676
|
+
role: {
|
|
677
|
+
slug: 'member',
|
|
678
|
+
},
|
|
679
|
+
});
|
|
680
|
+
}));
|
|
681
|
+
});
|
|
647
682
|
describe('getInvitation', () => {
|
|
648
683
|
it('sends a Get Invitation request', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
649
684
|
(0, test_utils_1.fetchOnce)(invitation_json_1.default);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Event } from '../common/interfaces';
|
|
2
2
|
export declare class Webhooks {
|
|
3
|
-
private
|
|
3
|
+
private cryptoProvider;
|
|
4
|
+
constructor(subtleCrypto?: typeof crypto.subtle);
|
|
4
5
|
constructEvent({ payload, sigHeader, secret, tolerance, }: {
|
|
5
6
|
payload: unknown;
|
|
6
7
|
sigHeader: string;
|
|
@@ -15,5 +16,4 @@ export declare class Webhooks {
|
|
|
15
16
|
}): Promise<boolean>;
|
|
16
17
|
getTimestampAndSignatureHash(sigHeader: string): [string, string];
|
|
17
18
|
computeSignature(timestamp: any, payload: any, secret: string): Promise<string>;
|
|
18
|
-
secureCompare(stringA: string, stringB: string): Promise<boolean>;
|
|
19
19
|
}
|
package/lib/webhooks/webhooks.js
CHANGED
|
@@ -12,9 +12,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.Webhooks = void 0;
|
|
13
13
|
const exceptions_1 = require("../common/exceptions");
|
|
14
14
|
const serializers_1 = require("../common/serializers");
|
|
15
|
+
const crypto_1 = require("../common/crypto");
|
|
15
16
|
class Webhooks {
|
|
16
|
-
constructor() {
|
|
17
|
-
|
|
17
|
+
constructor(subtleCrypto) {
|
|
18
|
+
if (typeof crypto !== 'undefined' && typeof crypto.subtle !== 'undefined') {
|
|
19
|
+
this.cryptoProvider = new crypto_1.SubtleCryptoProvider(subtleCrypto);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
this.cryptoProvider = new crypto_1.NodeCryptoProvider();
|
|
23
|
+
}
|
|
18
24
|
}
|
|
19
25
|
constructEvent({ payload, sigHeader, secret, tolerance = 180000, }) {
|
|
20
26
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -34,7 +40,8 @@ class Webhooks {
|
|
|
34
40
|
throw new exceptions_1.SignatureVerificationException('Timestamp outside the tolerance zone');
|
|
35
41
|
}
|
|
36
42
|
const expectedSig = yield this.computeSignature(timestamp, payload, secret);
|
|
37
|
-
if ((yield this.secureCompare(expectedSig, signatureHash)) ===
|
|
43
|
+
if ((yield this.cryptoProvider.secureCompare(expectedSig, signatureHash)) ===
|
|
44
|
+
false) {
|
|
38
45
|
throw new exceptions_1.SignatureVerificationException('Signature hash does not match the expected signature hash for payload');
|
|
39
46
|
}
|
|
40
47
|
return true;
|
|
@@ -54,41 +61,8 @@ class Webhooks {
|
|
|
54
61
|
return __awaiter(this, void 0, void 0, function* () {
|
|
55
62
|
payload = JSON.stringify(payload);
|
|
56
63
|
const signedPayload = `${timestamp}.${payload}`;
|
|
57
|
-
|
|
58
|
-
const signatureBuffer = yield crypto.subtle.sign('HMAC', key, this.encoder.encode(signedPayload));
|
|
59
|
-
// crypto.subtle returns the signature in base64 format. This must be
|
|
60
|
-
// encoded in hex to match the CryptoProvider contract. We map each byte in
|
|
61
|
-
// the buffer to its corresponding hex octet and then combine into a string.
|
|
62
|
-
const signatureBytes = new Uint8Array(signatureBuffer);
|
|
63
|
-
const signatureHexCodes = new Array(signatureBytes.length);
|
|
64
|
-
for (let i = 0; i < signatureBytes.length; i++) {
|
|
65
|
-
signatureHexCodes[i] = byteHexMapping[signatureBytes[i]];
|
|
66
|
-
}
|
|
67
|
-
return signatureHexCodes.join('');
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
secureCompare(stringA, stringB) {
|
|
71
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
-
const bufferA = this.encoder.encode(stringA);
|
|
73
|
-
const bufferB = this.encoder.encode(stringB);
|
|
74
|
-
if (bufferA.length !== bufferB.length) {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
const algorithm = { name: 'HMAC', hash: 'SHA-256' };
|
|
78
|
-
const key = (yield crypto.subtle.generateKey(algorithm, false, [
|
|
79
|
-
'sign',
|
|
80
|
-
'verify',
|
|
81
|
-
]));
|
|
82
|
-
const hmac = yield crypto.subtle.sign(algorithm, key, bufferA);
|
|
83
|
-
const equal = yield crypto.subtle.verify(algorithm, key, hmac, bufferB);
|
|
84
|
-
return equal;
|
|
64
|
+
return yield this.cryptoProvider.computeHMACSignatureAsync(signedPayload, secret);
|
|
85
65
|
});
|
|
86
66
|
}
|
|
87
67
|
}
|
|
88
68
|
exports.Webhooks = Webhooks;
|
|
89
|
-
// Cached mapping of byte to hex representation. We do this once to avoid re-
|
|
90
|
-
// computing every time we need to convert the result of a signature to hex.
|
|
91
|
-
const byteHexMapping = new Array(256);
|
|
92
|
-
for (let i = 0; i < byteHexMapping.length; i++) {
|
|
93
|
-
byteHexMapping[i] = i.toString(16).padStart(2, '0');
|
|
94
|
-
}
|
|
@@ -17,6 +17,7 @@ const workos_1 = require("../workos");
|
|
|
17
17
|
const webhook_json_1 = __importDefault(require("./fixtures/webhook.json"));
|
|
18
18
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
19
19
|
const exceptions_1 = require("../common/exceptions");
|
|
20
|
+
const crypto_2 = require("../common/crypto");
|
|
20
21
|
describe('Webhooks', () => {
|
|
21
22
|
let payload;
|
|
22
23
|
let secret;
|
|
@@ -187,4 +188,32 @@ describe('Webhooks', () => {
|
|
|
187
188
|
expect(signature).toEqual(signatureHash);
|
|
188
189
|
}));
|
|
189
190
|
});
|
|
191
|
+
describe('when in an environment that supports SubtleCrypto', () => {
|
|
192
|
+
it('automatically uses the subtle crypto library', () => {
|
|
193
|
+
// tslint:disable-next-line
|
|
194
|
+
expect(workos.webhooks['cryptoProvider']).toBeInstanceOf(crypto_2.SubtleCryptoProvider);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
describe('CryptoProvider', () => {
|
|
198
|
+
describe('when computing HMAC signature', () => {
|
|
199
|
+
it('returns the same for the Node crypto and Web Crypto versions', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
200
|
+
const nodeCryptoProvider = new crypto_2.NodeCryptoProvider();
|
|
201
|
+
const subtleCryptoProvider = new crypto_2.SubtleCryptoProvider();
|
|
202
|
+
const stringifiedPayload = JSON.stringify(payload);
|
|
203
|
+
const payloadHMAC = `${timestamp}.${stringifiedPayload}`;
|
|
204
|
+
const nodeCompare = yield nodeCryptoProvider.computeHMACSignatureAsync(payloadHMAC, secret);
|
|
205
|
+
const subtleCompare = yield subtleCryptoProvider.computeHMACSignatureAsync(payloadHMAC, secret);
|
|
206
|
+
expect(nodeCompare).toEqual(subtleCompare);
|
|
207
|
+
}));
|
|
208
|
+
});
|
|
209
|
+
describe('when securely comparing', () => {
|
|
210
|
+
it('returns the same for the Node crypto and Web Crypto versions', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
211
|
+
const nodeCryptoProvider = new crypto_2.NodeCryptoProvider();
|
|
212
|
+
const subtleCryptoProvider = new crypto_2.SubtleCryptoProvider();
|
|
213
|
+
const signature = yield workos.webhooks.computeSignature(timestamp, payload, secret);
|
|
214
|
+
expect(nodeCryptoProvider.secureCompare(signature, signatureHash)).toEqual(subtleCryptoProvider.secureCompare(signature, signatureHash));
|
|
215
|
+
expect(nodeCryptoProvider.secureCompare(signature, 'foo')).toEqual(subtleCryptoProvider.secureCompare(signature, 'foo'));
|
|
216
|
+
}));
|
|
217
|
+
});
|
|
218
|
+
});
|
|
190
219
|
});
|
package/lib/workos.d.ts
CHANGED
package/lib/workos.js
CHANGED
|
@@ -23,8 +23,8 @@ const mfa_1 = require("./mfa/mfa");
|
|
|
23
23
|
const audit_logs_1 = require("./audit-logs/audit-logs");
|
|
24
24
|
const user_management_1 = require("./user-management/user-management");
|
|
25
25
|
const bad_request_exception_1 = require("./common/exceptions/bad-request.exception");
|
|
26
|
-
const
|
|
27
|
-
const VERSION = '7.
|
|
26
|
+
const net_1 = require("./common/net");
|
|
27
|
+
const VERSION = '7.5.0';
|
|
28
28
|
const DEFAULT_HOSTNAME = 'api.workos.com';
|
|
29
29
|
class WorkOS {
|
|
30
30
|
constructor(key, options = {}) {
|
|
@@ -64,7 +64,7 @@ class WorkOS {
|
|
|
64
64
|
const { name, version } = options.appInfo;
|
|
65
65
|
userAgent += ` ${name}: ${version}`;
|
|
66
66
|
}
|
|
67
|
-
this.client =
|
|
67
|
+
this.client = (0, net_1.createHttpClient)(this.baseURL, Object.assign(Object.assign({}, options.config), { headers: Object.assign(Object.assign({}, (_a = options.config) === null || _a === void 0 ? void 0 : _a.headers), { Authorization: `Bearer ${this.key}`, 'User-Agent': userAgent }) }), options.fetchFn);
|
|
68
68
|
}
|
|
69
69
|
get version() {
|
|
70
70
|
return VERSION;
|
|
@@ -76,13 +76,14 @@ class WorkOS {
|
|
|
76
76
|
requestHeaders['Idempotency-Key'] = options.idempotencyKey;
|
|
77
77
|
}
|
|
78
78
|
try {
|
|
79
|
-
|
|
79
|
+
const res = yield this.client.post(path, entity, {
|
|
80
80
|
params: options.query,
|
|
81
81
|
headers: requestHeaders,
|
|
82
82
|
});
|
|
83
|
+
return { data: yield res.toJSON() };
|
|
83
84
|
}
|
|
84
85
|
catch (error) {
|
|
85
|
-
this.
|
|
86
|
+
this.handleHttpError({ path, error });
|
|
86
87
|
throw error;
|
|
87
88
|
}
|
|
88
89
|
});
|
|
@@ -91,15 +92,16 @@ class WorkOS {
|
|
|
91
92
|
return __awaiter(this, void 0, void 0, function* () {
|
|
92
93
|
try {
|
|
93
94
|
const { accessToken } = options;
|
|
94
|
-
|
|
95
|
+
const res = yield this.client.get(path, {
|
|
95
96
|
params: options.query,
|
|
96
97
|
headers: accessToken
|
|
97
98
|
? { Authorization: `Bearer ${accessToken}` }
|
|
98
99
|
: undefined,
|
|
99
100
|
});
|
|
101
|
+
return { data: yield res.toJSON() };
|
|
100
102
|
}
|
|
101
103
|
catch (error) {
|
|
102
|
-
this.
|
|
104
|
+
this.handleHttpError({ path, error });
|
|
103
105
|
throw error;
|
|
104
106
|
}
|
|
105
107
|
});
|
|
@@ -111,13 +113,14 @@ class WorkOS {
|
|
|
111
113
|
requestHeaders['Idempotency-Key'] = options.idempotencyKey;
|
|
112
114
|
}
|
|
113
115
|
try {
|
|
114
|
-
|
|
116
|
+
const res = yield this.client.put(path, entity, {
|
|
115
117
|
params: options.query,
|
|
116
118
|
headers: requestHeaders,
|
|
117
119
|
});
|
|
120
|
+
return { data: yield res.toJSON() };
|
|
118
121
|
}
|
|
119
122
|
catch (error) {
|
|
120
|
-
this.
|
|
123
|
+
this.handleHttpError({ path, error });
|
|
121
124
|
throw error;
|
|
122
125
|
}
|
|
123
126
|
});
|
|
@@ -130,7 +133,7 @@ class WorkOS {
|
|
|
130
133
|
});
|
|
131
134
|
}
|
|
132
135
|
catch (error) {
|
|
133
|
-
this.
|
|
136
|
+
this.handleHttpError({ path, error });
|
|
134
137
|
throw error;
|
|
135
138
|
}
|
|
136
139
|
});
|
|
@@ -143,12 +146,15 @@ class WorkOS {
|
|
|
143
146
|
}
|
|
144
147
|
return process.emitWarning(warning, 'WorkOS');
|
|
145
148
|
}
|
|
146
|
-
|
|
149
|
+
handleHttpError({ path, error }) {
|
|
147
150
|
var _a;
|
|
151
|
+
if (!(error instanceof net_1.HttpClientError)) {
|
|
152
|
+
throw new Error(`Unexpected error: ${error}`);
|
|
153
|
+
}
|
|
148
154
|
const { response } = error;
|
|
149
155
|
if (response) {
|
|
150
156
|
const { status, data, headers } = response;
|
|
151
|
-
const requestID = (_a = headers
|
|
157
|
+
const requestID = (_a = headers['X-Request-ID']) !== null && _a !== void 0 ? _a : '';
|
|
152
158
|
const { code, error_description: errorDescription, error, errors, message, } = data;
|
|
153
159
|
switch (status) {
|
|
154
160
|
case 401: {
|
|
@@ -170,6 +176,10 @@ class WorkOS {
|
|
|
170
176
|
requestID,
|
|
171
177
|
});
|
|
172
178
|
}
|
|
179
|
+
case 429: {
|
|
180
|
+
const retryAfter = headers.get('Retry-After');
|
|
181
|
+
throw new exceptions_1.RateLimitExceededException(data.message, requestID, retryAfter ? Number(retryAfter) : null);
|
|
182
|
+
}
|
|
173
183
|
default: {
|
|
174
184
|
if (error || errorDescription) {
|
|
175
185
|
throw new exceptions_1.OauthException(status, requestID, error, errorDescription, data);
|
package/lib/workos.spec.js
CHANGED
|
@@ -17,6 +17,8 @@ const test_utils_1 = require("./common/utils/test-utils");
|
|
|
17
17
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
18
18
|
const exceptions_1 = require("./common/exceptions");
|
|
19
19
|
const workos_1 = require("./workos");
|
|
20
|
+
const rate_limit_exceeded_exception_1 = require("./common/exceptions/rate-limit-exceeded.exception");
|
|
21
|
+
const net_1 = require("./common/net");
|
|
20
22
|
describe('WorkOS', () => {
|
|
21
23
|
beforeEach(() => jest_fetch_mock_1.default.resetMocks());
|
|
22
24
|
describe('constructor', () => {
|
|
@@ -94,10 +96,40 @@ describe('WorkOS', () => {
|
|
|
94
96
|
});
|
|
95
97
|
yield workos.post('/somewhere', {});
|
|
96
98
|
expect((0, test_utils_1.fetchHeaders)()).toMatchObject({
|
|
97
|
-
'User-Agent': `workos-node/${packageJson.version} fooApp: 1.0.0`,
|
|
99
|
+
'User-Agent': `workos-node/${packageJson.version}/fetch fooApp: 1.0.0`,
|
|
98
100
|
});
|
|
99
101
|
}));
|
|
100
102
|
});
|
|
103
|
+
describe('when no `appInfo` option is provided', () => {
|
|
104
|
+
it('adds the HTTP client name to the user-agent', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
105
|
+
(0, test_utils_1.fetchOnce)('{}');
|
|
106
|
+
const packageJson = JSON.parse(yield promises_1.default.readFile('package.json', 'utf8'));
|
|
107
|
+
const workos = new workos_1.WorkOS('sk_test');
|
|
108
|
+
yield workos.post('/somewhere', {});
|
|
109
|
+
expect((0, test_utils_1.fetchHeaders)()).toMatchObject({
|
|
110
|
+
'User-Agent': `workos-node/${packageJson.version}/fetch`,
|
|
111
|
+
});
|
|
112
|
+
}));
|
|
113
|
+
});
|
|
114
|
+
describe('when no `appInfo` option is provided', () => {
|
|
115
|
+
it('adds the HTTP client name to the user-agent', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
116
|
+
(0, test_utils_1.fetchOnce)('{}');
|
|
117
|
+
const packageJson = JSON.parse(yield promises_1.default.readFile('package.json', 'utf8'));
|
|
118
|
+
const workos = new workos_1.WorkOS('sk_test');
|
|
119
|
+
yield workos.post('/somewhere', {});
|
|
120
|
+
expect((0, test_utils_1.fetchHeaders)()).toMatchObject({
|
|
121
|
+
'User-Agent': `workos-node/${packageJson.version}/fetch`,
|
|
122
|
+
});
|
|
123
|
+
}));
|
|
124
|
+
});
|
|
125
|
+
describe('when using an environment that supports fetch', () => {
|
|
126
|
+
it('automatically uses the fetch HTTP client', () => {
|
|
127
|
+
const workos = new workos_1.WorkOS('sk_test');
|
|
128
|
+
// Bracket notation gets past private visibility
|
|
129
|
+
// tslint:disable-next-line
|
|
130
|
+
expect(workos['client']).toBeInstanceOf(net_1.FetchHttpClient);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
101
133
|
});
|
|
102
134
|
describe('version', () => {
|
|
103
135
|
it('matches the version in `package.json`', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -163,13 +195,47 @@ describe('WorkOS', () => {
|
|
|
163
195
|
yield expect(workos.post('/path', {})).rejects.toStrictEqual(new exceptions_1.OauthException(400, 'a-request-id', 'error', 'error description', { error: 'error', error_description: 'error description' }));
|
|
164
196
|
}));
|
|
165
197
|
});
|
|
198
|
+
describe('when the api responses with a 429', () => {
|
|
199
|
+
it('throws a RateLimitExceededException', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
200
|
+
(0, test_utils_1.fetchOnce)({
|
|
201
|
+
message: 'Too many requests',
|
|
202
|
+
}, {
|
|
203
|
+
status: 429,
|
|
204
|
+
headers: { 'X-Request-ID': 'a-request-id', 'Retry-After': '10' },
|
|
205
|
+
});
|
|
206
|
+
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
207
|
+
yield expect(workos.get('/path')).rejects.toStrictEqual(new rate_limit_exceeded_exception_1.RateLimitExceededException('Too many requests', 'a-request-id', 10));
|
|
208
|
+
}));
|
|
209
|
+
});
|
|
166
210
|
describe('when the entity is null', () => {
|
|
167
|
-
it('sends
|
|
211
|
+
it('sends an empty string body', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
168
212
|
(0, test_utils_1.fetchOnce)();
|
|
169
213
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
170
214
|
yield workos.post('/somewhere', null);
|
|
171
|
-
expect((0, test_utils_1.fetchBody)({ raw: true })).
|
|
215
|
+
expect((0, test_utils_1.fetchBody)({ raw: true })).toBe('');
|
|
172
216
|
}));
|
|
173
217
|
});
|
|
174
218
|
});
|
|
219
|
+
describe('when in an environment that does not support fetch', () => {
|
|
220
|
+
const fetchFn = globalThis.fetch;
|
|
221
|
+
beforeEach(() => {
|
|
222
|
+
// @ts-ignore
|
|
223
|
+
delete globalThis.fetch;
|
|
224
|
+
});
|
|
225
|
+
afterEach(() => {
|
|
226
|
+
globalThis.fetch = fetchFn;
|
|
227
|
+
});
|
|
228
|
+
it('automatically uses the node HTTP client', () => {
|
|
229
|
+
const workos = new workos_1.WorkOS('sk_test_key');
|
|
230
|
+
// tslint:disable-next-line
|
|
231
|
+
expect(workos['client']).toBeInstanceOf(net_1.NodeHttpClient);
|
|
232
|
+
});
|
|
233
|
+
it('uses a fetch function if provided', () => {
|
|
234
|
+
const workos = new workos_1.WorkOS('sk_test_key', {
|
|
235
|
+
fetchFn,
|
|
236
|
+
});
|
|
237
|
+
// tslint:disable-next-line
|
|
238
|
+
expect(workos['client']).toBeInstanceOf(net_1.FetchHttpClient);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
175
241
|
});
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "7.
|
|
2
|
+
"version": "7.5.0-beta.node-compatibility",
|
|
3
3
|
"name": "@workos-inc/node",
|
|
4
4
|
"author": "WorkOS",
|
|
5
5
|
"description": "A Node wrapper for the WorkOS API",
|
|
@@ -13,9 +13,8 @@
|
|
|
13
13
|
"yarn": "1.22.19"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
16
|
-
"node": ">=
|
|
16
|
+
"node": ">=16"
|
|
17
17
|
},
|
|
18
|
-
"engineStrict": true,
|
|
19
18
|
"main": "lib/index.js",
|
|
20
19
|
"typings": "lib/index.d.ts",
|
|
21
20
|
"files": [
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export declare class FetchClient {
|
|
2
|
-
readonly baseURL: string;
|
|
3
|
-
readonly options?: RequestInit | undefined;
|
|
4
|
-
constructor(baseURL: string, options?: RequestInit | undefined);
|
|
5
|
-
get(path: string, options: {
|
|
6
|
-
params?: Record<string, any>;
|
|
7
|
-
headers?: HeadersInit;
|
|
8
|
-
}): Promise<{
|
|
9
|
-
data: any;
|
|
10
|
-
}>;
|
|
11
|
-
post<Entity = any>(path: string, entity: Entity, options: {
|
|
12
|
-
params?: Record<string, any>;
|
|
13
|
-
headers?: HeadersInit;
|
|
14
|
-
}): Promise<{
|
|
15
|
-
data: any;
|
|
16
|
-
}>;
|
|
17
|
-
put<Entity = any>(path: string, entity: Entity, options: {
|
|
18
|
-
params?: Record<string, any>;
|
|
19
|
-
headers?: HeadersInit;
|
|
20
|
-
}): Promise<{
|
|
21
|
-
data: any;
|
|
22
|
-
}>;
|
|
23
|
-
delete(path: string, options: {
|
|
24
|
-
params?: Record<string, any>;
|
|
25
|
-
headers?: HeadersInit;
|
|
26
|
-
}): Promise<{
|
|
27
|
-
data: any;
|
|
28
|
-
}>;
|
|
29
|
-
private getResourceURL;
|
|
30
|
-
private fetch;
|
|
31
|
-
}
|