Files
陈银军 1b0c3c012c feat: 按域名授权、iframe 多帧填充、自动提交限流修正,并补齐工程规范与文档
安全与权限:
- 站点访问权限改为 optional_host_permissions,保存配置时逐域名授权;移除 host_permissions 与 tabs,安装提示不再出现全站数据访问与浏览记录
- 主口令不再写入 storage.session,改用 IndexedDB 中不可导出的 CryptoKey 句柄恢复解锁,锁定即丢弃
- content script 注入范围只注册已授权域名,并随权限变化即时收敛

自动登录:
- 支持 iframe 内的登录表单:脚本注入所有帧,逐帧按自身地址匹配配置
- 手动填充改为逐帧探测,定向发送到真正含密码框的帧
- 限流修正:只在真正触发提交后计数;判定登录成功后立即清零;达到上限时页面给出可见提示;重置判断只看启用中的配置
- SPA 重试改为 DOM 变更门控 + 退避,并单独监听已发现的 Shadow Root

工程化与文档:
- 引入 ESLint(扁平配置)与 Prettier,CI 增加 lint 与 format:check
- 信息类日志改为 debugLog(默认静默,chrome.storage.local.debugLog 开关)
- 测试 123 → 128 用例(新增权限、iframe、限流相关用例)
- README / PRIVACY / CHANGELOG 同步
2026-09-18 00:05:20 +08:00

