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:
parent
81c014173c
commit
2ce6ba3447
3 changed files with 45 additions and 2 deletions
|
@ -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,
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue