Skip to content

Commit

Permalink
Make proxy related permissions optional
Browse files Browse the repository at this point in the history
  • Loading branch information
ruihildt committed Apr 22, 2024
1 parent 6b2f3c6 commit c0f405d
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 39 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,16 @@ local storage). It will require some manual configuration:
Mullvad Browser Extension requires the following permissions:

- `management` to be able to recommend third party extensions
- `privacy` to disable webRTC
- `proxy` to configure and use Mullvad proxy servers
- `privacy` to disable webRTC and check HTTPS-Only status
- `storage` to save preferences
- `search` to recommend other search engines
- `tabs` to be able to show proxy settings based on the active tab
- `*://*.mullvad.net/*` to get proxy servers list and display your connection information (See
`Network requests` for details)

The following permissions are optional, but are needed to use the proxy feature:

- `proxy` to configure and use Mullvad proxy servers
- `tabs` to show proxy settings from active tab
- `<all_urls>` to have granular proxy settings

_Permissions are automatically accepted when testing the extension._
Expand Down
12 changes: 6 additions & 6 deletions src/background/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { addExtensionsListeners } from '@/helpers/extensions';
import { initBrowserAction } from '@/helpers/browserAction';
import { initProxyRequests } from '@/helpers/socksProxy';
import { cleanProxyListeners, initProxyListeners } from '@/helpers/socksProxy';

