Skip to content

Commit a5b76bf

Browse files
committed
Document pagination authorization handling
Fixes #2449
1 parent d00d845 commit a5b76bf

3 files changed

Lines changed: 72 additions & 0 deletions

File tree

documentation/4-pagination.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ Therefore the options returned by `pagination.paginate(…)` must reflect change
132132
**Note:**
133133
> - The `url` option (if set) accepts **only** a [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) instance.\
134134
> This prevents `prefixUrl` ambiguity. In order to use a relative URL string, merge it via `new URL(relativeUrl, response.url)`.
135+
> - When pagination navigates to a different origin, Got strips inherited sensitive headers such as `authorization`, `cookie`, and `proxy-authorization`. If you trust the next-page URL and want to forward a sensitive header, return it explicitly from `pagination.paginate(…)`.
135136
136137
#### `filter`
137138

source/core/options.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,8 @@ export type PaginationOptions<ElementType, BodyType> = {
793793
794794
It should return an object representing Got options pointing to the next page. The options are merged automatically with the previous request, therefore the options returned `pagination.paginate(...)` must reflect changes only. If there are no more pages, `false` should be returned.
795795
796+
When pagination navigates to a different origin, Got strips inherited sensitive headers such as `authorization`, `cookie`, and `proxy-authorization`. If you trust the next-page URL and want to forward a sensitive header, return it explicitly from `pagination.paginate(...)`.
797+
796798
@example
797799
```
798800
import got from 'got';

test/pagination.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,75 @@ test('strips sensitive headers when pagination navigates cross-origin', async t
817817
await evilServer.close();
818818
});
819819

820+
test('extended instance strips inherited authorization when custom pagination navigates cross-origin', async t => {
821+
const createHttpTestServer = (await import('./helpers/create-http-test-server.js')).default;
822+
823+
const crossOriginServer = await createHttpTestServer();
824+
const receivedAuthorizations: Array<string | undefined> = [];
825+
crossOriginServer.get('/items', (request, response) => {
826+
receivedAuthorizations.push(request.headers.authorization);
827+
response.end('[2]');
828+
});
829+
830+
const trustedServer = await createHttpTestServer();
831+
trustedServer.get('/items', (_request, response) => {
832+
response.end(JSON.stringify({
833+
items: [1],
834+
nextUrl: `${crossOriginServer.url}/items`,
835+
}));
836+
});
837+
838+
const instance = got.extend({
839+
prefixUrl: trustedServer.url,
840+
headers: {
841+
authorization: 'Bearer SECRET',
842+
},
843+
});
844+
845+
const createPaginationOptions = (authorization?: string) => ({
846+
pagination: {
847+
requestLimit: 2,
848+
transform(response: Response) {
849+
const body = JSON.parse(response.body as string);
850+
return Array.isArray(body) ? body : body.items;
851+
},
852+
paginate({response}: {response: Response}) {
853+
const body = JSON.parse(response.body as string);
854+
855+
if (body.nextUrl && authorization) {
856+
return {
857+
url: new URL(body.nextUrl),
858+
headers: {
859+
authorization,
860+
},
861+
};
862+
}
863+
864+
if (body.nextUrl) {
865+
return {
866+
url: new URL(body.nextUrl),
867+
};
868+
}
869+
870+
return false;
871+
},
872+
},
873+
});
874+
875+
const items = await instance.paginate.all<number>('items', createPaginationOptions());
876+
877+
t.deepEqual(items, [1, 2]);
878+
t.deepEqual(receivedAuthorizations, [undefined]);
879+
880+
const itemsWithExplicitAuthorization = await instance.paginate.all<number>('items', createPaginationOptions('Bearer SECRET'));
881+
882+
t.deepEqual(itemsWithExplicitAuthorization, [1, 2]);
883+
t.deepEqual(receivedAuthorizations, [undefined, 'Bearer SECRET']);
884+
885+
await trustedServer.close();
886+
await crossOriginServer.close();
887+
});
888+
820889
test('strips cookie header when pagination navigates cross-origin', async t => {
821890
const createHttpTestServer = (await import('./helpers/create-http-test-server.js')).default;
822891

0 commit comments

Comments
 (0)