0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

refactor(core): rename account prefix to my-account (#6821)

This commit is contained in:
wangsijie 2024-11-19 16:17:39 +08:00 committed by GitHub
parent 9a3f78d342
commit 9795412f43
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 48 additions and 243 deletions

View file

@ -1 +1 @@
export const accountApiPrefix = '/account'; export const accountApiPrefix = '/my-account';

View file

@ -1,12 +1,12 @@
{ {
"tags": [ "tags": [
{ {
"name": "Account", "name": "My account",
"description": "Account routes provide functionality for managing user profile for the end user to interact directly with access tokens." "description": "Account routes provide functionality for managing user profile for the end user to interact directly with access tokens."
} }
], ],
"paths": { "paths": {
"/api/account": { "/api/my-account": {
"get": { "get": {
"operationId": "GetProfile", "operationId": "GetProfile",
"summary": "Get profile", "summary": "Get profile",
@ -53,7 +53,7 @@
} }
} }
}, },
"/api/account/profile": { "/api/my-account/profile": {
"patch": { "patch": {
"operationId": "UpdateOtherProfile", "operationId": "UpdateOtherProfile",
"summary": "Update other profile", "summary": "Update other profile",
@ -111,7 +111,7 @@
} }
} }
}, },
"/api/account/password": { "/api/my-account/password": {
"post": { "post": {
"operationId": "UpdatePassword", "operationId": "UpdatePassword",
"summary": "Update password", "summary": "Update password",
@ -139,7 +139,7 @@
} }
} }
}, },
"/api/account/primary-email": { "/api/my-account/primary-email": {
"post": { "post": {
"operationId": "UpdatePrimaryEmail", "operationId": "UpdatePrimaryEmail",
"summary": "Update primary email", "summary": "Update primary email",
@ -183,7 +183,7 @@
} }
} }
}, },
"/api/account/primary-phone": { "/api/my-account/primary-phone": {
"post": { "post": {
"operationId": "UpdatePrimaryPhone", "operationId": "UpdatePrimaryPhone",
"summary": "Update primary phone", "summary": "Update primary phone",
@ -227,7 +227,7 @@
} }
} }
}, },
"/api/account/identities": { "/api/my-account/identities": {
"post": { "post": {
"operationId": "AddUserIdentities", "operationId": "AddUserIdentities",
"summary": "Add a user identity", "summary": "Add a user identity",
@ -252,7 +252,7 @@
} }
} }
}, },
"/api/account/identities/{target}": { "/api/my-account/identities/{target}": {
"delete": { "delete": {
"operationId": "DeleteIdentity", "operationId": "DeleteIdentity",
"summary": "Delete a user identity", "summary": "Delete a user identity",

View file

@ -4,6 +4,9 @@ import pluralize from 'pluralize';
import { EnvSet } from '#src/env-set/index.js'; import { EnvSet } from '#src/env-set/index.js';
import { accountApiPrefix } from '../../account/constants.js';
import { verificationApiPrefix } from '../../verification/index.js';
import { shouldThrow } from './general.js'; import { shouldThrow } from './general.js';
const chunk = <T>(array: T[], chunkSize: number): T[][] => const chunk = <T>(array: T[], chunkSize: number): T[][] =>
@ -124,8 +127,8 @@ const exceptionPrefixes = Object.freeze([
'/interaction', '/interaction',
'/experience', '/experience',
'/sign-in-exp/default/check-password', '/sign-in-exp/default/check-password',
'/account', accountApiPrefix,
'/verifications', verificationApiPrefix,
]); ]);
const isPathParameter = (segment?: string) => const isPathParameter = (segment?: string) =>

View file

@ -8,7 +8,7 @@ export const updatePassword = async (
verificationRecordId: string, verificationRecordId: string,
password: string password: string
) => ) =>
api.post('api/account/password', { api.post('api/my-account/password', {
json: { password }, json: { password },
headers: { [verificationRecordIdHeader]: verificationRecordId }, headers: { [verificationRecordIdHeader]: verificationRecordId },
}); });
@ -19,13 +19,13 @@ export const updatePrimaryEmail = async (
verificationRecordId: string, verificationRecordId: string,
newIdentifierVerificationRecordId: string newIdentifierVerificationRecordId: string
) => ) =>
api.post('api/account/primary-email', { api.post('api/my-account/primary-email', {
json: { email, newIdentifierVerificationRecordId }, json: { email, newIdentifierVerificationRecordId },
headers: { [verificationRecordIdHeader]: verificationRecordId }, headers: { [verificationRecordIdHeader]: verificationRecordId },
}); });
export const deletePrimaryEmail = async (api: KyInstance, verificationRecordId: string) => export const deletePrimaryEmail = async (api: KyInstance, verificationRecordId: string) =>
api.delete('api/account/primary-email', { api.delete('api/my-account/primary-email', {
headers: { [verificationRecordIdHeader]: verificationRecordId }, headers: { [verificationRecordIdHeader]: verificationRecordId },
}); });
@ -35,13 +35,13 @@ export const updatePrimaryPhone = async (
verificationRecordId: string, verificationRecordId: string,
newIdentifierVerificationRecordId: string newIdentifierVerificationRecordId: string
) => ) =>
api.post('api/account/primary-phone', { api.post('api/my-account/primary-phone', {
json: { phone, newIdentifierVerificationRecordId }, json: { phone, newIdentifierVerificationRecordId },
headers: { [verificationRecordIdHeader]: verificationRecordId }, headers: { [verificationRecordIdHeader]: verificationRecordId },
}); });
export const deletePrimaryPhone = async (api: KyInstance, verificationRecordId: string) => export const deletePrimaryPhone = async (api: KyInstance, verificationRecordId: string) =>
api.delete('api/account/primary-phone', { api.delete('api/my-account/primary-phone', {
headers: { [verificationRecordIdHeader]: verificationRecordId }, headers: { [verificationRecordIdHeader]: verificationRecordId },
}); });
@ -50,7 +50,7 @@ export const updateIdentities = async (
verificationRecordId: string, verificationRecordId: string,
newIdentifierVerificationRecordId: string newIdentifierVerificationRecordId: string
) => ) =>
api.post('api/account/identities', { api.post('api/my-account/identities', {
json: { newIdentifierVerificationRecordId }, json: { newIdentifierVerificationRecordId },
headers: { [verificationRecordIdHeader]: verificationRecordId }, headers: { [verificationRecordIdHeader]: verificationRecordId },
}); });
@ -60,15 +60,17 @@ export const deleteIdentity = async (
target: string, target: string,
verificationRecordId: string verificationRecordId: string
) => ) =>
api.delete(`api/account/identities/${target}`, { api.delete(`api/my-account/identities/${target}`, {
headers: { [verificationRecordIdHeader]: verificationRecordId }, headers: { [verificationRecordIdHeader]: verificationRecordId },
}); });
export const updateUser = async (api: KyInstance, body: Record<string, unknown>) => export const updateUser = async (api: KyInstance, body: Record<string, unknown>) =>
api.patch('api/account', { json: body }).json<Partial<UserProfileResponse>>(); api.patch('api/my-account', { json: body }).json<Partial<UserProfileResponse>>();
export const updateOtherProfile = async (api: KyInstance, body: Record<string, unknown>) => export const updateOtherProfile = async (api: KyInstance, body: Record<string, unknown>) =>
api.patch('api/account/profile', { json: body }).json<Partial<UserProfileResponse['profile']>>(); api
.patch('api/my-account/profile', { json: body })
.json<Partial<UserProfileResponse['profile']>>();
export const getUserInfo = async (api: KyInstance) => export const getUserInfo = async (api: KyInstance) =>
api.get('api/account').json<Partial<UserProfileResponse>>(); api.get('api/my-account').json<Partial<UserProfileResponse>>();

