fix(browser-bridge): auth bypass when secret empty, popup status fix, force ping on open

This commit is contained in:
mirivlad 2026-06-09 00:44:51 +08:00
parent 58751945eb
commit fa5001341e
7 changed files with 134 additions and 4095 deletions

View File

@ -0,0 +1 @@
{"uploadUuid":"8634d0ba5e1641b2a504640b5992b540","channel":"unlisted","xpiCrcHash":"124e35e2b615071abcbccd2e75a8f4247f42ddb700077632affa3a7c3c82c2ef"}

View File

@ -60,6 +60,10 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
flushQueue(); flushQueue();
sendResponse({ ok: true }); sendResponse({ ok: true });
} }
if (msg.type === 'FORCE_PING') {
pingBridge();
sendResponse({ ok: true });
}
if (msg.type === 'SET_TRACKING') { if (msg.type === 'SET_TRACKING') {
chrome.storage.local.set({ 'verstak_tracking_enabled': msg.enabled }); chrome.storage.local.set({ 'verstak_tracking_enabled': msg.enabled });
sendResponse({ ok: true }); sendResponse({ ok: true });
@ -184,6 +188,7 @@ function flushQueue() {
'X-Verstak-Secret': secret, 'X-Verstak-Secret': secret,
}, },
body: JSON.stringify(payload), body: JSON.stringify(payload),
signal: AbortSignal.timeout(5000),
}) })
.then((res) => { .then((res) => {
if (res.ok) { if (res.ok) {
@ -191,13 +196,27 @@ function flushQueue() {
updateRecent(ev); updateRecent(ev);
} }
chrome.storage.local.set({ [STORAGE_KEY]: [] }); chrome.storage.local.set({ [STORAGE_KEY]: [] });
chrome.storage.local.set({
[BRIDGE_KEY]: { ...config, bridgeReachable: true, lastPing: Date.now() }
});
chrome.runtime.sendMessage({ type: 'UI_UPDATE' }).catch(() => {}); chrome.runtime.sendMessage({ type: 'UI_UPDATE' }).catch(() => {});
} else if (res.status === 401) { } else if (res.status === 401) {
console.warn('[verstak] bridge auth failed, check secret'); console.warn('[verstak] bridge auth failed, check secret');
chrome.storage.local.set({
[BRIDGE_KEY]: { ...config, bridgeReachable: false, lastPing: Date.now() }
});
} else {
console.warn('[verstak] bridge error', res.status);
chrome.storage.local.set({
[BRIDGE_KEY]: { ...config, bridgeReachable: false, lastPing: Date.now() }
});
} }
}) })
.catch(() => { .catch((err) => {
// Bridge not available — keep events in queue console.warn('[verstak] bridge unreachable:', err.message);
chrome.storage.local.set({
[BRIDGE_KEY]: { ...config, bridgeReachable: false, lastPing: Date.now() }
});
}); });
}); });
} }
@ -207,17 +226,21 @@ function pingBridge() {
const config = data[BRIDGE_KEY] || DEFAULT_CONFIG; const config = data[BRIDGE_KEY] || DEFAULT_CONFIG;
const port = config.port || 9786; const port = config.port || 9786;
fetch(`http://127.0.0.1:${port}/api/ping`) fetch(`http://127.0.0.1:${port}/api/ping`, {
signal: AbortSignal.timeout(3000),
})
.then((res) => res.json()) .then((res) => res.json())
.then((info) => { .then((info) => {
chrome.storage.local.set({ chrome.storage.local.set({
[BRIDGE_KEY]: { ...config, ...info, bridgeReachable: true, lastPing: Date.now() } [BRIDGE_KEY]: { ...config, ...info, bridgeReachable: true, lastPing: Date.now() }
}); });
chrome.runtime.sendMessage({ type: 'UI_UPDATE' }).catch(() => {});
}) })
.catch(() => { .catch(() => {
chrome.storage.local.set({ chrome.storage.local.set({
[BRIDGE_KEY]: { ...config, bridgeReachable: false, lastPing: Date.now() } [BRIDGE_KEY]: { ...config, bridgeReachable: false, lastPing: Date.now() }
}); });
chrome.runtime.sendMessage({ type: 'UI_UPDATE' }).catch(() => {});
}); });
}); });
} }

View File

