mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
Merge pull request #5487 from logto-io/yemq-log-8357-prepare-jwt-user-info-context
feat(core,schemas): add JWT customizer user info context
This commit is contained in:
commit
c8eaa45417
5 changed files with 130 additions and 5 deletions
84
packages/core/src/libraries/jwt-customizer.ts
Normal file
84
packages/core/src/libraries/jwt-customizer.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
import type { JwtCustomizerUserContext } from '@logto/schemas';
|
||||
import {
|
||||
userInfoSelectFields,
|
||||
OrganizationScopes,
|
||||
jwtCustomizerUserContextGuard,
|
||||
} from '@logto/schemas';
|
||||
import { deduplicate, pick, pickState } from '@silverhand/essentials';
|
||||
|
||||
import { type UserLibrary } from '#src/libraries/user.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
|
||||
export const createJwtCustomizerLibrary = (queries: Queries, userLibrary: UserLibrary) => {
|
||||
const {
|
||||
users: { findUserById },
|
||||
rolesScopes: { findRolesScopesByRoleId },
|
||||
scopes: { findScopeById },
|
||||
resources: { findResourceById },
|
||||
userSsoIdentities,
|
||||
organizations: { relations },
|
||||
} = queries;
|
||||
const { findUserRoles } = userLibrary;
|
||||
|
||||
const getUserContext = async (userId: string): Promise<JwtCustomizerUserContext> => {
|
||||
const user = await findUserById(userId);
|
||||
const fullSsoIdentities = await userSsoIdentities.findUserSsoIdentitiesByUserId(userId);
|
||||
const roles = await findUserRoles(userId);
|
||||
const organizationsWithRoles = await relations.users.getOrganizationsByUserId(userId);
|
||||
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 })
|
||||
)),
|
||||
};
|
||||
})
|
||||
),
|
||||
};
|
||||
})
|
||||
),
|
||||
// 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')),
|
||||
};
|
||||
})
|
||||
),
|
||||
},
|
||||
])
|
||||
)
|
||||
),
|
||||
};
|
||||
|
||||
return jwtCustomizerUserContextGuard.parse(userContext);
|
||||
};
|
||||
|
||||
return {
|
||||
getUserContext,
|
||||
};
|
||||
};
|
|
@ -22,11 +22,7 @@ export const encryptUserPassword = async (
|
|||
passwordEncryptionMethod: UsersPasswordEncryptionMethod;
|
||||
}> => {
|
||||
const passwordEncryptionMethod = UsersPasswordEncryptionMethod.Argon2i;
|
||||
const passwordEncrypted = await encryptPassword(
|
||||
password,
|
||||
|
||||
passwordEncryptionMethod
|
||||
);
|
||||
const passwordEncrypted = await encryptPassword(password, passwordEncryptionMethod);
|
||||
|
||||
return { passwordEncrypted, passwordEncryptionMethod };
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@ import { type CloudConnectionLibrary } from '#src/libraries/cloud-connection.js'
|
|||
import type { ConnectorLibrary } from '#src/libraries/connector.js';
|
||||
import { createDomainLibrary } from '#src/libraries/domain.js';
|
||||
import { createHookLibrary } from '#src/libraries/hook/index.js';
|
||||
import { createJwtCustomizerLibrary } from '#src/libraries/jwt-customizer.js';
|
||||
import { OrganizationInvitationLibrary } from '#src/libraries/organization-invitation.js';
|
||||
import { createPasscodeLibrary } from '#src/libraries/passcode.js';
|
||||
import { createPhraseLibrary } from '#src/libraries/phrase.js';
|
||||
|
@ -22,6 +23,7 @@ export default class Libraries {
|
|||
phrases = createPhraseLibrary(this.queries);
|
||||
hooks = createHookLibrary(this.queries);
|
||||
socials = createSocialLibrary(this.queries, this.connectors);
|
||||
jwtCustomizers = createJwtCustomizerLibrary(this.queries, this.users);
|
||||
passcodes = createPasscodeLibrary(this.queries, this.connectors);
|
||||
applications = createApplicationLibrary(this.queries);
|
||||
verificationStatuses = createVerificationStatusLibrary(this.queries);
|
||||
|
|
|
@ -26,3 +26,4 @@ export * from './tenant.js';
|
|||
export * from './tenant-organization.js';
|
||||
export * from './mapi-proxy.js';
|
||||
export * from './consent.js';
|
||||
export * from './jwt-customizer.js';
|
||||
|
|
42
packages/schemas/src/types/jwt-customizer.ts
Normal file
42
packages/schemas/src/types/jwt-customizer.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
OrganizationRoles,
|
||||
OrganizationScopes,
|
||||
Resources,
|
||||
Roles,
|
||||
Scopes,
|
||||
UserSsoIdentities,
|
||||
} from '../db-entries/index.js';
|
||||
import { mfaFactorsGuard } from '../foundations/index.js';
|
||||
|
||||
import { userInfoGuard } from './user.js';
|
||||
|
||||
const organizationDetailGuard = z.object({
|
||||
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 })
|
||||
),
|
||||
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 }))
|
||||
),
|
||||
})
|
||||
),
|
||||
organizations: z.record(organizationDetailGuard),
|
||||
});
|
||||
|
||||
export type JwtCustomizerUserContext = z.infer<typeof jwtCustomizerUserContextGuard>;
|
Loading…
Reference in a new issue