mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(core): rename prefix to account (#6814)
This commit is contained in:
parent
3cfa6843bd
commit
4658bd2623
19 changed files with 321 additions and 142 deletions
|
@ -11,6 +11,7 @@
|
|||
"paths": {
|
||||
"/api/account-center": {
|
||||
"get": {
|
||||
"operationId": "GetAccountCenterSettings",
|
||||
"summary": "Get account center settings",
|
||||
"description": "Get the account center settings.",
|
||||
"responses": {
|
||||
|
@ -20,6 +21,7 @@
|
|||
}
|
||||
},
|
||||
"patch": {
|
||||
"operationId": "UpdateAccountCenterSettings",
|
||||
"summary": "Update account center settings",
|
||||
"description": "Update the account center settings with the provided settings.",
|
||||
"requestBody": {
|
||||
|
|
1
packages/core/src/routes/account/constants.ts
Normal file
1
packages/core/src/routes/account/constants.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const accountApiPrefix = '/account';
|
|
@ -9,6 +9,8 @@ import { buildVerificationRecordByIdAndType } from '../../libraries/verification
|
|||
import assertThat from '../../utils/assert-that.js';
|
||||
import type { UserRouter, RouterInitArgs } from '../types.js';
|
||||
|
||||
import { accountApiPrefix } from './constants.js';
|
||||
|
||||
export default function emailAndPhoneRoutes<T extends UserRouter>(...args: RouterInitArgs<T>) {
|
||||
const [router, { queries, libraries }] = args;
|
||||
const {
|
||||
|
@ -21,7 +23,7 @@ export default function emailAndPhoneRoutes<T extends UserRouter>(...args: Route
|
|||
} = libraries;
|
||||
|
||||
router.post(
|
||||
'/profile/primary-email',
|
||||
`${accountApiPrefix}/primary-email`,
|
||||
koaGuard({
|
||||
body: z.object({
|
||||
email: z.string().regex(emailRegEx),
|
||||
|
@ -67,7 +69,7 @@ export default function emailAndPhoneRoutes<T extends UserRouter>(...args: Route
|
|||
);
|
||||
|
||||
router.delete(
|
||||
'/profile/primary-email',
|
||||
`${accountApiPrefix}/primary-email`,
|
||||
koaGuard({
|
||||
status: [204, 400, 401],
|
||||
}),
|
||||
|
@ -106,7 +108,7 @@ export default function emailAndPhoneRoutes<T extends UserRouter>(...args: Route
|
|||
);
|
||||
|
||||
router.post(
|
||||
'/profile/primary-phone',
|
||||
`${accountApiPrefix}/primary-phone`,
|
||||
koaGuard({
|
||||
body: z.object({
|
||||
phone: z.string().regex(phoneRegEx),
|
||||
|
@ -152,7 +154,7 @@ export default function emailAndPhoneRoutes<T extends UserRouter>(...args: Route
|
|||
);
|
||||
|
||||
router.delete(
|
||||
'/profile/primary-phone',
|
||||
`${accountApiPrefix}/primary-phone`,
|
||||
koaGuard({
|
||||
status: [204, 400, 401],
|
||||
}),
|
|
@ -9,6 +9,8 @@ import { buildVerificationRecordByIdAndType } from '../../libraries/verification
|
|||
import assertThat from '../../utils/assert-that.js';
|
||||
import type { UserRouter, RouterInitArgs } from '../types.js';
|
||||
|
||||
import { accountApiPrefix } from './constants.js';
|
||||
|
||||
export default function identitiesRoutes<T extends UserRouter>(
|
||||
...[router, { queries, libraries }]: RouterInitArgs<T>
|
||||
) {
|
||||
|
@ -21,7 +23,7 @@ export default function identitiesRoutes<T extends UserRouter>(
|
|||
} = libraries;
|
||||
|
||||
router.post(
|
||||
'/profile/identities',
|
||||
`${accountApiPrefix}/identities`,
|
||||
koaGuard({
|
||||
body: z.object({
|
||||
newIdentifierVerificationRecordId: z.string(),
|
||||
|
@ -81,7 +83,7 @@ export default function identitiesRoutes<T extends UserRouter>(
|
|||
);
|
||||
|
||||
router.delete(
|
||||
'/profile/identities/:target',
|
||||
`${accountApiPrefix}/identities/:target`,
|
||||
koaGuard({
|
||||
params: z.object({ target: z.string() }),
|
||||
status: [204, 400, 401, 404],
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"tags": [
|
||||
{
|
||||
"name": "Profile",
|
||||
"description": "Profile routes provide functionality for managing user profiles for the end user to interact directly with access tokens."
|
||||
"name": "Account",
|
||||
"description": "Account routes provide functionality for managing user profile for the end user to interact directly with access tokens."
|
||||
},
|
||||
{
|
||||
"name": "Dev feature"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/api/profile": {
|
||||
"/api/account": {
|
||||
"get": {
|
||||
"operationId": "GetProfile",
|
||||
"summary": "Get profile",
|
||||
|
@ -56,7 +56,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/profile/profile": {
|
||||
"/api/account/profile": {
|
||||
"patch": {
|
||||
"operationId": "UpdateOtherProfile",
|
||||
"summary": "Update other profile",
|
||||
|
@ -114,7 +114,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/profile/password": {
|
||||
"/api/account/password": {
|
||||
"post": {
|
||||
"operationId": "UpdatePassword",
|
||||
"summary": "Update password",
|
||||
|
@ -142,7 +142,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/profile/primary-email": {
|
||||
"/api/account/primary-email": {
|
||||
"post": {
|
||||
"operationId": "UpdatePrimaryEmail",
|
||||
"summary": "Update primary email",
|
||||
|
@ -186,7 +186,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/profile/primary-phone": {
|
||||
"/api/account/primary-phone": {
|
||||
"post": {
|
||||
"operationId": "UpdatePrimaryPhone",
|
||||
"summary": "Update primary phone",
|
||||
|
@ -230,7 +230,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/profile/identities": {
|
||||
"/api/account/identities": {
|
||||
"post": {
|
||||
"operationId": "AddUserIdentities",
|
||||
"summary": "Add a user identity",
|
||||
|
@ -255,7 +255,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/profile/identities/{target}": {
|
||||
"/api/account/identities/{target}": {
|
||||
"delete": {
|
||||
"operationId": "DeleteIdentity",
|
||||
"summary": "Delete a user identity",
|
|
@ -16,12 +16,13 @@ import assertThat from '../../utils/assert-that.js';
|
|||
import { PasswordValidator } from '../experience/classes/libraries/password-validator.js';
|
||||
import type { UserRouter, RouterInitArgs } from '../types.js';
|
||||
|
||||
import { accountApiPrefix } from './constants.js';
|
||||
import emailAndPhoneRoutes from './email-and-phone.js';
|
||||
import identitiesRoutes from './identities.js';
|
||||
import koaAccountCenter from './middlewares/koa-account-center.js';
|
||||
import { getAccountCenterFilteredProfile, getScopedProfile } from './utils/get-scoped-profile.js';
|
||||
|
||||
export default function profileRoutes<T extends UserRouter>(...args: RouterInitArgs<T>) {
|
||||
export default function accountRoutes<T extends UserRouter>(...args: RouterInitArgs<T>) {
|
||||
const [router, { queries, libraries }] = args;
|
||||
const {
|
||||
users: { updateUserById, findUserById },
|
||||
|
@ -39,7 +40,7 @@ export default function profileRoutes<T extends UserRouter>(...args: RouterInitA
|
|||
}
|
||||
|
||||
router.get(
|
||||
'/profile',
|
||||
`${accountApiPrefix}`,
|
||||
koaGuard({
|
||||
response: userProfileResponseGuard.partial(),
|
||||
status: [200],
|
||||
|
@ -53,7 +54,7 @@ export default function profileRoutes<T extends UserRouter>(...args: RouterInitA
|
|||
);
|
||||
|
||||
router.patch(
|
||||
'/profile',
|
||||
`${accountApiPrefix}`,
|
||||
koaGuard({
|
||||
body: z.object({
|
||||
name: z.string().nullable().optional(),
|
||||
|
@ -111,7 +112,7 @@ export default function profileRoutes<T extends UserRouter>(...args: RouterInitA
|
|||
);
|
||||
|
||||
router.patch(
|
||||
'/profile/profile',
|
||||
`${accountApiPrefix}/profile`,
|
||||
koaGuard({
|
||||
body: userProfileGuard,
|
||||
response: userProfileGuard,
|
||||
|
@ -146,7 +147,7 @@ export default function profileRoutes<T extends UserRouter>(...args: RouterInitA
|
|||
);
|
||||
|
||||
router.post(
|
||||
'/profile/password',
|
||||
`${accountApiPrefix}/password`,
|
||||
koaGuard({
|
||||
body: z.object({ password: z.string().min(1) }),
|
||||
status: [204, 400, 401, 422],
|
|
@ -13,6 +13,8 @@ import koaAuth from '../middleware/koa-auth/index.js';
|
|||
import koaOidcAuth from '../middleware/koa-auth/koa-oidc-auth.js';
|
||||
import koaCors from '../middleware/koa-cors.js';
|
||||
|
||||
import { accountApiPrefix } from './account/constants.js';
|
||||
import accountRoutes from './account/index.js';
|
||||
import accountCentersRoutes from './account-center/index.js';
|
||||
import adminUserRoutes from './admin-user/index.js';
|
||||
import applicationOrganizationRoutes from './applications/application-organization.js';
|
||||
|
@ -34,7 +36,6 @@ import interactionRoutes from './interaction/index.js';
|
|||
import logRoutes from './log.js';
|
||||
import logtoConfigRoutes from './logto-config/index.js';
|
||||
import organizationRoutes from './organization/index.js';
|
||||
import profileRoutes from './profile/index.js';
|
||||
import resourceRoutes from './resource.js';
|
||||
import resourceScopeRoutes from './resource.scope.js';
|
||||
import roleRoutes from './role.js';
|
||||
|
@ -47,7 +48,7 @@ import swaggerRoutes from './swagger/index.js';
|
|||
import systemRoutes from './system.js';
|
||||
import type { AnonymousRouter, ManagementApiRouter, UserRouter } from './types.js';
|
||||
import userAssetsRoutes from './user-assets.js';
|
||||
import verificationRoutes from './verification/index.js';
|
||||
import verificationRoutes, { verificationApiPrefix } from './verification/index.js';
|
||||
import verificationCodeRoutes from './verification-code.js';
|
||||
import wellKnownRoutes from './well-known/index.js';
|
||||
import wellKnownOpenApiRoutes from './well-known/well-known.openapi.js';
|
||||
|
@ -107,7 +108,7 @@ const createRouters = (tenant: TenantContext) => {
|
|||
userRouter.use(koaOidcAuth(tenant));
|
||||
// TODO(LOG-10147): Rename to koaApiHooks, this middleware is used for both management API and user API
|
||||
userRouter.use(koaManagementApiHooks(tenant.libraries.hooks));
|
||||
profileRoutes(userRouter, tenant);
|
||||
accountRoutes(userRouter, tenant);
|
||||
verificationRoutes(userRouter, tenant);
|
||||
|
||||
wellKnownRoutes(anonymousRouter, tenant);
|
||||
|
@ -136,7 +137,7 @@ const createRouters = (tenant: TenantContext) => {
|
|||
export default function initApis(tenant: TenantContext): Koa {
|
||||
const apisApp = new Koa();
|
||||
const { adminUrlSet, cloudUrlSet } = EnvSet.values;
|
||||
apisApp.use(koaCors([adminUrlSet, cloudUrlSet], ['/profile', '/verifications']));
|
||||
apisApp.use(koaCors([adminUrlSet, cloudUrlSet], [accountApiPrefix, verificationApiPrefix]));
|
||||
apisApp.use(koaBodyEtag());
|
||||
|
||||
for (const router of createRouters(tenant)) {
|
||||
|
|
|
@ -124,7 +124,7 @@ const exceptionPrefixes = Object.freeze([
|
|||
'/interaction',
|
||||
'/experience',
|
||||
'/sign-in-exp/default/check-password',
|
||||
'/profile',
|
||||
'/account',
|
||||
'/verifications',
|
||||
]);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import type { WithI18nContext } from '#src/middleware/koa-i18next.js';
|
|||
import { type WithHookContext } from '#src/middleware/koa-management-api-hooks.js';
|
||||
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||
|
||||
import { type WithAccountCenterContext } from './profile/middlewares/koa-account-center.js';
|
||||
import { type WithAccountCenterContext } from './account/middlewares/koa-account-center.js';
|
||||
|
||||
export type AnonymousRouter = Router<unknown, WithLogContext & WithI18nContext>;
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ import { PasswordVerification } from '../experience/classes/verifications/passwo
|
|||
import { SocialVerification } from '../experience/classes/verifications/social-verification.js';
|
||||
import type { UserRouter, RouterInitArgs } from '../types.js';
|
||||
|
||||
export const verificationApiPrefix = '/verifications';
|
||||
|
||||
export default function verificationRoutes<T extends UserRouter>(
|
||||
...[router, tenantContext]: RouterInitArgs<T>
|
||||
) {
|
||||
|
@ -34,7 +36,7 @@ export default function verificationRoutes<T extends UserRouter>(
|
|||
}
|
||||
|
||||
router.post(
|
||||
'/verifications/password',
|
||||
`${verificationApiPrefix}/password`,
|
||||
koaGuard({
|
||||
body: z.object({ password: z.string().min(1) }),
|
||||
response: z.object({ verificationRecordId: z.string(), expiresAt: z.string() }),
|
||||
|
@ -76,7 +78,7 @@ export default function verificationRoutes<T extends UserRouter>(
|
|||
);
|
||||
|
||||
router.post(
|
||||
'/verifications/verification-code',
|
||||
`${verificationApiPrefix}/verification-code`,
|
||||
koaGuard({
|
||||
body: z.object({
|
||||
identifier: verificationCodeIdentifierGuard,
|
||||
|
@ -119,7 +121,7 @@ export default function verificationRoutes<T extends UserRouter>(
|
|||
);
|
||||
|
||||
router.post(
|
||||
'/verifications/verification-code/verify',
|
||||
`${verificationApiPrefix}/verification-code/verify`,
|
||||
koaGuard({
|
||||
body: z.object({
|
||||
identifier: verificationCodeIdentifierGuard,
|
||||
|
@ -164,7 +166,7 @@ export default function verificationRoutes<T extends UserRouter>(
|
|||
);
|
||||
|
||||
router.post(
|
||||
'/verifications/social',
|
||||
`${verificationApiPrefix}/social`,
|
||||
koaGuard({
|
||||
body: socialAuthorizationUrlPayloadGuard.extend({
|
||||
connectorId: z.string(),
|
||||
|
@ -202,7 +204,7 @@ export default function verificationRoutes<T extends UserRouter>(
|
|||
);
|
||||
|
||||
router.post(
|
||||
'/verifications/social/verify',
|
||||
`${verificationApiPrefix}/social/verify`,
|
||||
koaGuard({
|
||||
body: socialVerificationCallbackPayloadGuard
|
||||
.pick({
|
||||
|
|
|
@ -31,7 +31,7 @@ export default function openapiRoutes<T extends AnonymousRouter, R extends Unkno
|
|||
|
||||
// Find supplemental documents
|
||||
const supplementDocuments = await getSupplementDocuments('routes', {
|
||||
excludeDirectories: ['experience', 'interaction', 'profile', 'verification'],
|
||||
excludeDirectories: ['experience', 'interaction', 'account', 'verification'],
|
||||
});
|
||||
const baseDocument = buildManagementApiBaseDocument(pathMap, tags, ctx.request.origin);
|
||||
|
||||
|
@ -71,7 +71,7 @@ export default function openapiRoutes<T extends AnonymousRouter, R extends Unkno
|
|||
|
||||
// Find supplemental documents
|
||||
const supplementDocuments = await getSupplementDocuments('routes', {
|
||||
includeDirectories: ['profile', 'verification'],
|
||||
includeDirectories: ['account', 'verification'],
|
||||
});
|
||||
const baseDocument = buildUserApiBaseDocument(pathMap, tags, ctx.request.origin);
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ export const updatePassword = async (
|
|||
verificationRecordId: string,
|
||||
password: string
|
||||
) =>
|
||||
api.post('api/profile/password', {
|
||||
api.post('api/account/password', {
|
||||
json: { password },
|
||||
headers: { [verificationRecordIdHeader]: verificationRecordId },
|
||||
});
|
||||
|
@ -19,13 +19,13 @@ export const updatePrimaryEmail = async (
|
|||
verificationRecordId: string,
|
||||
newIdentifierVerificationRecordId: string
|
||||
) =>
|
||||
api.post('api/profile/primary-email', {
|
||||
api.post('api/account/primary-email', {
|
||||
json: { email, newIdentifierVerificationRecordId },
|
||||
headers: { [verificationRecordIdHeader]: verificationRecordId },
|
||||
});
|
||||
|
||||
export const deletePrimaryEmail = async (api: KyInstance, verificationRecordId: string) =>
|
||||
api.delete('api/profile/primary-email', {
|
||||
api.delete('api/account/primary-email', {
|
||||
headers: { [verificationRecordIdHeader]: verificationRecordId },
|
||||
});
|
||||
|
||||
|
@ -35,13 +35,13 @@ export const updatePrimaryPhone = async (
|
|||
verificationRecordId: string,
|
||||
newIdentifierVerificationRecordId: string
|
||||
) =>
|
||||
api.post('api/profile/primary-phone', {
|
||||
api.post('api/account/primary-phone', {
|
||||
json: { phone, newIdentifierVerificationRecordId },
|
||||
headers: { [verificationRecordIdHeader]: verificationRecordId },
|
||||
});
|
||||
|
||||
export const deletePrimaryPhone = async (api: KyInstance, verificationRecordId: string) =>
|
||||
api.delete('api/profile/primary-phone', {
|
||||
api.delete('api/account/primary-phone', {
|
||||
headers: { [verificationRecordIdHeader]: verificationRecordId },
|
||||
});
|
||||
|
||||
|
@ -50,7 +50,7 @@ export const updateIdentities = async (
|
|||
verificationRecordId: string,
|
||||
newIdentifierVerificationRecordId: string
|
||||
) =>
|
||||
api.post('api/profile/identities', {
|
||||
api.post('api/account/identities', {
|
||||
json: { newIdentifierVerificationRecordId },
|
||||
headers: { [verificationRecordIdHeader]: verificationRecordId },
|
||||
});
|
||||
|
@ -60,15 +60,15 @@ export const deleteIdentity = async (
|
|||
target: string,
|
||||
verificationRecordId: string
|
||||
) =>
|
||||
api.delete(`api/profile/identities/${target}`, {
|
||||
api.delete(`api/account/identities/${target}`, {
|
||||
headers: { [verificationRecordIdHeader]: verificationRecordId },
|
||||
});
|
||||
|
||||
export const updateUser = async (api: KyInstance, body: Record<string, unknown>) =>
|
||||
api.patch('api/profile', { json: body }).json<Partial<UserProfileResponse>>();
|
||||
api.patch('api/account', { 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']>>();
|
||||
api.patch('api/account/profile', { json: body }).json<Partial<UserProfileResponse['profile']>>();
|
||||
|
||||
export const getUserInfo = async (api: KyInstance) =>
|
||||
api.get('api/profile').json<Partial<UserProfileResponse>>();
|
||||
api.get('api/account').json<Partial<UserProfileResponse>>();
|
||||
|
|
|
@ -30,7 +30,7 @@ const expectedError = {
|
|||
status: 400,
|
||||
};
|
||||
|
||||
describe('profile, account center fields disabled', () => {
|
||||
describe('account center fields disabled', () => {
|
||||
beforeAll(async () => {
|
||||
await enableAllPasswordSignInMethods();
|
||||
await updateAccountCenter({
|
||||
|
@ -42,7 +42,7 @@ describe('profile, account center fields disabled', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should return only name in GET /profile', async () => {
|
||||
it('should return only name in GET /account', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password, {
|
||||
scopes: [UserScope.Email],
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable max-lines */
|
||||
import { UserScope } from '@logto/core-kit';
|
||||
import { SignInIdentifier } from '@logto/schemas';
|
||||
|
||||
|
@ -26,7 +27,7 @@ import { devFeatureTest, generateEmail, generatePhone } from '#src/utils.js';
|
|||
|
||||
const { describe, it } = devFeatureTest;
|
||||
|
||||
describe('profile (email and phone)', () => {
|
||||
describe('account (email and phone)', () => {
|
||||
beforeAll(async () => {
|
||||
await enableAllPasswordSignInMethods();
|
||||
await setEmailConnector(authedAdminApi);
|
||||
|
@ -34,7 +35,7 @@ describe('profile (email and phone)', () => {
|
|||
await enableAllAccountCenterFields(authedAdminApi);
|
||||
});
|
||||
|
||||
describe('POST /profile/primary-email', () => {
|
||||
describe('POST /account/primary-email', () => {
|
||||
it('should fail if scope is missing', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
||||
|
@ -141,7 +142,7 @@ describe('profile (email and phone)', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('DELETE /profile/primary-email', () => {
|
||||
describe('DELETE /account/primary-email', () => {
|
||||
it('should fail if scope is missing', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
||||
|
@ -237,20 +238,112 @@ describe('profile (email and phone)', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('POST /profile/primary-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 newPhone = generatePhone();
|
||||
const verificationRecordId = await createVerificationRecordByPassword(api, password);
|
||||
|
||||
await expectRejects(
|
||||
updatePrimaryPhone(api, newPhone, verificationRecordId, 'new-verification-record-id'),
|
||||
{
|
||||
code: 'auth.unauthorized',
|
||||
status: 400,
|
||||
}
|
||||
);
|
||||
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 () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
||||
const verificationRecordId = await createVerificationRecordByPassword(api, password);
|
||||
|
||||
await expectRejects(deletePrimaryPhone(api, verificationRecordId), {
|
||||
code: 'auth.unauthorized',
|
||||
status: 400,
|
||||
});
|
||||
|
||||
await deleteDefaultTenantUser(user.id);
|
||||
});
|
||||
|
@ -260,50 +353,171 @@ describe('profile (email and phone)', () => {
|
|||
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 expectRejects(deletePrimaryPhone(api, 'invalid-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 () => {
|
||||
it('should fail if phone is the only sign-up identifier', 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 enableAllPasswordSignInMethods({
|
||||
identifiers: [SignInIdentifier.Phone],
|
||||
password: true,
|
||||
verify: true,
|
||||
});
|
||||
|
||||
await expectRejects(
|
||||
updatePrimaryPhone(api, newPhone, verificationRecordId, 'new-verification-record-id'),
|
||||
{
|
||||
code: 'verification_record.not_found',
|
||||
status: 400,
|
||||
}
|
||||
);
|
||||
await expectRejects(deletePrimaryPhone(api, verificationRecordId), {
|
||||
code: 'user.phone_required',
|
||||
status: 400,
|
||||
});
|
||||
|
||||
await enableAllPasswordSignInMethods();
|
||||
await deleteDefaultTenantUser(user.id);
|
||||
});
|
||||
|
||||
it('should be able to update primary phone by verifying password', async () => {
|
||||
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.Phone],
|
||||
});
|
||||
const newPhone = generatePhone();
|
||||
const verificationRecordId = await createVerificationRecordByPassword(api, password);
|
||||
await enableAllPasswordSignInMethods({
|
||||
identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone],
|
||||
password: true,
|
||||
verify: true,
|
||||
});
|
||||
|
||||
await expectRejects(deletePrimaryPhone(api, verificationRecordId), {
|
||||
code: 'user.email_or_phone_required',
|
||||
status: 400,
|
||||
});
|
||||
|
||||
await enableAllPasswordSignInMethods();
|
||||
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, {
|
||||
scopes: [UserScope.Profile, UserScope.Phone],
|
||||
});
|
||||
const verificationRecordId = await createVerificationRecordByPassword(api, password);
|
||||
const newPhone = generatePhone();
|
||||
const newVerificationRecordId = await createAndVerifyVerificationCode(api, {
|
||||
type: SignInIdentifier.Phone,
|
||||
value: newPhone,
|
||||
|
@ -314,37 +528,16 @@ describe('profile (email and phone)', () => {
|
|||
const userInfo = await getUserInfo(api);
|
||||
expect(userInfo).toHaveProperty('primaryPhone', newPhone);
|
||||
|
||||
await deleteDefaultTenantUser(user.id);
|
||||
});
|
||||
await deletePrimaryPhone(api, verificationRecordId);
|
||||
|
||||
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);
|
||||
const userInfoAfterDelete = await getUserInfo(api);
|
||||
expect(userInfoAfterDelete).toHaveProperty('primaryPhone', null);
|
||||
|
||||
await deleteDefaultTenantUser(user.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /profile/primary-phone', () => {
|
||||
describe('DELETE /account/primary-phone', () => {
|
||||
it('should fail if scope is missing', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
||||
|
@ -440,3 +633,4 @@ describe('profile (email and phone)', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
/* eslint-enable max-lines */
|
|
@ -21,10 +21,10 @@ import { assertHookLogResult } from '../hook/utils.js';
|
|||
|
||||
const { describe, it } = devFeatureTest;
|
||||
|
||||
describe('profile', () => {
|
||||
describe('account', () => {
|
||||
const webHookMockServer = new WebhookMockServer(9999);
|
||||
const webHookApi = new WebHookApiTest();
|
||||
const hookName = 'profileApiHookEventListener';
|
||||
const hookName = 'accountApiHookEventListener';
|
||||
|
||||
beforeAll(async () => {
|
||||
await webHookMockServer.listen();
|
||||
|
@ -48,11 +48,11 @@ describe('profile', () => {
|
|||
await webHookApi.cleanUp();
|
||||
});
|
||||
|
||||
describe('GET /profile', () => {
|
||||
describe('GET /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/profile');
|
||||
const response = await api.get('api/account');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*');
|
||||
|
||||
|
@ -121,7 +121,7 @@ describe('profile', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('PATCH /profile', () => {
|
||||
describe('PATCH /account', () => {
|
||||
it('should be able to update name', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
||||
|
@ -211,7 +211,7 @@ describe('profile', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('PATCH /profile/profile', () => {
|
||||
describe('PATCH /account/profile', () => {
|
||||
it('should be able to update other profile', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
||||
|
@ -258,7 +258,7 @@ describe('profile', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('POST /profile/password', () => {
|
||||
describe('POST /account/password', () => {
|
||||
it('should fail if verification record is invalid', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
|
@ -29,7 +29,7 @@ import { devFeatureTest } from '#src/utils.js';
|
|||
|
||||
const { describe, it } = devFeatureTest;
|
||||
|
||||
describe('profile (social)', () => {
|
||||
describe('account (social)', () => {
|
||||
const state = 'fake_state';
|
||||
const redirectUri = 'http://localhost:3000/redirect';
|
||||
const authorizationCode = 'fake_code';
|
||||
|
@ -50,7 +50,7 @@ describe('profile (social)', () => {
|
|||
await clearConnectorsByTypes([ConnectorType.Social, ConnectorType.Email]);
|
||||
});
|
||||
|
||||
describe('POST /profile/identities', () => {
|
||||
describe('POST /account/identities', () => {
|
||||
it('should fail if scope is missing', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
||||
|
@ -169,7 +169,7 @@ describe('profile (social)', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('DELETE /profile/identities/:target', () => {
|
||||
describe('DELETE /account/identities/:target', () => {
|
||||
it('should fail if scope is missing', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
|
@ -1,26 +0,0 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export enum AccountCenterControlValue {
|
||||
Off = 'Off',
|
||||
ReadOnly = 'ReadOnly',
|
||||
Edit = 'Edit',
|
||||
}
|
||||
|
||||
// Control list of each field in the account center (profile API)
|
||||
// all fields are optional, if not set, the default value is `Off`
|
||||
// this can make the alteration of the field control easier
|
||||
export const accountCenterFieldControlGuard = z
|
||||
.object({
|
||||
name: z.nativeEnum(AccountCenterControlValue),
|
||||
avatar: z.nativeEnum(AccountCenterControlValue),
|
||||
profile: z.nativeEnum(AccountCenterControlValue),
|
||||
email: z.nativeEnum(AccountCenterControlValue),
|
||||
phone: z.nativeEnum(AccountCenterControlValue),
|
||||
password: z.nativeEnum(AccountCenterControlValue),
|
||||
username: z.nativeEnum(AccountCenterControlValue),
|
||||
social: z.nativeEnum(AccountCenterControlValue),
|
||||
customData: z.nativeEnum(AccountCenterControlValue),
|
||||
})
|
||||
.partial();
|
||||
|
||||
export type AccountCenterFieldControl = z.infer<typeof accountCenterFieldControlGuard>;
|
Loading…
Reference in a new issue