@workos-inc/node 2.5.0 → 2.6.1
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 +5 -1
- package/lib/audit-trail/audit-trail.spec.js +15 -6
- package/lib/audit-trail/interfaces/event.interface.d.ts +0 -1
- package/lib/common/exceptions/unprocessable-entity.exception.d.ts +8 -2
- package/lib/common/exceptions/unprocessable-entity.exception.js +15 -6
- package/lib/directory-sync/directory-sync.spec.js +110 -50
- package/lib/directory-sync/interfaces/directory.interface.d.ts +0 -1
- package/lib/directory-sync/interfaces/user.interface.d.ts +2 -2
- package/lib/mfa/interfaces/challenge-factor-options.d.ts +3 -3
- package/lib/mfa/interfaces/factor.interface.d.ts +0 -1
- package/lib/mfa/interfaces/verify-factor-options.d.ts +1 -1
- package/lib/mfa/interfaces/verify-factor-response.d.ts +1 -6
- package/lib/mfa/mfa.js +23 -3
- package/lib/mfa/mfa.spec.js +115 -14
- package/lib/organizations/organizations.spec.js +45 -10
- package/lib/passwordless/passwordless.spec.js +9 -3
- package/lib/portal/portal.spec.js +21 -3
- package/lib/sso/sso.spec.js +42 -21
- package/lib/webhooks/interfaces/index.d.ts +2 -0
- package/lib/webhooks/interfaces/index.js +2 -0
- package/lib/webhooks/interfaces/webhook-directory-group.interface.d.ts +9 -0
- package/lib/webhooks/interfaces/webhook-directory-group.interface.js +2 -0
- package/lib/webhooks/interfaces/webhook-directory-user.interface.d.ts +5 -0
- package/lib/webhooks/interfaces/webhook-directory-user.interface.js +2 -0
- package/lib/webhooks/interfaces/webhook-directory.interface.d.ts +19 -3
- package/lib/webhooks/interfaces/webhook.interface.d.ts +7 -6
- package/lib/workos.js +29 -9
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# WorkOS Node.js Library
|
|
2
2
|
|
|
3
3
|

