Skip to content

Commit

Permalink
feat(core,schemas): update jwt customizer user info context
Browse files Browse the repository at this point in the history
  • Loading branch information
darcyYe committed Mar 11, 2024
1 parent bab50fc commit 0eae3b1
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 10 deletions.
91 changes: 81 additions & 10 deletions packages/core/src/libraries/user.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import type { User, CreateUser, Scope, BindMfa, MfaVerification } from '@logto/schemas';
import { MfaFactor, Users, UsersPasswordEncryptionMethod } from '@logto/schemas';
import type {
User,
CreateUser,
Scope,
BindMfa,
MfaVerification,
JwtCustomizerUserContext,
} from '@logto/schemas';
import {
userInfoSelectFields,
MfaFactor,
Users,
UsersPasswordEncryptionMethod,
OrganizationScopes,
jwtCustomizerUserContextGuard,
} from '@logto/schemas';
import { generateStandardShortId, generateStandardId } from '@logto/shared';
import type { OmitAutoSetFields } from '@logto/shared';
import type { Nullable } from '@silverhand/essentials';
import { deduplicate } from '@silverhand/essentials';
import { deduplicate, pick, pickState } from '@silverhand/essentials';
import { argon2Verify, bcryptVerify, md5, sha1, sha256 } from 'hash-wasm';
import pRetry from 'p-retry';

Expand All @@ -22,11 +36,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 };
};
Expand Down Expand Up @@ -90,8 +100,11 @@ export const createUserLibrary = (queries: Queries) => {
findUserById,
},
usersRoles: { findUsersRolesByRoleId, findUsersRolesByUserId },
rolesScopes: { findRolesScopesByRoleIds },
scopes: { findScopesByIdsAndResourceIndicator },
rolesScopes: { findRolesScopesByRoleId, findRolesScopesByRoleIds },
scopes: { findScopeById, findScopesByIdsAndResourceIndicator },
resources: { findResourceById },
userSsoIdentities,
organizations: { relations },
} = queries;

const generateUserId = async (retries = 500) =>
Expand Down Expand Up @@ -263,6 +276,63 @@ export const createUserLibrary = (queries: Queries) => {
return user;
};

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 {
generateUserId,
insertUser,
Expand All @@ -272,5 +342,6 @@ export const createUserLibrary = (queries: Queries) => {
findUserRoles,
addUserMfaVerification,
verifyUserPassword,
getUserContext,
};
};
1 change: 1 addition & 0 deletions packages/schemas/src/types/logto-config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { jsonObjectGuard } from '../../foundations/index.js';
import { accessTokenPayloadGuard, clientCredentialsPayloadGuard } from './oidc-provider.js';

export * from './oidc-provider.js';
export * from './jwt-customizer-user-context.js';

/**
* Logto OIDC signing key types, used mainly in REST API routes.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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>;

0 comments on commit 0eae3b1

Please sign in to comment.