harden: list and end site connections made without Remember in Settings (closes #406)
A site allowed without "Remember" lives only in the background's in-memory connectedSites map. Settings never listed it, and AUTISTMASK_REMOVE_SITE, sent on every remove, did nothing, so the user could not end such a connection. Settings now asks the background for those sites and lists them under Connected Sites. Removing a site from Allowed Sites or Connected Sites drops its remembered entry under every address and sends AUTISTMASK_REMOVE_SITE with the hostname; the background deletes every matching connectedSites entry and sends accountsChanged with an empty list to the site's tabs. Only the extension's own pages may send either message. Model: opus-5-5
This commit was merged in pull request #419.
This commit is contained in:
+41
-1
@@ -1110,6 +1110,24 @@ async function broadcastAccountsChanged() {
|
||||
}
|
||||
}
|
||||
|
||||
// Tell every open tab of a site Settings removed that it has no account.
|
||||
async function broadcastSiteRemoved(hostname) {
|
||||
let tabs;
|
||||
try {
|
||||
tabs = await tabsQuery({});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
for (const tab of tabs) {
|
||||
if (!tab.url || extractHostname(tab.url) !== hostname) continue;
|
||||
tabsSendMessage(tab.id, {
|
||||
type: "AUTISTMASK_EVENT",
|
||||
eventName: "accountsChanged",
|
||||
data: [],
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
// Background balance refresh: every 60 seconds when the popup isn't open.
|
||||
// When the popup IS open, its 10-second interval keeps lastBalanceRefresh
|
||||
// fresh, so this naturally skips.
|
||||
@@ -1306,6 +1324,8 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
"AUTISTMASK_TX_RESPONSE",
|
||||
"AUTISTMASK_SIGN_RESPONSE",
|
||||
"AUTISTMASK_ADDRESSES_REMOVED",
|
||||
"AUTISTMASK_GET_CONNECTED_SITES",
|
||||
"AUTISTMASK_REMOVE_SITE",
|
||||
];
|
||||
if (POPUP_ONLY_TYPES.includes(msg.type) && !isExtensionSender(sender)) {
|
||||
sendResponse({ error: "Unauthorized sender" });
|
||||
@@ -1662,8 +1682,28 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Settings lists the sites connected without "Remember", which only this
|
||||
// worker knows. The origin is what precedes the key's last colon.
|
||||
if (msg.type === "AUTISTMASK_GET_CONNECTED_SITES") {
|
||||
sendResponse(
|
||||
Object.keys(connectedSites).map((key) =>
|
||||
extractHostname(key.slice(0, key.lastIndexOf(":"))),
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Settings removed this site and has already dropped its remembered
|
||||
// entries. Its connections approved without "Remember" end here, under
|
||||
// every address, and its open tabs are told it has no account.
|
||||
if (msg.type === "AUTISTMASK_REMOVE_SITE") {
|
||||
// Popup already saved state; nothing else needed
|
||||
for (const key of Object.keys(connectedSites)) {
|
||||
const origin = key.slice(0, key.lastIndexOf(":"));
|
||||
if (extractHostname(origin) === msg.hostname) {
|
||||
delete connectedSites[key];
|
||||
}
|
||||
}
|
||||
broadcastSiteRemoved(msg.hostname);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1064,6 +1064,15 @@
|
||||
<div id="settings-allowed-sites"></div>
|
||||
</div>
|
||||
|
||||
<div class="bg-well p-3 mx-1 mb-3">
|
||||
<h3 class="font-bold mb-1">Connected Sites</h3>
|
||||
<p class="text-xs text-muted mb-2">
|
||||
Sites you allowed without "Remember my choice".
|
||||
Switching address disconnects them.
|
||||
</p>
|
||||
<div id="settings-connected-sites"></div>
|
||||
</div>
|
||||
|
||||
<div class="bg-well p-3 mx-1 mb-3">
|
||||
<h3 class="font-bold mb-1">Denied Sites</h3>
|
||||
<p class="text-xs text-muted mb-2">
|
||||
|
||||
+51
-23
@@ -29,46 +29,63 @@ const {
|
||||
GITEA_COMMIT_URL,
|
||||
} = require("../../shared/buildInfo");
|
||||
|
||||
const { notify } = require("../../shared/browserApi");
|
||||
const { notify, sendMessage } = require("../../shared/browserApi");
|
||||
|
||||
let versionClickCount = 0;
|
||||
let versionClickTimer = null;
|
||||
|
||||
function renderSiteList(containerId, siteMap, stateKey) {
|
||||
// One row per hostname, however many addresses or origins it appears under,
|
||||
// each with an [x] that hands it to onRemove.
|
||||
function renderSiteList(containerId, hostnames, onRemove) {
|
||||
const container = $(containerId);
|
||||
const hostnames = [...new Set(Object.values(siteMap).flat())];
|
||||
if (hostnames.length === 0) {
|
||||
const unique = [...new Set(hostnames)];
|
||||
if (unique.length === 0) {
|
||||
container.innerHTML = '<p class="text-xs text-muted">None</p>';
|
||||
return;
|
||||
}
|
||||
let html = "";
|
||||
hostnames.forEach((hostname) => {
|
||||
unique.forEach((hostname) => {
|
||||
html += `<div class="flex justify-between items-center text-xs py-1 border-b border-border-light">`;
|
||||
// A hostname the URL parser produced cannot carry a delimiter, so
|
||||
// this is escaped for the rule rather than for a known hole — the
|
||||
// rule being that nothing reaches innerHTML unescaped.
|
||||
html += `<span>${escapeHtml(hostname)}</span>`;
|
||||
html += `<button class="btn-remove-site border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer" data-key="${escapeHtml(stateKey)}" data-hostname="${escapeHtml(hostname)}">[x]</button>`;
|
||||
html += `<button class="btn-remove-site border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer" data-hostname="${escapeHtml(hostname)}">[x]</button>`;
|
||||
html += `</div>`;
|
||||
});
|
||||
container.innerHTML = html;
|
||||
container.querySelectorAll(".btn-remove-site").forEach((btn) => {
|
||||
btn.addEventListener("click", async () => {
|
||||
const key = btn.dataset.key;
|
||||
const host = btn.dataset.hostname;
|
||||
for (const addr of Object.keys(state[key])) {
|
||||
state[key][addr] = state[key][addr].filter((h) => h !== host);
|
||||
if (state[key][addr].length === 0) {
|
||||
delete state[key][addr];
|
||||
}
|
||||
}
|
||||
await saveState();
|
||||
notify({ type: "AUTISTMASK_REMOVE_SITE" });
|
||||
renderSiteList(containerId, state[key], key);
|
||||
});
|
||||
btn.addEventListener("click", () => onRemove(btn.dataset.hostname));
|
||||
});
|
||||
}
|
||||
|
||||
// Drop a hostname from a remembered site list under every address.
|
||||
function forgetHostname(siteMap, hostname) {
|
||||
for (const addr of Object.keys(siteMap)) {
|
||||
siteMap[addr] = siteMap[addr].filter((h) => h !== hostname);
|
||||
if (siteMap[addr].length === 0) {
|
||||
delete siteMap[addr];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Removing a site from Allowed Sites or Connected Sites disconnects it: it is
|
||||
// no longer allowed under any address, and the background ends its
|
||||
// connections approved without "Remember" and tells its open tabs.
|
||||
async function removeAllowedSite(hostname) {
|
||||
forgetHostname(state.allowedSites, hostname);
|
||||
await saveState();
|
||||
notify({ type: "AUTISTMASK_REMOVE_SITE", hostname });
|
||||
await renderSiteLists();
|
||||
}
|
||||
|
||||
// Removing a denied site only forgets the refusal; it connects nothing.
|
||||
async function removeDeniedSite(hostname) {
|
||||
forgetHostname(state.deniedSites, hostname);
|
||||
await saveState();
|
||||
await renderSiteLists();
|
||||
}
|
||||
|
||||
function renderTrackedTokens() {
|
||||
const container = $("settings-tracked-tokens");
|
||||
if (state.trackedTokens.length === 0) {
|
||||
@@ -202,13 +219,24 @@ function show() {
|
||||
showView("settings");
|
||||
}
|
||||
|
||||
function renderSiteLists() {
|
||||
async function renderSiteLists() {
|
||||
renderSiteList(
|
||||
"settings-allowed-sites",
|
||||
state.allowedSites,
|
||||
"allowedSites",
|
||||
Object.values(state.allowedSites).flat(),
|
||||
removeAllowedSite,
|
||||
);
|
||||
renderSiteList(
|
||||
"settings-denied-sites",
|
||||
Object.values(state.deniedSites).flat(),
|
||||
removeDeniedSite,
|
||||
);
|
||||
// Sites allowed without "Remember" are held only by the background, in
|
||||
// memory, so it is asked for them.
|
||||
renderSiteList(
|
||||
"settings-connected-sites",
|
||||
await sendMessage({ type: "AUTISTMASK_GET_CONNECTED_SITES" }),
|
||||
removeAllowedSite,
|
||||
);
|
||||
renderSiteList("settings-denied-sites", state.deniedSites, "deniedSites");
|
||||
}
|
||||
|
||||
function init(ctx) {
|
||||
|
||||
Reference in New Issue
Block a user