diff --git a/packages/core/src/routes/account/constants.ts b/packages/core/src/routes/account/constants.ts index 0728f936e..d9b92df0b 100644 --- a/packages/core/src/routes/account/constants.ts +++ b/packages/core/src/routes/account/constants.ts @@ -1 +1 @@ -export const accountApiPrefix = '/account'; +export const accountApiPrefix = '/my-account'; diff --git a/packages/core/src/routes/account/index.openapi.json b/packages/core/src/routes/account/index.openapi.json index 166f2f7d5..f312bef6b 100644 --- a/packages/core/src/routes/account/index.openapi.json +++ b/packages/core/src/routes/account/index.openapi.json @@ -1,12 +1,12 @@ { "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." } ], "paths": { - "/api/account": { + "/api/my-account": { "get": { "operationId": "GetProfile", "summary": "Get profile", @@ -53,7 +53,7 @@ } } }, - "/api/account/profile": { + "/api/my-account/profile": { "patch": { "operationId": "UpdateOtherProfile", "summary": "Update other profile", @@ -111,7 +111,7 @@ } } }, - "/api/account/password": { + "/api/my-account/password": { "post": { "operationId": "UpdatePassword", "summary": "Update password", @@ -139,7 +139,7 @@ } } }, - "/api/account/primary-email": { + "/api/my-account/primary-email": { "post": { "operationId": "UpdatePrimaryEmail", "summary": "Update primary email", @@ -183,7 +183,7 @@ } } }, - "/api/account/primary-phone": { + "/api/my-account/primary-phone": { "post": { "operationId": "UpdatePrimaryPhone", "summary": "Update primary phone", @@ -227,7 +227,7 @@ } } }, - "/api/account/identities": { + "/api/my-account/identities": { "post": { "operationId": "AddUserIdentities", "summary": "Add a user identity", @@ -252,7 +252,7 @@ } } }, - "/api/account/identities/{target}": { + "/api/my-account/identities/{target}": { "delete": { "operationId": "DeleteIdentity", "summary": "Delete a user identity", diff --git a/packages/core/src/routes/swagger/utils/operation-id.ts b/packages/core/src/routes/swagger/utils/operation-id.ts index 2d0d8d55c..293df454b 100644 --- a/packages/core/src/routes/swagger/utils/operation-id.ts +++ b/packages/core/src/routes/swagger/utils/operation-id.ts @@ -4,6 +4,9 @@ import pluralize from 'pluralize'; 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'; const chunk = (array: T[], chunkSize: number): T[][] => @@ -124,8 +127,8 @@ const exceptionPrefixes = Object.freeze([ '/interaction', '/experience', '/sign-in-exp/default/check-password', - '/account', - '/verifications', + accountApiPrefix, + verificationApiPrefix, ]); const isPathParameter = (segment?: string) => diff --git a/packages/integration-tests/src/api/profile.ts b/packages/integration-tests/src/api/my-account.ts similarity index 77% rename from packages/integration-tests/src/api/profile.ts rename to packages/integration-tests/src/api/my-account.ts index ea9ebc54a..a6fe5943a 100644 --- a/packages/integration-tests/src/api/profile.ts +++ b/packages/integration-tests/src/api/my-account.ts @@ -8,7 +8,7 @@ export const updatePassword = async ( verificationRecordId: string, password: string ) => - api.post('api/account/password', { + api.post('api/my-account/password', { json: { password }, headers: { [verificationRecordIdHeader]: verificationRecordId }, }); @@ -19,13 +19,13 @@ export const updatePrimaryEmail = async ( verificationRecordId: string, newIdentifierVerificationRecordId: string ) => - api.post('api/account/primary-email', { + api.post('api/my-account/primary-email', { json: { email, newIdentifierVerificationRecordId }, headers: { [verificationRecordIdHeader]: verificationRecordId }, }); export const deletePrimaryEmail = async (api: KyInstance, verificationRecordId: string) => - api.delete('api/account/primary-email', { + api.delete('api/my-account/primary-email', { headers: { [verificationRecordIdHeader]: verificationRecordId }, }); @@ -35,13 +35,13 @@ export const updatePrimaryPhone = async ( verificationRecordId: string, newIdentifierVerificationRecordId: string ) => - api.post('api/account/primary-phone', { + api.post('api/my-account/primary-phone', { json: { phone, newIdentifierVerificationRecordId }, headers: { [verificationRecordIdHeader]: verificationRecordId }, }); export const deletePrimaryPhone = async (api: KyInstance, verificationRecordId: string) => - api.delete('api/account/primary-phone', { + api.delete('api/my-account/primary-phone', { headers: { [verificationRecordIdHeader]: verificationRecordId }, }); @@ -50,7 +50,7 @@ export const updateIdentities = async ( verificationRecordId: string, newIdentifierVerificationRecordId: string ) => - api.post('api/account/identities', { + api.post('api/my-account/identities', { json: { newIdentifierVerificationRecordId }, headers: { [verificationRecordIdHeader]: verificationRecordId }, }); @@ -60,15 +60,17 @@ export const deleteIdentity = async ( target: string, verificationRecordId: string ) => - api.delete(`api/account/identities/${target}`, { + api.delete(`api/my-account/identities/${target}`, { headers: { [verificationRecordIdHeader]: verificationRecordId }, }); export const updateUser = async (api: KyInstance, body: Record) => - api.patch('api/account', { json: body }).json>(); + api.patch('api/my-account', { json: body }).json>(); export const updateOtherProfile = async (api: KyInstance, body: Record) => - api.patch('api/account/profile', { json: body }).json>(); + api + .patch('api/my-account/profile', { json: body }) + .json>(); export const getUserInfo = async (api: KyInstance) => - api.get('api/account').json>(); + api.get('api/my-account').json>(); diff --git a/packages/integration-tests/src/tests/api/account/account-center-reject.test.ts b/packages/integration-tests/src/tests/api/account/account-center-reject.test.ts index 360c0d537..0c3f0f97a 100644 --- a/packages/integration-tests/src/tests/api/account/account-center-reject.test.ts +++ b/packages/integration-tests/src/tests/api/account/account-center-reject.test.ts @@ -12,7 +12,7 @@ import { updatePrimaryEmail, updatePrimaryPhone, updateUser, -} from '#src/api/profile.js'; +} from '#src/api/my-account.js'; import { createVerificationRecordByPassword } from '#src/api/verification-record.js'; import { expectRejects } from '#src/helpers/index.js'; 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 api = await signInAndGetUserApi(username, password, { scopes: [UserScope.Email], diff --git a/packages/integration-tests/src/tests/api/account/email-and-phone.test.ts b/packages/integration-tests/src/tests/api/account/email-and-phone.test.ts index 5703f4a23..fa5dd3317 100644 --- a/packages/integration-tests/src/tests/api/account/email-and-phone.test.ts +++ b/packages/integration-tests/src/tests/api/account/email-and-phone.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable max-lines */ import { UserScope } from '@logto/core-kit'; import { SignInIdentifier } from '@logto/schemas'; @@ -10,7 +9,7 @@ import { getUserInfo, updatePrimaryEmail, updatePrimaryPhone, -} from '#src/api/profile.js'; +} from '#src/api/my-account.js'; import { createAndVerifyVerificationCode, createVerificationRecordByPassword, @@ -33,7 +32,7 @@ describe('account (email and phone)', () => { await enableAllAccountCenterFields(authedAdminApi); }); - describe('POST /account/primary-email', () => { + describe('POST /my-account/primary-email', () => { it('should fail if scope is missing', async () => { const { user, username, password } = await createDefaultTenantUserWithPassword(); 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 () => { const { user, username, password } = await createDefaultTenantUserWithPassword(); const api = await signInAndGetUserApi(username, password); @@ -236,103 +235,7 @@ describe('account (email and phone)', () => { }); }); - 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 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', () => { + 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); @@ -402,113 +305,6 @@ describe('account (email and phone)', () => { 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 () => { const { user, username, password } = await createDefaultTenantUserWithPassword(); 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 () => { const { user, username, password } = await createDefaultTenantUserWithPassword(); const api = await signInAndGetUserApi(username, password); @@ -631,4 +427,3 @@ describe('account (email and phone)', () => { }); }); }); -/* eslint-enable max-lines */ diff --git a/packages/integration-tests/src/tests/api/account/index.test.ts b/packages/integration-tests/src/tests/api/account/index.test.ts index 47382f653..94d89d742 100644 --- a/packages/integration-tests/src/tests/api/account/index.test.ts +++ b/packages/integration-tests/src/tests/api/account/index.test.ts @@ -2,7 +2,12 @@ import { UserScope } from '@logto/core-kit'; import { hookEvents, SignInIdentifier } from '@logto/schemas'; 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 { createVerificationRecordByPassword } from '#src/api/verification-record.js'; import { WebHookApiTest } from '#src/helpers/hook.js'; @@ -46,11 +51,11 @@ describe('account', () => { await webHookApi.cleanUp(); }); - describe('GET /account', () => { + describe('GET /my-account', () => { it('should allow all origins', async () => { const { user, username, password } = await createDefaultTenantUserWithPassword(); 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.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 () => { const { user, username, password } = await createDefaultTenantUserWithPassword(); 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 () => { const { user, username, password } = await createDefaultTenantUserWithPassword(); 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 () => { const { user, username, password } = await createDefaultTenantUserWithPassword(); const api = await signInAndGetUserApi(username, password); diff --git a/packages/integration-tests/src/tests/api/account/social.test.ts b/packages/integration-tests/src/tests/api/account/social.test.ts index 8c97f1a3e..b972596f1 100644 --- a/packages/integration-tests/src/tests/api/account/social.test.ts +++ b/packages/integration-tests/src/tests/api/account/social.test.ts @@ -7,7 +7,7 @@ import { mockSocialConnectorTarget, } from '#src/__mocks__/connectors-mock.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 { createSocialVerificationRecord, createVerificationRecordByPassword, @@ -26,7 +26,7 @@ import { } from '#src/helpers/profile.js'; import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js'; -describe('account (social)', () => { +describe('my-account (social)', () => { const state = 'fake_state'; const redirectUri = 'http://localhost:3000/redirect'; const authorizationCode = 'fake_code'; @@ -47,7 +47,7 @@ describe('account (social)', () => { await clearConnectorsByTypes([ConnectorType.Social, ConnectorType.Email]); }); - describe('POST /account/identities', () => { + describe('POST /my-account/identities', () => { it('should fail if scope is missing', async () => { const { user, username, password } = await createDefaultTenantUserWithPassword(); 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 () => { const { user, username, password } = await createDefaultTenantUserWithPassword(); const api = await signInAndGetUserApi(username, password);