From 348124b60e67ea017ba60e83a645310ffd07ce82 Mon Sep 17 00:00:00 2001 From: Darcy Ye <darcyye@silverhand.io> Date: Tue, 19 Mar 2024 16:01:55 +0800 Subject: [PATCH 1/5] refactor(core): update user context type --- packages/core/src/libraries/jwt-customizer.ts | 40 ++++++++----------- packages/schemas/src/types/jwt-customizer.ts | 3 +- packages/schemas/src/types/user.ts | 21 ++++++++-- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/packages/core/src/libraries/jwt-customizer.ts b/packages/core/src/libraries/jwt-customizer.ts index 2fba78875..e51270a07 100644 --- a/packages/core/src/libraries/jwt-customizer.ts +++ b/packages/core/src/libraries/jwt-customizer.ts @@ -49,29 +49,23 @@ export const createJwtCustomizerLibrary = (queries: Queries, userLibrary: UserLi }; }) ), - // No need to deal with the type here, the type will be enforced by the guard when return the result. - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - organizations: Object.fromEntries( - await Promise.all( - organizationsWithRoles.map(async ({ organizationRoles, ...organization }) => [ - organization.id, - { - roles: await Promise.all( - organizationRoles.map(async ({ id, name }) => { - const [_, fullOrganizationScopes] = await relations.rolesScopes.getEntities( - OrganizationScopes, - { organizationRoleId: id } - ); - return { - id, - name, - scopes: fullOrganizationScopes.map(pickState('id', 'name', 'description')), - }; - }) - ), - }, - ]) - ) + organizations: await Promise.all( + organizationsWithRoles.map(async ({ organizationRoles, ...organization }) => ({ + id: organization.id, + roles: await Promise.all( + organizationRoles.map(async ({ id, name }) => { + const [_, fullOrganizationScopes] = await relations.rolesScopes.getEntities( + OrganizationScopes, + { organizationRoleId: id } + ); + return { + id, + name, + scopes: fullOrganizationScopes.map(pickState('id', 'name', 'description')), + }; + }) + ), + })) ), }; diff --git a/packages/schemas/src/types/jwt-customizer.ts b/packages/schemas/src/types/jwt-customizer.ts index ca4b5c551..6d6e0b733 100644 --- a/packages/schemas/src/types/jwt-customizer.ts +++ b/packages/schemas/src/types/jwt-customizer.ts @@ -14,6 +14,7 @@ import { jwtCustomizerGuard } from './logto-config/index.js'; import { userInfoGuard } from './user.js'; const organizationDetailGuard = z.object({ + id: z.string(), roles: z.array( OrganizationRoles.guard.pick({ id: true, name: true }).extend({ scopes: z.array(OrganizationScopes.guard.pick({ id: true, name: true, description: true })), @@ -37,7 +38,7 @@ export const jwtCustomizerUserContextGuard = userInfoGuard.extend({ ), }) ), - organizations: z.record(organizationDetailGuard), + organizations: z.array(organizationDetailGuard), }); export type JwtCustomizerUserContext = z.infer<typeof jwtCustomizerUserContextGuard>; diff --git a/packages/schemas/src/types/user.ts b/packages/schemas/src/types/user.ts index d51d16203..ea059930f 100644 --- a/packages/schemas/src/types/user.ts +++ b/packages/schemas/src/types/user.ts @@ -18,9 +18,24 @@ export const userInfoSelectFields = Object.freeze([ 'isSuspended', ] as const); -export const userInfoGuard = Users.guard.pick( - Object.fromEntries(userInfoSelectFields.map((key) => [key, true])) -); +/** + * The `pick` method of previous implementation will be overridden by `merge`/`extend` method, should explicitly specify keys in `pick` method. + * DO REMEMBER TO UPDATE THIS GUARD WHEN YOU UPDATE `userInfoSelectFields`. + */ +export const userInfoGuard = Users.guard.pick({ + id: true, + username: true, + primaryEmail: true, + primaryPhone: true, + name: true, + avatar: true, + customData: true, + identities: true, + lastSignInAt: true, + createdAt: true, + applicationId: true, + isSuspended: true, +}); export type UserInfo = z.infer<typeof userInfoGuard>; From 8f5baac585efdc9d5df4d7782b86cd72a13e5848 Mon Sep 17 00:00:00 2001 From: Darcy Ye <darcyye@silverhand.io> Date: Wed, 20 Mar 2024 00:47:56 +0800 Subject: [PATCH 2/5] refactor(core,schemas): refactor to improve lib method performance --- packages/core/src/libraries/jwt-customizer.ts | 87 +++++++++---------- packages/core/src/libraries/scope.ts | 30 +++++++ packages/core/src/routes/role.scope.ts | 18 +--- packages/core/src/tenants/Libraries.ts | 4 +- packages/schemas/src/types/jwt-customizer.ts | 47 +++++----- packages/schemas/src/types/user.ts | 25 ++---- 6 files changed, 102 insertions(+), 109 deletions(-) create mode 100644 packages/core/src/libraries/scope.ts diff --git a/packages/core/src/libraries/jwt-customizer.ts b/packages/core/src/libraries/jwt-customizer.ts index e51270a07..36f437145 100644 --- a/packages/core/src/libraries/jwt-customizer.ts +++ b/packages/core/src/libraries/jwt-customizer.ts @@ -1,71 +1,64 @@ import type { JwtCustomizerUserContext } from '@logto/schemas'; -import { - userInfoSelectFields, - OrganizationScopes, - jwtCustomizerUserContextGuard, -} from '@logto/schemas'; +import { userInfoSelectFields, jwtCustomizerUserContextGuard } from '@logto/schemas'; import { deduplicate, pick, pickState } from '@silverhand/essentials'; +import { type ScopeLibrary } from '#src/libraries/scope.js'; import { type UserLibrary } from '#src/libraries/user.js'; import type Queries from '#src/tenants/Queries.js'; -export const createJwtCustomizerLibrary = (queries: Queries, userLibrary: UserLibrary) => { +// Show top 20 organization roles. +const limit = 20; +const offset = 0; + +export const createJwtCustomizerLibrary = ( + queries: Queries, + userLibrary: UserLibrary, + scopeLibrary: ScopeLibrary +) => { const { users: { findUserById }, - rolesScopes: { findRolesScopesByRoleId }, - scopes: { findScopeById }, - resources: { findResourceById }, + rolesScopes: { findRolesScopesByRoleIds }, + scopes: { findScopesByIds }, userSsoIdentities, - organizations: { relations }, + organizations: { relations, roles: organizationRoles }, } = queries; const { findUserRoles } = userLibrary; + const { attachResourceToScopes } = scopeLibrary; const getUserContext = async (userId: string): Promise<JwtCustomizerUserContext> => { const user = await findUserById(userId); const fullSsoIdentities = await userSsoIdentities.findUserSsoIdentitiesByUserId(userId); const roles = await findUserRoles(userId); + const rolesScopes = await findRolesScopesByRoleIds(roles.map(({ id }) => id)); + const scopeIds = rolesScopes.map(({ scopeId }) => scopeId); + const scopes = await findScopesByIds(scopeIds); + const scopesWithResources = await attachResourceToScopes(scopes); const organizationsWithRoles = await relations.users.getOrganizationsByUserId(userId); + const [_, organizationRolesWithScopes] = await organizationRoles.findAll(limit, offset); const userContext = { ...pick(user, ...userInfoSelectFields), ssoIdentities: fullSsoIdentities.map(pickState('issuer', 'identityId', 'detail')), mfaVerificationFactors: deduplicate(user.mfaVerifications.map(({ type }) => type)), - roles: await Promise.all( - roles.map(async (role) => { - const fullRolesScopes = await findRolesScopesByRoleId(role.id); - const scopeIds = fullRolesScopes.map(({ scopeId }) => scopeId); - return { - ...pick(role, 'id', 'name', 'description'), - scopes: await Promise.all( - scopeIds.map(async (scopeId) => { - const scope = await findScopeById(scopeId); - return { - ...pick(scope, 'id', 'name', 'description'), - ...(await findResourceById(scope.resourceId).then( - ({ indicator, id: resourceId }) => ({ indicator, resourceId }) - )), - }; - }) - ), - }; - }) - ), - organizations: await Promise.all( - organizationsWithRoles.map(async ({ organizationRoles, ...organization }) => ({ - id: organization.id, - roles: await Promise.all( - organizationRoles.map(async ({ id, name }) => { - const [_, fullOrganizationScopes] = await relations.rolesScopes.getEntities( - OrganizationScopes, - { organizationRoleId: id } - ); - return { - id, - name, - scopes: fullOrganizationScopes.map(pickState('id', 'name', 'description')), - }; - }) - ), - })) + roles: roles.map((role) => { + const scopeIds = new Set( + rolesScopes.filter(({ roleId }) => roleId === role.id).map(({ scopeId }) => scopeId) + ); + return { + ...pick(role, 'id', 'name', 'description'), + scopes: scopesWithResources + .filter(({ id }) => scopeIds.has(id)) + .map(pickState('id', 'name', 'description', 'resourceId', 'resource')), + }; + }), + organizations: organizationsWithRoles.map(pickState('id', 'name', 'description')), + organizationRoles: organizationsWithRoles.flatMap( + ({ id: organizationId, organizationRoles }) => + organizationRoles.map(({ id: roleId, name: roleName }) => ({ + organizationId, + roleId, + roleName, + scopes: organizationRolesWithScopes.find(({ id }) => id === roleId)?.scopes ?? [], + })) ), }; diff --git a/packages/core/src/libraries/scope.ts b/packages/core/src/libraries/scope.ts new file mode 100644 index 000000000..2b6d60fed --- /dev/null +++ b/packages/core/src/libraries/scope.ts @@ -0,0 +1,30 @@ +import type { Scope, ScopeResponse } from '@logto/schemas'; + +import type Queries from '#src/tenants/Queries.js'; +import assertThat from '#src/utils/assert-that.js'; + +export type ScopeLibrary = ReturnType<typeof createScopeLibrary>; + +export const createScopeLibrary = (queries: Queries) => { + const { + resources: { findResourcesByIds }, + } = queries; + + const attachResourceToScopes = async (scopes: readonly Scope[]): Promise<ScopeResponse[]> => { + const resources = await findResourcesByIds(scopes.map(({ resourceId }) => resourceId)); + return scopes.map((scope) => { + const resource = resources.find(({ id }) => id === scope.resourceId); + + assertThat(resource, new Error(`Cannot find resource for id ${scope.resourceId}`)); + + return { + ...scope, + resource, + }; + }); + }; + + return { + attachResourceToScopes, + }; +}; diff --git a/packages/core/src/routes/role.scope.ts b/packages/core/src/routes/role.scope.ts index 8cbafdf39..61b731cb6 100644 --- a/packages/core/src/routes/role.scope.ts +++ b/packages/core/src/routes/role.scope.ts @@ -1,4 +1,3 @@ -import type { Scope, ScopeResponse } from '@logto/schemas'; import { scopeResponseGuard, Scopes } from '@logto/schemas'; import { generateStandardId } from '@logto/shared'; import { tryThat } from '@silverhand/essentials'; @@ -7,7 +6,6 @@ import { object, string } from 'zod'; import RequestError from '#src/errors/RequestError/index.js'; import koaGuard from '#src/middleware/koa-guard.js'; import koaPagination from '#src/middleware/koa-pagination.js'; -import assertThat from '#src/utils/assert-that.js'; import { parseSearchParamsForSearch } from '#src/utils/search.js'; import type { AuthedRouter, RouterInitArgs } from './types.js'; @@ -16,7 +14,6 @@ export default function roleScopeRoutes<T extends AuthedRouter>( ...[router, { queries, libraries }]: RouterInitArgs<T> ) { const { - resources: { findResourcesByIds }, rolesScopes: { deleteRolesScope, findRolesScopesByRoleId, insertRolesScopes }, roles: { findRoleById }, scopes: { findScopesByIds, countScopesByScopeIds, searchScopesByScopeIds }, @@ -24,22 +21,9 @@ export default function roleScopeRoutes<T extends AuthedRouter>( const { quota, roleScopes: { validateRoleScopeAssignment }, + scopes: { attachResourceToScopes }, } = libraries; - const attachResourceToScopes = async (scopes: readonly Scope[]): Promise<ScopeResponse[]> => { - const resources = await findResourcesByIds(scopes.map(({ resourceId }) => resourceId)); - return scopes.map((scope) => { - const resource = resources.find(({ id }) => id === scope.resourceId); - - assertThat(resource, new Error(`Cannot find resource for id ${scope.resourceId}`)); - - return { - ...scope, - resource, - }; - }); - }; - router.get( '/roles/:id/scopes', koaPagination({ isOptional: true }), diff --git a/packages/core/src/tenants/Libraries.ts b/packages/core/src/tenants/Libraries.ts index 449c92553..12a703748 100644 --- a/packages/core/src/tenants/Libraries.ts +++ b/packages/core/src/tenants/Libraries.ts @@ -10,6 +10,7 @@ import { createPhraseLibrary } from '#src/libraries/phrase.js'; import { createProtectedAppLibrary } from '#src/libraries/protected-app.js'; import { createQuotaLibrary } from '#src/libraries/quota.js'; import { createRoleScopeLibrary } from '#src/libraries/role-scope.js'; +import { createScopeLibrary } from '#src/libraries/scope.js'; import { createSignInExperienceLibrary } from '#src/libraries/sign-in-experience/index.js'; import { createSocialLibrary } from '#src/libraries/social.js'; import { createSsoConnectorLibrary } from '#src/libraries/sso-connector.js'; @@ -22,8 +23,9 @@ export default class Libraries { users = createUserLibrary(this.queries); phrases = createPhraseLibrary(this.queries); hooks = createHookLibrary(this.queries); + scopes = createScopeLibrary(this.queries); socials = createSocialLibrary(this.queries, this.connectors); - jwtCustomizers = createJwtCustomizerLibrary(this.queries, this.users); + jwtCustomizers = createJwtCustomizerLibrary(this.queries, this.users, this.scopes); passcodes = createPasscodeLibrary(this.queries, this.connectors); applications = createApplicationLibrary(this.queries); verificationStatuses = createVerificationStatusLibrary(this.queries); diff --git a/packages/schemas/src/types/jwt-customizer.ts b/packages/schemas/src/types/jwt-customizer.ts index 6d6e0b733..8ec51b1d3 100644 --- a/packages/schemas/src/types/jwt-customizer.ts +++ b/packages/schemas/src/types/jwt-customizer.ts @@ -1,44 +1,39 @@ import { z } from 'zod'; import { - OrganizationRoles, + Organizations, OrganizationScopes, - Resources, Roles, - Scopes, UserSsoIdentities, } from '../db-entries/index.js'; import { mfaFactorsGuard, jsonObjectGuard } from '../foundations/index.js'; import { jwtCustomizerGuard } from './logto-config/index.js'; +import { scopeResponseGuard } from './scope.js'; import { userInfoGuard } from './user.js'; -const organizationDetailGuard = z.object({ - id: z.string(), - roles: z.array( - OrganizationRoles.guard.pick({ id: true, name: true }).extend({ - scopes: z.array(OrganizationScopes.guard.pick({ id: true, name: true, description: true })), - }) - ), -}); - -export type OrganizationDetail = z.infer<typeof organizationDetailGuard>; - export const jwtCustomizerUserContextGuard = userInfoGuard.extend({ - ssoIdentities: z.array( - UserSsoIdentities.guard.pick({ issuer: true, identityId: true, detail: true }) - ), + ssoIdentities: UserSsoIdentities.guard + .pick({ issuer: true, identityId: true, detail: true }) + .array(), mfaVerificationFactors: mfaFactorsGuard, - roles: z.array( - Roles.guard.pick({ id: true, name: true, description: true }).extend({ - scopes: z.array( - Scopes.guard - .pick({ id: true, name: true, description: true, resourceId: true }) - .merge(Resources.guard.pick({ indicator: true })) - ), + roles: Roles.guard + .pick({ id: true, name: true, description: true }) + .extend({ + scopes: scopeResponseGuard + .pick({ id: true, name: true, description: true, resourceId: true, resource: true }) + .array(), }) - ), - organizations: z.array(organizationDetailGuard), + .array(), + organizations: Organizations.guard.pick({ id: true, name: true, description: true }).array(), + organizationRoles: z + .object({ + organizationId: z.string(), + roleId: z.string(), + roleName: z.string(), + scopes: OrganizationScopes.guard.pick({ id: true, name: true }).array(), + }) + .array(), }); export type JwtCustomizerUserContext = z.infer<typeof jwtCustomizerUserContextGuard>; diff --git a/packages/schemas/src/types/user.ts b/packages/schemas/src/types/user.ts index ea059930f..a34c763ce 100644 --- a/packages/schemas/src/types/user.ts +++ b/packages/schemas/src/types/user.ts @@ -18,24 +18,13 @@ export const userInfoSelectFields = Object.freeze([ 'isSuspended', ] as const); -/** - * The `pick` method of previous implementation will be overridden by `merge`/`extend` method, should explicitly specify keys in `pick` method. - * DO REMEMBER TO UPDATE THIS GUARD WHEN YOU UPDATE `userInfoSelectFields`. - */ -export const userInfoGuard = Users.guard.pick({ - id: true, - username: true, - primaryEmail: true, - primaryPhone: true, - name: true, - avatar: true, - customData: true, - identities: true, - lastSignInAt: true, - createdAt: true, - applicationId: true, - isSuspended: true, -}); +export const userInfoGuard = Users.guard.pick( + // eslint-disable-next-line no-restricted-syntax + Object.fromEntries(userInfoSelectFields.map((field) => [field, true])) as Record< + (typeof userInfoSelectFields)[number], + true + > +); export type UserInfo = z.infer<typeof userInfoGuard>; From 8ac95a1bc20802c0f339ba87db3d6160790ffd32 Mon Sep 17 00:00:00 2001 From: Darcy Ye <darcyye@silverhand.io> Date: Wed, 20 Mar 2024 01:12:54 +0800 Subject: [PATCH 3/5] fix(console): fix some user related type in console --- packages/console/src/components/ItemPreview/UserPreview.tsx | 4 ++-- .../console/src/components/UserAccountInformation/index.tsx | 4 ++-- .../src/pages/Roles/components/AssignToRoleModal/index.tsx | 4 ++-- packages/console/src/pages/UserDetails/utils.ts | 4 ++-- packages/console/src/utils/user.ts | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/console/src/components/ItemPreview/UserPreview.tsx b/packages/console/src/components/ItemPreview/UserPreview.tsx index 7f16a3d49..f2aaf3785 100644 --- a/packages/console/src/components/ItemPreview/UserPreview.tsx +++ b/packages/console/src/components/ItemPreview/UserPreview.tsx @@ -1,4 +1,4 @@ -import { type User } from '@logto/schemas'; +import { type UserInfo } from '@logto/schemas'; import { conditional } from '@silverhand/essentials'; import SuspendedTag from '@/pages/Users/components/SuspendedTag'; @@ -9,7 +9,7 @@ import UserAvatar from '../UserAvatar'; import ItemPreview from '.'; type Props = { - user: User; + user: UserInfo; }; /** A component that renders a preview of a user. It's useful for displaying a user in a list. */ diff --git a/packages/console/src/components/UserAccountInformation/index.tsx b/packages/console/src/components/UserAccountInformation/index.tsx index ad4fc6676..e1260b998 100644 --- a/packages/console/src/components/UserAccountInformation/index.tsx +++ b/packages/console/src/components/UserAccountInformation/index.tsx @@ -1,5 +1,5 @@ import type { AdminConsoleKey } from '@logto/phrases'; -import type { User } from '@logto/schemas'; +import type { UserProfileResponse } from '@logto/schemas'; import { conditionalArray } from '@silverhand/essentials'; import { useState } from 'react'; import { toast } from 'react-hot-toast'; @@ -15,7 +15,7 @@ import * as modalStyles from '@/scss/modal.module.scss'; import * as styles from './index.module.scss'; type Props = { - user: User; + user: UserProfileResponse; password: string; title: AdminConsoleKey; onClose: () => void; diff --git a/packages/console/src/pages/Roles/components/AssignToRoleModal/index.tsx b/packages/console/src/pages/Roles/components/AssignToRoleModal/index.tsx index e7b93fb97..9d6197c06 100644 --- a/packages/console/src/pages/Roles/components/AssignToRoleModal/index.tsx +++ b/packages/console/src/pages/Roles/components/AssignToRoleModal/index.tsx @@ -1,4 +1,4 @@ -import type { RoleResponse, User, Application } from '@logto/schemas'; +import type { RoleResponse, UserProfileResponse, Application } from '@logto/schemas'; import { RoleType } from '@logto/schemas'; import { useState } from 'react'; import { toast } from 'react-hot-toast'; @@ -15,7 +15,7 @@ import { getUserTitle } from '@/utils/user'; type Props = | { - entity: User; + entity: UserProfileResponse; onClose: (success?: boolean) => void; type: RoleType.User; } diff --git a/packages/console/src/pages/UserDetails/utils.ts b/packages/console/src/pages/UserDetails/utils.ts index 053d6f8d5..f6e32b82f 100644 --- a/packages/console/src/pages/UserDetails/utils.ts +++ b/packages/console/src/pages/UserDetails/utils.ts @@ -1,11 +1,11 @@ -import type { User } from '@logto/schemas'; +import type { UserProfileResponse } from '@logto/schemas'; import { formatToInternationalPhoneNumber } from '@logto/shared/universal'; import { conditional } from '@silverhand/essentials'; import type { UserDetailsForm } from './types'; export const userDetailsParser = { - toLocalForm: (data: User): UserDetailsForm => { + toLocalForm: (data: UserProfileResponse): UserDetailsForm => { const { primaryEmail, primaryPhone, username, name, avatar, customData } = data; const parsedPhoneNumber = conditional( primaryPhone && formatToInternationalPhoneNumber(primaryPhone) diff --git a/packages/console/src/utils/user.ts b/packages/console/src/utils/user.ts index 6e81cc191..03cf80907 100644 --- a/packages/console/src/utils/user.ts +++ b/packages/console/src/utils/user.ts @@ -1,11 +1,11 @@ -import type { User } from '@logto/schemas'; +import type { UserInfo } from '@logto/schemas'; import { getUserDisplayName } from '@logto/shared/universal'; import { t } from 'i18next'; -export const getUserTitle = (user?: User): string => +export const getUserTitle = (user?: UserInfo): string => (user ? getUserDisplayName(user) : undefined) ?? t('admin_console.users.unnamed'); -export const getUserSubtitle = (user?: User) => { +export const getUserSubtitle = (user?: UserInfo) => { if (!user?.name) { return; } From e5e378d1bb9d634e2f49e96d7b4aa0ebb78f4e4f Mon Sep 17 00:00:00 2001 From: Darcy Ye <darcyye@silverhand.io> Date: Wed, 20 Mar 2024 12:58:12 +0800 Subject: [PATCH 4/5] chore: remove org role scopes field since it relies on pagination setup --- packages/core/src/libraries/jwt-customizer.ts | 8 +------- packages/schemas/src/types/jwt-customizer.ts | 8 +------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/core/src/libraries/jwt-customizer.ts b/packages/core/src/libraries/jwt-customizer.ts index 36f437145..34f13c6f1 100644 --- a/packages/core/src/libraries/jwt-customizer.ts +++ b/packages/core/src/libraries/jwt-customizer.ts @@ -6,10 +6,6 @@ import { type ScopeLibrary } from '#src/libraries/scope.js'; import { type UserLibrary } from '#src/libraries/user.js'; import type Queries from '#src/tenants/Queries.js'; -// Show top 20 organization roles. -const limit = 20; -const offset = 0; - export const createJwtCustomizerLibrary = ( queries: Queries, userLibrary: UserLibrary, @@ -20,7 +16,7 @@ export const createJwtCustomizerLibrary = ( rolesScopes: { findRolesScopesByRoleIds }, scopes: { findScopesByIds }, userSsoIdentities, - organizations: { relations, roles: organizationRoles }, + organizations: { relations }, } = queries; const { findUserRoles } = userLibrary; const { attachResourceToScopes } = scopeLibrary; @@ -34,7 +30,6 @@ export const createJwtCustomizerLibrary = ( const scopes = await findScopesByIds(scopeIds); const scopesWithResources = await attachResourceToScopes(scopes); const organizationsWithRoles = await relations.users.getOrganizationsByUserId(userId); - const [_, organizationRolesWithScopes] = await organizationRoles.findAll(limit, offset); const userContext = { ...pick(user, ...userInfoSelectFields), ssoIdentities: fullSsoIdentities.map(pickState('issuer', 'identityId', 'detail')), @@ -57,7 +52,6 @@ export const createJwtCustomizerLibrary = ( organizationId, roleId, roleName, - scopes: organizationRolesWithScopes.find(({ id }) => id === roleId)?.scopes ?? [], })) ), }; diff --git a/packages/schemas/src/types/jwt-customizer.ts b/packages/schemas/src/types/jwt-customizer.ts index 8ec51b1d3..22a21a712 100644 --- a/packages/schemas/src/types/jwt-customizer.ts +++ b/packages/schemas/src/types/jwt-customizer.ts @@ -1,11 +1,6 @@ import { z } from 'zod'; -import { - Organizations, - OrganizationScopes, - Roles, - UserSsoIdentities, -} from '../db-entries/index.js'; +import { Organizations, Roles, UserSsoIdentities } from '../db-entries/index.js'; import { mfaFactorsGuard, jsonObjectGuard } from '../foundations/index.js'; import { jwtCustomizerGuard } from './logto-config/index.js'; @@ -31,7 +26,6 @@ export const jwtCustomizerUserContextGuard = userInfoGuard.extend({ organizationId: z.string(), roleId: z.string(), roleName: z.string(), - scopes: OrganizationScopes.guard.pick({ id: true, name: true }).array(), }) .array(), }); From a2f20df9c91ba9f000cdbd5cde05676aebbc15b0 Mon Sep 17 00:00:00 2001 From: Darcy Ye <darcyye@silverhand.io> Date: Wed, 20 Mar 2024 14:35:08 +0800 Subject: [PATCH 5/5] chore: add comments --- packages/core/src/libraries/jwt-customizer.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/core/src/libraries/jwt-customizer.ts b/packages/core/src/libraries/jwt-customizer.ts index 34f13c6f1..2e1d73e7b 100644 --- a/packages/core/src/libraries/jwt-customizer.ts +++ b/packages/core/src/libraries/jwt-customizer.ts @@ -21,6 +21,12 @@ export const createJwtCustomizerLibrary = ( const { findUserRoles } = userLibrary; const { attachResourceToScopes } = scopeLibrary; + /** + * We does not include org roles' scopes for the following reason: + * 1. The org scopes query method requires `limit` and `offset` parameters. Other management API get + * these APIs from console setup while this library method is a backend used method. + * 2. Logto developers can get the org roles' id from this user context and hence query the org roles' scopes via management API. + */ const getUserContext = async (userId: string): Promise<JwtCustomizerUserContext> => { const user = await findUserById(userId); const fullSsoIdentities = await userSsoIdentities.findUserSsoIdentitiesByUserId(userId);