View file

@ -12,7 +12,7 @@ import {
updatePrimaryEmail, updatePrimaryEmail,
updatePrimaryPhone, updatePrimaryPhone,
updateUser, updateUser,
} from '#src/api/profile.js'; } from '#src/api/my-account.js';
import { createVerificationRecordByPassword } from '#src/api/verification-record.js'; import { createVerificationRecordByPassword } from '#src/api/verification-record.js';
import { expectRejects } from '#src/helpers/index.js'; import { expectRejects } from '#src/helpers/index.js';
import { import {
@ -40,7 +40,7 @@ describe('account center fields disabled', () => {
}); });
}); });
it('should return only name in GET /account', async () => { it('should return only name in GET /my-account', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword(); const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password, { const api = await signInAndGetUserApi(username, password, {
scopes: [UserScope.Email], scopes: [UserScope.Email],

View file

@ -1,4 +1,3 @@
/* eslint-disable max-lines */
import { UserScope } from '@logto/core-kit'; import { UserScope } from '@logto/core-kit';
import { SignInIdentifier } from '@logto/schemas'; import { SignInIdentifier } from '@logto/schemas';
@ -10,7 +9,7 @@ import {
getUserInfo, getUserInfo,
updatePrimaryEmail, updatePrimaryEmail,
updatePrimaryPhone, updatePrimaryPhone,
} from '#src/api/profile.js'; } from '#src/api/my-account.js';
import { import {
createAndVerifyVerificationCode, createAndVerifyVerificationCode,
createVerificationRecordByPassword, createVerificationRecordByPassword,
@ -33,7 +32,7 @@ describe('account (email and phone)', () => {
await enableAllAccountCenterFields(authedAdminApi); await enableAllAccountCenterFields(authedAdminApi);
}); });
describe('POST /account/primary-email', () => { describe('POST /my-account/primary-email', () => {
it('should fail if scope is missing', async () => { it('should fail if scope is missing', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword(); const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password); const api = await signInAndGetUserApi(username, password);
@ -140,7 +139,7 @@ describe('account (email and phone)', () => {
}); });
}); });
describe('DELETE /account/primary-email', () => { describe('DELETE /my-account/primary-email', () => {
it('should fail if scope is missing', async () => { it('should fail if scope is missing', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword(); const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password); const api = await signInAndGetUserApi(username, password);
@ -236,103 +235,7 @@ describe('account (email and phone)', () => {
}); });
}); });
describe('POST /account/primary-phone', () => { describe('POST /my-account/primary-phone', () => {
it('should fail if scope is missing', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password);
const verificationRecordId = await createVerificationRecordByPassword(api, password);
await expectRejects(deletePrimaryEmail(api, verificationRecordId), {
code: 'auth.unauthorized',
status: 400,
});
await deleteDefaultTenantUser(user.id);
});
it('should fail if verification record is invalid', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password, {
scopes: [UserScope.Profile, UserScope.Email],
});
await expectRejects(deletePrimaryEmail(api, 'invalid-verification-record-id'), {
code: 'verification_record.permission_denied',
status: 401,
});
await deleteDefaultTenantUser(user.id);
});
it('should fail if email is the only sign-up identifier', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password, {
scopes: [UserScope.Profile, UserScope.Email],
});
const verificationRecordId = await createVerificationRecordByPassword(api, password);
await enableAllPasswordSignInMethods({
identifiers: [SignInIdentifier.Email],
password: true,
verify: true,
});
await expectRejects(deletePrimaryEmail(api, verificationRecordId), {
code: 'user.email_required',
status: 400,
});
await enableAllPasswordSignInMethods();
await deleteDefaultTenantUser(user.id);
});
it('should fail if email or phone is the sign-up identifier', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password, {
scopes: [UserScope.Profile, UserScope.Email],
});
const verificationRecordId = await createVerificationRecordByPassword(api, password);
await enableAllPasswordSignInMethods({
identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone],
password: true,
verify: true,
});
await expectRejects(deletePrimaryEmail(api, verificationRecordId), {
code: 'user.email_or_phone_required',
status: 400,
});
await enableAllPasswordSignInMethods();
await deleteDefaultTenantUser(user.id);
});
it('should be able to delete primary email', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password, {
scopes: [UserScope.Profile, UserScope.Email],
});
const verificationRecordId = await createVerificationRecordByPassword(api, password);
const newEmail = generateEmail();
const newVerificationRecordId = await createAndVerifyVerificationCode(api, {
type: SignInIdentifier.Email,
value: newEmail,
});
await updatePrimaryEmail(api, newEmail, verificationRecordId, newVerificationRecordId);
const userInfo = await getUserInfo(api);
expect(userInfo).toHaveProperty('primaryEmail', newEmail);
await deletePrimaryEmail(api, verificationRecordId);
const userInfoAfterDelete = await getUserInfo(api);
expect(userInfoAfterDelete).toHaveProperty('primaryEmail', null);
await deleteDefaultTenantUser(user.id);
});
});
describe('DELETE /account/primary-phone', () => {
it('should fail if scope is missing', async () => { it('should fail if scope is missing', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword(); const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password); const api = await signInAndGetUserApi(username, password);
@ -402,113 +305,6 @@ describe('account (email and phone)', () => {
await deleteDefaultTenantUser(user.id); await deleteDefaultTenantUser(user.id);
}); });
describe('POST /account/primary-phone', () => {
it('should fail if scope is missing', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password);
const newPhone = generatePhone();
const verificationRecordId = await createVerificationRecordByPassword(api, password);
await expectRejects(
updatePrimaryPhone(api, newPhone, verificationRecordId, 'new-verification-record-id'),
{
code: 'auth.unauthorized',
status: 400,
}
);
await deleteDefaultTenantUser(user.id);
});
it('should fail if verification record is invalid', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password, {
scopes: [UserScope.Profile, UserScope.Phone],
});
const newPhone = generatePhone();
await expectRejects(
updatePrimaryPhone(
api,
newPhone,
'invalid-verification-record-id',
'new-verification-record-id'
),
{
code: 'verification_record.permission_denied',
status: 401,
}
);
await deleteDefaultTenantUser(user.id);
});
it('should fail if new identifier verification record is invalid', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password, {
scopes: [UserScope.Profile, UserScope.Phone],
});
const newPhone = generatePhone();
const verificationRecordId = await createVerificationRecordByPassword(api, password);
await expectRejects(
updatePrimaryPhone(api, newPhone, verificationRecordId, 'new-verification-record-id'),
{
code: 'verification_record.not_found',
status: 400,
}
);
await deleteDefaultTenantUser(user.id);
});
it('should be able to update primary phone by verifying password', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password, {
scopes: [UserScope.Profile, UserScope.Phone],
});
const newPhone = generatePhone();
const verificationRecordId = await createVerificationRecordByPassword(api, password);
const newVerificationRecordId = await createAndVerifyVerificationCode(api, {
type: SignInIdentifier.Phone,
value: newPhone,
});
await updatePrimaryPhone(api, newPhone, verificationRecordId, newVerificationRecordId);
const userInfo = await getUserInfo(api);
expect(userInfo).toHaveProperty('primaryPhone', newPhone);
await deleteDefaultTenantUser(user.id);
});
it('should be able to update primary phone by verifying existing phone', async () => {
const primaryPhone = generatePhone();
const { user, username, password } = await createDefaultTenantUserWithPassword({
primaryPhone,
});
const api = await signInAndGetUserApi(username, password, {
scopes: [UserScope.Profile, UserScope.Phone],
});
const newPhone = generatePhone();
const verificationRecordId = await createAndVerifyVerificationCode(api, {
type: SignInIdentifier.Phone,
value: primaryPhone,
});
const newVerificationRecordId = await createAndVerifyVerificationCode(api, {
type: SignInIdentifier.Phone,
value: newPhone,
});
await updatePrimaryPhone(api, newPhone, verificationRecordId, newVerificationRecordId);
const userInfo = await getUserInfo(api);
expect(userInfo).toHaveProperty('primaryPhone', newPhone);
await deleteDefaultTenantUser(user.id);
});
});
it('should be able to delete primary phone', async () => { it('should be able to delete primary phone', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword(); const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password, { const api = await signInAndGetUserApi(username, password, {
@ -535,7 +331,7 @@ describe('account (email and phone)', () => {
}); });
}); });
describe('DELETE /account/primary-phone', () => { describe('DELETE /my-account/primary-phone', () => {
it('should fail if scope is missing', async () => { it('should fail if scope is missing', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword(); const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password); const api = await signInAndGetUserApi(username, password);
@ -631,4 +427,3 @@ describe('account (email and phone)', () => {
}); });
}); });
}); });
/* eslint-enable max-lines */