@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "Verstak Bridge", "name": "Verstak Bridge",
"version": "1.0.2", "version": "1.0.3",
"description": "Отслеживает активные вкладки и отправляет события в Verstak", "description": "Отслеживает активные вкладки и отправляет события в Verstak",
"author": "Verstak", "author": "Verstak",
"homepage_url": "https://git.mirv.top/mirivlad/verstak", "homepage_url": "https://git.mirv.top/mirivlad/verstak",

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,5 @@
{ {
"name": "verstak-bridge-firefox", "dependencies": {
"version": "1.0.0", "jsonwebtoken": "^9.0.3"
"private": true,
"description": "Verstak Bridge Firefox extension",
"scripts": {
"lint:firefox": "web-ext lint --source-dir .",
"build:firefox": "web-ext build --source-dir . --artifacts-dir ../web-ext-artifacts --overwrite-dest",
"sign:firefox": "../scripts/sign-firefox-xpi.sh",
"release:firefox": "../scripts/release-firefox-xpi.sh"
},
"devDependencies": {
"web-ext": "^8.0.0"
} }
} }

View File

@ -7,9 +7,12 @@ const RECENT_KEY = 'verstak_recent';
const DEFAULT_PORT = 9786; const DEFAULT_PORT = 9786;
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
updateUI(); // Force a fresh ping check on popup open, don't rely on stale cache
chrome.runtime.sendMessage({ type: 'FORCE_PING' });
// Read cached state for immediate render
restoreToggleState(); restoreToggleState();
restoreSettingsPanel(); restoreSettingsPanel();
updateUI();
document.getElementById('sync-btn').addEventListener('click', forceSync); document.getElementById('sync-btn').addEventListener('click', forceSync);
document.getElementById('track-toggle').addEventListener('change', onToggle); document.getElementById('track-toggle').addEventListener('change', onToggle);
@ -32,16 +35,22 @@ function updateUI() {
const portEl = document.getElementById('bridge-port'); const portEl = document.getElementById('bridge-port');
const dotEl = document.getElementById('status-dot'); const dotEl = document.getElementById('status-dot');
if (config.bridgeReachable) { if (config.bridgeReachable === true) {
statusEl.textContent = 'Доступен'; statusEl.textContent = 'Доступен';
statusEl.className = 'value online'; statusEl.className = 'value online';
dotEl.className = 'dot online'; dotEl.className = 'dot online';
portEl.textContent = '127.0.0.1:' + (config.port || DEFAULT_PORT); portEl.textContent = '127.0.0.1:' + (config.port || DEFAULT_PORT);
} else { } else if (config.bridgeReachable === false) {
statusEl.textContent = 'Недоступен'; statusEl.textContent = 'Недоступен';
statusEl.className = 'value offline'; statusEl.className = 'value offline';
dotEl.className = 'dot offline'; dotEl.className = 'dot offline';
portEl.textContent = config.lastPing ? '—' : 'проверка...'; portEl.textContent = '127.0.0.1:' + (config.port || DEFAULT_PORT);
} else {
// Unknown — not yet checked
statusEl.textContent = 'Проверка...';
statusEl.className = 'value';
dotEl.className = 'dot';
portEl.textContent = '127.0.0.1:' + (config.port || DEFAULT_PORT);
} }
// Update settings panel port field // Update settings panel port field
@ -146,11 +155,12 @@ function savePort() {
chrome.storage.local.get(BRIDGE_KEY, (data) => { chrome.storage.local.get(BRIDGE_KEY, (data) => {
const config = data[BRIDGE_KEY] || {}; const config = data[BRIDGE_KEY] || {};
config.port = port; config.port = port;
config.bridgeReachable = null; // Force re-check on next popup open
chrome.storage.local.set({ [BRIDGE_KEY]: config }, () => { chrome.storage.local.set({ [BRIDGE_KEY]: config }, () => {
statusEl.textContent = 'Сохранено'; statusEl.textContent = 'Сохранено';
statusEl.className = 'port-status ok'; statusEl.className = 'port-status ok';
// Trigger immediate ping with new port // Trigger immediate ping with new port
chrome.runtime.sendMessage({ type: 'FORCE_FLUSH' }); chrome.runtime.sendMessage({ type: 'FORCE_PING' });
setTimeout(() => { updateUI(); statusEl.textContent = ''; }, 1500); setTimeout(() => { updateUI(); statusEl.textContent = ''; }, 1500);
}); });
}); });

View File

@ -239,6 +239,11 @@ func (s *Server) handleEvents(w http.ResponseWriter, r *http.Request) {
// withAuth wraps a handler with shared-secret authentication. // withAuth wraps a handler with shared-secret authentication.
func (s *Server) withAuth(next http.HandlerFunc) http.HandlerFunc { func (s *Server) withAuth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
// If no secret is configured, skip authentication
if s.secret == "" {
next(w, r)
return
}
auth := r.Header.Get("X-Verstak-Secret") auth := r.Header.Get("X-Verstak-Secret")
if auth != s.secret { if auth != s.secret {
http.Error(w, "unauthorized", 401) http.Error(w, "unauthorized", 401)