Skip to content

Commit

Permalink
feat(core): create user with avatar and custom data (#5476)
Browse files Browse the repository at this point in the history
  • Loading branch information
wangsijie committed Mar 8, 2024
1 parent f44ba31 commit 1724119
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 40 deletions.
5 changes: 5 additions & 0 deletions .changeset/clever-buttons-behave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@logto/core": minor
---

Add avatar and customData fields to create user API (POST /api/users)
6 changes: 6 additions & 0 deletions packages/core/src/routes/admin-user/basics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ export default function adminUserBasicsRoutes<T extends AuthedRouter>(...args: R
passwordDigest: string(),
passwordAlgorithm: nativeEnum(UsersPasswordEncryptionMethod),
name: string(),
avatar: string().url().or(literal('')).nullable(),
customData: jsonObjectGuard,
}).partial(),
response: userProfileResponseGuard,
status: [200, 404, 422],
Expand All @@ -132,6 +134,8 @@ export default function adminUserBasicsRoutes<T extends AuthedRouter>(...args: R
name,
passwordDigest,
passwordAlgorithm,
avatar,
customData,
} = ctx.guard.body;

assertThat(!(password && passwordDigest), new RequestError('user.password_and_digest'));
Expand Down Expand Up @@ -165,6 +169,8 @@ export default function adminUserBasicsRoutes<T extends AuthedRouter>(...args: R
primaryPhone,
username,
name,
avatar,
...conditional(customData && { customData }),
...conditional(password && (await encryptUserPassword(password))),
...conditional(
passwordDigest && {
Expand Down
27 changes: 14 additions & 13 deletions packages/integration-tests/src/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,30 @@ import fs from 'node:fs/promises';
import { createServer, type RequestListener } from 'node:http';

import { mockConnectorFilePaths, type SendMessagePayload } from '@logto/connector-kit';
import { type UsersPasswordEncryptionMethod } from '@logto/schemas';
import { type JsonObject, type UsersPasswordEncryptionMethod } from '@logto/schemas';
import { RequestError } from 'got';

import { createUser } from '#src/api/index.js';
import { generateUsername } from '#src/utils.js';

export const createUserByAdmin = async (
username?: string,
password?: string,
primaryEmail?: string,
primaryPhone?: string,
name?: string,
passwordDigest?: string,
passwordAlgorithm?: UsersPasswordEncryptionMethod
payload: {
username?: string;
password?: string;
primaryEmail?: string;
primaryPhone?: string;
name?: string;
passwordDigest?: string;
passwordAlgorithm?: UsersPasswordEncryptionMethod;
customData?: JsonObject;
} = {}
) => {
const { username, name, ...rest } = payload;

return createUser({
...rest,
username: username ?? generateUsername(),
password,
name: name ?? username ?? 'John',
primaryEmail,
primaryPhone,
passwordDigest,
passwordAlgorithm,
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe('admin console user search params', () => {
const primaryPhone =
phonePrefix[index % phonePrefix.length]! + index.toString().padStart(5, '0');

return createUserByAdmin(prefix + username, undefined, primaryEmail, primaryPhone, name);
return createUserByAdmin({ username: prefix + username, primaryEmail, primaryPhone, name });
})
);
});
Expand Down
47 changes: 27 additions & 20 deletions packages/integration-tests/src/tests/api/admin-user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,36 +38,39 @@ describe('admin console user management', () => {
});

it('should create user with password digest successfully', async () => {
const user = await createUserByAdmin(
undefined,
undefined,
undefined,
undefined,
undefined,
'5f4dcc3b5aa765d61d8327deb882cf99',
UsersPasswordEncryptionMethod.MD5
);
const user = await createUserByAdmin({
passwordDigest: '5f4dcc3b5aa765d61d8327deb882cf99',
passwordAlgorithm: UsersPasswordEncryptionMethod.MD5,
});

await expect(verifyUserPassword(user.id, 'password')).resolves.not.toThrow();
});

it('should create user with custom data successfully', async () => {
const user = await createUserByAdmin({
customData: { foo: 'bar' },
});
const { customData } = await getUser(user.id);
expect(customData).toStrictEqual({ foo: 'bar' });
});

it('should fail when create user with conflict identifiers', async () => {
const [username, password, email, phone] = [
const [username, password, primaryEmail, primaryPhone] = [
generateUsername(),
generatePassword(),
generateEmail(),
generatePhone(),
];
await createUserByAdmin(username, password, email, phone);
await expectRejects(createUserByAdmin(username, password), {
await createUserByAdmin({ username, password, primaryEmail, primaryPhone });
await expectRejects(createUserByAdmin({ username, password }), {
code: 'user.username_already_in_use',
statusCode: 422,
});
await expectRejects(createUserByAdmin(undefined, undefined, email), {
await expectRejects(createUserByAdmin({ primaryEmail }), {
code: 'user.email_already_in_use',
statusCode: 422,
});
await expectRejects(createUserByAdmin(undefined, undefined, undefined, phone), {
await expectRejects(createUserByAdmin({ primaryPhone }), {
code: 'user.phone_already_in_use',
statusCode: 422,
});
Expand Down Expand Up @@ -108,21 +111,25 @@ describe('admin console user management', () => {
});

it('should fail when update userinfo with conflict identifiers', async () => {
const [username, email, phone] = [generateUsername(), generateEmail(), generatePhone()];
await createUserByAdmin(username, undefined, email, phone);
const [username, primaryEmail, primaryPhone] = [
generateUsername(),
generateEmail(),
generatePhone(),
];
await createUserByAdmin({ username, primaryEmail, primaryPhone });
const anotherUser = await createUserByAdmin();

await expectRejects(updateUser(anotherUser.id, { username }), {
code: 'user.username_already_in_use',
statusCode: 422,
});

await expectRejects(updateUser(anotherUser.id, { primaryEmail: email }), {
await expectRejects(updateUser(anotherUser.id, { primaryEmail }), {
code: 'user.email_already_in_use',
statusCode: 422,
});

await expectRejects(updateUser(anotherUser.id, { primaryPhone: phone }), {
await expectRejects(updateUser(anotherUser.id, { primaryPhone }), {
code: 'user.phone_already_in_use',
statusCode: 422,
});
Expand Down Expand Up @@ -229,13 +236,13 @@ describe('admin console user management', () => {
});

it('should return 204 if password is correct', async () => {
const user = await createUserByAdmin(undefined, 'new_password');
const user = await createUserByAdmin({ password: 'new_password' });
expect(await verifyUserPassword(user.id, 'new_password')).toHaveProperty('statusCode', 204);
await deleteUser(user.id);
});

it('should return 422 if password is incorrect', async () => {
const user = await createUserByAdmin(undefined, 'new_password');
const user = await createUserByAdmin({ password: 'new_password' });
await expectRejects(verifyUserPassword(user.id, 'wrong_password'), {
code: 'session.invalid_credentials',
statusCode: 422,
Expand Down
4 changes: 2 additions & 2 deletions packages/integration-tests/src/tests/api/dashboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('admin console dashboard', () => {

const password = generatePassword();
const username = generateUsername();
await createUserByAdmin(username, password);
await createUserByAdmin({ username, password });

const { totalUserCount } = await getTotalUsersCount();

Expand All @@ -63,7 +63,7 @@ describe('admin console dashboard', () => {

const password = generatePassword();
const username = generateUsername();
await createUserByAdmin(username, password);
await createUserByAdmin({ username, password });

await signInWithPassword({ username, password });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('always issue Refresh Token config', () => {
};

beforeAll(async () => {
await createUserByAdmin(username, password);
await createUserByAdmin({ username, password });
await enableAllPasswordSignInMethods();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ describe('get access token', () => {
const testApiScopeNames = ['read', 'write', 'delete', 'update'];

beforeAll(async () => {
await createUserByAdmin(guestUsername, password);
const user = await createUserByAdmin(username, password);
await createUserByAdmin({ username: guestUsername, password });
const user = await createUserByAdmin({ username, password });
const testApiResource = await createResource(
testApiResourceInfo.name,
testApiResourceInfo.indicator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('OpenID Connect ID token', () => {
};

beforeAll(async () => {
const { id } = await createUserByAdmin(username, password);
const { id } = await createUserByAdmin({ username, password });
// eslint-disable-next-line @silverhand/fp/no-mutation
userId = id;
await enableAllPasswordSignInMethods();
Expand Down

0 comments on commit 1724119

Please sign in to comment.