Skip to content

Commit c72ca2b

Browse files
committed
Make proxy related permissions optional
1 parent 7ff0218 commit c72ca2b

File tree

10 files changed

+132
-39
lines changed

10 files changed

+132
-39
lines changed

README.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,16 @@ local storage). It will require some manual configuration:
7777
Mullvad Browser Extension requires the following permissions:
7878

7979
- `management` to be able to recommend third party extensions
80-
- `privacy` to disable webRTC
81-
- `proxy` to configure and use Mullvad proxy servers
80+
- `privacy` to disable webRTC and check HTTPS-Only status
8281
- `storage` to save preferences
8382
- `search` to recommend other search engines
84-
- `tabs` to be able to show proxy settings based on the active tab
8583
- `*://*.mullvad.net/*` to get proxy servers list and display your connection information (See
8684
`Network requests` for details)
85+
86+
The following permissions are optional, but are needed to use the proxy feature:
87+
88+
- `proxy` to configure and use Mullvad proxy servers
89+
- `tabs` to show proxy settings from active tab
8790
- `<all_urls>` to have granular proxy settings
8891

8992
_Permissions are automatically accepted when testing the extension._

src/background/main.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { addExtensionsListeners } from '@/helpers/extensions';
2-
import { initBrowserAction } from '@/helpers/browserAction';
3-
import { initProxyRequests } from '@/helpers/socksProxy';
2+
import { cleanProxyListeners, initProxyListeners } from '@/helpers/socksProxy';
43

