Skip to content

Commit 174ede4

Browse files
authored
feat: add the addPadding option to pwnedPassword and pwnedPasswordRange (#421)
1 parent c4ca77a commit 174ede4

File tree

6 files changed

+45
-10
lines changed

6 files changed

+45
-10
lines changed

.changeset/wicked-insects-melt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'hibp': minor
3+
---
4+
5+
Add `addPadding` option to `pwnedPassword` and `pwnedPasswordRange` modules. See https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/ for more information.

src/__tests__/pwned-password-range.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,18 @@ describe('pwnedPasswordRange', () => {
1919
});
2020
});
2121
});
22+
23+
describe('addPadding option', () => {
24+
it('causes Add-Padding header to be included in the request', async () => {
25+
expect.assertions(1);
26+
server.use(
27+
http.get('*', ({ request }) => {
28+
expect(request.headers.get('Add-Padding')).toBe('true');
29+
return new Response(EXAMPLE_PASSWORD_HASHES);
30+
}),
31+
);
32+
33+
await pwnedPasswordRange('5BAA6', { addPadding: true });
34+
});
35+
});
2236
});

src/__tests__/pwned-password.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,18 @@ describe('pwnedPassword', () => {
2727
return expect(pwnedPassword('kjfhsdksjf454145jkhk!!!')).resolves.toBe(0);
2828
});
2929
});
30+
31+
describe('addPadding option', () => {
32+
it('causes Add-Padding header to be included in the request', async () => {
33+
expect.assertions(1);
34+
server.use(
35+
http.get('*', ({ request }) => {
36+
expect(request.headers.get('Add-Padding')).toBe('true');
37+
return new Response(EXAMPLE_PASSWORD_HASHES);
38+
}),
39+
);
40+
41+
await pwnedPassword('password', { addPadding: true });
42+
});
43+
});
3044
});

src/api/pwnedpasswords/fetch-from-api.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { BAD_REQUEST } from './responses.js';
1515
* pwnedpasswords.com API endpoints (default: `https://api.pwnedpasswords.com`)
1616
* @param {string} [options.userAgent] a custom string to send as the User-Agent
1717
* field in the request headers (default: `hibp <version>`)
18+
* @param {boolean} [options.addPadding] ask the remote API to add padding to
19+
* the response to obscure the password prefix (default: `false`)
1820
* @returns {Promise<string>} a Promise which resolves to the data resulting
1921
* from the query, or rejects with an Error
2022
*/
@@ -23,17 +25,13 @@ export async function fetchFromApi(
2325
{
2426
baseUrl = 'https://api.pwnedpasswords.com',
2527
userAgent,
26-
}: { baseUrl?: string; userAgent?: string } = {},
28+
addPadding = false,
29+
}: { baseUrl?: string; userAgent?: string; addPadding?: boolean } = {},
2730
): Promise<string> {
2831
const config = Object.assign(
2932
{},
30-
userAgent
31-
? {
32-
headers: {
33-
'User-Agent': userAgent,
34-
},
35-
}
36-
: {},
33+
userAgent ? { headers: { 'User-Agent': userAgent } } : {},
34+
addPadding ? { headers: { 'Add-Padding': 'true' } } : {},
3735
);
3836
const url = `${baseUrl.replace(/\/$/g, '')}${endpoint}`;
3937
const response = await fetch(url, config);

src/pwned-password-range.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export type PwnedPasswordSuffixes = Record<string, number>;
2525
* pwnedpasswords.com API endpoints (default: `https://api.pwnedpasswords.com`)
2626
* @param {string} [options.userAgent] a custom string to send as the User-Agent
2727
* field in the request headers (default: `hibp <version>`)
28+
* @param {boolean} [options.addPadding] ask the remote API to add padding to
29+
* the response to obscure the password prefix (default: `false`)
2830
* @returns {Promise<PwnedPasswordSuffixes>} a Promise which resolves to an
2931
* object mapping the `suffix` that when matched with the prefix composes the
3032
* complete hash, to the `count` of how many times it appears in the breached
@@ -54,7 +56,7 @@ export type PwnedPasswordSuffixes = Record<string, number>;
5456
*/
5557
export async function pwnedPasswordRange(
5658
prefix: string,
57-
options: { baseUrl?: string; userAgent?: string } = {},
59+
options: { baseUrl?: string; userAgent?: string; addPadding?: boolean } = {},
5860
): Promise<PwnedPasswordSuffixes> {
5961
const data = await fetchFromApi(
6062
`/range/${encodeURIComponent(prefix)}`,

src/pwned-password.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { pwnedPasswordRange } from './pwned-password-range.js';
1212
* pwnedpasswords.com API endpoints (default: `https://api.pwnedpasswords.com`)
1313
* @param {string} [options.userAgent] a custom string to send as the User-Agent
1414
* field in the request headers (default: `hibp <version>`)
15+
* @param {boolean} [options.addPadding] ask the remote API to add padding to
16+
* the response to obscure the password prefix (default: `false`)
1517
* @returns {Promise<number>} a Promise which resolves to the number of times
1618
* the password has been exposed in a breach, or rejects with an Error
1719
* @example
@@ -30,7 +32,7 @@ import { pwnedPasswordRange } from './pwned-password-range.js';
3032
*/
3133
export async function pwnedPassword(
3234
password: string,
33-
options: { baseUrl?: string; userAgent?: string } = {},
35+
options: { baseUrl?: string; userAgent?: string; addPadding?: boolean } = {},
3436
): Promise<number> {
3537
/* eslint-disable */
3638
// @ts-expect-error: JSSHA types are busted

0 commit comments

Comments
 (0)