Files
UHD_Media_Tool/scripts/browser-verify/cookie-real.ts
T

99 lines
4.5 KiB
TypeScript

/**
* 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=... \
* bun scripts/browser-verify/cookie-real.ts <site key>
* 密钥只从环境变量读,不写进文件。
*/
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 = process.env[k];
if (!v) throw new Error(`缺少环境变量 ${k}`);
return v;
};
const siteKey = process.argv.slice(2)[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<string, unknown> = { 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));