0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

feat(core): add hasPassword field to custom JWT user context (#6096)

This commit is contained in:
Darcy Ye 2024-06-25 14:58:25 +08:00 committed by GitHub
parent f2c7799ee6
commit b52609a1ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 44 additions and 10 deletions

View file

@ -0,0 +1,7 @@
---
"@logto/console": minor
"@logto/schemas": minor
"@logto/core": minor
---
add `hasPassword` to custom JWT user context

View file

@ -182,6 +182,7 @@ export const defaultClientCredentialsPayload: ClientCredentialsPayload = {
const defaultUserContext: Partial<JwtCustomizerUserContext> = { const defaultUserContext: Partial<JwtCustomizerUserContext> = {
id: '123', id: '123',
hasPassword: false,
username: 'foo', username: 'foo',
primaryEmail: 'foo@logto.io', primaryEmail: 'foo@logto.io',
primaryPhone: '+1234567890', primaryPhone: '+1234567890',

View file

@ -87,6 +87,7 @@ export class JwtCustomizerLibrary {
await this.queries.organizations.relations.users.getOrganizationsByUserId(userId); await this.queries.organizations.relations.users.getOrganizationsByUserId(userId);
const userContext = { const userContext = {
...pick(user, ...userInfoSelectFields), ...pick(user, ...userInfoSelectFields),
hasPassword: Boolean(user.passwordEncrypted),
ssoIdentities: fullSsoIdentities.map(pickState('issuer', 'identityId', 'detail')), ssoIdentities: fullSsoIdentities.map(pickState('issuer', 'identityId', 'detail')),
mfaVerificationFactors: deduplicate(user.mfaVerifications.map(({ type }) => type)), mfaVerificationFactors: deduplicate(user.mfaVerifications.map(({ type }) => type)),
roles: roles.map((role) => { roles: roles.map((role) => {

View file

@ -55,8 +55,8 @@ export const accessTokenJwtCustomizerPayload = {
}; };
export const accessTokenSampleScript = `const getCustomJwtClaims = async ({ token, context, environmentVariables }) => { export const accessTokenSampleScript = `const getCustomJwtClaims = async ({ token, context, environmentVariables }) => {
return { user_id: context?.user?.id ?? 'unknown' }; return { user_id: context?.user?.id ?? 'unknown', hasPassword: context?.user?.hasPassword };
}`; };`;
export const clientCredentialsSampleScript = `const getCustomJwtClaims = async ({ token, context, environmentVariables }) => { export const clientCredentialsSampleScript = `const getCustomJwtClaims = async ({ token, context, environmentVariables }) => {
return { ...environmentVariables }; return { ...environmentVariables };

View file

@ -125,6 +125,8 @@ describe('get access token', () => {
testApiScopeNames.join(' ') testApiScopeNames.join(' ')
); );
expect(getAccessTokenPayload(accessToken)).toHaveProperty('user_id', guestUserId); expect(getAccessTokenPayload(accessToken)).toHaveProperty('user_id', guestUserId);
// The guest user has password.
expect(getAccessTokenPayload(accessToken)).toHaveProperty('hasPassword', true);
await deleteJwtCustomizer('access-token'); await deleteJwtCustomizer('access-token');
}); });

View file

@ -1,10 +1,17 @@
import { jsonObjectGuard } from '@logto/connector-kit'; import { jsonObjectGuard } from '@logto/connector-kit';
import { z } from 'zod'; import { type ZodType, z } from 'zod';
import { Organizations, Roles, UserSsoIdentities } from '../../db-entries/index.js'; import {
import { mfaFactorsGuard } from '../../foundations/index.js'; Organizations,
import { scopeResponseGuard } from '../scope.js'; type Organization,
import { userInfoGuard } from '../user.js'; type Role,
Roles,
UserSsoIdentities,
type UserSsoIdentity,
} from '../../db-entries/index.js';
import { mfaFactorsGuard, type MfaFactors } from '../../foundations/index.js';
import { scopeResponseGuard, type ScopeResponse } from '../scope.js';
import { userInfoGuard, type UserInfo } from '../user.js';
import { accessTokenPayloadGuard, clientCredentialsPayloadGuard } from './oidc-provider.js'; import { accessTokenPayloadGuard, clientCredentialsPayloadGuard } from './oidc-provider.js';
@ -19,7 +26,25 @@ export enum LogtoJwtTokenKeyType {
ClientCredentials = 'client-credentials', ClientCredentials = 'client-credentials',
} }
export type JwtCustomizerUserContext = UserInfo & {
hasPassword: boolean;
ssoIdentities: Array<Pick<UserSsoIdentity, 'issuer' | 'identityId' | 'detail'>>;
mfaVerificationFactors: MfaFactors;
roles: Array<
Pick<Role, 'id' | 'name' | 'description'> & {
scopes: Array<Pick<ScopeResponse, 'id' | 'name' | 'description' | 'resourceId' | 'resource'>>;
}
>;
organizations: Array<Pick<Organization, 'id' | 'name' | 'description'>>;
organizationRoles: Array<{
organizationId: string;
roleId: string;
roleName: string;
}>;
};
export const jwtCustomizerUserContextGuard = userInfoGuard.extend({ export const jwtCustomizerUserContextGuard = userInfoGuard.extend({
hasPassword: z.boolean(),
ssoIdentities: UserSsoIdentities.guard ssoIdentities: UserSsoIdentities.guard
.pick({ issuer: true, identityId: true, detail: true }) .pick({ issuer: true, identityId: true, detail: true })
.array(), .array(),
@ -40,9 +65,7 @@ export const jwtCustomizerUserContextGuard = userInfoGuard.extend({
roleName: z.string(), roleName: z.string(),
}) })
.array(), .array(),
}); }) satisfies ZodType<JwtCustomizerUserContext>;
export type JwtCustomizerUserContext = z.infer<typeof jwtCustomizerUserContextGuard>;
export const accessTokenJwtCustomizerGuard = jwtCustomizerGuard export const accessTokenJwtCustomizerGuard = jwtCustomizerGuard
.extend({ .extend({