0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -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 findSubjectToken = jest.fn();
const updateSubjectTokenById = jest.fn();
const findApplicationById = jest.fn().mockResolvedValue(mockApplication);
const mockTenant = new MockTenant(undefined, {
subjectTokens: {
@ -21,7 +22,7 @@ const mockTenant = new MockTenant(undefined, {
updateSubjectTokenById,
},
applications: {
findApplicationById: jest.fn().mockResolvedValue(mockApplication),
findApplicationById,
},
});
const mockHandler = (tenant = mockTenant) => {
@ -78,11 +79,21 @@ afterAll(() => {
});
describe('token exchange', () => {
afterEach(() => {
findApplicationById.mockClear();
});
it('should throw when client is not available', async () => {
const ctx = createOidcContext({ ...validOidcContext, client: undefined });
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 () => {
const ctx = createOidcContext({
...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 assertThat from '#src/utils/assert-that.js';
import { isThirdPartyApplication } from '../resource.js';
const { InvalidClient, InvalidGrant } = errors;
/**
@ -51,6 +53,11 @@ export const buildHandler: (
assertThat(params, new InvalidGrant('parameters 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(
params.subject_token_type === 'urn:ietf:params:oauth:token-type:access_token',
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 { createSubjectToken } from '#src/api/subject-token.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;
@ -109,5 +109,30 @@ describe('Token Exchange', () => {
})
).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);
});
});
});