204 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Auto Login Manager
> 针对特定域名自动填充用户名 / 密码并完成登录,支持多环境(个人 / 测试 / 生产)管理与本地加密存储。
Chrome Manifest V3 扩展。所有凭据只保存在本机,不上传任何服务器,无网络请求、无统计分析、无第三方 SDK。
## 功能特性
**配置与匹配**
- 域名规则支持精确匹配、通配符(`*.example.com`、`api.*.example.com`)与路径(`oa.com/login/*`)
- 多环境隔离(个人 / 测试 / 生产…),同一域名在不同环境下保存不同账号,命中多个环境时弹窗选择
- 支持拖拽排序、启用 / 禁用单条配置、按域名 / 用户名 / 别名搜索
- 从浏览器书签批量导入域名
**填充与登录**
- 自动填充用户名与密码,可选择是否自动点击登录
- 三级降级探测字段:显式选择器 → 关键词启发式(`name` / `id` / `placeholder` / `autocomplete`)→ 全量兜底
- 支持 Shadow DOM 穿透(组件库封装的登录框也能识别)
- 支持 iframe 内的登录表单(如 SSO 内嵌登录页):脚本注入所有帧,各帧按自己的地址匹配配置;iframe 内命中多个环境时不弹浮窗(避免被裁切),改用当前激活环境
- 针对 SPA 的延迟重试:表单晚于脚本渲染时,最长 30 秒内持续重试。重试按 DOM 变更门控并逐步退避,且会监听已发现的 Shadow Root,因此既不会在大页面空转,也不会漏掉组件内部后渲染的登录框
- 检测到验证码(reCAPTCHA / hCaptcha / 滑块 / 图形码)时只填充、不自动提交,并提示手动完成
- 同一域名连续**自动提交** 3 次后暂停自动提交(仍填充),5 分钟后自动恢复;检测到登录成功(登录框消失或已离开匹配地址)会立即清零计数,达到上限时页面上会给出提示
- 手动填充:在当前页面按需注入并填充,无需预先打开对应页面
**安全与数据**
- 保险库加密:PBKDF2-SHA256(20 万次迭代)派生密钥 + AES-GCM-256 整库加密
- 派生密钥为不可导出(`extractable: false`)的 `CryptoKey`,以密钥句柄形式存于 IndexedDB;**主口令不写入任何存储**
- 解锁期间明文只存在于 `chrome.storage.session`(浏览器关闭即清除),10 分钟无操作自动锁定
- 导出 / 导入支持文件口令加密,兼容 v1 / v2 / v3 三种历史格式
- 内置口令强度与暴力破解耗时估算
- 脚本注入范围收敛:只注入用户配置过的域名,未配置的站点不执行任何扩展脚本
**其他**
- 中英双语界面
- 侧边栏(Side Panel)形态,可与网页并排使用
## 安全设计
```
主口令 ──PBKDF2-SHA256(20万次, 随机盐)──▶ CryptoKey(不可导出)
│
┌───────────────────────┴───────────────────────┐
▼ ▼
AES-GCM 加密 environments IndexedDB 存密钥句柄
│ │
chrome.storage.local(密文) Service Worker 重启后取回句柄解锁
```
- **口令不落盘**:Service Worker 空闲被回收后,靠 IndexedDB 中的密钥句柄恢复解锁,无需重新输入口令,同时避免了口令被读取
- **句柄不可导出**:`crypto.subtle.exportKey()` 对句柄会失败,拿到句柄只能"使用"不能"抄走"
- **锁定即失效**:手动锁定或自动锁定会删除会话明文与密钥句柄
- **默认零站点权限**:不声明 `host_permissions`,站点访问权走 `optional_host_permissions`,安装时**不申请**任何网站访问权限
- **按域名授权**:保存配置时才询问一次"允许访问该网站";拒绝也只是该域名不自动填充,**手动填充始终可用**(靠 `activeTab`)
- **注入范围 = 已授权域名**:通过 `chrome.scripting.registerContentScripts` 动态注册,只注册已授权的站点,锁定时自动清空
## 技术栈
- TypeScript(`strict` 全开)+ Manifest V3
- 构建:esbuild(打包 popup、压缩产物);tsc 仅做类型检查
- 测试:`node:test` + jsdom,无运行时依赖
## 目录结构
```
├── manifest.json 扩展清单(不含静态 content_scripts)
├── popup.html / popup.css 侧边栏界面
├── src/
│ ├── background.ts Service Worker:加解密持久化、密钥恢复、注入范围注册、消息路由
│ ├── content.ts 内容脚本:表单探测与填充引擎(按需注入)
│ ├── utils.ts URL 匹配 / HTML 转义 / match pattern 转换
│ ├── env-store.ts 环境与配置数据层(session 明文)
│ ├── crypto-store.ts 保险库:PBKDF2 + AES-GCM,锁定 / 解锁
│ ├── crypto-file.ts 导入导出文件的加解密
│ ├── key-store.ts 不可导出密钥句柄的 IndexedDB 存取
│ ├── strength.ts 口令强度与破解耗时估算
│ ├── messaging.ts runtime 消息封装(含重试)
│ ├── i18n.ts 国际化
│ └── popup/ 侧边栏各模块(入口 main.ts,通过 PopupCtx 互相调用)
├── scripts/ 构建 / 打包 / 发布脚本
├── tests/ 单元与集成测试
├── updates.xml 自托管自更新清单(部署用,不入扩展包)
├── install-policy.reg Windows 强制安装策略(部署用,不入扩展包)
└── dist/ 构建产物(不入库,需自行构建)
```
## 开发
### 环境要求
Node.js 18+(开发时使用 v24)。
```bash
npm install
```
> 若 `~/.npm` 权限异常导致安装失败,可指定缓存目录:`npm install --cache /tmp/npm-cache-autologin`
### 常用命令
| 命令 | 说明 |
| --- | --- |
| `npm run typecheck` | 仅类型检查(`tsc --noEmit`) |
| `npm run lint` | ESLint 检查(未使用变量、常见错误) |
| `npm run lint:fix` | ESLint 自动修复 |
| `npm run format` | Prettier 格式化 `src` / `scripts` / `tests` |
| `npm run format:check` | 仅检查格式(CI 使用) |
| `npm run build` | 开发构建,带 sourcemap,输出到 `dist/` |
| `npm run build:release` | 发布构建,压缩、无 sourcemap |
| `npm run watch` | 监听源码变更自动重建 |
| `npm test` | 构建 + 运行全部测试 |
| `npm run test:only` | 只跑测试(需已有 `dist/`) |
| `npm run package` | 发布构建 + 打出 `release/auto-login-v<版本>.zip` |
| `npm run release -- patch` | 版本递增 + 同步版本号 + 构建打包(见下文) |
> 格式化范围暂定为源码与测试(`src` / `scripts` / `tests`),未包含 `popup.html`、`popup.css` 与 `_locales`,以免产生上千行的纯空白 diff;需要时可自行扩展 `package.json` 里的 globs。
### 调试日志
信息类日志(填充过程、注入范围、解锁恢复等)**默认静默**,避免刷满网页控制台与 Service Worker 控制台。需要排查问题时,在扩展的 Service Worker 控制台执行一次:
```js
chrome.storage.local.set({ debugLog: true }); // 打开
chrome.storage.local.remove("debugLog"); // 关闭
```
打开后各项日志会以 `[AutoLogin]` 前缀输出。异常类告警(如注册 content script 失败、填充超时诊断)不受该开关影响,始终输出。
`dist/` 不纳入版本控制,克隆后必须先执行 `npm run build` 才能加载扩展。
### 本地加载
1. `npm install && npm run build`
2. 打开 `chrome://extensions`,右上角开启「开发者模式」
3. 点击「加载已解压的扩展程序」,选择本仓库根目录(即含 `manifest.json` 的目录)
4. 点击扩展图标打开侧边栏;首次使用会要求设置本机口令
### 测试
```bash
npm test
```
- `tests/crypto-store.test.js` 保险库加解密、解锁 / 锁定、密钥句柄解锁
- `tests/key-store.test.js` 密钥句柄的存取与降级行为
- `tests/permissions.test.js` 可选主机权限的授权状态判断与申请
- `tests/env-store.test.js` 环境与配置 CRUD
- `tests/utils.test.js` URL 匹配、HTML 转义、match pattern 转换、调试日志开关
- `tests/detect-captcha.test.js` 验证码识别
- `tests/content-fill.test.js` 表单探测与填充引擎(jsdom 真实 DOM,含 Shadow DOM、多环境浮窗、失败次数限制、SPA 重试)
## 构建与发布
产物分三类,对应不同运行环境(详见 `scripts/build.mjs` 注释):
1. **全局脚本**(`utils` / `env-store` / `crypto-store` / `key-store`):UMD,各自独立成文件,浏览器挂 `globalThis`、Node 测试可直接 `require`
2. **独立入口**(`background` / `content`):不打包,运行期靠 `importScripts` 与全局变量协作
3. **模块入口**(`popup/main`):唯一参与打包的入口,ESM,静态依赖全部内联
构建时会校验 `manifest.json` 与 `popup.html` 引用的文件是否都存在,并检查版本号是否一致。
### 发布流程
```bash
# 1. 更新 CHANGELOG.md
# 2. 递增版本并打包(同步 manifest.json / package.json / updates.xml)
npm run release -- patch # 或 minor / major / 1.2.0
# 3. 提交并打标签
git add -A && git commit -m "release: v1.0.1"
git tag v1.0.1 && git push && git push --tags
```
产物位于 `release/auto-login-v<版本>.zip`:上传 Chrome Web Store 用该 zip;自托管自更新需用同一份内容打包为 `.crx`,并更新 `updates.xml` 的 `codebase`。
推送 `v*` 标签会触发 `.gitea/workflows/release.yml`:安装依赖 → 测试 → 打包 → 上传 zip 产物。
## 权限说明
| 权限 | 用途 |
| --- | --- |
| `storage` | 保存域名配置(密文存 `local`)与解锁期间的会话数据 |
| `activeTab` | 你点击扩展图标时临时访问当前标签页,用于「在当前页面填充」与「填入当前域名」 |
| `scripting` | 按需注入填充脚本;注入范围仅限你已授权的域名 |
| `sidePanel` | 提供侧边栏界面 |
| `bookmarks` | 从浏览器书签批量导入域名(仅在你点击「从书签导入」时读取) |
| `optional_host_permissions: *://*/*` | **仅为"可申请"声明,安装时不授予**。你在保存某个域名配置时会被询问一次是否允许访问该网站;未授权的站点不会注入任何脚本 |
> 注入粒度是"已授权域名下的所有帧"(`allFrames`):iframe 里的登录表单也能被填充,因此同一域名下嵌的第三方 iframe 也会执行脚本;但每个帧都会用自己的地址去匹配配置,不匹配的帧不做任何操作。
> 未声明 `host_permissions`,也未声明 `tabs`,因此安装提示中不会出现"读取和更改您在所有网站上的数据"或"读取您的浏览记录"。
>
> 撤销授权:在 `chrome://extensions` 的扩展详情页中移除对应站点权限即可,扩展会立即同步注销注入范围(该域名退化为只能手动填充)。
## 隐私
不收集、不上传任何数据。数据处理方式详见 [PRIVACY.md](PRIVACY.md)。
## 许可证
[MIT](LICENSE)