Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,13 @@ export class RequiredError extends Error {
super(msg);
}
}

/**
* Error thrown when a path parameter is "." or "..", which would change the request path.
*/
export class InvalidPathParameterError extends Error {
override name = 'InvalidPathParameterError' as const;
constructor(msg?: string) {
super(msg);
}
}
14 changes: 13 additions & 1 deletion src/lib/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { retry } from './retry.js';
import { FetchError, RequiredError, TimeoutError } from './errors.js';
import { FetchError, InvalidPathParameterError, RequiredError, TimeoutError } from './errors.js';
import {
RequestOpts,
InitOverrideFunction,
Expand All @@ -11,6 +11,17 @@

export * from './models.js';

// "." and ".." are dot segments: URL resolution would remove them and send the request to a different path.
function validatePathSegments(path: string) {
for (const segment of path.split('/')) {
if (segment === '.' || segment === '..') {
throw new InvalidPathParameterError(
`Invalid path parameter "${segment}": "." and ".." are not allowed because they change the request path.`
);
}
}
}

/**
* @private
* This is the base class for all generated API classes.
Expand Down Expand Up @@ -55,6 +66,7 @@
context: RequestOpts,
initOverrides?: RequestInit | InitOverrideFunction
) {
validatePathSegments(context.path);
let url = this.configuration.baseUrl + context.path;
if (context.query !== undefined && Object.keys(context.query).length !== 0) {
// only add the querystring to the URL if there are query parameters.
Expand Down Expand Up @@ -277,7 +289,7 @@
if (config.isCollectionFormatMulti) {
value = requestParameters[key];
} else {
value = requestParameters[key].join(COLLECTION_FORMATS[config.collectionFormat!]);

Check warning on line 292 in src/lib/runtime.ts

View workflow job for this annotation

GitHub Actions / Build and Test (18.17)

Forbidden non-null assertion

Check warning on line 292 in src/lib/runtime.ts

View workflow job for this annotation

GitHub Actions / Build and Test (20.3)

Forbidden non-null assertion
}
} else {
if (requestParameters[key] !== undefined) {
Expand Down
34 changes: 34 additions & 0 deletions test/lib/runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ResponseError,
ManagementApiError,
AuthApiError,
InvalidPathParameterError,
} from '../../src/index.js';
import { InitOverrideFunction, RequestOpts } from '../../src/lib/models.js';
import { BaseAPI, applyQueryParams, CustomDomainHeader } from '../../src/lib/runtime.js';
Expand Down Expand Up @@ -407,6 +408,39 @@ describe('Runtime for ManagementClient', () => {
jest.clearAllMocks();
});

it.each(['..', '.'])(
'should reject "%s" as a path parameter without sending a request',
async (id) => {
const request = nock(URL).get(/.*/).reply(200, []);
const client = new ManagementClient({ domain: 'tenant.auth0.com', token: 'TOKEN' });

await expect(client.users.getRoles({ id })).rejects.toThrow(InvalidPathParameterError);
expect(request.isDone()).toBe(false);
}
);

it('should not request a token when the path parameter is rejected', async () => {
const tokenRequest = nock('https://tenant.auth0.com')
.post('/oauth/token')
.reply(200, { access_token: 'TOKEN', expires_in: 86400 });
const client = new ManagementClient({
domain: 'tenant.auth0.com',
clientId: 'cid',
clientSecret: 'csecret',
});

await expect(client.users.getRoles({ id: '..' })).rejects.toThrow(InvalidPathParameterError);
expect(tokenRequest.isDone()).toBe(false);
});

it.each(['...', 'a.b'])('should allow "%s" as a path parameter', async (id) => {
const request = nock(URL).get(`/users/${id}/roles`).reply(200, []);
const client = new ManagementClient({ domain: 'tenant.auth0.com', token: 'TOKEN' });

await client.users.getRoles({ id });
expect(request.isDone()).toBe(true);
});

it('should retry if enabled', async () => {
const request = nock(URL, { encodedQueryParams: true })
.get('/clients')
Expand Down
Loading