Skip to content

Commit

Permalink
Change the issue request flow for Cloudflare
Browse files Browse the repository at this point in the history
Previously we used axios to send an issuance request in
handleBeforeRequest, but now we will send the request in
handleBeforeSendHeaders because we need to read the Referer header
first.

We need a new "issueInfo" property to remember the request body we can
read in handleBeforeRequest and then it will be read in
handleBeforeSendHeaders so that in handleBeforeSendHeaders we will have
both the body and the Referer header.
  • Loading branch information
ppopth committed Feb 2, 2022
1 parent 977e0c3 commit 8dd58b9
Showing 1 changed file with 74 additions and 47 deletions.
121 changes: 74 additions & 47 deletions src/background/providers/cloudflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,17 @@ interface RedeemInfo {
token: Token;
}

interface IssueInfo {
requestId: string;
formData: { [key: string]: string[] | string };
}

export class CloudflareProvider implements Provider {
static readonly ID: number = 1;
private callbacks: Callbacks;
private storage: Storage;

private issueInfo: IssueInfo | null;
private redeemInfo: RedeemInfo | null;

constructor(storage: Storage, callbacks: Callbacks) {
Expand All @@ -44,6 +50,7 @@ export class CloudflareProvider implements Provider {

this.callbacks = callbacks;
this.storage = storage;
this.issueInfo = null;
this.redeemInfo = null;
}

Expand Down Expand Up @@ -113,6 +120,7 @@ export class CloudflareProvider implements Provider {

private async issue(
url: string,
headers: { [name: string]: string },
formData: { [key: string]: string[] | string },
): Promise<Token[]> {
const tokens = Array.from(Array(NUMBER_OF_REQUESTED_TOKENS).keys()).map(() => new Token());
Expand All @@ -127,13 +135,14 @@ export class CloudflareProvider implements Provider {
[ISSUANCE_BODY_PARAM_NAME]: param,
});

const headers = {
const newHeaders = {
...headers,
'content-type': 'application/x-www-form-urlencoded',
[ISSUE_HEADER_NAME]: CloudflareProvider.ID.toString(),
};

const response = await axios.post<string, { data: string }>(url, body, {
headers,
headers: newHeaders,
responseType: 'text',
});

Expand Down Expand Up @@ -190,44 +199,76 @@ export class CloudflareProvider implements Provider {
handleBeforeSendHeaders(
details: chrome.webRequest.WebRequestHeadersDetails,
): chrome.webRequest.BlockingResponse | void {
if (this.redeemInfo === null || details.requestId !== this.redeemInfo.requestId) {
return;
// If we suppose to redeem a token with this request
if (this.redeemInfo !== null && details.requestId === this.redeemInfo.requestId) {
const url = new URL(details.url);

const token = this.redeemInfo.token;
// Clear the redeem info to indicate that we are already redeeming the token.
this.redeemInfo = null;

const key = token.getMacKey();
const binding = voprf.createRequestBinding(key, [
voprf.getBytesFromString(url.hostname),
voprf.getBytesFromString(details.method + ' ' + url.pathname),
]);

const contents = [
voprf.getBase64FromBytes(token.getInput()),
binding,
voprf.getBase64FromString(JSON.stringify(voprf.defaultECSettings)),
];
const redemption = btoa(JSON.stringify({ type: 'Redeem', contents }));

const headers = details.requestHeaders ?? [];
headers.push({ name: 'challenge-bypass-token', value: redemption });

this.callbacks.updateIcon(this.getBadgeText());

return {
requestHeaders: headers,
};
}

const url = new URL(details.url);

const token = this.redeemInfo!.token;
// Clear the redeem info to indicate that we are already redeeming the token.
this.redeemInfo = null;

const key = token.getMacKey();
const binding = voprf.createRequestBinding(key, [
voprf.getBytesFromString(url.hostname),
voprf.getBytesFromString(details.method + ' ' + url.pathname),
]);

const contents = [
voprf.getBase64FromBytes(token.getInput()),
binding,
voprf.getBase64FromString(JSON.stringify(voprf.defaultECSettings)),
];
const redemption = btoa(JSON.stringify({ type: 'Redeem', contents }));

const headers = details.requestHeaders ?? [];
headers.push({ name: 'challenge-bypass-token', value: redemption });

this.callbacks.updateIcon(this.getBadgeText());
// If we suppose to issue tokens with this request
if (this.issueInfo !== null && details.requestId === this.issueInfo.requestId) {
const formData = this.issueInfo.formData;
// Clear the issue info to indicate that we are already issuing tokens.
this.issueInfo = null;

const headers: { [name: string]: string } = {};

if (details.requestHeaders !== undefined) {
details.requestHeaders.forEach((header) => {
// Filter only for Referrer header.
if (header.name === 'Referer' && header.value !== undefined) {
headers[header.name] = header.value;
}
});
}

return {
requestHeaders: headers,
};
(async () => {
// Issue tokens.
const tokens = await this.issue(details.url, headers, formData);
// Store tokens.
const cached = this.getStoredTokens();
this.setStoredTokens(cached.concat(tokens));

const url = new URL(details.url);
this.callbacks.navigateUrl(`${url.origin}${url.pathname}`);
})();

// TODO I tried to use redirectUrl with data URL or text/html and text/plain but it didn't work, so I continue
// cancelling the request. However, it seems that we can use image/* except image/svg+html. Let's figure how to
// use image data URL later.
// https://blog.mozilla.org/security/2017/11/27/blocking-top-level-navigations-data-urls-firefox-59/
return { cancel: true };
}
}

handleBeforeRequest(
details: chrome.webRequest.WebRequestBodyDetails,
): chrome.webRequest.BlockingResponse | void {
const url = new URL(details.url);

if (
details.requestBody === null ||
details.requestBody === undefined ||
Expand All @@ -253,21 +294,7 @@ export class CloudflareProvider implements Provider {
}
}

(async () => {
// Issue tokens.
const tokens = await this.issue(details.url, flattenFormData);
// Store tokens.
const cached = this.getStoredTokens();
this.setStoredTokens(cached.concat(tokens));

this.callbacks.navigateUrl(`${url.origin}${url.pathname}`);
})();

// TODO I tried to use redirectUrl with data URL or text/html and text/plain but it didn't work, so I continue
// cancelling the request. However, it seems that we can use image/* except image/svg+html. Let's figure how to
// use image data URL later.
// https://blog.mozilla.org/security/2017/11/27/blocking-top-level-navigations-data-urls-firefox-59/
return { cancel: true };
this.issueInfo = { requestId: details.requestId, formData: flattenFormData };
}

handleHeadersReceived(
Expand Down

0 comments on commit 8dd58b9

Please sign in to comment.