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:
parent
69b8f93dd6
commit
0c9e8cba0d
11 changed files with 70 additions and 62 deletions
|
@ -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 });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
|
@ -1,2 +0,0 @@
|
|||
export const verificationTimeout = 10 * 60; // 10 mins.
|
||||
export const continueSignInTimeout = 10 * 60; // 10 mins.
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue