diff --git a/README.md b/README.md index 5f655d5e..dbe5639c 100644 --- a/README.md +++ b/README.md @@ -531,9 +531,9 @@ await descopeClient.management.user.create( [{ tenantId: 'tenant-ID1', roleNames: ['role-name1'] }], ); -// Alternatively, a user can be created and invited via an email message. +// Alternatively, a user can be created and invited via an email / text message. // Make sure to configure the invite URL in the Descope console prior to using this function, -// and that an email address is provided in the information. +// and that an email address / phone number is provided in the information. await descopeClient.management.user.invite( 'desmond@descope.com', 'desmond@descope.com', @@ -543,6 +543,23 @@ await descopeClient.management.user.invite( [{ tenantId: 'tenant-ID1', roleNames: ['role-name1'] }], ); +// You can invite batch of users via an email / text message. +// Make sure to configure the invite URL in the Descope console prior to using this function, +// and that an email address / phone number is provided in the information. +await descopeClient.management.user.inviteBatch( + users: [{ + loginId: 'desmond@descope.com', + email: 'desmond@descope.com', + phone: '+123456789123', + displayName: 'Desmond Copeland', + userTenants: [{ tenantId: 'tenant-ID1', roleNames: ['role-name1'] }], + } + ], + sendMail: true, + sendSMS: false, + inviteUrl: "", +); + // Update will override all fields as is. Use carefully. await descopeClient.management.user.update( 'desmond@descope.com', diff --git a/lib/management/paths.ts b/lib/management/paths.ts index 38e4f908..9a08c0a1 100644 --- a/lib/management/paths.ts +++ b/lib/management/paths.ts @@ -2,6 +2,7 @@ export default { user: { create: '/v1/mgmt/user/create', + createBatch: '/v1/mgmt/user/create/batch', update: '/v1/mgmt/user/update', delete: '/v1/mgmt/user/delete', deleteAllTestUsers: '/v1/mgmt/user/test/delete/all', diff --git a/lib/management/types.ts b/lib/management/types.ts index 64634214..0d3f4640 100644 --- a/lib/management/types.ts +++ b/lib/management/types.ts @@ -1,3 +1,5 @@ +import { UserResponse } from '@descope/core-js-sdk'; + /** Represents a tenant association for a User or Access Key. The tenantId is required to denote * which tenant the user or access key belongs to. The roleNames array is an optional list of * roles for the user or access key in this specific tenant. @@ -159,6 +161,20 @@ export type GenerateEmbeddedLinkResponse = { export type AttributesTypes = string | boolean | number; +export type User = { + loginId: string; + email?: string; + phone?: string; + displayName?: string; + roles?: string[]; + userTenants?: AssociatedTenant[]; + customAttributes?: Record; + picture?: string; + verifiedEmail?: boolean; + verifiedPhone?: boolean; + test?: boolean; +}; + export type UserMapping = { name: string; email: string; @@ -200,6 +216,16 @@ export type ProviderTokenResponse = { scopes: string[]; }; +export type UserFailedResponse = { + failure: string; + user: UserResponse; +}; + +export type InviteBatchResponse = { + createdUsers: UserResponse[]; + failedUsers: UserFailedResponse[]; +}; + /** * Search options to filter which audit records we should retrieve. * All parameters are optional. `From` is currently limited to 30 days. diff --git a/lib/management/user.test.ts b/lib/management/user.test.ts index 8f12e743..3c02f36e 100644 --- a/lib/management/user.test.ts +++ b/lib/management/user.test.ts @@ -9,6 +9,7 @@ import { ProviderTokenResponse, UserStatus, GenerateEmbeddedLinkResponse, + InviteBatchResponse, } from './types'; const management = withManagement(mockCoreSdk, 'key'); @@ -25,6 +26,16 @@ const mockMgmtUsersResponse = { users: [mockUserResponse], }; +const mockMgmtInviteBatchResponse = { + createdUsers: [{ loginId: 'one', email: 'one@one' }], + failedUsers: [ + { + failure: 'some failure', + user: { loginId: 'two', email: 'two@two' }, + }, + ], +}; + describe('Management User', () => { afterEach(() => { jest.clearAllMocks(); @@ -225,6 +236,56 @@ describe('Management User', () => { }); }); + describe('invite batch', () => { + it('should send the correct request and receive correct response', async () => { + const httpResponse = { + ok: true, + json: () => mockMgmtInviteBatchResponse, + clone: () => ({ + json: () => Promise.resolve(mockMgmtInviteBatchResponse), + }), + status: 200, + }; + mockHttpClient.post.mockResolvedValue(httpResponse); + + const resp: SdkResponse = await management.user.inviteBatch( + [ + { loginId: 'one', email: 'one@one' }, + { loginId: 'two', email: 'two@two' }, + ], + 'https://invite.me', + true, + ); + + expect(mockHttpClient.post).toHaveBeenCalledWith( + apiPaths.user.createBatch, + { + users: [ + { + loginId: 'one', + email: 'one@one', + }, + { + loginId: 'two', + email: 'two@two', + }, + ], + invite: true, + inviteUrl: 'https://invite.me', + sendMail: true, + }, + { token: 'key' }, + ); + + expect(resp).toEqual({ + code: 200, + data: mockMgmtInviteBatchResponse, + ok: true, + response: httpResponse, + }); + }); + }); + describe('update', () => { it('should send the correct request and receive correct response', async () => { const httpResponse = { diff --git a/lib/management/user.ts b/lib/management/user.ts index 47314985..eb94519a 100644 --- a/lib/management/user.ts +++ b/lib/management/user.ts @@ -8,6 +8,8 @@ import { GenerateEmbeddedLinkResponse, AttributesTypes, UserStatus, + User, + InviteBatchResponse, } from './types'; import { CoreSdk } from '../types'; import apiPaths from './paths'; @@ -132,6 +134,26 @@ const withUser = (sdk: CoreSdk, managementKey?: string) => ({ ), (data) => data.user, ), + inviteBatch: ( + users: User[], + inviteUrl?: string, + sendMail?: boolean, // send invite via mail, default is according to project settings + sendSMS?: boolean, // send invite via text message, default is according to project settings + ): Promise> => + transformResponse( + sdk.httpClient.post( + apiPaths.user.createBatch, + { + users, + invite: true, + inviteUrl, + sendMail, + sendSMS, + }, + { token: managementKey }, + ), + (data) => data, + ), update: ( loginId: string, email?: string,