|
|
4
|
-
[](https://workos.semaphoreci.com/projects/workos-node)
|
|
5
5
|
|
|
6
6
|
The WorkOS library for Node.js provides convenient access to the WorkOS API from applications written in server-side JavaScript.
|
|
7
7
|
|
|
@@ -33,6 +33,10 @@ import WorkOS from '@workos-inc/node';
|
|
|
33
33
|
const workos = new WorkOS('sk_1234');
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
## SDK Versioning
|
|
37
|
+
|
|
38
|
+
For our SDKs WorkOS follows a Semantic Versioning ([SemVer](https://semver.org/)) process where all releases will have a version X.Y.Z (like 1.0.0) pattern wherein Z would be a bug fix (e.g., 1.0.1), Y would be a minor release (1.1.0) and X would be a major release (2.0.0). We permit any breaking changes to only be released in major versions and strongly recommend reading changelogs before making any major version upgrades.
|
|
39
|
+
|
|
36
40
|
## More Information
|
|
37
41
|
|
|
38
42
|
- [Single Sign-On Guide](https://workos.com/docs/sso/guide)
|
|
@@ -28,12 +28,15 @@ const event = {
|
|
|
28
28
|
action_type: 'U',
|
|
29
29
|
action: 'document.updated',
|
|
30
30
|
};
|
|
31
|
+
const serializeEventOptions = (options) => (Object.assign(Object.assign({}, options), { occurred_at: options.occurred_at.toISOString() }));
|
|
31
32
|
describe('AuditTrail', () => {
|
|
32
33
|
describe('createEvent', () => {
|
|
33
34
|
describe('when the api responds with a 201 CREATED', () => {
|
|
34
35
|
describe('with an idempotency key', () => {
|
|
35
36
|
it('includes an idempotency key with request', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
36
|
-
mock
|
|
37
|
+
mock
|
|
38
|
+
.onPost('/events', serializeEventOptions(event))
|
|
39
|
+
.replyOnce(201, { success: true });
|
|
37
40
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
38
41
|
yield expect(workos.auditTrail.createEvent(event, {
|
|
39
42
|
idempotencyKey: 'the-idempotency-key',
|
|
@@ -42,14 +45,16 @@ describe('AuditTrail', () => {
|
|
|
42
45
|
}));
|
|
43
46
|
});
|
|
44
47
|
it('posts Event successfully', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
45
|
-
mock
|
|
48
|
+
mock
|
|
49
|
+
.onPost('/events', serializeEventOptions(event))
|
|
50
|
+
.replyOnce(201, { success: true });
|
|
46
51
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
47
52
|
yield expect(workos.auditTrail.createEvent(event)).resolves.toBeUndefined();
|
|
48
53
|
}));
|
|
49
54
|
});
|
|
50
55
|
describe('when the api responds with a 401', () => {
|
|
51
56
|
it('throws an UnauthorizedException', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
52
|
-
mock.onPost().
|
|
57
|
+
mock.onPost('/events', serializeEventOptions(event)).replyOnce(401, {
|
|
53
58
|
message: 'Unauthorized',
|
|
54
59
|
}, { 'X-Request-ID': 'a-request-id' });
|
|
55
60
|
const workos = new workos_1.WorkOS('invalid apikey');
|
|
@@ -68,7 +73,7 @@ describe('AuditTrail', () => {
|
|
|
68
73
|
code: 'occurred_at must be an ISO 8601 date string',
|
|
69
74
|
},
|
|
70
75
|
];
|
|
71
|
-
mock.onPost().
|
|
76
|
+
mock.onPost('/events', serializeEventOptions(event)).replyOnce(422, {
|
|
72
77
|
message: 'Validation failed',
|
|
73
78
|
errors,
|
|
74
79
|
}, { 'X-Request-ID': 'a-request-id' });
|
|
@@ -80,7 +85,7 @@ describe('AuditTrail', () => {
|
|
|
80
85
|
describe('listEvents', () => {
|
|
81
86
|
describe('With no filters', () => {
|
|
82
87
|
it('Returns all events', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
|
-
mock.onGet().
|
|
88
|
+
mock.onGet('/events').replyOnce(200, {
|
|
84
89
|
data: [
|
|
85
90
|
{
|
|
86
91
|
object: 'event',
|
|
@@ -136,7 +141,11 @@ describe('AuditTrail', () => {
|
|
|
136
141
|
});
|
|
137
142
|
describe('With a filter', () => {
|
|
138
143
|
it('Returns events that match the filter', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
139
|
-
mock
|
|
144
|
+
mock
|
|
145
|
+
.onGet('/events', {
|
|
146
|
+
action: ['user.searched_directories'],
|
|
147
|
+
})
|
|
148
|
+
.replyOnce(200, {
|
|
140
149
|
data: [
|
|
141
150
|
{
|
|
142
151
|
object: 'event',
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { UnprocessableEntityError } from '../interfaces';
|
|
2
2
|
export declare class UnprocessableEntityException extends Error {
|
|
3
|
-
readonly requestID: string;
|
|
4
3
|
readonly status: number;
|
|
5
4
|
readonly name: string;
|
|
6
5
|
readonly message: string;
|
|
7
|
-
|
|
6
|
+
readonly code?: string;
|
|
7
|
+
readonly requestID: string;
|
|
8
|
+
constructor({ code, errors, message, requestID, }: {
|
|
9
|
+
code?: string;
|
|
10
|
+
errors?: UnprocessableEntityError[];
|
|
11
|
+
message?: string;
|
|
12
|
+
requestID: string;
|
|
13
|
+
});
|
|
8
14
|
}
|
|
@@ -6,15 +6,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.UnprocessableEntityException = void 0;
|
|
7
7
|
const pluralize_1 = __importDefault(require("pluralize"));
|
|
8
8
|
class UnprocessableEntityException extends Error {
|
|
9
|
-
constructor(errors, requestID) {
|
|
9
|
+
constructor({ code, errors, message, requestID, }) {
|
|
10
10
|
super();
|
|
11
|
-
this.requestID = requestID;
|
|
12
11
|
this.status = 422;
|
|
13
12
|
this.name = 'UnprocessableEntityException';
|
|
14
|
-
|
|
15
|
-
this.
|
|
16
|
-
|
|
17
|
-
this.message =
|
|
13
|
+
this.message = 'Unprocessable entity';
|
|
14
|
+
this.requestID = requestID;
|
|
15
|
+
if (message) {
|
|
16
|
+
this.message = message;
|
|
17
|
+
}
|
|
18
|
+
if (code) {
|
|
19
|
+
this.code = code;
|
|
20
|
+
}
|
|
21
|
+
if (errors) {
|
|
22
|
+
const requirement = (0, pluralize_1.default)('requirement', errors.length);
|
|
23
|
+
this.message = `The following ${requirement} must be met:\n`;
|
|
24
|
+
for (const { code } of errors) {
|
|
25
|
+
this.message = this.message.concat(`\t${code}\n`);
|
|
26
|
+
}
|
|
18
27
|
}
|
|
19
28
|
}
|
|
20
29
|
}
|
|
@@ -18,81 +18,149 @@ const workos_1 = require("../workos");
|
|
|
18
18
|
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
19
19
|
describe('DirectorySync', () => {
|
|
20
20
|
afterEach(() => mock.resetHistory());
|
|
21
|
+
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
22
|
+
const directoryResponse = {
|
|
23
|
+
id: 'directory_123',
|
|
24
|
+
created_at: '2020-05-06 04:21:48.649164',
|
|
25
|
+
domain: 'foo-corp.com',
|
|
26
|
+
external_key: '9asBRBVHz2ASEkgg',
|
|
27
|
+
name: 'Foo',
|
|
28
|
+
object: 'directory',
|
|
29
|
+
organization_id: 'org_01EXSR7M9QTKCC5D531SMCWMYG',
|
|
30
|
+
state: 'linked',
|
|
31
|
+
type: 'okta scim v1.1',
|
|
32
|
+
updated_at: '2021-10-27 15:21:50.640958',
|
|
33
|
+
};
|
|
34
|
+
const groupResponse = {
|
|
35
|
+
id: 'dir_grp_123',
|
|
36
|
+
directory_id: 'dir_123',
|
|
37
|
+
name: 'Foo Group',
|
|
38
|
+
raw_attributes: {
|
|
39
|
+
foo: 'bar',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
const userWithGroupResponse = {
|
|
43
|
+
id: 'user_123',
|
|
44
|
+
custom_attributes: {
|
|
45
|
+
custom: true,
|
|
46
|
+
},
|
|
47
|
+
directory_id: 'dir_123',
|
|
48
|
+
emails: [
|
|
49
|
+
{
|
|
50
|
+
primary: true,
|
|
51
|
+
type: 'type',
|
|
52
|
+
value: 'jonsnow@workos.com',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
first_name: 'Jon',
|
|
56
|
+
groups: [groupResponse],
|
|
57
|
+
idp_id: 'idp_foo',
|
|
58
|
+
last_name: 'Snow',
|
|
59
|
+
raw_attributes: {},
|
|
60
|
+
state: 'active',
|
|
61
|
+
username: 'jonsnow',
|
|
62
|
+
};
|
|
21
63
|
describe('listDirectories', () => {
|
|
22
64
|
describe('with options', () => {
|
|
23
65
|
it('requests Directories with query parameters', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
66
|
+
const directoryListResponse = {
|
|
67
|
+
object: 'list',
|
|
68
|
+
data: [directoryResponse],
|
|
69
|
+
list_metadata: {},
|
|
70
|
+
};
|
|
71
|
+
mock
|
|
72
|
+
.onGet('/directories', {
|
|
27
73
|
domain: 'google.com',
|
|
28
|
-
})
|
|
29
|
-
|
|
74
|
+
})
|
|
75
|
+
.replyOnce(200, directoryListResponse);
|
|
76
|
+
const directories = yield workos.directorySync.listDirectories({
|
|
30
77
|
domain: 'google.com',
|
|
31
78
|
});
|
|
32
|
-
expect(
|
|
79
|
+
expect(directories).toEqual(directoryListResponse);
|
|
33
80
|
}));
|
|
34
81
|
});
|
|
35
82
|
});
|
|
36
83
|
describe('getDirectory', () => {
|
|
37
84
|
it(`requests a Directory`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
|
-
mock
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
85
|
+
mock
|
|
86
|
+
.onGet('/directories/directory_123')
|
|
87
|
+
.replyOnce(200, directoryResponse);
|
|
88
|
+
const directory = yield workos.directorySync.getDirectory('directory_123');
|
|
89
|
+
expect(directory).toEqual(directoryResponse);
|
|
42
90
|
}));
|
|
43
91
|
});
|
|
44
92
|
describe('deleteDirectory', () => {
|
|
45
93
|
it('sends a request to delete the directory', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
46
|
-
mock.onDelete().
|
|
47
|
-
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
94
|
+
mock.onDelete('/directories/directory_123').replyOnce(202, {});
|
|
48
95
|
yield workos.directorySync.deleteDirectory('directory_123');
|
|
49
96
|
expect(mock.history.delete[0].url).toEqual('/directories/directory_123');
|
|
50
97
|
}));
|
|
51
98
|
});
|
|
99
|
+
describe('getGroup', () => {
|
|
100
|
+
it(`requests a Directory Group`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
101
|
+
mock.onGet('/directory_groups/dir_grp_123').replyOnce(200, groupResponse);
|
|
102
|
+
const group = yield workos.directorySync.getGroup('dir_grp_123');
|
|
103
|
+
expect(group).toEqual(groupResponse);
|
|
104
|
+
}));
|
|
105
|
+
});
|
|
52
106
|
describe('listGroups', () => {
|
|
107
|
+
const groupListResponse = {
|
|
108
|
+
object: 'list',
|
|
109
|
+
data: [groupResponse],
|
|
110
|
+
list_metadata: {},
|
|
111
|
+
};
|
|
53
112
|
describe('with a Directory', () => {
|
|
54
113
|
it(`requests a Directory's Groups`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
55
|
-
mock
|
|
56
|
-
|
|
57
|
-
yield workos.directorySync.listGroups({
|
|
114
|
+
mock
|
|
115
|
+
.onGet('/directory_groups', {
|
|
58
116
|
directory: 'directory_123',
|
|
59
|
-
})
|
|
60
|
-
|
|
117
|
+
})
|
|
118
|
+
.replyOnce(200, groupListResponse);
|
|
119
|
+
const list = yield workos.directorySync.listGroups({
|
|
61
120
|
directory: 'directory_123',
|
|
62
121
|
});
|
|
63
|
-
expect(
|
|
122
|
+
expect(list).toEqual(groupListResponse);
|
|
64
123
|
}));
|
|
65
124
|
});
|
|
66
125
|
describe('with a User', () => {
|
|
67
126
|
it(`requests a Directory's Groups`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
68
|
-
mock
|
|
69
|
-
|
|
70
|
-
yield workos.directorySync.listGroups({
|
|
127
|
+
mock
|
|
128
|
+
.onGet('/directory_groups', {
|
|
71
129
|
user: 'directory_usr_123',
|
|
72
|
-
})
|
|
73
|
-
|
|
130
|
+
})
|
|
131
|
+
.replyOnce(200, groupListResponse);
|
|
132
|
+
const list = yield workos.directorySync.listGroups({
|
|
74
133
|
user: 'directory_usr_123',
|
|
75
134
|
});
|
|
76
|
-
expect(
|
|
135
|
+
expect(list).toEqual(groupListResponse);
|
|
77
136
|
}));
|
|
78
137
|
});
|
|
79
138
|
});
|
|
80
139
|
describe('listUsers', () => {
|
|
140
|
+
const userWithGroupListResponse = {
|
|
141
|
+
object: 'list',
|
|
142
|
+
data: [userWithGroupResponse],
|
|
143
|
+
list_metadata: {},
|
|
144
|
+
};
|
|
81
145
|
describe('with a Directory', () => {
|
|
82
146
|
it(`requests a Directory's Users`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
|
-
mock
|
|
84
|
-
|
|
85
|
-
yield workos.directorySync.listUsers({
|
|
147
|
+
mock
|
|
148
|
+
.onGet('/directory_users', {
|
|
86
149
|
directory: 'directory_123',
|
|
87
|
-
})
|
|
88
|
-
|
|
150
|
+
})
|
|
151
|
+
.replyOnce(200, userWithGroupListResponse);
|
|
152
|
+
const list = yield workos.directorySync.listUsers({
|
|
89
153
|
directory: 'directory_123',
|
|
90
154
|
});
|
|
91
|
-
expect(
|
|
155
|
+
expect(list).toEqual(userWithGroupListResponse);
|
|
92
156
|
}));
|
|
93
157
|
describe('with custom attributes', () => {
|
|
94
158
|
it('returns the custom attributes, using the provided type', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
95
|
-
mock
|
|
159
|
+
mock
|
|
160
|
+
.onGet('/directory_users', {
|
|
161
|
+
directory: 'directory_123',
|
|
162
|
+
})
|
|
163
|
+
.replyOnce(200, {
|
|
96
164
|
data: [
|
|
97
165
|
{
|
|
98
166
|
object: 'directory_user',
|
|
@@ -144,7 +212,6 @@ describe('DirectorySync', () => {
|
|
|
144
212
|
},
|
|
145
213
|
],
|
|
146
214
|
});
|
|
147
|
-
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
148
215
|
const users = yield workos.directorySync.listUsers({
|
|
149
216
|
directory: 'directory_123',
|
|
150
217
|
});
|
|
@@ -158,32 +225,25 @@ describe('DirectorySync', () => {
|
|
|
158
225
|
});
|
|
159
226
|
describe('with a Group', () => {
|
|
160
227
|
it(`requests a Directory's Users`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
161
|
-
mock
|
|
162
|
-
|
|
163
|
-
yield workos.directorySync.listUsers({
|
|
228
|
+
mock
|
|
229
|
+
.onGet('/directory_users', {
|
|
164
230
|
group: 'directory_grp_123',
|
|
165
|
-
})
|
|
166
|
-
|
|
231
|
+
})
|
|
232
|
+
.replyOnce(200, userWithGroupListResponse);
|
|
233
|
+
const list = yield workos.directorySync.listUsers({
|
|
167
234
|
group: 'directory_grp_123',
|
|
168
235
|
});
|
|
169
|
-
expect(
|
|
236
|
+
expect(list).toEqual(userWithGroupListResponse);
|
|
170
237
|
}));
|
|
171
238
|
});
|
|
172
239
|
});
|
|
173
240
|
describe('getUser', () => {
|
|
174
241
|
it(`requests a Directory User`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
175
|
-
mock
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
});
|
|
181
|
-
describe('getGroup', () => {
|
|
182
|
-
it(`requests a Directory Group`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
183
|
-
mock.onGet().reply(200, {});
|
|
184
|
-
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
185
|
-
yield workos.directorySync.getGroup('dir_grp_123');
|
|
186
|
-
expect(mock.history.get[0].url).toEqual('/directory_groups/dir_grp_123');
|
|
242
|
+
mock
|
|
243
|
+
.onGet('/directory_users/dir_usr_123')
|
|
244
|
+
.replyOnce(200, userWithGroupResponse);
|
|
245
|
+
const user = yield workos.directorySync.getUser('dir_usr_123');
|
|
246
|
+
expect(user).toEqual(userWithGroupResponse);
|
|
187
247
|
}));
|
|
188
248
|
});
|
|
189
249
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Group } from './group.interface';
|
|
2
2
|
export declare type DefaultCustomAttributes = Record<string, unknown>;
|
|
3
|
-
export interface User<TCustomAttributes extends object = DefaultCustomAttributes> {
|
|
3
|
+
export interface User<TCustomAttributes extends object = DefaultCustomAttributes, TRawAttributes = any> {
|
|
4
4
|
id: string;
|
|
5
5
|
directory_id: string;
|
|
6
|
-
raw_attributes:
|
|
6
|
+
raw_attributes: TRawAttributes;
|
|
7
7
|
custom_attributes: TCustomAttributes;
|
|
8
8
|
idp_id: string;
|
|
9
9
|
first_name: string;
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { Challenge } from './challenge.interface';
|
|
2
|
-
export interface
|
|
2
|
+
export interface VerifyResponse {
|
|
3
3
|
challenge: Challenge;
|
|
4
4
|
valid: boolean;
|
|
5
5
|
}
|
|
6
|
-
export interface VerifyResponseError {
|
|
7
|
-
code: string;
|
|
8
|
-
message: string;
|
|
9
|
-
}
|
|
10
|
-
export declare type VerifyResponse = VerifyResponseSuccess | VerifyResponseError;
|
package/lib/mfa/mfa.js
CHANGED
|
@@ -27,19 +27,39 @@ class Mfa {
|
|
|
27
27
|
}
|
|
28
28
|
enrollFactor(options) {
|
|
29
29
|
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
-
const { data } = yield this.workos.post('/auth/factors/enroll', options)
|
|
30
|
+
const { data } = yield this.workos.post('/auth/factors/enroll', Object.assign({ type: options.type }, (() => {
|
|
31
|
+
switch (options.type) {
|
|
32
|
+
case 'sms':
|
|
33
|
+
return {
|
|
34
|
+
phone_number: options.phoneNumber,
|
|
35
|
+
};
|
|
36
|
+
case 'totp':
|
|
37
|
+
return {
|
|
38
|
+
totp_issuer: options.issuer,
|
|
39
|
+
totp_user: options.user,
|
|
40
|
+
};
|
|
41
|
+
default:
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
})()));
|
|
31
45
|
return data;
|
|
32
46
|
});
|
|
33
47
|
}
|
|
34
48
|
challengeFactor(options) {
|
|
35
49
|
return __awaiter(this, void 0, void 0, function* () {
|
|
36
|
-
const { data } = yield this.workos.post('/auth/factors/challenge',
|
|
50
|
+
const { data } = yield this.workos.post('/auth/factors/challenge', {
|
|
51
|
+
authentication_factor_id: options.authenticationFactorId,
|
|
52
|
+
sms_template: 'smsTemplate' in options ? options.smsTemplate : undefined,
|
|
53
|
+
});
|
|
37
54
|
return data;
|
|
38
55
|
});
|
|
39
56
|
}
|
|
40
57
|
verifyFactor(options) {
|
|
41
58
|
return __awaiter(this, void 0, void 0, function* () {
|
|
42
|
-
const { data } = yield this.workos.post('/auth/factors/verify',
|
|
59
|
+
const { data } = yield this.workos.post('/auth/factors/verify', {
|
|
60
|
+
authentication_challenge_id: options.authenticationChallengeId,
|
|
61
|
+
code: options.code,
|
|
62
|
+
});
|
|
43
63
|
return data;
|
|
44
64
|
});
|
|
45
65
|
}
|
package/lib/mfa/mfa.spec.js
CHANGED
|
@@ -14,6 +14,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
const axios_1 = __importDefault(require("axios"));
|
|
16
16
|
const axios_mock_adapter_1 = __importDefault(require("axios-mock-adapter"));
|
|
17
|
+
const exceptions_1 = require("../common/exceptions");
|
|
17
18
|
const workos_1 = require("../workos");
|
|
18
19
|
describe('MFA', () => {
|
|
19
20
|
describe('getFactor', () => {
|
|
@@ -44,7 +45,6 @@ describe('MFA', () => {
|
|
|
44
45
|
created_at: '2022-03-15T20:39:19.892Z',
|
|
45
46
|
updated_at: '2022-03-15T20:39:19.892Z',
|
|
46
47
|
type: 'generic_otp',
|
|
47
|
-
environment_id: 'environment_1234',
|
|
48
48
|
});
|
|
49
49
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU', {
|
|
50
50
|
apiHostname: 'api.workos.dev',
|
|
@@ -55,7 +55,6 @@ describe('MFA', () => {
|
|
|
55
55
|
expect(enrollResponse).toMatchInlineSnapshot(`
|
|
56
56
|
Object {
|
|
57
57
|
"created_at": "2022-03-15T20:39:19.892Z",
|
|
58
|
-
"environment_id": "environment_1234",
|
|
59
58
|
"id": "auth_factor_1234",
|
|
60
59
|
"object": "authentication_factor",
|
|
61
60
|
"type": "generic_otp",
|
|
@@ -73,7 +72,6 @@ describe('MFA', () => {
|
|
|
73
72
|
created_at: '2022-03-15T20:39:19.892Z',
|
|
74
73
|
updated_at: '2022-03-15T20:39:19.892Z',
|
|
75
74
|
type: 'totp',
|
|
76
|
-
environment_id: 'environment_1234',
|
|
77
75
|
totp: {
|
|
78
76
|
qr_code: 'qr-code-test',
|
|
79
77
|
secret: 'secret-test',
|
|
@@ -90,7 +88,6 @@ describe('MFA', () => {
|
|
|
90
88
|
expect(enrollResponse).toMatchInlineSnapshot(`
|
|
91
89
|
Object {
|
|
92
90
|
"created_at": "2022-03-15T20:39:19.892Z",
|
|
93
|
-
"environment_id": "environment_1234",
|
|
94
91
|
"id": "auth_factor_1234",
|
|
95
92
|
"object": "authentication_factor",
|
|
96
93
|
"totp": Object {
|
|
@@ -112,7 +109,6 @@ describe('MFA', () => {
|
|
|
112
109
|
created_at: '2022-03-15T20:39:19.892Z',
|
|
113
110
|
updated_at: '2022-03-15T20:39:19.892Z',
|
|
114
111
|
type: 'sms',
|
|
115
|
-
environment_id: 'environment_1234',
|
|
116
112
|
sms: {
|
|
117
113
|
phone_number: '+15555555555',
|
|
118
114
|
},
|
|
@@ -127,7 +123,6 @@ describe('MFA', () => {
|
|
|
127
123
|
expect(enrollResponse).toMatchInlineSnapshot(`
|
|
128
124
|
Object {
|
|
129
125
|
"created_at": "2022-03-15T20:39:19.892Z",
|
|
130
|
-
"environment_id": "environment_1234",
|
|
131
126
|
"id": "auth_factor_1234",
|
|
132
127
|
"object": "authentication_factor",
|
|
133
128
|
"sms": Object {
|
|
@@ -138,13 +133,35 @@ describe('MFA', () => {
|
|
|
138
133
|
}
|
|
139
134
|
`);
|
|
140
135
|
}));
|
|
136
|
+
describe('when phone number is invalid', () => {
|
|
137
|
+
it('throws an exception', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
138
|
+
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
139
|
+
mock.onPost('/auth/factors/enroll').reply(422, {
|
|
140
|
+
message: `Phone number is invalid: 'foo'`,
|
|
141
|
+
code: 'invalid_phone_number',
|
|
142
|
+
}, {
|
|
143
|
+
'X-Request-ID': 'req_123',
|
|
144
|
+
});
|
|
145
|
+
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU', {
|
|
146
|
+
apiHostname: 'api.workos.dev',
|
|
147
|
+
});
|
|
148
|
+
yield expect(workos.mfa.enrollFactor({
|
|
149
|
+
type: 'sms',
|
|
150
|
+
phoneNumber: 'foo',
|
|
151
|
+
})).rejects.toThrow(exceptions_1.UnprocessableEntityException);
|
|
152
|
+
}));
|
|
153
|
+
});
|
|
141
154
|
});
|
|
142
155
|
});
|
|
143
156
|
describe('challengeFactor', () => {
|
|
144
157
|
describe('with no sms template', () => {
|
|
145
158
|
it('challenge a factor with no sms template', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
146
159
|
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
147
|
-
mock
|
|
160
|
+
mock
|
|
161
|
+
.onPost('/auth/factors/challenge', {
|
|
162
|
+
authentication_factor_id: 'auth_factor_1234',
|
|
163
|
+
})
|
|
164
|
+
.reply(200, {
|
|
148
165
|
object: 'authentication_challenge',
|
|
149
166
|
id: 'auth_challenge_1234',
|
|
150
167
|
created_at: '2022-03-15T20:39:19.892Z',
|
|
@@ -157,7 +174,7 @@ describe('MFA', () => {
|
|
|
157
174
|
apiHostname: 'api.workos.dev',
|
|
158
175
|
});
|
|
159
176
|
const challengeResponse = yield workos.mfa.challengeFactor({
|
|
160
|
-
|
|
177
|
+
authenticationFactorId: 'auth_factor_1234',
|
|
161
178
|
});
|
|
162
179
|
expect(challengeResponse).toMatchInlineSnapshot(`
|
|
163
180
|
Object {
|
|
@@ -172,10 +189,15 @@ describe('MFA', () => {
|
|
|
172
189
|
`);
|
|
173
190
|
}));
|
|
174
191
|
});
|
|
175
|
-
describe('with
|
|
192
|
+
describe('with sms template', () => {
|
|
176
193
|
it('challenge a factor with sms template', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
177
194
|
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
178
|
-
mock
|
|
195
|
+
mock
|
|
196
|
+
.onPost('/auth/factors/challenge', {
|
|
197
|
+
authentication_factor_id: 'auth_factor_1234',
|
|
198
|
+
sms_template: 'This is your code: 12345',
|
|
199
|
+
})
|
|
200
|
+
.reply(200, {
|
|
179
201
|
object: 'authentication_challenge',
|
|
180
202
|
id: 'auth_challenge_1234',
|
|
181
203
|
created_at: '2022-03-15T20:39:19.892Z',
|
|
@@ -188,8 +210,8 @@ describe('MFA', () => {
|
|
|
188
210
|
apiHostname: 'api.workos.dev',
|
|
189
211
|
});
|
|
190
212
|
const challengeResponse = yield workos.mfa.challengeFactor({
|
|
191
|
-
|
|
192
|
-
|
|
213
|
+
authenticationFactorId: 'auth_factor_1234',
|
|
214
|
+
smsTemplate: 'This is your code: 12345',
|
|
193
215
|
});
|
|
194
216
|
expect(challengeResponse).toMatchInlineSnapshot(`
|
|
195
217
|
Object {
|
|
@@ -209,7 +231,12 @@ describe('MFA', () => {
|
|
|
209
231
|
describe('verify with successful response', () => {
|
|
210
232
|
it('verifies a successful factor', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
211
233
|
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
212
|
-
mock
|
|
234
|
+
mock
|
|
235
|
+
.onPost('/auth/factors/verify', {
|
|
236
|
+
authentication_challenge_id: 'auth_challenge_1234',
|
|
237
|
+
code: '12345',
|
|
238
|
+
})
|
|
239
|
+
.reply(200, {
|
|
213
240
|
challenge: {
|
|
214
241
|
object: 'authentication_challenge',
|
|
215
242
|
id: 'auth_challenge_1234',
|
|
@@ -225,7 +252,7 @@ describe('MFA', () => {
|
|
|
225
252
|
apiHostname: 'api.workos.dev',
|
|
226
253
|
});
|
|
227
254
|
const verifyResponse = yield workos.mfa.verifyFactor({
|
|
228
|
-
|
|
255
|
+
authenticationChallengeId: 'auth_challenge_1234',
|
|
229
256
|
code: '12345',
|
|
230
257
|
});
|
|
231
258
|
expect(verifyResponse).toMatchInlineSnapshot(`
|
|
@@ -244,5 +271,79 @@ describe('MFA', () => {
|
|
|
244
271
|
`);
|
|
245
272
|
}));
|
|
246
273
|
});
|
|
274
|
+
describe('when the challenge has been previously verified', () => {
|
|
275
|
+
it('throws an exception', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
276
|
+
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
277
|
+
mock
|
|
278
|
+
.onPost('/auth/factors/verify', {
|
|
279
|
+
authentication_challenge_id: 'auth_challenge_1234',
|
|
280
|
+
code: '12345',
|
|
281
|
+
})
|
|
282
|
+
.reply(422, {
|
|
283
|
+
message: `The authentication challenge '12345' has already been verified.`,
|
|
284
|
+
code: 'authentication_challenge_previously_verified',
|
|
285
|
+
}, {
|
|
286
|
+
'X-Request-ID': 'req_123',
|
|
287
|
+
});
|
|
288
|
+
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU', {
|
|
289
|
+
apiHostname: 'api.workos.dev',
|
|
290
|
+
});
|
|
291
|
+
yield expect(workos.mfa.verifyFactor({
|
|
292
|
+
authenticationChallengeId: 'auth_challenge_1234',
|
|
293
|
+
code: '12345',
|
|
294
|
+
})).rejects.toThrow(exceptions_1.UnprocessableEntityException);
|
|
295
|
+
}));
|
|
296
|
+
});
|
|
297
|
+
describe('when the challenge has expired', () => {
|
|
298
|
+
it('throws an exception', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
299
|
+
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
300
|
+
mock
|
|
301
|
+
.onPost('/auth/factors/verify', {
|
|
302
|
+
authentication_challenge_id: 'auth_challenge_1234',
|
|
303
|
+
code: '12345',
|
|
304
|
+
})
|
|
305
|
+
.reply(422, {
|
|
306
|
+
message: `The authentication challenge '12345' has expired.`,
|
|
307
|
+
code: 'authentication_challenge_expired',
|
|
308
|
+
}, {
|
|
309
|
+
'X-Request-ID': 'req_123',
|
|
310
|
+
});
|
|
311
|
+
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU', {
|
|
312
|
+
apiHostname: 'api.workos.dev',
|
|
313
|
+
});
|
|
314
|
+
yield expect(workos.mfa.verifyFactor({
|
|
315
|
+
authenticationChallengeId: 'auth_challenge_1234',
|
|
316
|
+
code: '12345',
|
|
317
|
+
})).rejects.toThrow(exceptions_1.UnprocessableEntityException);
|
|
318
|
+
}));
|
|
319
|
+
it('exception has code', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
320
|
+
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
321
|
+
mock
|
|
322
|
+
.onPost('/auth/factors/verify', {
|
|
323
|
+
authentication_challenge_id: 'auth_challenge_1234',
|
|
324
|
+
code: '12345',
|
|
325
|
+
})
|
|
326
|
+
.reply(422, {
|
|
327
|
+
message: `The authentication challenge '12345' has expired.`,
|
|
328
|
+
code: 'authentication_challenge_expired',
|
|
329
|
+
}, {
|
|
330
|
+
'X-Request-ID': 'req_123',
|
|
331
|
+
});
|
|
332
|
+
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU', {
|
|
333
|
+
apiHostname: 'api.workos.dev',
|
|
334
|
+
});
|
|
335
|
+
try {
|
|
336
|
+
yield workos.mfa.verifyFactor({
|
|
337
|
+
authenticationChallengeId: 'auth_challenge_1234',
|
|
338
|
+
code: '12345',
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
expect(error).toMatchObject({
|
|
343
|
+
code: 'authentication_challenge_expired',
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}));
|
|
347
|
+
});
|
|
247
348
|
});
|
|
248
349
|
});
|
|
@@ -27,7 +27,7 @@ describe('Organizations', () => {
|
|
|
27
27
|
describe('listOrganizations', () => {
|
|
28
28
|
describe('without any options', () => {
|
|
29
29
|
it('returns organizations and metadata', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
30
|
-
mock.onGet().
|
|
30
|
+
mock.onGet('/organizations').replyOnce(200, list_organizations_json_1.default);
|
|
31
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');
|
|
@@ -40,7 +40,11 @@ describe('Organizations', () => {
|
|
|
40
40
|
});
|
|
41
41
|
describe('with the domain option', () => {
|
|
42
42
|
it('forms the proper request to the API', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
43
|
-
mock
|
|
43
|
+
mock
|
|
44
|
+
.onGet('/organizations', {
|
|
45
|
+
domains: ['example.com'],
|
|
46
|
+
})
|
|
47
|
+
.replyOnce(200, list_organizations_json_1.default);
|
|
44
48
|
const { data } = yield workos.organizations.listOrganizations({
|
|
45
49
|
domains: ['example.com'],
|
|
46
50
|
});
|
|
@@ -53,7 +57,11 @@ describe('Organizations', () => {
|
|
|
53
57
|
});
|
|
54
58
|
describe('with the before option', () => {
|
|
55
59
|
it('forms the proper request to the API', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
56
|
-
mock
|
|
60
|
+
mock
|
|
61
|
+
.onGet('/organizations', {
|
|
62
|
+
before: 'before-id',
|
|
63
|
+
})
|
|
64
|
+
.replyOnce(200, list_organizations_json_1.default);
|
|
57
65
|
const { data } = yield workos.organizations.listOrganizations({
|
|
58
66
|
before: 'before-id',
|
|
59
67
|
});
|
|
@@ -66,7 +74,11 @@ describe('Organizations', () => {
|
|
|
66
74
|
});
|
|
67
75
|
describe('with the after option', () => {
|
|
68
76
|
it('forms the proper request to the API', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
69
|
-
mock
|
|
77
|
+
mock
|
|
78
|
+
.onGet('/organizations', {
|
|
79
|
+
after: 'after-id',
|
|
80
|
+
})
|
|
81
|
+
.replyOnce(200, list_organizations_json_1.default);
|
|
70
82
|
const { data } = yield workos.organizations.listOrganizations({
|
|
71
83
|
after: 'after-id',
|
|
72
84
|
});
|
|
@@ -79,7 +91,11 @@ describe('Organizations', () => {
|
|
|
79
91
|
});
|
|
80
92
|
describe('with the limit option', () => {
|
|
81
93
|
it('forms the proper request to the API', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
82
|
-
mock
|
|
94
|
+
mock
|
|
95
|
+
.onGet('/organizations', {
|
|
96
|
+
limit: 10,
|
|
97
|
+
})
|
|
98
|
+
.replyOnce(200, list_organizations_json_1.default);
|
|
83
99
|
const { data } = yield workos.organizations.listOrganizations({
|
|
84
100
|
limit: 10,
|
|
85
101
|
});
|
|
@@ -94,7 +110,12 @@ describe('Organizations', () => {
|
|
|
94
110
|
describe('createOrganization', () => {
|
|
95
111
|
describe('with a valid payload', () => {
|
|
96
112
|
it('creates an organization', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
97
|
-
mock
|
|
113
|
+
mock
|
|
114
|
+
.onPost('/organizations', {
|
|
115
|
+
domains: ['example.com'],
|
|
116
|
+
name: 'Test Organization',
|
|
117
|
+
})
|
|
118
|
+
.replyOnce(201, create_organization_json_1.default);
|
|
98
119
|
const subject = yield workos.organizations.createOrganization({
|
|
99
120
|
domains: ['example.com'],
|
|
100
121
|
name: 'Test Organization',
|
|
@@ -106,7 +127,12 @@ describe('Organizations', () => {
|
|
|
106
127
|
});
|
|
107
128
|
describe('with an invalid payload', () => {
|
|
108
129
|
it('returns an error', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
109
|
-
mock
|
|
130
|
+
mock
|
|
131
|
+
.onPost('/organizations', {
|
|
132
|
+
domains: ['example.com'],
|
|
133
|
+
name: 'Test Organization',
|
|
134
|
+
})
|
|
135
|
+
.replyOnce(409, create_organization_invalid_json_1.default, {
|
|
110
136
|
'X-Request-ID': 'a-request-id',
|
|
111
137
|
});
|
|
112
138
|
yield expect(workos.organizations.createOrganization({
|
|
@@ -119,7 +145,9 @@ describe('Organizations', () => {
|
|
|
119
145
|
describe('getOrganization', () => {
|
|
120
146
|
it(`requests an Organization`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
121
147
|
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
122
|
-
mock
|
|
148
|
+
mock
|
|
149
|
+
.onGet('/organizations/org_01EHT88Z8J8795GZNQ4ZP1J81T')
|
|
150
|
+
.replyOnce(200, get_organization_json_1.default);
|
|
123
151
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
124
152
|
const subject = yield workos.organizations.getOrganization('org_01EHT88Z8J8795GZNQ4ZP1J81T');
|
|
125
153
|
expect(mock.history.get[0].url).toEqual('/organizations/org_01EHT88Z8J8795GZNQ4ZP1J81T');
|
|
@@ -132,7 +160,9 @@ describe('Organizations', () => {
|
|
|
132
160
|
describe('deleteOrganization', () => {
|
|
133
161
|
it('sends request to delete an Organization', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
134
162
|
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
135
|
-
mock
|
|
163
|
+
mock
|
|
164
|
+
.onDelete('/organizations/org_01EHT88Z8J8795GZNQ4ZP1J81T')
|
|
165
|
+
.replyOnce(200, {});
|
|
136
166
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
137
167
|
yield workos.organizations.deleteOrganization('org_01EHT88Z8J8795GZNQ4ZP1J81T');
|
|
138
168
|
expect(mock.history.delete[0].url).toEqual('/organizations/org_01EHT88Z8J8795GZNQ4ZP1J81T');
|
|
@@ -141,7 +171,12 @@ describe('Organizations', () => {
|
|
|
141
171
|
describe('updateOrganization', () => {
|
|
142
172
|
describe('with a valid payload', () => {
|
|
143
173
|
it('updates an organization', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
144
|
-
mock
|
|
174
|
+
mock
|
|
175
|
+
.onPut('/organizations/org_01EHT88Z8J8795GZNQ4ZP1J81T', {
|
|
176
|
+
domains: ['example.com'],
|
|
177
|
+
name: 'Test Organization 2',
|
|
178
|
+
})
|
|
179
|
+
.replyOnce(201, update_organization_json_1.default);
|
|
145
180
|
const subject = yield workos.organizations.updateOrganization({
|
|
146
181
|
organization: 'org_01EHT88Z8J8795GZNQ4ZP1J81T',
|
|
147
182
|
domains: ['example.com'],
|
|
@@ -22,10 +22,16 @@ describe('Passwordless', () => {
|
|
|
22
22
|
describe('createSession', () => {
|
|
23
23
|
describe('with valid options', () => {
|
|
24
24
|
it('creates a passwordless session', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
25
|
-
mock.onPost().reply(201, create_session_json_1.default);
|
|
26
|
-
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
27
25
|
const email = 'passwordless-session-email@workos.com';
|
|
28
26
|
const redirectURI = 'https://example.com/passwordless/callback';
|
|
27
|
+
mock
|
|
28
|
+
.onPost('/passwordless/sessions', {
|
|
29
|
+
type: 'MagicLink',
|
|
30
|
+
email,
|
|
31
|
+
redirect_uri: redirectURI,
|
|
32
|
+
})
|
|
33
|
+
.replyOnce(201, create_session_json_1.default);
|
|
34
|
+
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
29
35
|
const session = yield workos.passwordless.createSession({
|
|
30
36
|
type: 'MagicLink',
|
|
31
37
|
email,
|
|
@@ -42,7 +48,7 @@ describe('Passwordless', () => {
|
|
|
42
48
|
describe('sendEmail', () => {
|
|
43
49
|
describe('with a valid session id', () => {
|
|
44
50
|
it(`sends a request to send a magic link email`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
45
|
-
mock.onPost().
|
|
51
|
+
mock.onPost('/passwordless/sessions/session_123/send').replyOnce(200);
|
|
46
52
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
47
53
|
const sessionId = 'session_123';
|
|
48
54
|
yield workos.passwordless.sendSession(sessionId);
|
|
@@ -26,7 +26,13 @@ describe('Portal', () => {
|
|
|
26
26
|
describe('with a valid organization', () => {
|
|
27
27
|
describe('with the sso intent', () => {
|
|
28
28
|
it('returns an Admin Portal link', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
|
-
mock
|
|
29
|
+
mock
|
|
30
|
+
.onPost('/portal/generate_link', {
|
|
31
|
+
intent: generate_portal_link_intent_interface_1.GeneratePortalLinkIntent.SSO,
|
|
32
|
+
organization: 'org_01EHQMYV6MBK39QC5PZXHY59C3',
|
|
33
|
+
return_url: 'https://www.example.com',
|
|
34
|
+
})
|
|
35
|
+
.replyOnce(201, generate_link_json_1.default);
|
|
30
36
|
const { link } = yield workos.portal.generateLink({
|
|
31
37
|
intent: generate_portal_link_intent_interface_1.GeneratePortalLinkIntent.SSO,
|
|
32
38
|
organization: 'org_01EHQMYV6MBK39QC5PZXHY59C3',
|
|
@@ -37,7 +43,13 @@ describe('Portal', () => {
|
|
|
37
43
|
});
|
|
38
44
|
describe('with the dsync intent', () => {
|
|
39
45
|
it('returns an Admin Portal link', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
40
|
-
mock
|
|
46
|
+
mock
|
|
47
|
+
.onPost('/portal/generate_link', {
|
|
48
|
+
intent: generate_portal_link_intent_interface_1.GeneratePortalLinkIntent.DSync,
|
|
49
|
+
organization: 'org_01EHQMYV6MBK39QC5PZXHY59C3',
|
|
50
|
+
return_url: 'https://www.example.com',
|
|
51
|
+
})
|
|
52
|
+
.reply(201, generate_link_json_1.default);
|
|
41
53
|
const { link } = yield workos.portal.generateLink({
|
|
42
54
|
intent: generate_portal_link_intent_interface_1.GeneratePortalLinkIntent.DSync,
|
|
43
55
|
organization: 'org_01EHQMYV6MBK39QC5PZXHY59C3',
|
|
@@ -49,7 +61,13 @@ describe('Portal', () => {
|
|
|
49
61
|
});
|
|
50
62
|
describe('with an invalid organization', () => {
|
|
51
63
|
it('throws an error', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
52
|
-
mock
|
|
64
|
+
mock
|
|
65
|
+
.onPost('/portal/generate_link', {
|
|
66
|
+
intent: generate_portal_link_intent_interface_1.GeneratePortalLinkIntent.SSO,
|
|
67
|
+
organization: 'bogus-id',
|
|
68
|
+
return_url: 'https://www.example.com',
|
|
69
|
+
})
|
|
70
|
+
.reply(400, generate_link_invalid_json_1.default, {
|
|
53
71
|
'X-Request-ID': 'a-request-id',
|
|
54
72
|
});
|
|
55
73
|
yield expect(workos.portal.generateLink({
|
package/lib/sso/sso.spec.js
CHANGED
|
@@ -134,23 +134,40 @@ describe('SSO', () => {
|
|
|
134
134
|
describe('with all information provided', () => {
|
|
135
135
|
it('sends a request to the WorkOS api for a profile', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
136
136
|
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
137
|
+
const expectedBody = new URLSearchParams({
|
|
138
|
+
client_id: 'proj_123',
|
|
139
|
+
client_secret: 'sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU',
|
|
140
|
+
code: 'authorization_code',
|
|
141
|
+
grant_type: 'authorization_code',
|
|
142
|
+
});
|
|
143
|
+
expectedBody.sort();
|
|
144
|
+
mock.onPost('/sso/token').replyOnce((config) => {
|
|
145
|
+
const actualBody = new URLSearchParams(config.data);
|
|
146
|
+
actualBody.sort();
|
|
147
|
+
if (actualBody.toString() === expectedBody.toString()) {
|
|
148
|
+
return [
|
|
149
|
+
200,
|
|
150
|
+
{
|
|
151
|
+
access_token: '01DMEK0J53CVMC32CK5SE0KZ8Q',
|
|
152
|
+
profile: {
|
|
153
|
+
id: 'prof_123',
|
|
154
|
+
idp_id: '123',
|
|
155
|
+
organization_id: 'org_123',
|
|
156
|
+
connection_id: 'conn_123',
|
|
157
|
+
connection_type: 'OktaSAML',
|
|
158
|
+
email: 'foo@test.com',
|
|
159
|
+
first_name: 'foo',
|
|
160
|
+
last_name: 'bar',
|
|
161
|
+
raw_attributes: {
|
|
162
|
+
email: 'foo@test.com',
|
|
163
|
+
first_name: 'foo',
|
|
164
|
+
last_name: 'bar',
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
}
|
|
170
|
+
return [404];
|
|
154
171
|
});
|
|
155
172
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
156
173
|
const { access_token: accessToken, profile } = yield workos.sso.getProfileAndToken({
|
|
@@ -169,7 +186,11 @@ describe('SSO', () => {
|
|
|
169
186
|
describe('getProfile', () => {
|
|
170
187
|
it('calls the `/sso/profile` endpoint with the provided access token', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
171
188
|
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
172
|
-
mock
|
|
189
|
+
mock
|
|
190
|
+
.onGet('/sso/profile', {
|
|
191
|
+
accessToken: 'access_token',
|
|
192
|
+
})
|
|
193
|
+
.replyOnce(200, {
|
|
173
194
|
id: 'prof_123',
|
|
174
195
|
idp_id: '123',
|
|
175
196
|
organization_id: 'org_123',
|
|
@@ -197,7 +218,7 @@ describe('SSO', () => {
|
|
|
197
218
|
describe('deleteConnection', () => {
|
|
198
219
|
it('sends request to delete a Connection', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
199
220
|
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
200
|
-
mock.onDelete().
|
|
221
|
+
mock.onDelete('/connections/conn_123').replyOnce(200, {});
|
|
201
222
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
202
223
|
yield workos.sso.deleteConnection('conn_123');
|
|
203
224
|
expect(mock.history.delete[0].url).toEqual('/connections/conn_123');
|
|
@@ -206,7 +227,7 @@ describe('SSO', () => {
|
|
|
206
227
|
describe('getConnection', () => {
|
|
207
228
|
it(`requests a Connection`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
208
229
|
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
209
|
-
mock.onGet().
|
|
230
|
+
mock.onGet('/connections/conn_123').replyOnce(200, {});
|
|
210
231
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
211
232
|
yield workos.sso.getConnection('conn_123');
|
|
212
233
|
expect(mock.history.get[0].url).toEqual('/connections/conn_123');
|
|
@@ -215,7 +236,7 @@ describe('SSO', () => {
|
|
|
215
236
|
describe('listConnections', () => {
|
|
216
237
|
it(`requests a list of Connections`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
217
238
|
const mock = new axios_mock_adapter_1.default(axios_1.default);
|
|
218
|
-
mock.onGet().
|
|
239
|
+
mock.onGet('/connections').replyOnce(200, {});
|
|
219
240
|
const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
|
|
220
241
|
yield workos.sso.listConnections();
|
|
221
242
|
expect(mock.history.get[0].url).toEqual('/connections');
|
|
@@ -16,3 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./webhook.interface"), exports);
|
|
18
18
|
__exportStar(require("./webhook-directory.interface"), exports);
|
|
19
|
+
__exportStar(require("./webhook-directory-group.interface"), exports);
|
|
20
|
+
__exportStar(require("./webhook-directory-user.interface"), exports);
|
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
|
|
1
|
+
export declare type WebhookDirectoryType = 'okta scim v1.1' | 'okta scim v2.0' | 'azure scim v2.0' | 'bamboohr' | 'breathe hr' | 'cyberark scim v2.0' | 'fourth hr' | 'gsuite directory' | 'generic scim v1.1' | 'generic scim v2.0' | 'gusto' | 'hibob' | 'jump cloud scim v2.0' | 'onelogin scim v2.0' | 'people hr' | 'pingfederate scim v2.0' | 'rippling' | 's3' | 'workday';
|
|
2
|
+
export declare type WebhookDirectoryState = 'active' | 'validating' | 'invalid_credentials' | 'inactive' | 'deleting';
|
|
3
|
+
interface WebhookDirectoryDomain {
|
|
4
|
+
object: 'organization_domain';
|
|
5
|
+
id: string;
|
|
6
|
+
domain: string;
|
|
4
7
|
}
|
|
8
|
+
export interface WebhookDirectory {
|
|
9
|
+
object: 'directory';
|
|
10
|
+
id: string;
|
|
11
|
+
external_key: string;
|
|
12
|
+
type: WebhookDirectoryType;
|
|
13
|
+
state: WebhookDirectoryState;
|
|
14
|
+
name: string;
|
|
15
|
+
organization_id?: string;
|
|
16
|
+
domains: WebhookDirectoryDomain[];
|
|
17
|
+
created_at: string;
|
|
18
|
+
updated_at: string;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Group, User } from '../../directory-sync/interfaces';
|
|
2
1
|
import { Connection } from '../../sso/interfaces';
|
|
2
|
+
import { WebhookDirectoryGroup as Group } from './webhook-directory-group.interface';
|
|
3
|
+
import { WebhookDirectoryUser as User } from './webhook-directory-user.interface';
|
|
3
4
|
import { WebhookDirectory as Directory } from './webhook-directory.interface';
|
|
4
5
|
interface WebhookBase {
|
|
5
6
|
id: string;
|
|
@@ -26,7 +27,7 @@ export interface DsyncDeactivatedWebhook extends WebhookBase {
|
|
|
26
27
|
}
|
|
27
28
|
export interface DsyncDeletedWebhook extends WebhookBase {
|
|
28
29
|
event: 'dsync.deleted';
|
|
29
|
-
data: Directory
|
|
30
|
+
data: Omit<Directory, 'domains' | 'external_key'>;
|
|
30
31
|
}
|
|
31
32
|
export interface DsyncGroupCreatedWebhook extends WebhookBase {
|
|
32
33
|
event: 'dsync.group.created';
|
|
@@ -38,14 +39,14 @@ export interface DsyncGroupDeletedWebhook extends WebhookBase {
|
|
|
38
39
|
}
|
|
39
40
|
export interface DsyncGroupUpdatedWebhook extends WebhookBase {
|
|
40
41
|
event: 'dsync.group.updated';
|
|
41
|
-
data: Group
|
|
42
|
+
data: Group & Record<'previous_attributes', any>;
|
|
42
43
|
}
|
|
43
44
|
export interface DsyncGroupUserAddedWebhook extends WebhookBase {
|
|
44
45
|
event: 'dsync.group.user_added';
|
|
45
46
|
data: {
|
|
46
47
|
directory_id: string;
|
|
47
48
|
user: User;
|
|
48
|
-
group: Group
|
|
49
|
+
group: Pick<Group, 'id' | 'name'>;
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
export interface DsyncGroupUserRemovedWebhook extends WebhookBase {
|
|
@@ -53,7 +54,7 @@ export interface DsyncGroupUserRemovedWebhook extends WebhookBase {
|
|
|
53
54
|
data: {
|
|
54
55
|
directory_id: string;
|
|
55
56
|
user: User;
|
|
56
|
-
group: Group
|
|
57
|
+
group: Pick<Group, 'id' | 'name'>;
|
|
57
58
|
};
|
|
58
59
|
}
|
|
59
60
|
export interface DsyncUserCreatedWebhook extends WebhookBase {
|
|
@@ -66,7 +67,7 @@ export interface DsyncUserDeletedWebhook extends WebhookBase {
|
|
|
66
67
|
}
|
|
67
68
|
export interface DsyncUserUpdatedWebhook extends WebhookBase {
|
|
68
69
|
event: 'dsync.user.updated';
|
|
69
|
-
data: User
|
|
70
|
+
data: User & Record<'previous_attributes', any>;
|
|
70
71
|
}
|
|
71
72
|
export declare type Webhook = ConnectionActivatedWebhook | ConnectionDeactivatedWebhook | ConnectionDeletedWebhook | DsyncActivatedWebhook | DsyncDeactivatedWebhook | DsyncDeletedWebhook | DsyncGroupCreatedWebhook | DsyncGroupUpdatedWebhook | DsyncGroupDeletedWebhook | DsyncGroupUserAddedWebhook | DsyncGroupUserRemovedWebhook | DsyncUserCreatedWebhook | DsyncUserUpdatedWebhook | DsyncUserDeletedWebhook;
|
|
72
73
|
export {};
|
package/lib/workos.js
CHANGED
|
@@ -23,7 +23,7 @@ const portal_1 = require("./portal/portal");
|
|
|
23
23
|
const sso_1 = require("./sso/sso");
|
|
24
24
|
const webhooks_1 = require("./webhooks/webhooks");
|
|
25
25
|
const mfa_1 = require("./mfa/mfa");
|
|
26
|
-
const VERSION = '2.
|
|
26
|
+
const VERSION = '2.6.1';
|
|
27
27
|
const DEFAULT_HOSTNAME = 'api.workos.com';
|
|
28
28
|
class WorkOS {
|
|
29
29
|
constructor(key, options = {}) {
|
|
@@ -78,14 +78,19 @@ class WorkOS {
|
|
|
78
78
|
if (response) {
|
|
79
79
|
const { status, data, headers } = response;
|
|
80
80
|
const requestID = headers['X-Request-ID'];
|
|
81
|
-
const {
|
|
81
|
+
const { code, error_description: errorDescription, error, message, } = data;
|
|
82
82
|
switch (status) {
|
|
83
83
|
case 401: {
|
|
84
84
|
throw new exceptions_1.UnauthorizedException(requestID);
|
|
85
85
|
}
|
|
86
86
|
case 422: {
|
|
87
87
|
const { errors } = data;
|
|
88
|
-
throw new exceptions_1.UnprocessableEntityException(
|
|
88
|
+
throw new exceptions_1.UnprocessableEntityException({
|
|
89
|
+
code,
|
|
90
|
+
errors,
|
|
91
|
+
message,
|
|
92
|
+
requestID,
|
|
93
|
+
});
|
|
89
94
|
}
|
|
90
95
|
case 404: {
|
|
91
96
|
throw new exceptions_1.NotFoundException(path, requestID);
|
|
@@ -122,14 +127,19 @@ class WorkOS {
|
|
|
122
127
|
if (response) {
|
|
123
128
|
const { status, data, headers } = response;
|
|
124
129
|
const requestID = headers['X-Request-ID'];
|
|
125
|
-
const {
|
|
130
|
+
const { code, error_description: errorDescription, error, message, } = data;
|
|
126
131
|
switch (status) {
|
|
127
132
|
case 401: {
|
|
128
133
|
throw new exceptions_1.UnauthorizedException(requestID);
|
|
129
134
|
}
|
|
130
135
|
case 422: {
|
|
131
136
|
const { errors } = data;
|
|
132
|
-
throw new exceptions_1.UnprocessableEntityException(
|
|
137
|
+
throw new exceptions_1.UnprocessableEntityException({
|
|
138
|
+
code,
|
|
139
|
+
errors,
|
|
140
|
+
message,
|
|
141
|
+
requestID,
|
|
142
|
+
});
|
|
133
143
|
}
|
|
134
144
|
case 404: {
|
|
135
145
|
throw new exceptions_1.NotFoundException(path, requestID);
|
|
@@ -165,14 +175,19 @@ class WorkOS {
|
|
|
165
175
|
if (response) {
|
|
166
176
|
const { status, data, headers } = response;
|
|
167
177
|
const requestID = headers['X-Request-ID'];
|
|
168
|
-
const {
|
|
178
|
+
const { code, error_description: errorDescription, error, message, } = data;
|
|
169
179
|
switch (status) {
|
|
170
180
|
case 401: {
|
|
171
181
|
throw new exceptions_1.UnauthorizedException(requestID);
|
|
172
182
|
}
|
|
173
183
|
case 422: {
|
|
174
184
|
const { errors } = data;
|
|
175
|
-
throw new exceptions_1.UnprocessableEntityException(
|
|
185
|
+
throw new exceptions_1.UnprocessableEntityException({
|
|
186
|
+
code,
|
|
187
|
+
errors,
|
|
188
|
+
message,
|
|
189
|
+
requestID,
|
|
190
|
+
});
|
|
176
191
|
}
|
|
177
192
|
case 404: {
|
|
178
193
|
throw new exceptions_1.NotFoundException(path, requestID);
|
|
@@ -203,14 +218,19 @@ class WorkOS {
|
|
|
203
218
|
if (response) {
|
|
204
219
|
const { status, data, headers } = response;
|
|
205
220
|
const requestID = headers['X-Request-ID'];
|
|
206
|
-
const {
|
|
221
|
+
const { code, error_description: errorDescription, error, message, } = data;
|
|
207
222
|
switch (status) {
|
|
208
223
|
case 401: {
|
|
209
224
|
throw new exceptions_1.UnauthorizedException(requestID);
|
|
210
225
|
}
|
|
211
226
|
case 422: {
|
|
212
227
|
const { errors } = data;
|
|
213
|
-
throw new exceptions_1.UnprocessableEntityException(
|
|
228
|
+
throw new exceptions_1.UnprocessableEntityException({
|
|
229
|
+
code,
|
|
230
|
+
errors,
|
|
231
|
+
message,
|
|
232
|
+
requestID,
|
|
233
|
+
});
|
|
214
234
|
}
|
|
215
235
|
case 404: {
|
|
216
236
|
throw new exceptions_1.NotFoundException(path, requestID);
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.
|
|
2
|
+
"version": "2.6.1",
|
|
3
3
|
"name": "@workos-inc/node",
|
|
4
4
|
"author": "WorkOS",
|
|
5
5
|
"description": "A Node wrapper for the WorkOS API",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"workos"
|
|
10
10
|
],
|
|
11
11
|
"volta": {
|
|
12
|
-
"node": "14.19.
|
|
12
|
+
"node": "14.19.3",
|
|
13
13
|
"yarn": "1.22.18"
|
|
14
14
|
},
|
|
15
15
|
"main": "lib/index.js",
|
|
@@ -39,15 +39,15 @@
|
|
|
39
39
|
"query-string": "7.1.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@types/jest": "27.
|
|
43
|
-
"@types/node": "14.18.
|
|
42
|
+
"@types/jest": "27.5.1",
|
|
43
|
+
"@types/node": "14.18.18",
|
|
44
44
|
"@types/pluralize": "0.0.29",
|
|
45
45
|
"axios-mock-adapter": "1.20.0",
|
|
46
46
|
"jest": "27.5.1",
|
|
47
|
-
"prettier": "2.6.
|
|
48
|
-
"supertest": "6.2.
|
|
49
|
-
"ts-jest": "27.1.
|
|
47
|
+
"prettier": "2.6.2",
|
|
48
|
+
"supertest": "6.2.3",
|
|
49
|
+
"ts-jest": "27.1.5",
|
|
50
50
|
"tslint": "6.1.3",
|
|
51
|
-
"typescript": "4.6.
|
|
51
|
+
"typescript": "4.6.4"
|
|
52
52
|
}
|
|
53
53
|
}
|