54
// only on dev mode
65
if (import.meta.hot) {
@@ -11,8 +10,9 @@ if (import.meta.hot) {
1110
// Add listeners on extension actions
1211
addExtensionsListeners();
1312

14-
// Update browserAction for tabs and add listeners
15-
initBrowserAction();
13+
// Init proxy listeners
14+
initProxyListeners();
1615

17-
// Add listener for proxy requests
18-
initProxyRequests();
16+
// Listeners for permissions changes
17+
browser.permissions.onAdded.addListener(initProxyListeners);
18+
browser.permissions.onRemoved.addListener(cleanProxyListeners);

src/composables/useActiveTab.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import { ref } from 'vue';
2+
import useProxyPermissions from '@/composables/useProxyPermissions';
23

4+
const { proxyPermissionsGranted } = useProxyPermissions();
35
const activeTabHost = ref('');
46
const isAboutPage = ref(false);
57

68
const getActiveTab = async () => {
7-
const activeWindow = await browser.windows.getCurrent({ populate: true });
8-
const activeTab = activeWindow.tabs!.find((tab) => tab.active);
9+
if (proxyPermissionsGranted.value) {
10+
const activeWindow = await browser.windows.getCurrent({ populate: true });
11+
const activeTab = activeWindow.tabs!.find((tab) => tab.active);
912

10-
const activeTabURL = new URL(activeTab!.url!);
11-
activeTabHost.value = activeTabURL.hostname;
12-
isAboutPage.value = activeTabURL.protocol === 'about:';
13+
const activeTabURL = new URL(activeTab!.url!);
14+
activeTabHost.value = activeTabURL.hostname;
15+
isAboutPage.value = activeTabURL.protocol === 'about:';
16+
}
1317
};
1418

1519
const useActiveTab = () => {
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { ref } from 'vue';
2+
import { getProxyPermissions, requestProxyPermissions } from '@/helpers/permissions';
3+
4+
const useProxyPermissions = () => {
5+
const proxyPermissionsGranted = ref(false);
6+
7+
const checkProxyPermissions = async () => {
8+
proxyPermissionsGranted.value = await getProxyPermissions();
9+
};
10+
11+
const triggerProxyPermissions = async () => {
12+
proxyPermissionsGranted.value = await requestProxyPermissions();
13+
};
14+
15+
checkProxyPermissions();
16+
17+
return { proxyPermissionsGranted, triggerProxyPermissions };
18+
};
19+
20+
export default useProxyPermissions;

src/helpers/browserAction.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const updateTabsProxyBadges = async () => {
4444
}
4545
};
4646

47-
const updatedTabListener = async (
47+
export const updatedTabListener = async (
4848
_tabId: number,
4949
_changeInfo: browser.tabs._OnUpdatedChangeInfo,
5050
tab: browser.tabs.Tab,

src/helpers/permissions.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const getProxyPermissions = async () => {
2+
return await browser.permissions.contains({
3+
permissions: ['proxy', 'tabs'],
4+
origins: ['<all_urls>'],
5+
});
6+
};
7+
8+
export const requestProxyPermissions = async () => {
9+
return await browser.permissions.request({
10+
permissions: ['proxy', 'tabs'],
11+
origins: ['<all_urls>'],
12+
});
13+
};

src/helpers/socksProxy.ts

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import { RequestDetails, ProxyDetails } from './socksProxy.types';
21
import ipaddr from 'ipaddr.js';
32

3+
import { RequestDetails, ProxyDetails } from './socksProxy.types';
4+
import { getProxyPermissions } from './permissions';
5+
import { initBrowserAction, updatedTabListener } from './browserAction';
6+
47
const getGlobalProxyDetails = async (): Promise<ProxyDetails> => {
58
const response = await browser.storage.local.get('globalProxyDetails');
69

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

45+
export const initProxyListeners = async () => {
46+
const proxyPermissionsGranted = await getProxyPermissions();
47+
if (proxyPermissionsGranted) {
48+
await removeProxyListeners();
49+
await addProxyListeners();
50+
}
51+
};
52+
53+
export const cleanProxyListeners = async () => {
54+
const proxyPermissionsGranted = await getProxyPermissions();
55+
56+
if (!proxyPermissionsGranted) {
57+
await removeProxyListeners();
58+
}
59+
};
60+
61+
const addProxyListeners = async () => {
62+
initBrowserAction();
63+
initProxyRequests();
64+
};
65+
66+
const removeProxyListeners = async () => {
67+
browser.tabs.onUpdated.removeListener(updatedTabListener);
68+
browser.proxy.onRequest.removeListener(handleProxyRequest);
69+
};
70+
4271
// TODO decide what how to handle fallback proxy (if proxy is invalid, it will fallback to Firefox proxy if configured)
4372
// https://bugzilla.mozilla.org/show_bug.cgi?id=1750561
4473

src/manifest.ts

+2-10
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,8 @@ export async function getManifest() {
2727
'48': './assets/mullvad-logo.svg',
2828
'96': './assets/mullvad-logo.svg',
2929
},
30-
permissions: [
31-
'management',
32-
'privacy',
33-
'proxy',
34-
'search',
35-
'storage',
36-
'tabs',
37-
'*://*.mullvad.net/*',
38-
'<all_urls>',
39-
],
30+
permissions: ['management', 'privacy', 'search', 'storage', '*://*.mullvad.net/*'],
31+
optional_permissions: ['proxy', 'tabs', '<all_urls>'],
4032
browser_specific_settings: {
4133
gecko: {
4234
strict_min_version: '91.1.0',

src/popup/views/Home.vue

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,35 @@
11
<script lang="ts" setup>
2-
import { computed, inject } from 'vue';
2+
import { inject, onMounted, ref } from 'vue';
33
44
import ConnectionDetails from '@/components/ConnectionDetails/ConnectionDetails.vue';
55
import IconLabel from '@/components/IconLabel.vue';
66
import NotificationsCarousel from '@/components/NotificationsCarousel.vue';
77
88
import useActiveTab from '@/composables/useActiveTab';
99
import { ConnectionKey, defaultConnection } from '@/composables/useConnection';
10+
import useProxyPermissions from '@/composables/useProxyPermissions';
1011
import useSocksProxy from '@/composables/useSocksProxy';
1112
import useStore from '@/composables/useStore';
1213
14+
const { proxyPermissionsGranted } = useProxyPermissions();
1315
const { activeTabHost } = useActiveTab();
1416
const { currentHostProxyDetails, currentHostProxyEnabled } = useSocksProxy();
1517
const { excludedHosts } = useStore();
1618
const { isLoading, isError, connection } = inject(ConnectionKey, defaultConnection);
1719
18-
const currentHostExcluded = computed(() => excludedHosts.value.includes(activeTabHost.value));
20+
const currentHostExcluded = ref(false);
21+
22+
onMounted(async () => {
23+
if (proxyPermissionsGranted.value) {
24+
currentHostExcluded.value = excludedHosts.value.includes(activeTabHost.value);
25+
}
26+
});
1927
</script>
2028

2129
<template>
2230
<NotificationsCarousel v-if="!isLoading && !isError" />
2331
<ConnectionDetails />
24-
25-
<div v-if="!isLoading">
32+
<div v-if="!isLoading && proxyPermissionsGranted">
2633
<IconLabel v-if="currentHostExcluded" type="info" class="my-2">
2734
<strong>{{ activeTabHost }}</strong> is set to never be proxied
2835
</IconLabel>

src/popup/views/Proxy.vue

+34-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
<script lang="ts" setup>
22
import { computed, inject } from 'vue';
3+
import { NCard } from 'naive-ui';
34
5+
import Button from '@/components/Buttons/Button.vue';
46
import IconLabel from '@/components/IconLabel.vue';
57
import LocationDrawer from '@/components/ConnectionDetails/LocationDrawer.vue';
68
import ProxyGlobal from '@/components/Proxy/ProxyGlobal.vue';
79
import ProxyHost from '@/components/Proxy/ProxyHost.vue';
10+
import TitleCategory from '@/components/TitleCategory.vue';
811
912
import useActiveTab from '@/composables/useActiveTab';
1013
import { ConnectionKey, defaultConnection } from '@/composables/useConnection';
14+
import useProxyPermissions from '@/composables/useProxyPermissions';
1115
16+
const { proxyPermissionsGranted, triggerProxyPermissions } = useProxyPermissions();
1217
const { isAboutPage } = useActiveTab();
1318
const { connection } = inject(ConnectionKey, defaultConnection);
1419
@@ -20,13 +25,33 @@ const isWireGuard = computed(
2025
</script>
2126

2227
<template>
23-
<IconLabel v-if="!isWireGuard" type="warning" class="my-2">
24-
Connect first to Mullvad VPN (WireGuard) to use the proxy.
25-
</IconLabel>
26-
27-
<div>
28-
<ProxyHost v-if="!isAboutPage" />
29-
<ProxyGlobal />
30-
<LocationDrawer />
31-
</div>
28+
<template v-if="proxyPermissionsGranted">
29+
<IconLabel v-if="!isWireGuard" type="warning" class="my-2">
30+
Connect first to Mullvad VPN (WireGuard) to use the proxy.
31+
</IconLabel>
32+
33+
<div>
34+
<ProxyHost v-if="!isAboutPage" />
35+
<ProxyGlobal />
36+
<LocationDrawer />
37+
</div>
38+
</template>
39+
40+
<template v-else>
41+
<n-card :bordered="false" class="mb-4">
42+
<div class="flex justify-between">
43+
<TitleCategory title="Permissions required" />
44+
</div>
45+
46+
<IconLabel type="warning" class="my-2">
47+
<ul>
48+
<li>- <strong>tabs</strong> to show proxy settings from the active tab</li>
49+
<li>- <strong>proxy</strong> to configure and use Mullvad proxy servers</li>
50+
<li>- <strong>&lt;all_urls&gt;</strong> to have granular proxy settings</li>
51+
</ul>
52+
</IconLabel>
53+
54+
<Button class="mt-3" @click="triggerProxyPermissions"> Grant permissions </Button>
55+
</n-card>
56+
</template>
3257
</template>

0 commit comments

Comments
 (0)