fix: 固化NapCat验证码日志窗口规则
This commit is contained in:
parent
640c2dbf7e
commit
69354d55f2
@ -23,7 +23,7 @@
|
||||
- 生成验证进程清理计划:按项目路径和端口给出 PowerShell 检查命令,不直接杀进程。
|
||||
- 清理历史产物:统一治理 `.kt-workspace` 下的测试/验证产物,按目录最近修改时间只保留最近 3 轮,模板目录永久保留;CLI 默认 dry-run,真实清理必须显式传 `--execute`。
|
||||
- 检查 env 策略和变更风险:区分后端真实 env 与前端客户端 `.env*`,提醒锁文件、核心表格组件、API 时间序列化 KtDateTime 列/DTO 装饰器入口、部署链路和 Vue TSX 插槽写法等高风险改动。
|
||||
- 全局 CodeReview 只读扫描:汇总全部 KT 子仓库的 Git 状态、敏感文件跟踪、冲突标记、运行时调试输出、疑似凭据字面量、NapCat `latest` 镜像漂移风险、QQBot 重型服务单测导入风险、QQBot OneBot/QQ 登录态边界回归、NapCat 密码登录验证码 wait 阶段和清理前 pending 回归、普通处理中状态误吃旧验证码日志回归、根目录生成产物、`TASKS.md` 最近记录字段结构和当前变更风险;默认只对变更文件做内容扫描,并放过短中文显示标签和明显测试占位 token,避免历史误报污染上下文;任何文件改动后都要跑一遍。
|
||||
- 全局 CodeReview 只读扫描:汇总全部 KT 子仓库的 Git 状态、敏感文件跟踪、冲突标记、运行时调试输出、疑似凭据字面量、NapCat `latest` 镜像漂移风险、QQBot 重型服务单测导入风险、QQBot OneBot/QQ 登录态边界回归、NapCat 密码登录验证码 wait 阶段、日志窗口锚点和清理前 pending 回归、普通处理中状态误吃旧验证码日志回归、根目录生成产物、`TASKS.md` 最近记录字段结构和当前变更风险;默认只对变更文件做内容扫描,并放过短中文显示标签和明显测试占位 token,避免历史误报污染上下文;任何文件改动后都要跑一遍。
|
||||
- 生成或写入 `TASKS.md` 最近记录:默认 `dryRun=true`,确认后再落盘。
|
||||
- 生成提交前检查清单:校验 KT commit message 约定。
|
||||
|
||||
@ -106,7 +106,7 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login
|
||||
| 脚本 | 用途 |
|
||||
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `pnpm run admin-login` | 使用可见 Edge 打开 Admin 登录页,填写账号密码,拖动滑块,保存登录态和截图。默认账号来自初始化数据 `admin/123456`,生产或个人账号用 `KT_ADMIN_USERNAME` / `KT_ADMIN_PASSWORD` 或 CLI 参数覆盖。 |
|
||||
| `pnpm run global-review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅对变更文件做内容深扫,输出 JSON 复审报告,并校验 `TASKS.md` 最近记录只保留范围、关键词、验证字段、NapCat `latest` 漂移、QQBot 重型服务单测导入风险、OneBot/QQ 登录态边界、NapCat 密码登录验证码 wait 阶段及清理前 pending 回归,以及普通处理中状态误吃旧验证码日志回归;明显测试占位 token 不报凭据风险,确认误报时优先升级 `src/tools/review.ts`,不要把误报沉积到上下文。 |
|
||||
| `pnpm run global-review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅对变更文件做内容深扫,输出 JSON 复审报告,并校验 `TASKS.md` 最近记录只保留范围、关键词、验证字段、NapCat `latest` 漂移、QQBot 重型服务单测导入风险、OneBot/QQ 登录态边界、NapCat 密码登录验证码 wait 阶段、日志窗口锚点及清理前 pending 回归,以及普通处理中状态误吃旧验证码日志回归;明显测试占位 token 不报凭据风险,确认误报时优先升级 `src/tools/review.ts`,不要把误报沉积到上下文。 |
|
||||
| `pnpm run obsidian-context` | 输出 Obsidian 索引上下文;可传 `--module Admin`、`--query QQBot`、`--max-documents 10`。 |
|
||||
| `pnpm run obsidian-validate` | 校验 KT Obsidian vault 结构和链接;默认 warning 不让脚本失败,需要严格模式时传 `--fail-on-warnings`。 |
|
||||
| `pnpm run obsidian-sync` | 审计 Obsidian 工作流入口是否连通,并联动执行 validate。 |
|
||||
|
||||
@ -157,6 +157,12 @@ export async function runSelfTest(): Promise<void> {
|
||||
"private async waitForPasswordLoginStatus(container) {",
|
||||
" latestStatus = await this.getLoginStatus(container, true);",
|
||||
"}",
|
||||
"private async tryPasswordRelogin(session, container) {",
|
||||
" await this.containerService.ensureRuntimeLoginEnv(container, { loginPassword: 'x' });",
|
||||
" session.lastRestartedAt = Date.now();",
|
||||
" loginStatus = await this.waitForPasswordLoginStatus(container, session.lastRestartedAt);",
|
||||
" await this.resolvePasswordCaptchaUrl(container, loginStatus, session.lastRestartedAt);",
|
||||
"}",
|
||||
],
|
||||
{
|
||||
file: "src/qqbot/account/qqbot-napcat-login.service.ts",
|
||||
@ -166,11 +172,46 @@ export async function runSelfTest(): Promise<void> {
|
||||
for (const category of [
|
||||
"qqbot-napcat-captcha-error-throws-before-pending",
|
||||
"qqbot-napcat-captcha-log-single-read",
|
||||
"qqbot-napcat-captcha-log-window-late",
|
||||
]) {
|
||||
if (!qqbotCaptchaFlowFindings.some((item) => item.category === category)) {
|
||||
throw new Error(`QQBot captcha flow self-check failed: ${category}`);
|
||||
}
|
||||
}
|
||||
const qqbotCaptchaFlowValidFindings = findQqbotNapcatCaptchaFlowFindings(
|
||||
[
|
||||
"private async resolvePasswordCaptchaUrl(container, loginStatus, sinceMs) {",
|
||||
" await this.detectPasswordCaptchaUrl(container, sinceMs, false);",
|
||||
" await this.detectPasswordCaptchaUrl(container, sinceMs, true);",
|
||||
"}",
|
||||
"private async waitForPasswordLoginStatus(container, sinceMs) {",
|
||||
" if (this.toolsService.isNapcatCaptchaRequiredMessage(errorMessage)) {",
|
||||
" await this.detectPasswordCaptchaUrl(container, sinceMs);",
|
||||
" }",
|
||||
"}",
|
||||
"private async detectPasswordCaptchaUrl(container, sinceMs, allowTailFallback = true) {",
|
||||
" await this.containerService.detectRuntimeCaptchaUrl(container, sinceMs);",
|
||||
" await this.containerService.detectRuntimeCaptchaUrl(container);",
|
||||
"}",
|
||||
"private async tryPasswordRelogin(session, container) {",
|
||||
" const passwordLogSinceMs = Date.now();",
|
||||
" await this.containerService.ensureRuntimeLoginEnv(container, { loginPassword: 'x' });",
|
||||
" loginStatus = await this.waitForPasswordLoginStatus(container, passwordLogSinceMs);",
|
||||
" await this.resolvePasswordCaptchaUrl(container, loginStatus, passwordLogSinceMs);",
|
||||
"}",
|
||||
],
|
||||
{
|
||||
file: "src/qqbot/account/qqbot-napcat-login.service.ts",
|
||||
project: "Node/kt-template-online-api",
|
||||
},
|
||||
);
|
||||
if (
|
||||
qqbotCaptchaFlowValidFindings.some(
|
||||
(item) => item.category === "qqbot-napcat-captcha-log-window-late",
|
||||
)
|
||||
) {
|
||||
throw new Error("QQBot captcha flow valid self-check produced false positive");
|
||||
}
|
||||
|
||||
const reviewCliParser = {
|
||||
dashedAll: parseGlobalReviewCliArgs([
|
||||
|
||||
@ -354,8 +354,15 @@ export function findQqbotNapcatCaptchaFlowFindings(
|
||||
const helperLine = lines.findIndex((line) =>
|
||||
/private\s+async\s+detectPasswordCaptchaUrl\s*\(/.test(line),
|
||||
);
|
||||
const passwordReloginLine = lines.findIndex((line) =>
|
||||
/private\s+async\s+tryPasswordRelogin\s*\(/.test(line),
|
||||
);
|
||||
const waitBody =
|
||||
waitLine >= 0 ? lines.slice(waitLine, waitLine + 90).join("\n") : "";
|
||||
const passwordReloginBody =
|
||||
passwordReloginLine >= 0
|
||||
? lines.slice(passwordReloginLine, passwordReloginLine + 140).join("\n")
|
||||
: "";
|
||||
|
||||
if (
|
||||
content.includes("waitForPasswordLoginStatus") &&
|
||||
@ -408,6 +415,37 @@ export function findQqbotNapcatCaptchaFlowFindings(
|
||||
});
|
||||
}
|
||||
|
||||
const logAnchorMatch = passwordReloginBody.match(
|
||||
/const\s+([A-Za-z0-9_]*LogSinceMs)\s*=\s*Date\.now\(\)[\s\S]*?ensureRuntimeLoginEnv/,
|
||||
);
|
||||
const logAnchorName = logAnchorMatch?.[1];
|
||||
const waitUsesLogAnchor =
|
||||
!!logAnchorName &&
|
||||
new RegExp(
|
||||
`waitForPasswordLoginStatus\\s*\\(\\s*container\\s*,\\s*${logAnchorName}\\s*,?\\s*\\)`,
|
||||
).test(passwordReloginBody);
|
||||
const resolveUsesLogAnchor =
|
||||
!!logAnchorName &&
|
||||
new RegExp(
|
||||
`resolvePasswordCaptchaUrl\\s*\\(\\s*container\\s*,\\s*loginStatus\\s*,\\s*${logAnchorName}\\s*,?\\s*\\)`,
|
||||
).test(passwordReloginBody);
|
||||
if (
|
||||
passwordReloginBody &&
|
||||
(!logAnchorName || !waitUsesLogAnchor || !resolveUsesLogAnchor)
|
||||
) {
|
||||
findings.push({
|
||||
category: "qqbot-napcat-captcha-log-window-late",
|
||||
file: context.file,
|
||||
line: passwordReloginLine + 1,
|
||||
level: "P2",
|
||||
message:
|
||||
"NapCat 密码验证码日志窗口可能锚在容器重启之后,导致 proofWaterUrl 被漏抓。",
|
||||
project: context.project,
|
||||
suggestion:
|
||||
"在准备 NAPCAT_QUICK_PASSWORD 环境前记录日志窗口起点,并把同一个时间戳传给 waitForPasswordLoginStatus 和 resolvePasswordCaptchaUrl。",
|
||||
});
|
||||
}
|
||||
|
||||
return findings;
|
||||
}
|
||||
|
||||
|
||||
@ -621,6 +621,7 @@ export function buildComponentWorkflow(
|
||||
"账号登录密码前端只传 RSA-OAEP 加密字段 encryptedLoginPassword,后端用 QQBOT_ACCOUNT_SECRET_KEY 二次加密落库,列表不回显",
|
||||
"ACCOUNT 只指定目标账号;免扫码优先依赖数据卷历史登录态,密码登录失败或触发强制扫码时再进二维码",
|
||||
"密码登录触发验证码时保持 scan session pending,Admin 通过 TencentCaptcha 拿 ticket/randstr/sid,再由 API 调用同一 NapCat 容器 /api/QQLogin/CaptchaLogin",
|
||||
"密码登录验证码日志窗口必须从 NAPCAT_QUICK_PASSWORD 环境重建前开始,覆盖重建容器和后续 restart,避免 proofWaterUrl 被清理前漏抓",
|
||||
"验证码 pending、cancel、expire、success 和失败终态都必须清理临时密码环境变量,清理失败要阻断成功或二维码兜底",
|
||||
"SSE 返回 step/status/qrcode/error",
|
||||
"容器重启只在必要时触发,优先直接通信 NapCat API",
|
||||
@ -629,6 +630,7 @@ export function buildComponentWorkflow(
|
||||
"账号被踢下线后点击更新登录",
|
||||
"docker inspect 确认已绑定容器存在 ACCOUNT=<selfId>",
|
||||
"docker restart 后 WebUI CheckLoginStatus/GetQQLoginInfo 验证 isLogin/online/uin;失败时检查 NapCat 日志是否为无历史登录记录、密码登录失败或需扫码验证",
|
||||
"密码验证码触发时用 watcher 或 scan/status 证明 API 返回 captcha pending 且不暴露完整 proofWaterUrl",
|
||||
"scan/events 能看到 quick-login-start/quick-login-fallback/password-login-start/password-login-fallback/重置/生成二维码/等待扫码/成功或失败",
|
||||
"目标 Jest 覆盖 quick 成功、password 成功、password 失败进二维码、验证码 CaptchaLogin 回传、密文保存、容器 env 注入和清理失败阻断",
|
||||
"手动刷新不超时",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user