Skip to content

Commit

Permalink
Separate generic forbidden requests from those intentionally blocked
Browse files Browse the repository at this point in the history
  • Loading branch information
wKovacs64 committed Jan 20, 2019
1 parent 15e02f9 commit cd74e40
Show file tree
Hide file tree
Showing 12 changed files with 47 additions and 5 deletions.
1 change: 1 addition & 0 deletions src/breach.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe('breach', () => {

beforeAll(() => {
mockAxios.get.mockResolvedValue({
headers: {},
status: OK.status,
data,
});
Expand Down
1 change: 1 addition & 0 deletions src/breachedAccount.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('breachedAccount', () => {

beforeAll(() => {
mockAxios.get.mockResolvedValue({
headers: {},
status: OK.status,
data,
});
Expand Down
1 change: 1 addition & 0 deletions src/breaches.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ describe('breaches', () => {

beforeAll(() => {
mockAxios.get.mockResolvedValue({
headers: {},
status: OK.status,
data,
});
Expand Down
1 change: 1 addition & 0 deletions src/dataClasses.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ describe('dataClasses', () => {

beforeAll(() => {
mockAxios.get.mockResolvedValue({
headers: {},
status: OK.status,
data,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`internal (haveibeenpwned): fetchFromApi blocked request throws a "Forbidden" error 1`] = `[Error: Forbidden - access denied.]`;
exports[`internal (haveibeenpwned): fetchFromApi forbidden request throws a "Blocked Request" error if a cf-ray header is present 1`] = `[Error: Request blocked, contact haveibeenpwned.com if this continues (Ray ID: someRayId)]`;

exports[`internal (haveibeenpwned): fetchFromApi forbidden request throws a "Forbidden" error if no cf-ray header is present 1`] = `[Error: Forbidden - access denied.]`;

exports[`internal (haveibeenpwned): fetchFromApi invalid account format throws a "Bad Request" error 1`] = `[Error: Bad request — the account does not comply with an acceptable format.]`;

Expand Down
16 changes: 13 additions & 3 deletions src/internal/haveibeenpwned/fetchFromApi.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import AxiosError from 'AxiosError';
import breachedAccount from 'breachedAccount';
import dataClasses from 'dataClasses';
import { BAD_REQUEST, FORBIDDEN, TOO_MANY_REQUESTS } from './responses';
import {
BAD_REQUEST,
FORBIDDEN,
BLOCKED,
TOO_MANY_REQUESTS,
} from './responses';
import axios from './axiosInstance';

const mockAxios = axios as jest.Mocked<typeof axios>;
Expand All @@ -22,11 +27,16 @@ describe('internal (haveibeenpwned): fetchFromApi', () => {
});
});

describe('blocked request', () => {
it('throws a "Forbidden" error', () => {
describe('forbidden request', () => {
it('throws a "Forbidden" error if no cf-ray header is present', () => {
mockAxios.get.mockRejectedValueOnce(new AxiosError(FORBIDDEN));
expect(breachedAccount('forbidden')).rejects.toMatchSnapshot();
});

it('throws a "Blocked Request" error if a cf-ray header is present', () => {
mockAxios.get.mockRejectedValueOnce(new AxiosError(BLOCKED));
expect(breachedAccount('blocked')).rejects.toMatchSnapshot();
});
});

describe('rate limited', () => {
Expand Down
11 changes: 10 additions & 1 deletion src/internal/haveibeenpwned/fetchFromApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export type ApiData =
| string[] // dataclasses
| null; // most endpoints can return an empty response

const blockedWithRayId = (rayId: string): string =>
`Request blocked, contact haveibeenpwned.com if this continues (Ray ID: ${rayId})`;

/**
* Fetches data from the supplied API endpoint.
*
Expand All @@ -38,8 +41,14 @@ export default (endpoint: string): Promise<ApiData> =>
switch (err.response.status) {
case BAD_REQUEST.status:
throw new Error(BAD_REQUEST.statusText);
case FORBIDDEN.status:
case FORBIDDEN.status: {
const rayId =
err.response.headers && err.response.headers['cf-ray'];
if (rayId) {
throw new Error(blockedWithRayId(rayId));
}
throw new Error(FORBIDDEN.statusText);
}
case NOT_FOUND.status:
return null;
case TOO_MANY_REQUESTS.status:
Expand Down
12 changes: 12 additions & 0 deletions src/internal/haveibeenpwned/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

export interface HaveIBeenPwnedApiResponse {
headers: { 'cf-ray'?: string };
// eslint-disable-next-line no-restricted-globals
status: number;
statusText?: string;
Expand All @@ -19,24 +20,34 @@ export interface HaveIBeenPwnedApiResponse {

/** @internal */
export const OK: HaveIBeenPwnedApiResponse = {
headers: {},
status: 200,
};

/** @internal */
export const BAD_REQUEST: HaveIBeenPwnedApiResponse = {
headers: {},
status: 400,
statusText:
'Bad request — the account does not comply with an acceptable format.',
};

/** @internal */
export const FORBIDDEN: HaveIBeenPwnedApiResponse = {
headers: {},
status: 403,
statusText: 'Forbidden - access denied.',
};

/** @internal */
export const BLOCKED: HaveIBeenPwnedApiResponse = {
headers: { 'cf-ray': 'someRayId' },
status: 403,
};

/** @internal */
export const NOT_FOUND: HaveIBeenPwnedApiResponse = {
headers: {},
status: 404,
};

Expand All @@ -48,6 +59,7 @@ export const NOT_FOUND: HaveIBeenPwnedApiResponse = {
* @internal
*/
export const TOO_MANY_REQUESTS: HaveIBeenPwnedApiResponse = {
headers: {},
status: 429,
data:
'Rate limit exceeded, refer to acceptable use of the API: ' +
Expand Down
1 change: 1 addition & 0 deletions src/pasteAccount.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('pasteAccount', () => {

beforeAll(() => {
mockAxios.get.mockResolvedValue({
headers: {},
status: OK.status,
data,
});
Expand Down
1 change: 1 addition & 0 deletions src/pwnedPassword.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const mockAxios = axios as jest.Mocked<typeof axios>;
describe('pwnedPassword', () => {
describe('pwned', () => {
mockAxios.get.mockResolvedValue({
headers: {},
status: OK.status,
data: stripIndents`
003D68EB55068C33ACE09247EE4C639306B:3
Expand Down
1 change: 1 addition & 0 deletions src/pwnedPasswordRange.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe('pwnedPasswordRange', () => {
`;

mockAxios.get.mockResolvedValue({
headers: {},
status: OK.status,
data,
});
Expand Down
2 changes: 2 additions & 0 deletions src/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('search', () => {
const pastes = null;

mockAxios.get.mockResolvedValue({
headers: {},
status: OK.status,
data: breaches,
});
Expand All @@ -26,6 +27,7 @@ describe('search', () => {

mockAxios.get.mockImplementation(endpoint =>
Promise.resolve({
headers: {},
status: OK.status,
data: /breachedaccount/.test(endpoint) ? breaches : pastes,
}),
Expand Down

0 comments on commit cd74e40

Please sign in to comment.