mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(core): update other profile data (#6651)
This commit is contained in:
parent
1a938816b0
commit
ed849ca716
4 changed files with 148 additions and 10 deletions
|
@ -56,6 +56,64 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/profile/profile": {
|
||||
"patch": {
|
||||
"operationId": "UpdateOtherProfile",
|
||||
"summary": "Update other profile",
|
||||
"description": "Update other profile for the user, only the fields that are passed in will be updated, to update the address, the user must have the address scope.",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"familyName": {
|
||||
"description": "The new family name for the user."
|
||||
},
|
||||
"givenName": {
|
||||
"description": "The new given name for the user."
|
||||
},
|
||||
"middleName": {
|
||||
"description": "The new middle name for the user."
|
||||
},
|
||||
"nickname": {
|
||||
"description": "The new nickname for the user."
|
||||
},
|
||||
"preferredUsername": {
|
||||
"description": "The new preferred username for the user."
|
||||
},
|
||||
"profile": {
|
||||
"description": "The new profile for the user."
|
||||
},
|
||||
"website": {
|
||||
"description": "The new website for the user."
|
||||
},
|
||||
"gender": {
|
||||
"description": "The new gender for the user."
|
||||
},
|
||||
"birthdate": {
|
||||
"description": "The new birthdate for the user."
|
||||
},
|
||||
"zoneinfo": {
|
||||
"description": "The new zoneinfo for the user."
|
||||
},
|
||||
"locale": {
|
||||
"description": "The new locale for the user."
|
||||
},
|
||||
"address": {
|
||||
"description": "The new address for the user."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The profile was updated successfully."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/profile/password": {
|
||||
"post": {
|
||||
"operationId": "UpdatePassword",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { emailRegEx, usernameRegEx, UserScope } from '@logto/core-kit';
|
||||
import { VerificationType, userProfileResponseGuard } from '@logto/schemas';
|
||||
import { VerificationType, userProfileResponseGuard, userProfileGuard } from '@logto/schemas';
|
||||
import { z } from 'zod';
|
||||
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
|
@ -67,7 +67,11 @@ export default function profileRoutes<T extends UserRouter>(
|
|||
await checkIdentifierCollision({ username }, userId);
|
||||
}
|
||||
|
||||
const updatedUser = await updateUserById(userId, { name, avatar, username });
|
||||
const updatedUser = await updateUserById(userId, {
|
||||
name,
|
||||
avatar,
|
||||
username,
|
||||
});
|
||||
|
||||
ctx.appendDataHookContext('User.Data.Updated', { user: updatedUser });
|
||||
|
||||
|
@ -77,11 +81,41 @@ export default function profileRoutes<T extends UserRouter>(
|
|||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/profile/profile',
|
||||
koaGuard({
|
||||
body: userProfileGuard,
|
||||
response: userProfileGuard,
|
||||
status: [200, 400],
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { id: userId, scopes } = ctx.auth;
|
||||
const { body } = ctx.guard;
|
||||
|
||||
assertThat(scopes.has(UserScope.Profile), 'auth.unauthorized');
|
||||
|
||||
if (body.address !== undefined) {
|
||||
assertThat(scopes.has(UserScope.Address), 'auth.unauthorized');
|
||||
}
|
||||
|
||||
const updatedUser = await updateUserById(userId, {
|
||||
profile: body,
|
||||
});
|
||||
|
||||
ctx.appendDataHookContext('User.Data.Updated', { user: updatedUser });
|
||||
|
||||
const profile = await getScopedProfile(queries, libraries, scopes, userId);
|
||||
ctx.body = profile.profile;
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/profile/password',
|
||||
koaGuard({
|
||||
body: z.object({ password: z.string().min(1), verificationRecordId: z.string() }),
|
||||
status: [204, 400, 403],
|
||||
status: [204, 401, 422],
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { id: userId } = ctx.auth;
|
||||
|
|
|
@ -17,12 +17,11 @@ export const updatePrimaryEmail = async (
|
|||
json: { email, verificationRecordId, newIdentifierVerificationRecordId },
|
||||
});
|
||||
|
||||
export const updateUser = async (api: KyInstance, body: Record<string, string>) =>
|
||||
api.patch('api/profile', { json: body }).json<{
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
username?: string;
|
||||
}>();
|
||||
export const updateUser = async (api: KyInstance, body: Record<string, unknown>) =>
|
||||
api.patch('api/profile', { json: body }).json<Partial<UserProfileResponse>>();
|
||||
|
||||
export const updateOtherProfile = async (api: KyInstance, body: Record<string, unknown>) =>
|
||||
api.patch('api/profile/profile', { json: body }).json<Partial<UserProfileResponse['profile']>>();
|
||||
|
||||
export const getUserInfo = async (api: KyInstance) =>
|
||||
api.get('api/profile').json<Partial<UserProfileResponse>>();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { UserScope } from '@logto/core-kit';
|
||||
import { hookEvents } from '@logto/schemas';
|
||||
|
||||
import { getUserInfo, updatePassword, updateUser } from '#src/api/profile.js';
|
||||
import { getUserInfo, updateOtherProfile, updatePassword, updateUser } from '#src/api/profile.js';
|
||||
import { createVerificationRecordByPassword } from '#src/api/verification-record.js';
|
||||
import { WebHookApiTest } from '#src/helpers/hook.js';
|
||||
import { expectRejects } from '#src/helpers/index.js';
|
||||
|
@ -177,6 +177,53 @@ describe('profile', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('PATCH /profile/profile', () => {
|
||||
it('should be able to update other profile', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
||||
const newProfile = {
|
||||
profile: 'HI',
|
||||
middleName: 'middleName',
|
||||
};
|
||||
|
||||
const response = await updateOtherProfile(api, newProfile);
|
||||
expect(response).toMatchObject(newProfile);
|
||||
|
||||
await deleteDefaultTenantUser(user.id);
|
||||
});
|
||||
|
||||
it('should be able to update profile address', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password, {
|
||||
scopes: [UserScope.Address, UserScope.Profile],
|
||||
});
|
||||
const newProfile = {
|
||||
address: {
|
||||
country: 'USA',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await updateOtherProfile(api, newProfile);
|
||||
expect(response).toMatchObject(newProfile);
|
||||
|
||||
await deleteDefaultTenantUser(user.id);
|
||||
});
|
||||
|
||||
it('should fail if user does not have the address scope', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password, {
|
||||
scopes: [UserScope.Profile],
|
||||
});
|
||||
|
||||
await expectRejects(updateOtherProfile(api, { address: { country: 'USA' } }), {
|
||||
code: 'auth.unauthorized',
|
||||
status: 400,
|
||||
});
|
||||
|
||||
await deleteDefaultTenantUser(user.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /profile/password', () => {
|
||||
it('should fail if verification record is invalid', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
|
|
Loading…
Reference in a new issue