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

refactor(core): promote profile apis from session routes to its own api routes (#2583)

This commit is contained in:
Charles Zhao 2022-12-05 14:14:21 +08:00 committed by GitHub
parent 69b8f93dd6
commit 0c9e8cba0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 70 additions and 62 deletions

View file

@ -7,8 +7,9 @@ import pRetry from 'p-retry';
import { buildInsertInto } from '#src/database/insert-into.js';
import envSet from '#src/env-set/index.js';
import RequestError from '#src/errors/RequestError/index.js';
import { findRolesByRoleNames, insertRoles } from '#src/queries/roles.js';
import { hasUserWithId } from '#src/queries/user.js';
import { hasUser, hasUserWithEmail, hasUserWithId, hasUserWithPhone } from '#src/queries/user.js';
import assertThat from '#src/utils/assert-that.js';
import { encryptPassword } from '#src/utils/password.js';
@ -88,3 +89,26 @@ export const insertUser: typeof insertUserQuery = async ({ roleNames, ...rest })
return insertUserQuery({ roleNames: computedRoleNames, ...rest });
};
export const checkIdentifierCollision = async (
identifiers: {
username?: Nullable<string>;
primaryEmail?: Nullable<string>;
primaryPhone?: Nullable<string>;
},
excludeUserId?: string
) => {
const { username, primaryEmail, primaryPhone } = identifiers;
if (username && (await hasUser(username, excludeUserId))) {
throw new RequestError({ code: 'user.username_exists', status: 422 });
}
if (primaryEmail && (await hasUserWithEmail(primaryEmail, excludeUserId))) {
throw new RequestError({ code: 'user.email_exists', status: 422 });
}
if (primaryPhone && (await hasUserWithPhone(primaryPhone, excludeUserId))) {
throw new RequestError({ code: 'user.sms_exists', status: 422 });
}
};

View file

@ -66,6 +66,7 @@ jest.mock('#src/queries/user.js', () => ({
}));
jest.mock('#src/lib/user.js', () => ({
...jest.requireActual('#src/lib/user.js'),
generateUserId: jest.fn(() => 'fooId'),
encryptUserPassword: jest.fn(() => ({
passwordEncrypted: 'password',

View file

@ -6,7 +6,12 @@ import { boolean, literal, object, string } from 'zod';
import { isTrue } from '#src/env-set/parameters.js';
import RequestError from '#src/errors/RequestError/index.js';
import { encryptUserPassword, generateUserId, insertUser } from '#src/lib/user.js';
import {
checkIdentifierCollision,
encryptUserPassword,
generateUserId,
insertUser,
} from '#src/lib/user.js';
import koaGuard from '#src/middleware/koa-guard.js';
import koaPagination from '#src/middleware/koa-pagination.js';
import { revokeInstanceByUserId } from '#src/queries/oidc-model-instance.js';
@ -23,7 +28,6 @@ import {
} from '#src/queries/user.js';
import assertThat from '#src/utils/assert-that.js';
import { checkSignUpIdentifierCollision } from './session/utils.js';
import type { AuthedRouter } from './types.js';
export default function adminUserRoutes<T extends AuthedRouter>(router: T) {
@ -187,7 +191,7 @@ export default function adminUserRoutes<T extends AuthedRouter>(router: T) {
} = ctx.guard;
await findUserById(userId);
await checkSignUpIdentifierCollision(body, userId);
await checkIdentifierCollision(body, userId);
// Temp solution to validate the existence of input roleNames
if (body.roleNames?.length) {

View file

@ -6,3 +6,6 @@ export const routes = Object.freeze({
consent: signIn + '/consent',
},
});
export const verificationTimeout = 10 * 60; // 10 mins.
export const continueSignInTimeout = 10 * 60; // 10 mins.

View file

@ -4,26 +4,26 @@ import mount from 'koa-mount';
import Router from 'koa-router';
import type { Provider } from 'oidc-provider';
import koaAuth from '#src/middleware/koa-auth.js';
import koaLogSession from '#src/middleware/koa-log-session.js';
import adminUserRoutes from '#src/routes/admin-user.js';
import applicationRoutes from '#src/routes/application.js';
import authnRoutes from '#src/routes/authn.js';
import connectorRoutes from '#src/routes/connector.js';
import customPhraseRoutes from '#src/routes/custom-phrase.js';
import dashboardRoutes from '#src/routes/dashboard.js';
import logRoutes from '#src/routes/log.js';
import phraseRoutes from '#src/routes/phrase.js';
import resourceRoutes from '#src/routes/resource.js';
import roleRoutes from '#src/routes/role.js';
import sessionRoutes from '#src/routes/session/index.js';
import settingRoutes from '#src/routes/setting.js';
import signInExperiencesRoutes from '#src/routes/sign-in-experience.js';
import statusRoutes from '#src/routes/status.js';
import swaggerRoutes from '#src/routes/swagger.js';
import wellKnownRoutes from '#src/routes/well-known.js';
import koaAuth from '../middleware/koa-auth.js';
import koaLogSession from '../middleware/koa-log-session.js';
import adminUserRoutes from './admin-user.js';
import applicationRoutes from './application.js';
import authnRoutes from './authn.js';
import connectorRoutes from './connector.js';
import customPhraseRoutes from './custom-phrase.js';
import dashboardRoutes from './dashboard.js';
import logRoutes from './log.js';
import phraseRoutes from './phrase.js';
import profileRoutes from './profile.js';
import resourceRoutes from './resource.js';
import roleRoutes from './role.js';
import sessionRoutes from './session/index.js';
import settingRoutes from './setting.js';
import signInExperiencesRoutes from './sign-in-experience.js';
import statusRoutes from './status.js';
import swaggerRoutes from './swagger.js';
import type { AnonymousRouter, AuthedRouter } from './types.js';
import wellKnownRoutes from './well-known.js';
const createRouters = (provider: Provider) => {
const sessionRouter: AnonymousRouter = new Router();
@ -43,15 +43,18 @@ const createRouters = (provider: Provider) => {
dashboardRoutes(managementRouter);
customPhraseRoutes(managementRouter);
const profileRouter: AnonymousRouter = new Router();
profileRoutes(profileRouter, provider);
const anonymousRouter: AnonymousRouter = new Router();
phraseRoutes(anonymousRouter, provider);
wellKnownRoutes(anonymousRouter, provider);
statusRoutes(anonymousRouter);
authnRoutes(anonymousRouter);
// The swagger.json should contain all API routers.
swaggerRoutes(anonymousRouter, [sessionRouter, managementRouter, anonymousRouter]);
swaggerRoutes(anonymousRouter, [sessionRouter, profileRouter, managementRouter, anonymousRouter]);
return [sessionRouter, managementRouter, anonymousRouter];
return [sessionRouter, profileRouter, managementRouter, anonymousRouter];
};
export default function initRouter(app: Koa, provider: Provider) {

View file

@ -10,16 +10,15 @@ import { getLogtoConnectorById } from '#src/connectors/index.js';
import RequestError from '#src/errors/RequestError/index.js';
import { checkSessionHealth } from '#src/lib/session.js';
import { getUserInfoByAuthCode } from '#src/lib/social.js';
import { encryptUserPassword } from '#src/lib/user.js';
import { checkIdentifierCollision, encryptUserPassword } from '#src/lib/user.js';
import koaGuard from '#src/middleware/koa-guard.js';
import { deleteUserIdentity, findUserById, updateUserById } from '#src/queries/user.js';
import assertThat from '#src/utils/assert-that.js';
import type { AnonymousRouter } from '../types.js';
import { verificationTimeout } from './consts.js';
import { checkSignUpIdentifierCollision } from './utils.js';
import type { AnonymousRouter } from './types.js';
export const profileRoute = '/session/profile';
export const profileRoute = '/profile';
export default function profileRoutes<T extends AnonymousRouter>(router: T, provider: Provider) {
router.get(profileRoute, async (ctx, next) => {
@ -30,6 +29,7 @@ export default function profileRoutes<T extends AnonymousRouter>(router: T, prov
const user = await findUserById(userId);
ctx.body = pick(user, ...userInfoSelectFields);
ctx.status = 200;
return next();
});
@ -70,7 +70,7 @@ export default function profileRoutes<T extends AnonymousRouter>(router: T, prov
const { username } = ctx.guard.body;
await checkSignUpIdentifierCollision({ username }, userId);
await checkIdentifierCollision({ username }, userId);
const user = await updateUserById(userId, { username }, 'replace');
@ -120,7 +120,7 @@ export default function profileRoutes<T extends AnonymousRouter>(router: T, prov
const { primaryEmail } = ctx.guard.body;
await checkSignUpIdentifierCollision({ primaryEmail });
await checkIdentifierCollision({ primaryEmail });
await updateUserById(userId, { primaryEmail });
ctx.status = 204;
@ -157,7 +157,7 @@ export default function profileRoutes<T extends AnonymousRouter>(router: T, prov
const { primaryPhone } = ctx.guard.body;
await checkSignUpIdentifierCollision({ primaryPhone });
await checkIdentifierCollision({ primaryPhone });
await updateUserById(userId, { primaryPhone });
ctx.status = 204;

View file

@ -1,2 +0,0 @@
export const verificationTimeout = 10 * 60; // 10 mins.
export const continueSignInTimeout = 10 * 60; // 10 mins.

View file

@ -18,7 +18,6 @@ import forgotPasswordRoutes from './forgot-password.js';
import koaGuardSessionAction from './middleware/koa-guard-session-action.js';
import passwordRoutes from './password.js';
import passwordlessRoutes from './passwordless.js';
import profileRoutes from './profile.js';
import socialRoutes from './social.js';
import { getRoutePrefix } from './utils.js';
@ -106,5 +105,4 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
socialRoutes(router, provider);
continueRoutes(router, provider);
forgotPasswordRoutes(router, provider);
profileRoutes(router, provider);
}

View file

@ -9,7 +9,7 @@ import { mockSignInExperience, mockSignInMethod, mockUser } from '#src/__mocks__
import RequestError from '#src/errors/RequestError/index.js';
import { createRequester } from '#src/utils/test-utils.js';
import { verificationTimeout } from './consts.js';
import { verificationTimeout } from '../consts.js';
import * as passwordlessActions from './middleware/passwordless-action.js';
import passwordlessRoutes, { registerRoute, signInRoute } from './passwordless.js';

View file

@ -13,10 +13,10 @@ import { assignInteractionResults, getApplicationIdFromInteraction } from '#src/
import { getSignInExperienceForApplication } from '#src/lib/sign-in-experience/index.js';
import { verifyUserPassword } from '#src/lib/user.js';
import type { LogContext } from '#src/middleware/koa-log.js';
import { hasUser, hasUserWithEmail, hasUserWithPhone, updateUserById } from '#src/queries/user.js';
import { updateUserById } from '#src/queries/user.js';
import assertThat from '#src/utils/assert-that.js';
import { continueSignInTimeout, verificationTimeout } from './consts.js';
import { continueSignInTimeout, verificationTimeout } from '../consts.js';
import type { Method, Operation, VerificationResult, VerificationStorage } from './types.js';
import { continueSignInStorageGuard } from './types.js';
@ -196,29 +196,6 @@ export const checkRequiredProfile = async (
};
/* eslint-enable complexity */
export const checkSignUpIdentifierCollision = async (
identifiers: {
username?: Nullable<string>;
primaryEmail?: Nullable<string>;
primaryPhone?: Nullable<string>;
},
excludeUserId?: string
) => {
const { username, primaryEmail, primaryPhone } = identifiers;
if (username && (await hasUser(username, excludeUserId))) {
throw new RequestError({ code: 'user.username_exists', status: 422 });
}
if (primaryEmail && (await hasUserWithEmail(primaryEmail, excludeUserId))) {
throw new RequestError({ code: 'user.email_exists', status: 422 });
}
if (primaryPhone && (await hasUserWithPhone(primaryPhone, excludeUserId))) {
throw new RequestError({ code: 'user.sms_exists', status: 422 });
}
};
type SignInWithPasswordParameter = {
identifier: SignInIdentifier;
password: string;