From fcda26ac2f8fb618d51c8639897e899ae99197c9 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Fri, 10 Nov 2023 14:59:48 +0800 Subject: [PATCH] refactor(core): fix `resource` parameter --- packages/core/src/oidc/grants/index.ts | 27 +++++++++++++++++++ .../core/src/oidc/grants/refresh-token.ts | 9 ++++--- packages/core/src/oidc/init.test.ts | 2 +- packages/core/src/oidc/init.ts | 16 +++-------- packages/core/src/tenants/Tenant.ts | 2 +- packages/demo-app/src/App.tsx | 4 +-- 6 files changed, 39 insertions(+), 21 deletions(-) create mode 100644 packages/core/src/oidc/grants/index.ts diff --git a/packages/core/src/oidc/grants/index.ts b/packages/core/src/oidc/grants/index.ts new file mode 100644 index 000000000..49184aac8 --- /dev/null +++ b/packages/core/src/oidc/grants/index.ts @@ -0,0 +1,27 @@ +import { GrantType } from '@logto/schemas'; +import type Provider from 'oidc-provider'; +import instance from 'oidc-provider/lib/helpers/weak_cache.js'; + +import { type EnvSet } from '#src/env-set/index.js'; +import type Queries from '#src/tenants/Queries.js'; + +import * as refreshToken from './refresh-token.js'; + +export const registerGrants = (oidc: Provider, envSet: EnvSet, queries: Queries) => { + const { + features: { resourceIndicators }, + } = instance(oidc).configuration(); + + // If resource indicators are enabled, append `resource` to the parameters and allow it to + // be duplicated + const parameterConfig: [parameters: string[], duplicates: string[]] = resourceIndicators.enabled + ? [[...refreshToken.parameters, 'resource'], ['resource']] + : [[...refreshToken.parameters], []]; + + // Override the default `refresh_token` grant + oidc.registerGrantType( + GrantType.RefreshToken, + refreshToken.buildHandler(envSet, queries.organizations), + ...parameterConfig + ); +}; diff --git a/packages/core/src/oidc/grants/refresh-token.ts b/packages/core/src/oidc/grants/refresh-token.ts index 07eb98e87..eb3e4225e 100644 --- a/packages/core/src/oidc/grants/refresh-token.ts +++ b/packages/core/src/oidc/grants/refresh-token.ts @@ -20,7 +20,7 @@ */ import { UserScope, buildOrganizationUrn } from '@logto/core-kit'; -import { type Optional, cond, isKeyInObject } from '@silverhand/essentials'; +import { type Optional, isKeyInObject, cond } from '@silverhand/essentials'; import type Provider from 'oidc-provider'; import { errors } from 'oidc-provider'; import difference from 'oidc-provider/lib/helpers/_/difference.js'; @@ -53,7 +53,7 @@ const gty = 'refresh_token'; /** * The valid parameters for the `organization_token` grant type. Note the `resource` parameter is - * handled by oidc-provider so we don't need to include it here. + * not included here since it should be handled per configuration when registering the grant type. */ export const parameters = Object.freeze(['refresh_token', 'organization_id', 'scope'] as const); @@ -130,7 +130,9 @@ export const buildHandler: ( } /* === RFC 0001 === */ - if (params.organization_id) { + // The params object may have the key with `undefined` value, so we have to use `Boolean` to check. + const organizationId = cond(Boolean(params.organization_id) && String(params.organization_id)); + if (organizationId) { // Validate if the refresh token has the required scope from RFC 0001. if (!refreshToken.scopes.has(UserScope.Organizations)) { throw new InsufficientScope('refresh token missing required scope', UserScope.Organizations); @@ -218,7 +220,6 @@ export const buildHandler: ( /* === RFC 0001 === */ // Check membership - const organizationId = cond(Boolean(params.organization_id) && String(params.organization_id)); if ( organizationId && !(await queries.relations.users.exists(organizationId, account.accountId)) diff --git a/packages/core/src/oidc/init.test.ts b/packages/core/src/oidc/init.test.ts index 58abc41d5..212f63204 100644 --- a/packages/core/src/oidc/init.test.ts +++ b/packages/core/src/oidc/init.test.ts @@ -7,6 +7,6 @@ describe('oidc provider init', () => { it('init should not throw', async () => { const { queries, libraries } = new MockTenant(); - expect(() => initOidc('mock_id', mockEnvSet, queries, libraries)).not.toThrow(); + expect(() => initOidc(mockEnvSet, queries, libraries)).not.toThrow(); }); }); diff --git a/packages/core/src/oidc/init.ts b/packages/core/src/oidc/init.ts index ee11aeb22..e388e0bd3 100644 --- a/packages/core/src/oidc/init.ts +++ b/packages/core/src/oidc/init.ts @@ -8,7 +8,6 @@ import { customClientMetadataDefault, CustomClientMetadataKey, demoAppApplicationId, - GrantType, inSeconds, logtoCookieKey, type LogtoUiCookie, @@ -31,7 +30,7 @@ import type Libraries from '#src/tenants/Libraries.js'; import type Queries from '#src/tenants/Queries.js'; import defaults from './defaults.js'; -import * as refreshToken from './grants/refresh-token.js'; +import { registerGrants } from './grants/index.js'; import { findResource, findResourceScopes, getSharedResourceServerData } from './resource.js'; import { getUserClaimData, getUserClaims } from './scope.js'; import { OIDCExtraParametersKey, InteractionMode } from './type.js'; @@ -39,12 +38,7 @@ import { OIDCExtraParametersKey, InteractionMode } from './type.js'; // Temporarily removed 'EdDSA' since it's not supported by browser yet const supportedSigningAlgs = Object.freeze(['RS256', 'PS256', 'ES256', 'ES384', 'ES512'] as const); -export default function initOidc( - tenantId: string, - envSet: EnvSet, - queries: Queries, - libraries: Libraries -): Provider { +export default function initOidc(envSet: EnvSet, queries: Queries, libraries: Libraries): Provider { const { resources: { findDefaultResource }, users: { findUserById }, @@ -287,11 +281,7 @@ export default function initOidc( }); addOidcEventListeners(oidc, queries); - - // Override the default `refresh_token` grant - oidc.registerGrantType(GrantType.RefreshToken, refreshToken.buildHandler(envSet, organizations), [ - ...refreshToken.parameters, - ]); + registerGrants(oidc, envSet, queries); // Provide audit log context for event listeners oidc.use(koaAuditLog(queries)); diff --git a/packages/core/src/tenants/Tenant.ts b/packages/core/src/tenants/Tenant.ts index aabbe8e14..1cf584bf2 100644 --- a/packages/core/src/tenants/Tenant.ts +++ b/packages/core/src/tenants/Tenant.ts @@ -83,7 +83,7 @@ export default class Tenant implements TenantContext { app.use(koaSecurityHeaders(mountedApps, id)); // Mount OIDC - const provider = initOidc(id, envSet, queries, libraries); + const provider = initOidc(envSet, queries, libraries); app.use(mount('/oidc', provider.app)); const tenantContext: TenantContext = { diff --git a/packages/demo-app/src/App.tsx b/packages/demo-app/src/App.tsx index 7d65b5193..46a43dcaa 100644 --- a/packages/demo-app/src/App.tsx +++ b/packages/demo-app/src/App.tsx @@ -101,8 +101,8 @@ const App = () => { config={{ endpoint: window.location.origin, appId: demoAppApplicationId, - prompt: Prompt.Consent, - // Use enum values once JS SDK is updated + prompt: Prompt.Login, + // TODO: Use enum values once JS SDK is updated scopes: ['urn:logto:scope:organizations', 'urn:logto:scope:organization_roles'], resources: ['urn:logto:resource:organizations'], }}