0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-03 21:48:55 -05:00

feat(core): create user with avatar and custom data (#5476)

This commit is contained in:
wangsijie 2024-03-08 11:29:39 +08:00 committed by GitHub
parent f44ba31275
commit 172411946a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 59 additions and 40 deletions

View file

@ -0,0 +1,5 @@
---
"@logto/core": minor
---
Add avatar and customData fields to create user API (POST /api/users)

View file

@ -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],
@ -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'));
@ -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 && {

View file

@ -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,
});
};

View file

@ -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 });
})
);
});

View file

@ -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,
});
@ -108,8 +111,12 @@ 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 }), {
@ -117,12 +124,12 @@ describe('admin console user management', () => {
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,
});
@ -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,

View file

@ -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();
@ -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 });

View file

@ -34,7 +34,7 @@ describe('always issue Refresh Token config', () => {
};
beforeAll(async () => {
await createUserByAdmin(username, password);
await createUserByAdmin({ username, password });
await enableAllPasswordSignInMethods();
});

View file

@ -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

View file

@ -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();