/** * BR-2 真机核验:把站点账号存的 Cookie 注入一个全新的 camofox profile → 打开站点首页一次 → 判定是否登录态 * (地址不是登录页且页面有 a[href*=logout.php]),再经 umt-cookies 插件读回站点域名的 Cookie 个数。 * 只输出布尔值与个数:不截图、不输出页面内容与 Cookie 值;对真实站点只有一次导航。 * 用法(只读数据库;camofox 需带 umt-cookies 插件,见 docs/runbook/browser.md §9): * CAMOFOX_URL=http://127.0.0.1:49391 CAMOFOX_ACCESS_KEY=... DATABASE_URL=postgres://... SECRET_MASTER_KEY=... \ * deno run -A scripts/browser-verify/cookie-real.ts * 密钥只从环境变量读,不写进文件。 */ import { cookiesFromHeader, createCamofoxBrowserProvider, headerFromCookies, parseCookieHeader, staticBrowserSecrets, } from "@umt/adapters"; import type { NavigationBudget } from "@umt/contracts"; import { createDb } from "@umt/db"; import { createSecretStore } from "../../apps/server/src/modules/auth-settings/provided.ts"; import { readAccountCookie } from "../../apps/server/src/modules/sources-identity/service/credentials.ts"; const env = (k: string) => { const v = Deno.env.get(k); if (!v) throw new Error(`缺少环境变量 ${k}`); return v; }; const siteKey = Deno.args[0] ?? "hhanclub"; const db = createDb({ connectionString: env("DATABASE_URL"), max: 2 }); const secrets = createSecretStore({ db, masterKey: env("SECRET_MASTER_KEY") }); const quiet = { info() {}, warn() {}, error() {}, debug() {} } as never; const report: Record = { siteKey, at: new Date().toISOString() }; try { const site = await db.selectFrom("site").selectAll().where("key", "=", siteKey).executeTakeFirstOrThrow(); const acc = await db .selectFrom("site_account") .select(["id"]) .where("site_id", "=", site.id) .where("enabled", "=", true) .where("auth_kind", "in", ["cookie", "password"]) .orderBy("created_at") .executeTakeFirstOrThrow(); const cred = await readAccountCookie({ db, secrets, logger: quiet }, acc.id); if (!cred?.header) throw new Error("账号没有 Cookie"); report.source = cred.source; report.storedCookieCount = parseCookieHeader(cred.header).size; const PROVIDER = "00000000-0000-4000-8000-0000000000b2"; const key = env("CAMOFOX_ACCESS_KEY"); const p = createCamofoxBrowserProvider({ providerId: PROVIDER, baseUrl: env("CAMOFOX_URL"), secrets: staticBrowserSecrets({ [`provider:${PROVIDER}`]: key }), }); const host = new URL(site.base_url).hostname; const budget: NavigationBudget = { domainAllowlist: [host], timeoutMs: 45_000, maxRetries: 0, maxBytes: 512 * 1024 }; // 全新 profile:证明登录态来自注入,不是 profile 里原有的 const profileKey = `umt-br2-verify-${Date.now()}`; const t0 = Date.now(); const opened = await p.openSession({ providerId: PROVIDER, siteId: site.id, accountId: acc.id, profileKey, initialUrl: site.base_url, budget, cookies: cookiesFromHeader(cred.header, site.base_url), }); report.inject = opened.cookies; report.openMs = Date.now() - t0; report.httpStatus = opened.navigation?.httpStatus ?? null; const info = p.sessionInfo(opened.session.id); report.loginPage = info?.loginPage ?? null; report.landedPath = opened.session.currentUrl ? new URL(opened.session.currentUrl).pathname : null; // 登录态标记:与站点定义 umt.loginSuccess(a[href='logout.php'])一致;只取布尔值 const res = await fetch(`${env("CAMOFOX_URL")}/tabs/${encodeURIComponent(opened.tab.upstreamTabId)}/evaluate`, { method: "POST", headers: { authorization: `Bearer ${key}`, "content-type": "application/json" }, body: JSON.stringify({ userId: profileKey, expression: `!!document.querySelector("a[href*='logout.php']")`, }), }); report.logoutLinkPresent = res.ok ? ((await res.json()) as { result?: unknown }).result === true : `HTTP ${res.status}`; report.loggedIn = report.logoutLinkPresent === true && report.loginPage === false; const back = await p.getCookies(profileKey, [site.base_url]); report.readBackCookieCount = back.length; report.readBackDomains = [...new Set(back.map((c) => c.domain))]; const header = headerFromCookies(back, site.base_url); report.readBackContainsInjected = header ? [...parseCookieHeader(cred.header).keys()].every((k) => parseCookieHeader(header).has(k)) : false; await p.closeSession(opened.session.id, "verify_done"); } finally { await db.destroy(); } console.log(JSON.stringify(report, null, 2));