// only on dev mode
if (import.meta.hot) {
Expand All @@ -11,8 +10,9 @@ if (import.meta.hot) {
// Add listeners on extension actions
addExtensionsListeners();

// Update browserAction for tabs and add listeners
initBrowserAction();
// Init proxy listeners
initProxyListeners();

// Add listener for proxy requests
initProxyRequests();
// Listeners for permissions changes
browser.permissions.onAdded.addListener(initProxyListeners);
browser.permissions.onRemoved.addListener(cleanProxyListeners);
14 changes: 9 additions & 5 deletions src/composables/useActiveTab.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { ref } from 'vue';
import useProxyPermissions from '@/composables/useProxyPermissions';

const { proxyPermissionsGranted } = useProxyPermissions();
const activeTabHost = ref('');
const isAboutPage = ref(false);

const getActiveTab = async () => {
const activeWindow = await browser.windows.getCurrent({ populate: true });
const activeTab = activeWindow.tabs!.find((tab) => tab.active);
if (proxyPermissionsGranted.value) {
const activeWindow = await browser.windows.getCurrent({ populate: true });
const activeTab = activeWindow.tabs!.find((tab) => tab.active);

const activeTabURL = new URL(activeTab!.url!);
activeTabHost.value = activeTabURL.hostname;
isAboutPage.value = activeTabURL.protocol === 'about:';
const activeTabURL = new URL(activeTab!.url!);
activeTabHost.value = activeTabURL.hostname;
isAboutPage.value = activeTabURL.protocol === 'about:';
}
};

const useActiveTab = () => {
Expand Down
20 changes: 20 additions & 0 deletions src/composables/useProxyPermissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ref } from 'vue';
import { getProxyPermissions, requestProxyPermissions } from '@/helpers/permissions';

const useProxyPermissions = () => {
const proxyPermissionsGranted = ref(false);

const checkProxyPermissions = async () => {
proxyPermissionsGranted.value = await getProxyPermissions();
};

const triggerProxyPermissions = async () => {
proxyPermissionsGranted.value = await requestProxyPermissions();
};

checkProxyPermissions();

return { proxyPermissionsGranted, triggerProxyPermissions };
};

export default useProxyPermissions;
2 changes: 1 addition & 1 deletion src/helpers/browserAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const updateTabsProxyBadges = async () => {
}
};

const updatedTabListener = async (
export const updatedTabListener = async (
_tabId: number,
_changeInfo: browser.tabs._OnUpdatedChangeInfo,
tab: browser.tabs.Tab,
Expand Down
13 changes: 13 additions & 0 deletions src/helpers/permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const getProxyPermissions = async () => {
return await browser.permissions.contains({
permissions: ['proxy', 'tabs'],
origins: ['<all_urls>'],
});
};

export const requestProxyPermissions = async () => {
return await browser.permissions.request({
permissions: ['proxy', 'tabs'],
origins: ['<all_urls>'],
});
};
31 changes: 30 additions & 1 deletion src/helpers/socksProxy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { RequestDetails, ProxyDetails } from './socksProxy.types';
import ipaddr from 'ipaddr.js';

import { RequestDetails, ProxyDetails } from './socksProxy.types';
import { getProxyPermissions } from './permissions';
import { initBrowserAction, updatedTabListener } from './browserAction';

const getGlobalProxyDetails = async (): Promise<ProxyDetails> => {
const response = await browser.storage.local.get('globalProxyDetails');

Expand Down Expand Up @@ -39,6 +42,32 @@ export const initProxyRequests = () => {
browser.proxy.onRequest.addListener(handleProxyRequest, { urls: ['<all_urls>'] });
};

export const initProxyListeners = async () => {
const proxyPermissionsGranted = await getProxyPermissions();
if (proxyPermissionsGranted) {
await removeProxyListeners();
await addProxyListeners();
}
};

export const cleanProxyListeners = async () => {
const proxyPermissionsGranted = await getProxyPermissions();

if (!proxyPermissionsGranted) {
await removeProxyListeners();
}
};

const addProxyListeners = async () => {
initBrowserAction();
initProxyRequests();
};

const removeProxyListeners = async () => {
browser.tabs.onUpdated.removeListener(updatedTabListener);
browser.proxy.onRequest.removeListener(handleProxyRequest);
};

// TODO decide what how to handle fallback proxy (if proxy is invalid, it will fallback to Firefox proxy if configured)
// https://bugzilla.mozilla.org/show_bug.cgi?id=1750561

Expand Down
12 changes: 2 additions & 10 deletions src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,8 @@ export async function getManifest() {
'48': './assets/mullvad-logo.svg',
'96': './assets/mullvad-logo.svg',
},
permissions: [
'management',
'privacy',
'proxy',
'search',
'storage',
'tabs',
'*://*.mullvad.net/*',
'<all_urls>',
],
permissions: ['management', 'privacy', 'search', 'storage', '*://*.mullvad.net/*'],
optional_permissions: ['proxy', 'tabs', '<all_urls>'],
browser_specific_settings: {
gecko: {
strict_min_version: '91.1.0',
Expand Down
15 changes: 11 additions & 4 deletions src/popup/views/Home.vue
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
<script lang="ts" setup>
import { computed, inject } from 'vue';
import { inject, onMounted, ref } from 'vue';
import ConnectionDetails from '@/components/ConnectionDetails/ConnectionDetails.vue';
import IconLabel from '@/components/IconLabel.vue';
import NotificationsCarousel from '@/components/NotificationsCarousel.vue';
import useActiveTab from '@/composables/useActiveTab';
import { ConnectionKey, defaultConnection } from '@/composables/useConnection';
import useProxyPermissions from '@/composables/useProxyPermissions';
import useSocksProxy from '@/composables/useSocksProxy';
import useStore from '@/composables/useStore';
const { proxyPermissionsGranted } = useProxyPermissions();
const { activeTabHost } = useActiveTab();
const { currentHostProxyDetails, currentHostProxyEnabled } = useSocksProxy();
const { excludedHosts } = useStore();
const { isLoading, isError, connection } = inject(ConnectionKey, defaultConnection);
const currentHostExcluded = computed(() => excludedHosts.value.includes(activeTabHost.value));
const currentHostExcluded = ref(false);
onMounted(async () => {
if (proxyPermissionsGranted.value) {
currentHostExcluded.value = excludedHosts.value.includes(activeTabHost.value);
}
});
</script>

<template>
<NotificationsCarousel v-if="!isLoading && !isError" />
<ConnectionDetails />

<div v-if="!isLoading">
<div v-if="!isLoading && proxyPermissionsGranted">
<IconLabel v-if="currentHostExcluded" type="info" class="my-2">
<strong>{{ activeTabHost }}</strong> is set to never be proxied
</IconLabel>
Expand Down
43 changes: 34 additions & 9 deletions src/popup/views/Proxy.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
<script lang="ts" setup>
import { computed, inject } from 'vue';
import { NCard } from 'naive-ui';
import Button from '@/components/Buttons/Button.vue';
import IconLabel from '@/components/IconLabel.vue';
import LocationDrawer from '@/components/ConnectionDetails/LocationDrawer.vue';
import ProxyGlobal from '@/components/Proxy/ProxyGlobal.vue';
import ProxyHost from '@/components/Proxy/ProxyHost.vue';
import TitleCategory from '@/components/TitleCategory.vue';
import useActiveTab from '@/composables/useActiveTab';
import { ConnectionKey, defaultConnection } from '@/composables/useConnection';
import useProxyPermissions from '@/composables/useProxyPermissions';
const { proxyPermissionsGranted, triggerProxyPermissions } = useProxyPermissions();
const { isAboutPage } = useActiveTab();
const { connection } = inject(ConnectionKey, defaultConnection);
Expand All @@ -20,13 +25,33 @@ const isWireGuard = computed(
</script>

<template>
<IconLabel v-if="!isWireGuard" type="warning" class="my-2">
Connect first to Mullvad VPN (WireGuard) to use the proxy.
</IconLabel>

<div>
<ProxyHost v-if="!isAboutPage" />
<ProxyGlobal />
<LocationDrawer />
</div>
<template v-if="proxyPermissionsGranted">
<IconLabel v-if="!isWireGuard" type="warning" class="my-2">
Connect first to Mullvad VPN (WireGuard) to use the proxy.
</IconLabel>

<div>
<ProxyHost v-if="!isAboutPage" />
<ProxyGlobal />
<LocationDrawer />
</div>
</template>

<template v-else>
<n-card :bordered="false" class="mb-4">
<div class="flex justify-between">
<TitleCategory title="Permissions required" />
</div>

<IconLabel type="warning" class="my-2">
<ul>
<li>- <strong>tabs</strong> to show proxy settings from the active tab</li>
<li>- <strong>proxy</strong> to configure and use Mullvad proxy servers</li>
<li>- <strong>&lt;all_urls&gt;</strong> to have granular proxy settings</li>
</ul>
</IconLabel>

<Button class="mt-3" @click="triggerProxyPermissions"> Grant permissions </Button>
</n-card>
</template>
</template>

0 comments on commit c0f405d

Please sign in to comment.