Skip to content

Commit

Permalink
refactor(core): split profile route (#6802)
Browse files Browse the repository at this point in the history
  • Loading branch information
wangsijie authored Nov 15, 2024
1 parent 859495b commit f8d21e4
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 105 deletions.
123 changes: 123 additions & 0 deletions packages/core/src/routes/profile/identities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { UserScope } from '@logto/core-kit';
import { VerificationType, AccountCenterControlValue } from '@logto/schemas';
import { z } from 'zod';

import koaGuard from '#src/middleware/koa-guard.js';

import RequestError from '../../errors/RequestError/index.js';
import { buildVerificationRecordByIdAndType } from '../../libraries/verification.js';
import assertThat from '../../utils/assert-that.js';
import type { UserRouter, RouterInitArgs } from '../types.js';

export default function identitiesRoutes<T extends UserRouter>(
...[router, { queries, libraries }]: RouterInitArgs<T>
) {
const {
users: { updateUserById, findUserById, deleteUserIdentity },
} = queries;

const {
users: { checkIdentifierCollision },
} = libraries;

router.post(
'/profile/identities',
koaGuard({
body: z.object({
newIdentifierVerificationRecordId: z.string(),
}),
status: [204, 400, 401],
}),
async (ctx, next) => {
const { id: userId, scopes, identityVerified } = ctx.auth;
assertThat(
identityVerified,
new RequestError({ code: 'verification_record.permission_denied', status: 401 })
);
const { newIdentifierVerificationRecordId } = ctx.guard.body;
const { fields } = ctx.accountCenter;
assertThat(
fields.social === AccountCenterControlValue.Edit,
'account_center.filed_not_editable'
);

assertThat(scopes.has(UserScope.Identities), 'auth.unauthorized');

// Check new identifier
const newVerificationRecord = await buildVerificationRecordByIdAndType({
type: VerificationType.Social,
id: newIdentifierVerificationRecordId,
queries,
libraries,
});
assertThat(newVerificationRecord.isVerified, 'verification_record.not_found');

const {
socialIdentity: { target, userInfo },
} = await newVerificationRecord.toUserProfile();

await checkIdentifierCollision({ identity: { target, id: userInfo.id } }, userId);

const user = await findUserById(userId);

assertThat(!user.identities[target], 'user.identity_already_in_use');

const updatedUser = await updateUserById(userId, {
identities: {
...user.identities,
[target]: {
userId: userInfo.id,
details: userInfo,
},
},
});

ctx.appendDataHookContext('User.Data.Updated', { user: updatedUser });

ctx.status = 204;

return next();
}
);

router.delete(
'/profile/identities/:target',
koaGuard({
params: z.object({ target: z.string() }),
status: [204, 400, 401, 404],
}),
async (ctx, next) => {
const { id: userId, scopes, identityVerified } = ctx.auth;
assertThat(
identityVerified,
new RequestError({ code: 'verification_record.permission_denied', status: 401 })
);
const { target } = ctx.guard.params;
const { fields } = ctx.accountCenter;
assertThat(
fields.social === AccountCenterControlValue.Edit,
'account_center.filed_not_editable'
);

assertThat(scopes.has(UserScope.Identities), 'auth.unauthorized');

const user = await findUserById(userId);

assertThat(
user.identities[target],
new RequestError({
code: 'user.identity_not_exist',
status: 404,
})
);

const updatedUser = await deleteUserIdentity(userId, target);

ctx.appendDataHookContext('User.Data.Updated', { user: updatedUser });

ctx.status = 204;

return next();
}
);
}
109 changes: 4 additions & 105 deletions packages/core/src/routes/profile/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable max-lines */
import { emailRegEx, phoneRegEx, usernameRegEx, UserScope } from '@logto/core-kit';
import {
VerificationType,
Expand All @@ -18,12 +17,12 @@ import assertThat from '../../utils/assert-that.js';
import { PasswordValidator } from '../experience/classes/libraries/password-validator.js';
import type { UserRouter, RouterInitArgs } from '../types.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>(
...[router, { queries, libraries }]: RouterInitArgs<T>
) {
export default function profileRoutes<T extends UserRouter>(...args: RouterInitArgs<T>) {
const [router, { queries, libraries }] = args;
const {
users: { updateUserById, findUserById, deleteUserIdentity },
signInExperiences: { findDefaultSignInExperience },
Expand Down Expand Up @@ -268,105 +267,5 @@ export default function profileRoutes<T extends UserRouter>(
}
);

router.post(
'/profile/identities',
koaGuard({
body: z.object({
newIdentifierVerificationRecordId: z.string(),
}),
status: [204, 400, 401],
}),
async (ctx, next) => {
const { id: userId, scopes, identityVerified } = ctx.auth;
assertThat(
identityVerified,
new RequestError({ code: 'verification_record.permission_denied', status: 401 })
);
const { newIdentifierVerificationRecordId } = ctx.guard.body;
const { fields } = ctx.accountCenter;
assertThat(
fields.social === AccountCenterControlValue.Edit,
'account_center.filed_not_editable'
);

assertThat(scopes.has(UserScope.Identities), 'auth.unauthorized');

// Check new identifier
const newVerificationRecord = await buildVerificationRecordByIdAndType({
type: VerificationType.Social,
id: newIdentifierVerificationRecordId,
queries,
libraries,
});
assertThat(newVerificationRecord.isVerified, 'verification_record.not_found');

const {
socialIdentity: { target, userInfo },
} = await newVerificationRecord.toUserProfile();

await checkIdentifierCollision({ identity: { target, id: userInfo.id } }, userId);

const user = await findUserById(userId);

assertThat(!user.identities[target], 'user.identity_already_in_use');

const updatedUser = await updateUserById(userId, {
identities: {
...user.identities,
[target]: {
userId: userInfo.id,
details: userInfo,
},
},
});

ctx.appendDataHookContext('User.Data.Updated', { user: updatedUser });

ctx.status = 204;

return next();
}
);

router.delete(
'/profile/identities/:target',
koaGuard({
params: z.object({ target: z.string() }),
status: [204, 400, 401, 404],
}),
async (ctx, next) => {
const { id: userId, scopes, identityVerified } = ctx.auth;
assertThat(
identityVerified,
new RequestError({ code: 'verification_record.permission_denied', status: 401 })
);
const { target } = ctx.guard.params;
const { fields } = ctx.accountCenter;
assertThat(
fields.social === AccountCenterControlValue.Edit,
'account_center.filed_not_editable'
);

assertThat(scopes.has(UserScope.Identities), 'auth.unauthorized');

const user = await findUserById(userId);

assertThat(
user.identities[target],
new RequestError({
code: 'user.identity_not_exist',
status: 404,
})
);

const updatedUser = await deleteUserIdentity(userId, target);

ctx.appendDataHookContext('User.Data.Updated', { user: updatedUser });

ctx.status = 204;

return next();
}
);
identitiesRoutes(...args);
}
/* eslint-enable max-lines */

0 comments on commit f8d21e4

Please sign in to comment.