Skip to content

Commit

Permalink
make non "API entry point"-based requests blocking opt-in feature, #312
Browse files Browse the repository at this point in the history
  • Loading branch information
vladimiry committed Oct 16, 2020
1 parent fd18a89 commit c8cbd33
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 31 deletions.
16 changes: 13 additions & 3 deletions src/electron-main/api/endpoints-builders/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export async function buildEndpoints(
login,
title,
entryUrl,
blockNonEntryUrlBasedRequests,
database,
persistentSession,
rotateUserAgent,
Expand All @@ -38,6 +39,7 @@ export async function buildEndpoints(
login,
title,
entryUrl,
blockNonEntryUrlBasedRequests,
database,
persistentSession,
rotateUserAgent,
Expand All @@ -52,7 +54,10 @@ export async function buildEndpoints(
return ctx.settingsStore.write(settings);
});

await initSessionByAccount(ctx, pick(account, ["login", "proxy", "rotateUserAgent", "entryUrl"]));
await initSessionByAccount(
ctx,
pick(account, ["login", "proxy", "rotateUserAgent", "entryUrl", "blockNonEntryUrlBasedRequests"]),
);

return result;
},
Expand All @@ -63,6 +68,7 @@ export async function buildEndpoints(
login,
title,
entryUrl,
blockNonEntryUrlBasedRequests,
database,
persistentSession,
rotateUserAgent,
Expand All @@ -79,12 +85,13 @@ export async function buildEndpoints(

const settings = await ctx.settingsStore.readExisting();
const account = pickAccountStrict(settings.accounts, {login});
const {credentials: existingCredentials} = account;

const shouldConfigureSession = (
account.entryUrl !== entryUrl
||
!equals(account.proxy, proxy)
||
account.blockNonEntryUrlBasedRequests !== blockNonEntryUrlBasedRequests
);
logger.info(JSON.stringify({shouldConfigureSession}));

Expand All @@ -97,8 +104,11 @@ export async function buildEndpoints(
throw new Error('"entryUrl" is undefined');
}
account.entryUrl = entryUrl;
account.blockNonEntryUrlBasedRequests = blockNonEntryUrlBasedRequests;

if (credentials) {
const {credentials: existingCredentials} = account;

if ("password" in credentials) {
existingCredentials.password = credentials.password;
}
Expand All @@ -117,7 +127,7 @@ export async function buildEndpoints(
if (shouldConfigureSession) {
await configureSessionByAccount(
ctx,
pick(account, ["login", "proxy", "entryUrl"]),
pick(account, ["login", "proxy", "entryUrl", "blockNonEntryUrlBasedRequests"]),
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/electron-main/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,8 @@ export const initApi = async (ctx: Context): Promise<IpcMainApiEndpoints> => {

ctx.settingsStore = store;

for (const {login, proxy, rotateUserAgent, entryUrl} of settings.accounts) {
await initSessionByAccount(ctx, {login, proxy, rotateUserAgent, entryUrl});
for (const {login, proxy, rotateUserAgent, entryUrl, blockNonEntryUrlBasedRequests} of settings.accounts) {
await initSessionByAccount(ctx, {login, proxy, rotateUserAgent, entryUrl, blockNonEntryUrlBasedRequests});
}

await (async (): Promise<void> => {
Expand Down
9 changes: 5 additions & 4 deletions src/electron-main/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {IPC_MAIN_API_NOTIFICATION_ACTIONS} from "src/shared/api/main";
import {LoginFieldContainer} from "src/shared/model/container";
import {ONE_SECOND_MS, PACKAGE_NAME} from "src/shared/constants";
import {curryFunctionMembers, getRandomInt, getWebViewPartition} from "src/shared/util";
import {initCorsTweakingWebRequestListenersByAccount} from "src/electron-main/web-request";
import {initWebRequestListenersByAccount} from "src/electron-main/web-request";
import {registerSessionProtocols} from "src/electron-main/protocol";

const logger = curryFunctionMembers(_logger, "[src/electron-main/session]");
Expand Down Expand Up @@ -75,7 +75,7 @@ export async function initSession(

export async function configureSessionByAccount(
ctx: DeepReadonly<Context>,
account: DeepReadonly<Pick<AccountConfig, "login" | "proxy" | "entryUrl">>,
account: NoExtraProps<DeepReadonly<Pick<AccountConfig, "login" | "proxy" | "entryUrl" | "blockNonEntryUrlBasedRequests">>>,
): Promise<void> {
logger.info("configureSessionByAccount()");

Expand All @@ -93,7 +93,7 @@ export async function configureSessionByAccount(
}),
};

initCorsTweakingWebRequestListenersByAccount(ctx, account);
initWebRequestListenersByAccount(ctx, account);

await race(
from(
Expand All @@ -107,7 +107,8 @@ export async function configureSessionByAccount(

export async function initSessionByAccount(
ctx: DeepReadonly<StrictOmit<Context, "userAgentsPool">> & Pick<Context, "userAgentsPool">,
account: DeepReadonly<Pick<AccountConfig, "login" | "proxy" | "rotateUserAgent" | "entryUrl">>,
// eslint-disable-next-line max-len
account: NoExtraProps<DeepReadonly<Pick<AccountConfig, "login" | "proxy" | "rotateUserAgent" | "entryUrl" | "blockNonEntryUrlBasedRequests">>>,
): Promise<void> {
const partition = getWebViewPartition(account.login);

Expand Down
21 changes: 13 additions & 8 deletions src/electron-main/web-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,29 +231,34 @@ const patchReponseHeaders: (arg: { requestProxy: RequestProxy; details: Response
return details.responseHeaders;
};

export function initCorsTweakingWebRequestListenersByAccount(
export function initWebRequestListenersByAccount(
ctx: DeepReadonly<Context>,
account: DeepReadonly<Pick<AccountConfig, "login" | "entryUrl">>,
{
login,
entryUrl,
blockNonEntryUrlBasedRequests,
}: NoExtraProps<DeepReadonly<Pick<AccountConfig, "login" | "entryUrl" | "blockNonEntryUrlBasedRequests">>>,
): void {
const session = resolveInitializedSession({login: account.login});
const {entryUrl: accountEntryApiUrl} = account;
const webClient = ctx.locations.webClients.find(({entryApiUrl}) => accountEntryApiUrl === entryApiUrl);
const session = resolveInitializedSession({login});
const webClient = ctx.locations.webClients.find(({entryApiUrl}) => entryApiUrl === entryUrl);

if (!webClient) {
throw new Error(`Failed to resolve the "web-client" bundle location by "${accountEntryApiUrl}" API entry point value`);
throw new Error(`Failed to resolve the "web-client" bundle location by "${entryUrl}" API entry point value`);
}

const allowedOrigins: readonly string[] = [
entryUrl,
webClient.entryUrl,
accountEntryApiUrl,
...(
BUILD_ENVIRONMENT === "development"
? ["devtools://devtools/"]
: []
),
].map(parseUrlOriginWithNullishCheck);

const verifyUrlAccess = buildUrlOriginsTester(allowedOrigins);
const verifyUrlAccess = blockNonEntryUrlBasedRequests
? buildUrlOriginsTester(allowedOrigins)
: () => true;

// according to electron docs "only the last attached listener will be used", so no need to unsubscribe previously registered handlers
session.webRequest.onBeforeRequest(
Expand Down
1 change: 1 addition & 0 deletions src/shared/model/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type AccountConfig = NoExtraProps<{
login: string;
title?: string;
entryUrl: string;
blockNonEntryUrlBasedRequests?: boolean;
database?: boolean; // TODO proton-v4: rename AccountConfig.database => AccountConfig.localStore
// databaseCalendar?: boolean;
credentials: NoExtraProps<Partial<Record<"password" | "twoFactorCode" | "mailPassword", string>>>;
Expand Down
1 change: 1 addition & 0 deletions src/shared/model/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type AccountConfigCreateUpdatePatch = NoExtraProps<Pick<AccountConfig,
| "credentials"
| "database"
| "entryUrl"
| "blockNonEntryUrlBasedRequests"
| "login"
| "loginDelaySecondsRange"
| "loginDelayUntilSelected"
Expand Down
43 changes: 30 additions & 13 deletions src/web/browser-window/app/_options/account-edit.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,36 @@
</div>
<accordion class="mb-3">
<accordion-group heading="Extended Options" [isOpen]="false">
<div class="form-group">
<label class="d-block">
Handle button alias
<i
popover="If specified will be displayed in the UI instead of login."
class="fa fa-info-circle text-primary align-self-center"
container="body"
[placement]="'bottom'"
triggers="mouseenter:mouseleave"
></i>
</label>
<input type="text" class="form-control" formControlName="title">
</div>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" formControlName="blockNonEntryUrlBasedRequests" id="blockNonEntryUrlBasedRequestsCheckbox">
<label class="custom-control-label d-flex" for="blockNonEntryUrlBasedRequestsCheckbox">
Block non "API entry point"-based network requests
<i
*ngIf="account"
class="fa fa-info-circle text-warning align-self-center ml-1"
container="body"
[placement]="'bottom'"
popover="So, for example, the inlined in the email messages images won't be loaded."
triggers="mouseenter:mouseleave"
></i>
<a class="ml-1" href="{{ PACKAGE_GITHUB_PROJECT_URL }}/issues/312">#312</a>
</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" formControlName="rotateUserAgent" id="rotateUserAgentCheckbox">
Expand All @@ -139,19 +169,6 @@
</label>
</div>
</div>
<div class="form-group">
<label class="d-block">
Handle button alias
<i
popover="If specified will be displayed in the UI instead of login."
class="fa fa-info-circle text-primary align-self-center"
container="body"
[placement]="'bottom'"
triggers="mouseenter:mouseleave"
></i>
</label>
<input type="text" class="form-control" formControlName="title">
</div>
<div class="row">
<div class="col-md-6 form-group">
<label class="d-block pull-left">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ export class AccountEditComponent implements OnInit, OnDestroy {
| "persistentSession"
| "rotateUserAgent"
| "entryUrl"
| "blockNonEntryUrlBasedRequests"
| "loginDelayUntilSelected"
| "loginDelaySecondsRange">
| keyof Pick<Required<Required<AccountConfig>["proxy"]>, "proxyRules" | "proxyBypassRules">
| keyof AccountConfig["credentials"],
AbstractControl> = {
blockNonEntryUrlBasedRequests: new FormControl(null),
login: new FormControl(
null,
Validators.required, // eslint-disable-line @typescript-eslint/unbound-method
Expand Down Expand Up @@ -124,7 +126,7 @@ export class AccountEditComponent implements OnInit, OnDestroy {
controls.persistentSession.patchValue(account.persistentSession);
controls.rotateUserAgent.patchValue(account.rotateUserAgent);
controls.entryUrl.patchValue(account.entryUrl);

controls.blockNonEntryUrlBasedRequests.patchValue(account.blockNonEntryUrlBasedRequests);
controls.proxyRules.patchValue(account.proxy ? account.proxy.proxyRules : null);
controls.proxyBypassRules.patchValue(account.proxy ? account.proxy.proxyBypassRules : null);

Expand Down Expand Up @@ -165,6 +167,8 @@ export class AccountEditComponent implements OnInit, OnDestroy {
controls.login.value,
title: controls.title.value, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
entryUrl: controls.entryUrl.value, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
blockNonEntryUrlBasedRequests: Boolean(controls.blockNonEntryUrlBasedRequests.value),
database: Boolean(controls.database.value),
persistentSession: Boolean(controls.persistentSession.value),
rotateUserAgent: Boolean(controls.rotateUserAgent.value),
Expand Down

0 comments on commit c8cbd33

Please sign in to comment.