From d3245f0c224eef8a3083935342bb7855f72f353e Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 3 Jun 2026 14:04:45 -0400 Subject: [PATCH] feat(ext): verify cookies in-browser before uploading (1.0.7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-upload verify request: after capturing the live browser cookies, hit a known authenticated endpoint with credentials:'include' from the extension's background context. If the platform reports we're not logged in, abort the upload so we don't overwrite FC-side credentials with stale data. - platforms.js: add `verify` config per cookie-auth platform - hentaifoundry: HEAD /?enterAgree=1 (mirrors gallery-dl's HF _init_site_filters; same 401 path the operator hit 2026-06-03) - patreon: GET /api/current_user (clean 401 when logged out) - subscribestar, deviantart: no stable auth endpoint, skip verify - cookies.js: verifyCookiesForPlatform() returns {ok, status, reason}. ok=true/false/null tri-state — null = verify not configured, caller treats as "proceed". - background.js EXPORT_COOKIES + EXPORT_ALL_COOKIES: verify gates the upload; failures bubble up with the platform's name + reason. - popup.js: success message now appends "(verified ✓)" when applicable. - manifest + package.json: 1.0.6 → 1.0.7. --- extension/background/background.js | 20 +++++++++++++++-- extension/lib/cookies.js | 35 ++++++++++++++++++++++++++++++ extension/lib/platforms.js | 21 ++++++++++++++++++ extension/manifest.json | 2 +- extension/package.json | 2 +- extension/popup/popup.js | 3 ++- 6 files changed, 78 insertions(+), 5 deletions(-) diff --git a/extension/background/background.js b/extension/background/background.js index a19c900..5b93be1 100644 --- a/extension/background/background.js +++ b/extension/background/background.js @@ -194,9 +194,20 @@ browser.runtime.onMessage.addListener(async (msg) => { if (platform.authType === 'cookies') { const cookies = await extractCookiesForPlatform(key); if (cookies.length === 0) return { error: 'No cookies found — log in first.' }; + // Verify the captured cookies are actually live BEFORE + // uploading. Skips upload on confirmed-stale sessions so we + // don't overwrite FC-side credentials with garbage. Platforms + // without a verify config (verify.ok === null) fall through + // to upload as before. + const v = await verifyCookiesForPlatform(key); + if (v.ok === false) { + return { + error: `Captured ${cookies.length} ${platform.name} cookies but they don't appear authenticated (${v.reason}). Log in again in this browser, then retry.`, + }; + } const data = toNetscapeFormat(cookies); await api.uploadCredentials(key, 'cookies', data); - return { success: true, cookieCount: cookies.length }; + return { success: true, cookieCount: cookies.length, verified: v.ok === true }; } if (key === 'discord') { if (!discordToken) return { error: 'Open discord.com to capture a token first.' }; @@ -229,8 +240,13 @@ browser.runtime.onMessage.addListener(async (msg) => { results[key] = { skipped: true, reason: 'no cookies' }; continue; } + const v = await verifyCookiesForPlatform(key); + if (v.ok === false) { + results[key] = { error: `verify failed: ${v.reason}` }; + continue; + } await api.uploadCredentials(key, 'cookies', toNetscapeFormat(cookies)); - results[key] = { success: true, cookieCount: cookies.length }; + results[key] = { success: true, cookieCount: cookies.length, verified: v.ok === true }; } catch (e) { results[key] = { error: e.message }; } diff --git a/extension/lib/cookies.js b/extension/lib/cookies.js index 8569337..3e733d4 100644 --- a/extension/lib/cookies.js +++ b/extension/lib/cookies.js @@ -76,3 +76,38 @@ async function getCookieCount(platformKey) { return 0; } } + +/** + * Verify cookies are live by hitting an authenticated endpoint with the + * browser's current cookie jar. Returns: + * { ok: true, status } — verified + * { ok: false, status, reason } — endpoint said we're not logged in + * { ok: null, reason } — no verify config for this platform; caller + * should treat as "verify not available, + * proceed with upload" + * + * Implementation note: extensions with `host_permissions` for the target + * domain get the user's cookies auto-attached to fetch() — same set + * gallery-dl will later use on the backend. + */ +async function verifyCookiesForPlatform(platformKey) { + const platform = PLATFORMS[platformKey]; + if (!platform) return { ok: false, reason: `Unknown platform: ${platformKey}` }; + if (!platform.verify) return { ok: null, reason: 'verify-not-configured' }; + + const { url, method, okStatuses } = platform.verify; + let resp; + try { + resp = await fetch(url, { method, credentials: 'include', cache: 'no-store' }); + } catch (e) { + return { ok: false, reason: `Verify request failed: ${e.message}` }; + } + if (okStatuses.includes(resp.status)) { + return { ok: true, status: resp.status }; + } + return { + ok: false, + status: resp.status, + reason: `${url} returned HTTP ${resp.status} — session looks stale or logged out`, + }; +} diff --git a/extension/lib/platforms.js b/extension/lib/platforms.js index 9092c80..3a9ed2a 100644 --- a/extension/lib/platforms.js +++ b/extension/lib/platforms.js @@ -13,6 +13,13 @@ const PLATFORMS = { authType: 'cookies', color: '#FF424D', urlPattern: /^https?:\/\/(www\.)?patreon\.com/, + // Patreon's `/api/current_user` returns 200 + the logged-in user + // when authenticated, 401 otherwise. Cheapest definitive check. + verify: { + url: 'https://www.patreon.com/api/current_user', + method: 'GET', + okStatuses: [200], + }, }, subscribestar: { name: 'SubscribeStar', @@ -26,6 +33,9 @@ const PLATFORMS = { authType: 'cookies', color: '#FFD700', urlPattern: /^https?:\/\/(www\.)?subscribestar\.(com|adult)/, + // No known stable auth-required endpoint that returns a definitive + // status code; skipping verify so we don't false-positive-fail + // good cookies. Operator can add later if a clean endpoint surfaces. }, hentaifoundry: { name: 'Hentai Foundry', @@ -33,6 +43,14 @@ const PLATFORMS = { authType: 'cookies', color: '#9C27B0', urlPattern: /^https?:\/\/(www\.)?hentai-foundry\.com/, + // Mirror gallery-dl's _init_site_filters: HEAD on `?enterAgree=1`. + // Logged in → 200, logged out → 401. Catches the exact failure mode + // the backend extractor would hit later. + verify: { + url: 'https://www.hentai-foundry.com/?enterAgree=1', + method: 'HEAD', + okStatuses: [200], + }, }, discord: { name: 'Discord', @@ -56,6 +74,9 @@ const PLATFORMS = { authType: 'cookies', color: '#05CC47', urlPattern: /^https?:\/\/(www\.)?deviantart\.com/, + // DA's logged-in-only endpoints sit behind their internal _napi + // namespace which shifts; skipping verify until a stable check + // surfaces. Same posture as SubscribeStar. }, }; diff --git a/extension/manifest.json b/extension/manifest.json index 701884e..fc930bc 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "FabledCurator", - "version": "1.0.6", + "version": "1.0.7", "description": "Export cookies from supported platforms to FabledCurator and add creators as sources in one click.", "browser_specific_settings": { diff --git a/extension/package.json b/extension/package.json index d6151b9..1f35145 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,6 +1,6 @@ { "name": "fabledcurator-extension", - "version": "1.0.6", + "version": "1.0.7", "private": true, "description": "Firefox extension for FabledCurator", "scripts": { diff --git a/extension/popup/popup.js b/extension/popup/popup.js index cc0ca7c..575e38c 100644 --- a/extension/popup/popup.js +++ b/extension/popup/popup.js @@ -132,8 +132,9 @@ async function exportPlatformCookies(key, card) { if (r.error) showError(r.error); else { const n = r.cookieCount ?? null; + const verifiedSuffix = r.verified ? ' (verified ✓)' : ''; const msg = n !== null - ? `${PLATFORMS[key].name}: ${n} cookies exported` + ? `${PLATFORMS[key].name}: ${n} cookies exported${verifiedSuffix}` : `${PLATFORMS[key].name}: token exported`; showSuccess(msg); await loadPlatformStatus();