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:
parent
f44ba31275
commit
172411946a
9 changed files with 59 additions and 40 deletions
5
.changeset/clever-buttons-behave.md
Normal file
5
.changeset/clever-buttons-behave.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@logto/core": minor
|
||||
---
|
||||
|
||||
Add avatar and customData fields to create user API (POST /api/users)
|
|
@ -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 && {
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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 });
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 });
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('always issue Refresh Token config', () => {
|
|||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await createUserByAdmin(username, password);
|
||||
await createUserByAdmin({ username, password });
|
||||
await enableAllPasswordSignInMethods();
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Reference in a new issue