Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/management/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { User } from './types';
* Transforms user objects by converting roles to roleNames
*/
export function transformUsersForBatch(users: User[]): any[] {
return users.map(({ roles, ...user }) => ({
return users.map(({ loginIdOrUserId, loginId, roles, ...user }) => ({
...user,
loginId: loginIdOrUserId ?? loginId,
roleNames: roles,
}));
}
17 changes: 15 additions & 2 deletions lib/management/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,6 @@ export type AttributesTypes = string | boolean | number | string[] | null;
export type TemplateOptions = Record<string, string>; // for providing messaging template options (templates that are being sent via email / text message)

export type User = {
loginId: string;
email?: string;
phone?: string;
displayName?: string;
Expand All @@ -388,7 +387,21 @@ export type User = {
seed?: string; // a TOTP seed to set for the user in case of batch invite
status?: UserStatus; // the status of the user (enabled, disabled, invited, expired)
createdTime?: number; // the time the user was created in seconds since epoch
};
} & (
| {
/** The login ID or user ID of the user. When a userId is provided, the user must
* already exist — no new user is created, and the invite is sent to the existing
* user (useful for re-inviting). */
loginIdOrUserId: string;
/** @deprecated Use loginIdOrUserId instead */
loginId?: string;
}
| {
/** @deprecated Use loginIdOrUserId instead */
loginId: string;
loginIdOrUserId?: string;
}
);

// The kind of prehashed password to set for a user (only one should be set)
export type UserPasswordHashed = {
Expand Down
87 changes: 83 additions & 4 deletions lib/management/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,29 @@ describe('Management User', () => {
response: httpResponse,
});
});

it('should send the correct request when passing a userId', async () => {
const httpResponse = {
ok: true,
json: () => mockMgmtUserResponse,
clone: () => ({
json: () => Promise.resolve(mockMgmtUserResponse),
}),
status: 200,
};
mockHttpClient.post.mockResolvedValue(httpResponse);

const userId = 'U2abc1234567890123456789';
await management.user.invite(userId, { email: 'a@b.c', sendMail: true });

expect(mockHttpClient.post).toHaveBeenCalledWith(apiPaths.user.create, {
loginId: userId,
email: 'a@b.c',
roleNames: undefined,
invite: true,
sendMail: true,
});
});
});

describe('invite batch', () => {
Expand Down Expand Up @@ -373,8 +396,14 @@ describe('Management User', () => {

const resp: SdkResponse<CreateOrInviteBatchResponse> = await management.user.inviteBatch(
[
{ loginId: 'one', roles: ['r1'], email: 'one@one', password: 'clear', seed: 'aaa' },
{ loginId: 'two', roles: ['r1'], email: 'two@two', hashedPassword: hashed },
{
loginIdOrUserId: 'one',
roles: ['r1'],
email: 'one@one',
password: 'clear',
seed: 'aaa',
},
{ loginIdOrUserId: 'two', roles: ['r1'], email: 'two@two', hashedPassword: hashed },
],
'https://invite.me',
true,
Expand Down Expand Up @@ -417,6 +446,50 @@ describe('Management User', () => {
response: httpResponse,
});
});

it('should support deprecated loginId field for backwards compatibility (createBatch)', async () => {
const httpResponse = {
ok: true,
json: () => mockMgmtInviteBatchResponse,
clone: () => ({
json: () => Promise.resolve(mockMgmtInviteBatchResponse),
}),
status: 200,
};
mockHttpClient.post.mockResolvedValue(httpResponse);

await management.user.createBatch([
{ loginId: 'legacy@user.com', roles: ['r1'], email: 'legacy@user.com' },
]);

expect(mockHttpClient.post).toHaveBeenCalledWith(apiPaths.user.createBatch, {
users: [{ loginId: 'legacy@user.com', roleNames: ['r1'], email: 'legacy@user.com' }],
});
});

it('should support deprecated loginId field for backwards compatibility', async () => {
const httpResponse = {
ok: true,
json: () => mockMgmtInviteBatchResponse,
clone: () => ({
json: () => Promise.resolve(mockMgmtInviteBatchResponse),
}),
status: 200,
};
mockHttpClient.post.mockResolvedValue(httpResponse);

await management.user.inviteBatch(
[{ loginId: 'legacy@user.com', roles: ['r1'], email: 'legacy@user.com' }],
'https://invite.me',
);

expect(mockHttpClient.post).toHaveBeenCalledWith(apiPaths.user.createBatch, {
users: [{ loginId: 'legacy@user.com', roleNames: ['r1'], email: 'legacy@user.com' }],
invite: true,
inviteUrl: 'https://invite.me',
sendMail: undefined,
});
});
});

describe('create batch', () => {
Expand All @@ -443,8 +516,14 @@ describe('Management User', () => {
};

const resp: SdkResponse<CreateOrInviteBatchResponse> = await management.user.createBatch([
{ loginId: 'one', roles: ['r1'], email: 'one@one', password: 'clear', seed: 'aaa' },
{ loginId: 'two', roles: ['r1'], email: 'two@two', hashedPassword: hashed },
{
loginIdOrUserId: 'one',
roles: ['r1'],
email: 'one@one',
password: 'clear',
seed: 'aaa',
},
{ loginIdOrUserId: 'two', roles: ['r1'], email: 'two@two', hashedPassword: hashed },
]);

expect(mockHttpClient.post).toHaveBeenCalledWith(apiPaths.user.createBatch, {
Expand Down
20 changes: 13 additions & 7 deletions lib/management/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,14 @@ const withUser = (httpClient: HttpClient) => {
/* Create Test User End */

/* Invite User */
/**
* Create a new user and invite them to set up their credentials.
* When loginIdOrUserId is a loginId, a new user is created if one doesn't already exist.
* When loginIdOrUserId is a userId, the user must already exist — no new user is created,
* and the invite is sent to the existing user (useful for re-inviting).
*/
function invite(
loginId: string,
loginIdOrUserId: string,
options?: UserOptions & {
inviteUrl?: string;
sendMail?: boolean; // send invite via mail, default is according to project settings
Expand All @@ -227,7 +233,7 @@ const withUser = (httpClient: HttpClient) => {
},
): Promise<SdkResponse<UserResponse>>;
function invite(
loginId: string,
loginIdOrUserId: string,
email?: string,
phone?: string,
displayName?: string,
Expand All @@ -248,7 +254,7 @@ const withUser = (httpClient: HttpClient) => {
): Promise<SdkResponse<UserResponse>>;

function invite(
loginId: string,
loginIdOrUserId: string,
emailOrOptions?: string | UserOptions,
phone?: string,
displayName?: string,
Expand All @@ -268,12 +274,12 @@ const withUser = (httpClient: HttpClient) => {
templateId?: string,
): Promise<SdkResponse<UserResponse>> {
// We support both the old and new parameters forms of invite user
// 1. The new form - invite(loginId, { email, phone, ... }})
// 2. The old form - invite(loginId, email, phone, ...)
// 1. The new form - invite(loginIdOrUserId, { email, phone, ... }})
// 2. The old form - invite(loginIdOrUserId, email, phone, ...)
const body =
typeof emailOrOptions === 'string'
? {
loginId,
loginId: loginIdOrUserId,
email: emailOrOptions,
phone,
displayName,
Expand All @@ -294,7 +300,7 @@ const withUser = (httpClient: HttpClient) => {
templateId,
}
: {
loginId,
loginId: loginIdOrUserId,
...emailOrOptions,
roleNames: emailOrOptions?.roles,
roles: undefined,
Expand Down
Loading