diff --git a/packages/core/src/oidc/grants/token-exchange/index.test.ts b/packages/core/src/oidc/grants/token-exchange/index.test.ts index ec6628375..0f45a0239 100644 --- a/packages/core/src/oidc/grants/token-exchange/index.test.ts +++ b/packages/core/src/oidc/grants/token-exchange/index.test.ts @@ -1,5 +1,4 @@ import { type SubjectToken } from '@logto/schemas'; -import { createMockUtils } from '@logto/shared/esm'; import { type KoaContextWithOIDC, errors } from 'oidc-provider'; import Sinon from 'sinon'; @@ -10,11 +9,6 @@ import { MockTenant } from '#src/test-utils/tenant.js'; import { TokenExchangeTokenType } from './types.js'; const { jest } = import.meta; -const { mockEsm } = createMockUtils(jest); - -const { handleActorToken } = mockEsm('./actor-token.js', () => ({ - handleActorToken: jest.fn().mockResolvedValue({ accountId: undefined }), -})); const { buildHandler } = await import('./index.js'); diff --git a/packages/core/src/oidc/grants/token-exchange/index.ts b/packages/core/src/oidc/grants/token-exchange/index.ts index 3805193bb..a9358a5ef 100644 --- a/packages/core/src/oidc/grants/token-exchange/index.ts +++ b/packages/core/src/oidc/grants/token-exchange/index.ts @@ -58,7 +58,7 @@ export const buildHandler: ( queries: Queries ) => Parameters['1'] = (envSet, queries) => async (ctx, next) => { const { client, params, requestParamScopes, provider } = ctx.oidc; - const { Account, AccessToken } = provider; + const { Account, AccessToken, Grant } = provider; assertThat(params, new InvalidGrant('parameters must be available')); assertThat(client, new InvalidClient('client must be available')); @@ -90,6 +90,11 @@ export const buildHandler: ( ctx.oidc.entity('Account', account); + const grant = new Grant({ + accountId: account.accountId, + clientId: client.clientId, + }); + const { organizationId } = await checkOrganizationAccess(ctx, queries, account); const accessToken = new AccessToken({ @@ -97,9 +102,7 @@ export const buildHandler: ( clientId: client.clientId, gty: GrantType.TokenExchange, client, - // The token exchange grant type does not have a grant ID or grant object, - // so we use an empty string here. - grantId: '', + grantId: await grant.save(), // eslint-disable-next-line @typescript-eslint/no-non-null-assertion scope: undefined!, extra: { @@ -147,6 +150,7 @@ export const buildHandler: ( scope: availableScopes.join(' '), }; accessToken.scope = issuedScopes; + grant.addResourceScope(audience, accessToken.scope); /* === End RFC 0001 === */ } else if (resource) { const resourceServerInfo = await resourceIndicators.getResourceServerInfo( @@ -163,6 +167,7 @@ export const buildHandler: ( // @ts-expect-error -- code from oidc-provider .filter(Set.prototype.has.bind(accessToken.resourceServer.scopes)) .join(' '); + grant.addResourceScope(resource, accessToken.scope); } else { accessToken.claims = ctx.oidc.claims; // Filter scopes from `oidcScopes`, @@ -173,6 +178,7 @@ export const buildHandler: ( // wrap it with `new Set` to make it work .filter((name) => new Set(oidcScopes).has(name)) .join(' '); + grant.addOIDCScope(accessToken.scope); } // Handle the actor token @@ -189,6 +195,8 @@ export const buildHandler: ( }; } + await grant.save(); + ctx.oidc.entity('Grant', grant); ctx.oidc.entity('AccessToken', accessToken); const accessTokenString = await accessToken.save(); diff --git a/packages/integration-tests/src/tests/api/oidc/token-exchange/personal-access-token.test.ts b/packages/integration-tests/src/tests/api/oidc/token-exchange/personal-access-token.test.ts index d22fcd96e..1b38348e3 100644 --- a/packages/integration-tests/src/tests/api/oidc/token-exchange/personal-access-token.test.ts +++ b/packages/integration-tests/src/tests/api/oidc/token-exchange/personal-access-token.test.ts @@ -106,6 +106,16 @@ describe('Token Exchange (Personal Access Token)', () => { .json(); expect(introspectionResponse).toHaveProperty('active', true); expect(introspectionResponse).toHaveProperty('sub', testUserId); + + // Should be able to get user info + const userInfoResponse = await oidcApi + .get('me', { + headers: { + Authorization: `Bearer ${access_token}`, + }, + }) + .json(); + expect(userInfoResponse).toHaveProperty('sub', testUserId); }); it('should be able to use for multiple times', async () => {