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

feat(core): third-party applications are not allowed for token exchange (#6100)

* feat(core,schemas): token exchange grant

* feat(core): third-party applications are not allowed for token exchange
This commit is contained in:
wangsijie 2024-07-02 10:47:19 +08:00 committed by GitHub
parent 81c014173c
commit 2ce6ba3447
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 45 additions and 2 deletions

View file

@ -14,6 +14,7 @@ const { jest } = import.meta;
const noop = async () => {}; const noop = async () => {};
const findSubjectToken = jest.fn(); const findSubjectToken = jest.fn();
const updateSubjectTokenById = jest.fn(); const updateSubjectTokenById = jest.fn();
const findApplicationById = jest.fn().mockResolvedValue(mockApplication);
const mockTenant = new MockTenant(undefined, { const mockTenant = new MockTenant(undefined, {
subjectTokens: { subjectTokens: {
@ -21,7 +22,7 @@ const mockTenant = new MockTenant(undefined, {
updateSubjectTokenById, updateSubjectTokenById,
}, },
applications: { applications: {
findApplicationById: jest.fn().mockResolvedValue(mockApplication), findApplicationById,
}, },
}); });
const mockHandler = (tenant = mockTenant) => { const mockHandler = (tenant = mockTenant) => {
@ -78,11 +79,21 @@ afterAll(() => {
}); });
describe('token exchange', () => { describe('token exchange', () => {
afterEach(() => {
findApplicationById.mockClear();
});
it('should throw when client is not available', async () => { it('should throw when client is not available', async () => {
const ctx = createOidcContext({ ...validOidcContext, client: undefined }); const ctx = createOidcContext({ ...validOidcContext, client: undefined });
await expect(mockHandler()(ctx, noop)).rejects.toThrow(errors.InvalidClient); await expect(mockHandler()(ctx, noop)).rejects.toThrow(errors.InvalidClient);
}); });
it('should throw when client is third-party application', async () => {
findApplicationById.mockResolvedValueOnce({ ...mockApplication, isThirdParty: true });
const ctx = createOidcContext(validOidcContext);
await expect(mockHandler()(ctx, noop)).rejects.toThrow(errors.InvalidClient);
});
it('should throw when subject token type is incorrect', async () => { it('should throw when subject token type is incorrect', async () => {
const ctx = createOidcContext({ const ctx = createOidcContext({
...validOidcContext, ...validOidcContext,

View file

@ -16,6 +16,8 @@ import { type EnvSet } from '#src/env-set/index.js';
import type Queries from '#src/tenants/Queries.js'; import type Queries from '#src/tenants/Queries.js';
import assertThat from '#src/utils/assert-that.js'; import assertThat from '#src/utils/assert-that.js';
import { isThirdPartyApplication } from '../resource.js';
const { InvalidClient, InvalidGrant } = errors; const { InvalidClient, InvalidGrant } = errors;
/** /**
@ -51,6 +53,11 @@ export const buildHandler: (
assertThat(params, new InvalidGrant('parameters must be available')); assertThat(params, new InvalidGrant('parameters must be available'));
assertThat(client, new InvalidClient('client must be available')); assertThat(client, new InvalidClient('client must be available'));
// We don't allow third-party applications to perform token exchange
assertThat(
!(await isThirdPartyApplication(queries, client.clientId)),
new InvalidClient('third-party applications are not allowed for this grant type')
);
assertThat( assertThat(
params.subject_token_type === 'urn:ietf:params:oauth:token-type:access_token', params.subject_token_type === 'urn:ietf:params:oauth:token-type:access_token',
new InvalidGrant('unsupported subject token type') new InvalidGrant('unsupported subject token type')

View file

@ -6,7 +6,7 @@ import { oidcApi } from '#src/api/api.js';
import { createApplication, deleteApplication } from '#src/api/application.js'; import { createApplication, deleteApplication } from '#src/api/application.js';
import { createSubjectToken } from '#src/api/subject-token.js'; import { createSubjectToken } from '#src/api/subject-token.js';
import { createUserByAdmin } from '#src/helpers/index.js'; import { createUserByAdmin } from '#src/helpers/index.js';
import { devFeatureTest } from '#src/utils.js'; import { devFeatureTest, generateName } from '#src/utils.js';
const { describe, it } = devFeatureTest; const { describe, it } = devFeatureTest;
@ -109,5 +109,30 @@ describe('Token Exchange', () => {
}) })
).rejects.toThrow(); ).rejects.toThrow();
}); });
it('should fail with a third-party application', async () => {
const { subjectToken } = await createSubjectToken(userId);
const thirdPartyApplication = await createApplication(
generateName(),
ApplicationType.Traditional,
{
isThirdParty: true,
}
);
await expect(
oidcApi.post('token', {
headers: formUrlEncodedHeaders,
body: new URLSearchParams({
client_id: thirdPartyApplication.id,
grant_type: GrantType.TokenExchange,
subject_token: subjectToken,
subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
}),
})
).rejects.toThrow();
await deleteApplication(thirdPartyApplication.id);
});
}); });
}); });