View file

@ -2,7 +2,12 @@ import { UserScope } from '@logto/core-kit';
import { hookEvents, SignInIdentifier } from '@logto/schemas'; import { hookEvents, SignInIdentifier } from '@logto/schemas';
import { enableAllAccountCenterFields } from '#src/api/account-center.js'; import { enableAllAccountCenterFields } from '#src/api/account-center.js';
import { getUserInfo, updateOtherProfile, updatePassword, updateUser } from '#src/api/profile.js'; import {
getUserInfo,
updateOtherProfile,
updatePassword,
updateUser,
} from '#src/api/my-account.js';
import { updateSignInExperience } from '#src/api/sign-in-experience.js'; import { updateSignInExperience } from '#src/api/sign-in-experience.js';
import { createVerificationRecordByPassword } from '#src/api/verification-record.js'; import { createVerificationRecordByPassword } from '#src/api/verification-record.js';
import { WebHookApiTest } from '#src/helpers/hook.js'; import { WebHookApiTest } from '#src/helpers/hook.js';
@ -46,11 +51,11 @@ describe('account', () => {
await webHookApi.cleanUp(); await webHookApi.cleanUp();
}); });
describe('GET /account', () => { describe('GET /my-account', () => {
it('should allow all origins', async () => { it('should allow all origins', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword(); const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password); const api = await signInAndGetUserApi(username, password);
const response = await api.get('api/account'); const response = await api.get('api/my-account');
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*'); expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*');
@ -119,7 +124,7 @@ describe('account', () => {
}); });
}); });
describe('PATCH /account', () => { describe('PATCH /my-account', () => {
it('should be able to update name', async () => { it('should be able to update name', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword(); const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password); const api = await signInAndGetUserApi(username, password);
@ -209,7 +214,7 @@ describe('account', () => {
}); });
}); });
describe('PATCH /account/profile', () => { describe('PATCH /my-account/profile', () => {
it('should be able to update other profile', async () => { it('should be able to update other profile', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword(); const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password); const api = await signInAndGetUserApi(username, password);
@ -256,7 +261,7 @@ describe('account', () => {
}); });
}); });
describe('POST /account/password', () => { describe('POST /my-account/password', () => {
it('should fail if verification record is invalid', async () => { it('should fail if verification record is invalid', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword(); const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password); const api = await signInAndGetUserApi(username, password);

View file

@ -7,7 +7,7 @@ import {
mockSocialConnectorTarget, mockSocialConnectorTarget,
} from '#src/__mocks__/connectors-mock.js'; } from '#src/__mocks__/connectors-mock.js';
import { enableAllAccountCenterFields } from '#src/api/account-center.js'; import { enableAllAccountCenterFields } from '#src/api/account-center.js';
import { deleteIdentity, getUserInfo, updateIdentities } from '#src/api/profile.js'; import { deleteIdentity, getUserInfo, updateIdentities } from '#src/api/my-account.js';
import { import {
createSocialVerificationRecord, createSocialVerificationRecord,
createVerificationRecordByPassword, createVerificationRecordByPassword,
@ -26,7 +26,7 @@ import {
} from '#src/helpers/profile.js'; } from '#src/helpers/profile.js';
import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js'; import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js';
describe('account (social)', () => { describe('my-account (social)', () => {
const state = 'fake_state'; const state = 'fake_state';
const redirectUri = 'http://localhost:3000/redirect'; const redirectUri = 'http://localhost:3000/redirect';
const authorizationCode = 'fake_code'; const authorizationCode = 'fake_code';
@ -47,7 +47,7 @@ describe('account (social)', () => {
await clearConnectorsByTypes([ConnectorType.Social, ConnectorType.Email]); await clearConnectorsByTypes([ConnectorType.Social, ConnectorType.Email]);
}); });
describe('POST /account/identities', () => { describe('POST /my-account/identities', () => {
it('should fail if scope is missing', async () => { it('should fail if scope is missing', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword(); const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password); const api = await signInAndGetUserApi(username, password);
@ -166,7 +166,7 @@ describe('account (social)', () => {
}); });
}); });
describe('DELETE /account/identities/:target', () => { describe('DELETE /my-account/identities/:target', () => {
it('should fail if scope is missing', async () => { it('should fail if scope is missing', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword(); const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password); const api = await signInAndGetUserApi(username, password);