@scalekit-sdk/node 2.0.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/jest.config.js +15 -0
- package/lib/core.d.ts +1 -1
- package/lib/core.js +31 -31
- package/lib/core.js.map +1 -1
- package/lib/errors/base-exception.d.ts +32 -0
- package/lib/errors/base-exception.js +238 -0
- package/lib/errors/base-exception.js.map +1 -0
- package/lib/errors/index.d.ts +2 -0
- package/lib/errors/index.js +20 -0
- package/lib/errors/index.js.map +1 -0
- package/lib/errors/specific-exceptions.d.ts +39 -0
- package/lib/errors/specific-exceptions.js +90 -0
- package/lib/errors/specific-exceptions.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.d.ts +34 -87
- package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.js +31 -120
- package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.d.ts +19 -10
- package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.js +18 -9
- package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.d.ts +209 -6
- package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.js +272 -5
- package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/domains/domains_pb.d.ts +29 -0
- package/lib/pkg/grpc/scalekit/v1/domains/domains_pb.js +40 -1
- package/lib/pkg/grpc/scalekit/v1/domains/domains_pb.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/errdetails/errdetails_pb.d.ts +25 -0
- package/lib/pkg/grpc/scalekit/v1/errdetails/errdetails_pb.js +38 -1
- package/lib/pkg/grpc/scalekit/v1/errdetails/errdetails_pb.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_connect.d.ts +21 -1
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_connect.js +20 -0
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_connect.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.d.ts +110 -5
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.js +164 -5
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/users/users_connect.d.ts +48 -1
- package/lib/pkg/grpc/scalekit/v1/users/users_connect.js +47 -0
- package/lib/pkg/grpc/scalekit/v1/users/users_connect.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/users/users_pb.d.ts +280 -4
- package/lib/pkg/grpc/scalekit/v1/users/users_pb.js +449 -11
- package/lib/pkg/grpc/scalekit/v1/users/users_pb.js.map +1 -1
- package/lib/scalekit.d.ts +29 -8
- package/lib/scalekit.js +78 -28
- package/lib/scalekit.js.map +1 -1
- package/lib/types/scalekit.d.ts +5 -0
- package/lib/types/user.d.ts +1 -1
- package/lib/user.d.ts +10 -3
- package/lib/user.js +26 -5
- package/lib/user.js.map +1 -1
- package/package.json +6 -2
- package/src/core.ts +31 -32
- package/src/errors/base-exception.ts +262 -0
- package/src/errors/index.ts +3 -0
- package/src/errors/specific-exceptions.ts +88 -0
- package/src/index.ts +3 -1
- package/src/pkg/grpc/scalekit/v1/commons/commons_pb.ts +49 -129
- package/src/pkg/grpc/scalekit/v1/connections/connections_connect.ts +19 -10
- package/src/pkg/grpc/scalekit/v1/connections/connections_pb.ts +377 -8
- package/src/pkg/grpc/scalekit/v1/domains/domains_pb.ts +44 -0
- package/src/pkg/grpc/scalekit/v1/errdetails/errdetails_pb.ts +49 -0
- package/src/pkg/grpc/scalekit/v1/organizations/organizations_connect.ts +21 -1
- package/src/pkg/grpc/scalekit/v1/organizations/organizations_pb.ts +218 -5
- package/src/pkg/grpc/scalekit/v1/users/users_connect.ts +48 -1
- package/src/pkg/grpc/scalekit/v1/users/users_pb.ts +558 -6
- package/src/scalekit.ts +95 -30
- package/src/types/scalekit.ts +6 -0
- package/src/types/user.ts +1 -1
- package/src/user.ts +34 -7
- package/tests/README.md +25 -0
- package/tests/connection.test.ts +42 -0
- package/tests/directory.test.ts +46 -0
- package/tests/organization.test.ts +65 -0
- package/tests/passwordless.test.ts +108 -0
- package/tests/scalekit.test.ts +104 -0
- package/tests/setup.ts +34 -0
- package/tests/users.test.ts +168 -0
- package/tests/utils/test-data.ts +248 -0
package/src/scalekit.ts
CHANGED
|
@@ -11,7 +11,8 @@ import OrganizationClient from './organization';
|
|
|
11
11
|
import PasswordlessClient from './passwordless';
|
|
12
12
|
import UserClient from './user';
|
|
13
13
|
import { IdpInitiatedLoginClaims, IdTokenClaim, User } from './types/auth';
|
|
14
|
-
import { AuthenticationOptions, AuthenticationResponse, AuthorizationUrlOptions, GrantType, LogoutUrlOptions, RefreshTokenResponse } from './types/scalekit';
|
|
14
|
+
import { AuthenticationOptions, AuthenticationResponse, AuthorizationUrlOptions, GrantType, LogoutUrlOptions, RefreshTokenResponse ,TokenValidationOptions } from './types/scalekit';
|
|
15
|
+
import { WebhookVerificationError, ScalekitValidateTokenFailureException } from './errors/base-exception';
|
|
15
16
|
|
|
16
17
|
const authorizeEndpoint = "oauth/authorize";
|
|
17
18
|
const logoutEndpoint = "oidc/logout";
|
|
@@ -175,27 +176,31 @@ export default class ScalekitClient {
|
|
|
175
176
|
* Get the idp initiated login claims
|
|
176
177
|
*
|
|
177
178
|
* @param {string} idpInitiatedLoginToken The idp_initiated_login query param from the URL
|
|
179
|
+
* @param {TokenValidationOptions} options Optional validation options for issuer and audience
|
|
178
180
|
* @returns {object} Returns the idp initiated login claims
|
|
179
181
|
*/
|
|
180
|
-
async getIdpInitiatedLoginClaims(idpInitiatedLoginToken: string): Promise<IdpInitiatedLoginClaims> {
|
|
181
|
-
return this.validateToken<IdpInitiatedLoginClaims>(idpInitiatedLoginToken);
|
|
182
|
+
async getIdpInitiatedLoginClaims(idpInitiatedLoginToken: string, options?: TokenValidationOptions): Promise<IdpInitiatedLoginClaims> {
|
|
183
|
+
return this.validateToken<IdpInitiatedLoginClaims>(idpInitiatedLoginToken, options);
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
/**
|
|
185
|
-
* Validates the access token.
|
|
187
|
+
* Validates the access token and returns a boolean result.
|
|
186
188
|
*
|
|
187
189
|
* @param {string} token The token to be validated.
|
|
190
|
+
* @param {TokenValidationOptions} options Optional validation options for issuer, audience, and scopes
|
|
188
191
|
* @return {Promise<boolean>} Returns true if the token is valid, false otherwise.
|
|
189
192
|
*/
|
|
190
|
-
async validateAccessToken(token: string): Promise<boolean> {
|
|
193
|
+
async validateAccessToken(token: string, options?: TokenValidationOptions): Promise<boolean> {
|
|
191
194
|
try {
|
|
192
|
-
await this.validateToken(token);
|
|
195
|
+
await this.validateToken(token, options);
|
|
193
196
|
return true;
|
|
194
197
|
} catch (_) {
|
|
195
198
|
return false;
|
|
196
199
|
}
|
|
197
200
|
}
|
|
198
201
|
|
|
202
|
+
|
|
203
|
+
|
|
199
204
|
/**
|
|
200
205
|
* Returns the logout URL that can be used to log out the user.
|
|
201
206
|
* @param {LogoutUrlOptions} options Logout URL options
|
|
@@ -222,7 +227,7 @@ export default class ScalekitClient {
|
|
|
222
227
|
}
|
|
223
228
|
|
|
224
229
|
/**
|
|
225
|
-
*
|
|
230
|
+
* Verify webhook payload
|
|
226
231
|
*
|
|
227
232
|
* @param {string} secret The secret
|
|
228
233
|
* @param {Record<string, string>} headers The headers
|
|
@@ -233,46 +238,106 @@ export default class ScalekitClient {
|
|
|
233
238
|
const webhookId = headers['webhook-id'];
|
|
234
239
|
const webhookTimestamp = headers['webhook-timestamp'];
|
|
235
240
|
const webhookSignature = headers['webhook-signature'];
|
|
241
|
+
|
|
236
242
|
if (!webhookId || !webhookTimestamp || !webhookSignature) {
|
|
237
|
-
throw new
|
|
243
|
+
throw new WebhookVerificationError("Missing required headers");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const secretParts = secret.split("_");
|
|
247
|
+
if (secretParts.length < 2) {
|
|
248
|
+
throw new WebhookVerificationError("Invalid secret");
|
|
238
249
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
const timestamp = this.verifyTimestamp(webhookTimestamp);
|
|
253
|
+
const data = `${webhookId}.${Math.floor(timestamp.getTime() / 1000)}.${payload}`;
|
|
254
|
+
const secretBytes = Buffer.from(secretParts[1], 'base64');
|
|
255
|
+
const computedSignature = this.computeSignature(secretBytes, data);
|
|
256
|
+
const receivedSignatures = webhookSignature.split(" ");
|
|
257
|
+
|
|
258
|
+
for (const versionedSignature of receivedSignatures) {
|
|
259
|
+
const [version, signature] = versionedSignature.split(",");
|
|
260
|
+
if (version !== WEBHOOK_SIGNATURE_VERSION) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (crypto.timingSafeEqual(Buffer.from(signature, 'base64'), Buffer.from(computedSignature, 'base64'))) {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
248
266
|
}
|
|
249
|
-
|
|
250
|
-
|
|
267
|
+
|
|
268
|
+
throw new WebhookVerificationError("Invalid signature");
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (error instanceof WebhookVerificationError) {
|
|
271
|
+
throw error;
|
|
251
272
|
}
|
|
273
|
+
throw new WebhookVerificationError("Invalid signature");
|
|
252
274
|
}
|
|
253
|
-
|
|
254
|
-
throw new Error("Invalid Signature");
|
|
255
275
|
}
|
|
256
276
|
|
|
257
277
|
/**
|
|
258
|
-
*
|
|
278
|
+
* Validates a token and returns its payload if valid.
|
|
279
|
+
* Supports issuer, audience, and scope validation.
|
|
259
280
|
*
|
|
260
281
|
* @param {string} token The token to be validated
|
|
261
|
-
* @
|
|
282
|
+
* @param {TokenValidationOptions} options Optional validation options for issuer, audience, and scopes
|
|
283
|
+
* @return {Promise<T>} Returns the token payload if valid
|
|
284
|
+
* @throws {ScalekitValidateTokenFailureException} If token is invalid or missing required scopes
|
|
262
285
|
*/
|
|
263
|
-
|
|
286
|
+
async validateToken<T>(token: string, options?: TokenValidationOptions): Promise<T> {
|
|
264
287
|
await this.coreClient.getJwks();
|
|
265
288
|
const jwks = jose.createLocalJWKSet({
|
|
266
289
|
keys: this.coreClient.keys
|
|
267
290
|
})
|
|
268
291
|
try {
|
|
269
|
-
const { payload } = await jose.jwtVerify<T>(token, jwks
|
|
292
|
+
const { payload } = await jose.jwtVerify<T>(token, jwks, {
|
|
293
|
+
...(options?.issuer && { issuer: options.issuer }),
|
|
294
|
+
...(options?.audience && { audience: options.audience })
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
if (options?.requiredScopes && options.requiredScopes.length > 0) {
|
|
298
|
+
this.verifyScopes(token, options.requiredScopes);
|
|
299
|
+
}
|
|
300
|
+
|
|
270
301
|
return payload;
|
|
271
|
-
} catch (
|
|
272
|
-
throw new
|
|
302
|
+
} catch (error) {
|
|
303
|
+
throw new ScalekitValidateTokenFailureException(error);
|
|
273
304
|
}
|
|
274
305
|
}
|
|
275
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Verify that the token contains the required scopes
|
|
309
|
+
*
|
|
310
|
+
* @param {string} token The token to verify
|
|
311
|
+
* @param {string[]} requiredScopes The scopes that must be present in the token
|
|
312
|
+
* @return {boolean} Returns true if all required scopes are present
|
|
313
|
+
* @throws {ScalekitValidateTokenFailureException} If required scopes are missing, with details about which scopes are missing
|
|
314
|
+
*/
|
|
315
|
+
verifyScopes(token: string, requiredScopes: string[]): boolean {
|
|
316
|
+
const payload = jose.decodeJwt(token);
|
|
317
|
+
const scopes = this.extractScopesFromPayload(payload);
|
|
318
|
+
|
|
319
|
+
const missingScopes = requiredScopes.filter(scope => !scopes.includes(scope));
|
|
320
|
+
|
|
321
|
+
if (missingScopes.length > 0) {
|
|
322
|
+
throw new ScalekitValidateTokenFailureException(`Token missing required scopes: ${missingScopes.join(', ')}`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Extract scopes from token payload
|
|
330
|
+
*
|
|
331
|
+
* @param {any} payload The token payload
|
|
332
|
+
* @return {string[]} Array of scopes found in the token
|
|
333
|
+
*/
|
|
334
|
+
private extractScopesFromPayload(payload: Record<string, any>): string[] {
|
|
335
|
+
const scopes = payload.scopes;
|
|
336
|
+
return Array.isArray(scopes)
|
|
337
|
+
? scopes.filter((scope) => !!scope.trim?.())
|
|
338
|
+
: [];
|
|
339
|
+
}
|
|
340
|
+
|
|
276
341
|
/**
|
|
277
342
|
* Verify the timestamp
|
|
278
343
|
*
|
|
@@ -283,13 +348,13 @@ export default class ScalekitClient {
|
|
|
283
348
|
const now = Math.floor(Date.now() / 1000);
|
|
284
349
|
const timestamp = parseInt(timestampStr, 10);
|
|
285
350
|
if (isNaN(timestamp)) {
|
|
286
|
-
throw new
|
|
351
|
+
throw new WebhookVerificationError("Invalid Signature Headers");
|
|
287
352
|
}
|
|
288
353
|
if (now - timestamp > WEBHOOK_TOLERANCE_IN_SECONDS) {
|
|
289
|
-
throw new
|
|
354
|
+
throw new WebhookVerificationError("Message timestamp too old");
|
|
290
355
|
}
|
|
291
356
|
if (timestamp > now + WEBHOOK_TOLERANCE_IN_SECONDS) {
|
|
292
|
-
throw new
|
|
357
|
+
throw new WebhookVerificationError("Message timestamp too new");
|
|
293
358
|
}
|
|
294
359
|
|
|
295
360
|
return new Date(timestamp * 1000);
|
package/src/types/scalekit.ts
CHANGED
|
@@ -24,6 +24,12 @@ export type AuthenticationOptions = {
|
|
|
24
24
|
codeVerifier?: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export type TokenValidationOptions = {
|
|
28
|
+
issuer?: string;
|
|
29
|
+
audience?: string[];
|
|
30
|
+
requiredScopes?: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
export type AuthenticationResponse = {
|
|
28
34
|
user: User;
|
|
29
35
|
idToken: string;
|
package/src/types/user.ts
CHANGED
package/src/user.ts
CHANGED
|
@@ -25,7 +25,9 @@ import {
|
|
|
25
25
|
ListOrganizationUsersRequest,
|
|
26
26
|
ListOrganizationUsersResponse,
|
|
27
27
|
CreateMembership,
|
|
28
|
-
UpdateMembership
|
|
28
|
+
UpdateMembership,
|
|
29
|
+
ResendInviteRequest,
|
|
30
|
+
ResendInviteResponse
|
|
29
31
|
} from './pkg/grpc/scalekit/v1/users/users_pb';
|
|
30
32
|
import { CreateUserRequest, UpdateUserRequest as UpdateUserRequestType } from './types/user';
|
|
31
33
|
|
|
@@ -67,8 +69,8 @@ export default class UserClient {
|
|
|
67
69
|
user
|
|
68
70
|
};
|
|
69
71
|
|
|
70
|
-
if (options.
|
|
71
|
-
request.
|
|
72
|
+
if (options.sendInvitationEmail !== undefined) {
|
|
73
|
+
request.sendInvitationEmail = options.sendInvitationEmail;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
const response = await this.coreClient.connectExec(
|
|
@@ -171,7 +173,7 @@ export default class UserClient {
|
|
|
171
173
|
* @param {object} options The membership options
|
|
172
174
|
* @param {string[]} options.roles The roles to assign
|
|
173
175
|
* @param {Record<string, string>} options.metadata Optional metadata
|
|
174
|
-
* @param {boolean} options.
|
|
176
|
+
* @param {boolean} options.sendInvitationEmail Whether to send invitation email
|
|
175
177
|
* @returns {Promise<CreateMembershipResponse>} The response with updated user
|
|
176
178
|
*/
|
|
177
179
|
async createMembership(
|
|
@@ -180,7 +182,7 @@ export default class UserClient {
|
|
|
180
182
|
options: {
|
|
181
183
|
roles?: string[],
|
|
182
184
|
metadata?: Record<string, string>,
|
|
183
|
-
|
|
185
|
+
sendInvitationEmail?: boolean
|
|
184
186
|
} = {}
|
|
185
187
|
): Promise<CreateMembershipResponse> {
|
|
186
188
|
const membership = new CreateMembership({
|
|
@@ -197,8 +199,8 @@ export default class UserClient {
|
|
|
197
199
|
membership
|
|
198
200
|
};
|
|
199
201
|
|
|
200
|
-
if (options.
|
|
201
|
-
request.
|
|
202
|
+
if (options.sendInvitationEmail !== undefined) {
|
|
203
|
+
request.sendInvitationEmail = options.sendInvitationEmail;
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
return this.coreClient.connectExec(
|
|
@@ -288,4 +290,29 @@ export default class UserClient {
|
|
|
288
290
|
}
|
|
289
291
|
);
|
|
290
292
|
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Resend an invitation to a user
|
|
296
|
+
* @param {string} organizationId The organization id
|
|
297
|
+
* @param {string} userId The user id
|
|
298
|
+
* @returns {Promise<ResendInviteResponse>} The response with the invite
|
|
299
|
+
*/
|
|
300
|
+
async resendInvite(organizationId: string, userId: string): Promise<ResendInviteResponse> {
|
|
301
|
+
if (!organizationId) {
|
|
302
|
+
throw new Error('organizationId is required');
|
|
303
|
+
}
|
|
304
|
+
if (!userId) {
|
|
305
|
+
throw new Error('userId is required');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const request = new ResendInviteRequest({
|
|
309
|
+
organizationId,
|
|
310
|
+
id: userId
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
return this.coreClient.connectExec(
|
|
314
|
+
this.client.resendInvite,
|
|
315
|
+
request
|
|
316
|
+
);
|
|
317
|
+
}
|
|
291
318
|
}
|
package/tests/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Scalekit Node SDK Tests
|
|
2
|
+
|
|
3
|
+
This directory contains the test suite for the Scalekit Node SDK.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
1. Create a `.env` file in the root directory with the following environment variables:
|
|
8
|
+
|
|
9
|
+
```env
|
|
10
|
+
# Required for client initialization
|
|
11
|
+
SCALEKIT_ENVIRONMENT_URL= "Your Scalekit environment URL."
|
|
12
|
+
SCALEKIT_CLIENT_ID= " Your Scalekit environment client id "
|
|
13
|
+
SCALEKIT_CLIENT_SECRET= " Your Scalekit environment client secret "
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
2. Install dependencies:
|
|
17
|
+
```bash
|
|
18
|
+
npm install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Running Tests
|
|
22
|
+
|
|
23
|
+
- Run all tests: `npm test`
|
|
24
|
+
- Run tests in watch mode: `npm run test:watch`
|
|
25
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import ScalekitClient from '../src/scalekit';
|
|
2
|
+
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
|
|
3
|
+
import { TestOrganizationManager } from './utils/test-data';
|
|
4
|
+
|
|
5
|
+
describe('Connections', () => {
|
|
6
|
+
let client: ScalekitClient;
|
|
7
|
+
let testOrg: string;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
// Use global client
|
|
11
|
+
client = global.client;
|
|
12
|
+
|
|
13
|
+
// Create test organization for each test
|
|
14
|
+
testOrg = await TestOrganizationManager.createTestOrganization(client);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
// Clean up test organization
|
|
19
|
+
await TestOrganizationManager.cleanupTestOrganization(client, testOrg);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('listConnections', () => {
|
|
23
|
+
it('should list connections by organization', async () => {
|
|
24
|
+
const connections = await client.connection.listConnections(testOrg);
|
|
25
|
+
|
|
26
|
+
expect(connections).toBeDefined();
|
|
27
|
+
expect(connections.connections).toBeDefined();
|
|
28
|
+
expect(Array.isArray(connections.connections)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('listConnectionsByDomain', () => {
|
|
33
|
+
it('should list connections by domain', async () => {
|
|
34
|
+
const domain = 'example.com';
|
|
35
|
+
const connections = await client.connection.listConnectionsByDomain(domain);
|
|
36
|
+
|
|
37
|
+
expect(connections).toBeDefined();
|
|
38
|
+
expect(connections.connections).toBeDefined();
|
|
39
|
+
expect(Array.isArray(connections.connections)).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import ScalekitClient from '../src/scalekit';
|
|
2
|
+
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
|
|
3
|
+
import { TestOrganizationManager } from './utils/test-data';
|
|
4
|
+
|
|
5
|
+
describe('Directories', () => {
|
|
6
|
+
let client: ScalekitClient;
|
|
7
|
+
let testOrg: string;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
// Use global client
|
|
11
|
+
client = global.client;
|
|
12
|
+
|
|
13
|
+
// Create test organization for each test
|
|
14
|
+
testOrg = await TestOrganizationManager.createTestOrganization(client);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
// Clean up test organization
|
|
19
|
+
await TestOrganizationManager.cleanupTestOrganization(client, testOrg);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('listDirectories', () => {
|
|
23
|
+
it('should list directories', async () => {
|
|
24
|
+
const directories = await client.directory.listDirectories(testOrg);
|
|
25
|
+
|
|
26
|
+
expect(directories).toBeDefined();
|
|
27
|
+
expect(directories.directories).toBeDefined();
|
|
28
|
+
expect(Array.isArray(directories.directories)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('getPrimaryDirectoryByOrganizationId', () => {
|
|
33
|
+
it('should get primary directory by organization id', async () => {
|
|
34
|
+
try {
|
|
35
|
+
const primaryDirectory = await client.directory.getPrimaryDirectoryByOrganizationId(testOrg);
|
|
36
|
+
|
|
37
|
+
expect(primaryDirectory).toBeDefined();
|
|
38
|
+
expect(primaryDirectory.id).toBeDefined();
|
|
39
|
+
expect(primaryDirectory.organizationId).toBe(testOrg);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
// Expected when no directories exist
|
|
42
|
+
expect(error).toBeDefined();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import ScalekitClient from '../src/scalekit';
|
|
2
|
+
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
|
|
3
|
+
import { TestDataGenerator, TestOrganizationManager } from './utils/test-data';
|
|
4
|
+
|
|
5
|
+
describe('Organizations', () => {
|
|
6
|
+
let client: ScalekitClient;
|
|
7
|
+
let testOrg: string;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
// Use global client
|
|
11
|
+
client = global.client;
|
|
12
|
+
|
|
13
|
+
// Create test organization for each test
|
|
14
|
+
testOrg = await TestOrganizationManager.createTestOrganization(client);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
// Clean up test organization
|
|
19
|
+
await TestOrganizationManager.cleanupTestOrganization(client, testOrg);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('listOrganization', () => {
|
|
23
|
+
it('should list organizations', async () => {
|
|
24
|
+
const organizations = await client.organization.listOrganization(TestDataGenerator.generatePaginationParams());
|
|
25
|
+
|
|
26
|
+
expect(organizations).toBeDefined();
|
|
27
|
+
expect(organizations.organizations).toBeDefined();
|
|
28
|
+
expect(Array.isArray(organizations.organizations)).toBe(true);
|
|
29
|
+
expect(organizations.organizations.length).toBeGreaterThan(0);
|
|
30
|
+
|
|
31
|
+
// Verify basic organization attributes
|
|
32
|
+
const firstOrg = organizations.organizations[0];
|
|
33
|
+
expect(firstOrg.id).toBeDefined();
|
|
34
|
+
expect(firstOrg.displayName).toBeDefined();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should handle pagination', async () => {
|
|
38
|
+
const firstPage = await client.organization.listOrganization(TestDataGenerator.generatePaginationParams(5));
|
|
39
|
+
|
|
40
|
+
expect(firstPage).toBeDefined();
|
|
41
|
+
expect(firstPage.organizations.length).toBeLessThanOrEqual(5);
|
|
42
|
+
|
|
43
|
+
if (firstPage.nextPageToken) {
|
|
44
|
+
const secondPage = await client.organization.listOrganization({
|
|
45
|
+
pageSize: 5,
|
|
46
|
+
pageToken: firstPage.nextPageToken
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(secondPage).toBeDefined();
|
|
50
|
+
expect(secondPage.organizations).toBeDefined();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('getOrganization', () => {
|
|
56
|
+
it('should get organization by ID', async () => {
|
|
57
|
+
const organization = await client.organization.getOrganization(testOrg);
|
|
58
|
+
|
|
59
|
+
expect(organization).toBeDefined();
|
|
60
|
+
expect(organization.organization).toBeDefined();
|
|
61
|
+
expect(organization.organization?.id).toBe(testOrg);
|
|
62
|
+
expect(organization.organization?.displayName).toBeDefined();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import ScalekitClient from '../src/scalekit';
|
|
2
|
+
import { TemplateType } from '../src/pkg/grpc/scalekit/v1/auth/passwordless_pb';
|
|
3
|
+
import { describe, it, expect, beforeEach } from '@jest/globals';
|
|
4
|
+
import { TestDataGenerator } from './utils/test-data';
|
|
5
|
+
|
|
6
|
+
describe('Passwordless', () => {
|
|
7
|
+
let client: ScalekitClient;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
// Use global client
|
|
11
|
+
client = global.client;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('sendPasswordlessEmail', () => {
|
|
15
|
+
it('should send passwordless email with basic parameters', async () => {
|
|
16
|
+
const email = TestDataGenerator.generateUniqueEmail();
|
|
17
|
+
|
|
18
|
+
const response = await client.passwordless.sendPasswordlessEmail(email, TestDataGenerator.generatePasswordlessEmailData());
|
|
19
|
+
|
|
20
|
+
expect(response).toBeDefined();
|
|
21
|
+
expect(response.authRequestId).toBeDefined();
|
|
22
|
+
expect(response.expiresAt).toBeDefined();
|
|
23
|
+
expect(response.expiresIn).toBe(3600);
|
|
24
|
+
expect(response.passwordlessType).toBeDefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should send passwordless email with template variables', async () => {
|
|
28
|
+
const email = TestDataGenerator.generateUniqueEmail();
|
|
29
|
+
|
|
30
|
+
const response = await client.passwordless.sendPasswordlessEmail(email, TestDataGenerator.generatePasswordlessEmailWithTemplateData());
|
|
31
|
+
|
|
32
|
+
expect(response).toBeDefined();
|
|
33
|
+
expect(response.authRequestId).toBeDefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should throw error for invalid email', async () => {
|
|
37
|
+
await expect(
|
|
38
|
+
client.passwordless.sendPasswordlessEmail('', {
|
|
39
|
+
template: TemplateType.SIGNIN
|
|
40
|
+
})
|
|
41
|
+
).rejects.toThrow('Email must be a valid string');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should throw error for invalid template type', async () => {
|
|
45
|
+
const email = TestDataGenerator.generateUniqueEmail();
|
|
46
|
+
|
|
47
|
+
await expect(
|
|
48
|
+
client.passwordless.sendPasswordlessEmail(email, {
|
|
49
|
+
template: 'INVALID_TEMPLATE' as any
|
|
50
|
+
})
|
|
51
|
+
).rejects.toThrow('Invalid template type');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('verifyPasswordlessEmail', () => {
|
|
56
|
+
it('should throw error when neither code nor linkToken is provided', async () => {
|
|
57
|
+
await expect(
|
|
58
|
+
client.passwordless.verifyPasswordlessEmail({})
|
|
59
|
+
).rejects.toThrow('Either code or linkToken must be provided');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should verify with code', async () => {
|
|
63
|
+
// Mock code for testing - expected to fail
|
|
64
|
+
const credential = TestDataGenerator.generateCredentialData('code');
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const response = await client.passwordless.verifyPasswordlessEmail(credential);
|
|
68
|
+
expect(response).toBeDefined();
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// Expected failure with mock code
|
|
71
|
+
expect(error).toBeDefined();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should verify with linkToken', async () => {
|
|
76
|
+
// Mock linkToken for testing - expected to fail
|
|
77
|
+
const credential = TestDataGenerator.generateCredentialData('linkToken');
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const response = await client.passwordless.verifyPasswordlessEmail(credential);
|
|
81
|
+
expect(response).toBeDefined();
|
|
82
|
+
} catch (error) {
|
|
83
|
+
// Expected failure with mock linkToken
|
|
84
|
+
expect(error).toBeDefined();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('resendPasswordlessEmail', () => {
|
|
90
|
+
it('should resend passwordless email', async () => {
|
|
91
|
+
// Send initial passwordless email
|
|
92
|
+
const email = TestDataGenerator.generateUniqueEmail();
|
|
93
|
+
|
|
94
|
+
const sendResponse = await client.passwordless.sendPasswordlessEmail(email, TestDataGenerator.generatePasswordlessEmailData());
|
|
95
|
+
|
|
96
|
+
// Resend the email
|
|
97
|
+
const resendResponse = await client.passwordless.resendPasswordlessEmail(
|
|
98
|
+
sendResponse.authRequestId
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(resendResponse).toBeDefined();
|
|
102
|
+
expect(resendResponse.authRequestId).toBeDefined();
|
|
103
|
+
expect(resendResponse.expiresAt).toBeDefined();
|
|
104
|
+
expect(resendResponse.expiresIn).toBeDefined();
|
|
105
|
+
expect(resendResponse.passwordlessType).toBeDefined();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|