Repository navigation
API Reference
以下接口以当前路由与处理器为准。基础地址示例为 https://2fa.example.com。
受保护接口接受登录接口签发的 JWT:浏览器默认通过 auth_token Cookie 发送,也可以使用 Authorization: Bearer <JWT>。同时存在时优先读取 Cookie。Bearer 值是登录响应的 token,不是自定义 ACCESS_TOKEN 环境变量。
Cookie 包含 HttpOnly; Secure; SameSite=Strict; Path=/。默认登录有效期 30 天,可通过设置修改为 1~365 天。JWT 使用已存储的密码哈希签名;修改密码会使此前签发的令牌失效。退出登录清除 Cookie,并不建立服务端 JWT 撤销列表,调用者也应删除自己持有的令牌。
JSON 请求使用 Content-Type: application/json。消息语言优先级为 X-Language → URL 的 lang / language → Accept-Language。无语言信息的 API 请求兼容原有中文响应;不支持的显式语言回退到英语。支持语言见配置说明中的语言代码表。标准 JSON 响应附带 Content-Language 以及语言相关 Vary,业务记录的名称、账户和密钥不会翻译。
API 没有统一的成功包装:列表可能是裸数组、设置是裸对象,多数变更返回 success / message / data,下载接口返回文件。不要假定每个成功响应都有 data 字段。常见错误包含 error、message、timestamp;应用异常还可能含 statusCode、details。应按 HTTP 状态和结构判断结果,不要匹配本地化的消息文本。
标准 CORS 接受 http://<Host> 和 https://<Host>,本地 localhost / 127.0.0.1 还允许这两个本地主机名及不同开发端口。允许的预检方法是 GET, POST, PUT, DELETE, OPTIONS,请求头为 Content-Type, Authorization, X-Requested-With, X-Language。合格的 OPTIONS 预检返回 204,拒绝的来源返回 403。部分文件下载和模块响应仍显式返回 Access-Control-Allow-Origin: *,但这不表示整套 API 开放任意来源的 Cookie 调用,也不绕过路由认证。服务端脚本不受浏览器 CORS 约束。
接近过期时,受保护 API 的响应可能带 X-Token-Refresh-Needed: true 和 X-Token-Remaining-Days;客户端再调用刷新接口。阈值是 max(1, floor(jwtExpiryDays / 4)) 天,默认 7 天,并非每次请求自动换发令牌。
表中的「登录」表示需要有效 Cookie 或 JWT。「内部校验」表示处理器自行验证凭证或 OAuth state。
| 方法 | 路径 | 认证 | 用途 |
|---|---|---|---|
| GET | / |
公开 | 主页面;首次访问未设置密码时跳转 /setup
|
| GET | /setup |
公开 | 首次设置页面;已设置则跳回首页 |
| POST | /api/setup |
仅首次 | 设置访问密码和可选语言 |
| POST | /api/login |
公开 | 密码登录 |
| POST | /api/logout |
来源校验 | 清除登录 Cookie |
| POST | /api/refresh-token |
内部校验 | 使用有效 JWT 换发 JWT |
| POST | /api/change-password |
登录 | 修改密码 |
| GET、POST | /api/settings |
登录 | 获取、保存系统设置 |
| GET | /api/time |
公开 | Worker 的 Unix 毫秒时间;不读取 KV |
| GET、POST | /api/secrets |
登录 | 获取完整密钥列表、新增账户 |
| PUT、DELETE | /api/secrets/{id} |
登录 | 更新、删除账户;无单项 GET 接口 |
| POST | /api/secrets/batch |
登录 | 批量新增 |
| POST | /api/secrets/export |
登录 | 将请求内的账户数组转换成下载文件 |
| POST | /api/secrets/{id}/counter |
登录 | 根据预期快照递增 HOTP 计数器 |
| POST | /api/secrets/counters/compact |
登录+维护头 | 将 HOTP 有效计数器压实到主数据 |
| GET、POST | /api/backup |
登录 | 列出备份、创建备份 |
| POST | /api/backup/restore |
登录 | 预览或全量恢复备份 |
| GET | /api/backup/export/{backupKey} |
登录 | 下载指定备份 |
| GET、POST、DELETE | /api/webdav/config |
登录 | WebDAV 目标管理 |
| POST | /api/webdav/test |
登录 | 测试 WebDAV 连接 |
| POST | /api/webdav/toggle |
登录 | 启用或停用 WebDAV 目标 |
| GET、POST、DELETE | /api/s3/config |
登录 | S3 目标管理 |
| POST | /api/s3/test |
登录 | 测试 S3 连接 |
| POST | /api/s3/toggle |
登录 | 启用或停用 S3 目标 |
| GET、POST、DELETE | /api/onedrive/config |
登录 | OneDrive 目标管理 |
| POST | /api/onedrive/toggle |
登录 | 启用或停用 OneDrive 目标 |
| POST | /api/onedrive/oauth/start |
登录 | 生成 OneDrive 授权地址 |
| GET | /api/onedrive/oauth/callback |
OAuth state | 接收 OneDrive 授权回调 |
| GET、POST、DELETE | /api/gdrive/config |
登录 | Google Drive 目标管理 |
| POST | /api/gdrive/toggle |
登录 | 启用或停用 Google Drive 目标 |
| POST | /api/gdrive/oauth/start |
登录 | 生成 Google Drive 授权地址 |
| GET | /api/gdrive/oauth/callback |
OAuth state | 接收 Google Drive 授权回调 |
| GET | /api/favicon/{domain} |
公开 | 服务图标代理 |
| GET | /otp |
公开 | 快捷 OTP 入口 |
| GET | /otp/{secret} |
公开 | 为 URL 中的密钥生成验证码 |
| GET |
/manifest.json、/sw.js
|
公开 | PWA 元数据与 Service Worker |
| GET |
/icon-192.png、/icon-512.png、/favicon.ico
|
公开 | 默认图标 |
| GET | /modules/{name}.js |
登录 | 按需加载前端模块 |
页面、OTP 和静态资源表中的 GET 是推荐用法,当前路由未对这些路径全部严格限制方法。API 已识别路径的方法不匹配通常返回 405;未知 API 返回 404。受保护路径先执行认证,因而未登录访问未知路径可能先得到 401。
/modules/ 只允许 import、export、backup、qrcode、tools、googleMigration。当前没有 /api/onedrive/test、/api/gdrive/test、单项密钥 GET 或备份 DELETE 路由。
| 接口 | JSON 请求体 | 主要结果 |
|---|---|---|
POST /api/setup |
password、confirmPassword;可选 language
|
首次保存密码,设置 Cookie,返回过期时间;已完成设置不能再次初始化 |
POST /api/login |
{"credential":"你的访问密码"} |
success、token、expiresAt、expiresIn,以及 Cookie |
POST /api/refresh-token |
无需业务请求体;必须携带现有有效令牌 | 新 token、过期时间和 Cookie |
POST /api/logout |
无需业务请求体;必须带 X-Requested-With: XMLHttpRequest
|
清除 Cookie;来源检查失败返回 403 |
POST /api/change-password |
currentPassword、newPassword、confirmPassword
|
修改成功后重新登录 |
GET /api/settings |
无 | 设置对象 |
POST /api/settings |
要修改的设置字段 |
success、message、settings
|
新密码至少 8 个字符,并同时包含大写字母、小写字母、数字和特殊字符。
可写设置字段:
| 字段 | 默认值 | 约束 |
|---|---|---|
jwtExpiryDays |
30 |
数值 1~365 天 |
maxBackups |
100 |
整数 0~1000,0 为不限制 KV 备份数量 |
defaultExportFormat |
"json" |
txt、json、csv、html
|
language |
"auto" |
auto 或项目支持的语言代码;未显式保存语言的 GET 响应可能省略此字段 |
POST 设置只更新已知字段,未提供字段保持原值。网页的「默认显示方式」及视图、排序偏好仅保存在当前浏览器,不是此 API 的设置字段。具体偏好与影响见配置说明。
以下是同源页面或浏览器控制台的最小调用示例,不会把密码写入 URL:
const login = await fetch("/api/login", {
method: "POST",
credentials: "same-origin",
headers: { "Content-Type": "application/json", "X-Language": "zh-CN" },
body: JSON.stringify({ credential: "替换为自己的访问密码" }),
});
if (!login.ok) throw new Error(`登录失败:${login.status}`);
const response = await fetch("/api/secrets", {
credentials: "same-origin",
headers: { "X-Language": "zh-CN" },
});
if (!response.ok) throw new Error(`读取失败:${response.status}`);
const secrets = await response.json(); // 数组,包含明文密钥;不要输出到公共日志新增账户的请求示例使用公开测试密钥,仅用于说明格式:
{
"name": "Example",
"account": "alice@example.com",
"secret": "JBSWY3DPEHPK3PXP",
"type": "TOTP",
"digits": 6,
"period": 30,
"algorithm": "SHA1"
}| 字段 | 要求 |
|---|---|
name |
必填,非空,最多 50 字符 |
secret |
必填,有效 Base32,至少 8 字符;短密钥可能返回强度警告 |
account |
可选,默认空字符串 |
type |
TOTP 或 HOTP,默认 TOTP
|
digits |
数字 6 或 8,默认 6
|
period |
新增时为数字 30、60、120,默认 30
|
algorithm |
SHA1、SHA256、SHA512,默认 SHA1
|
counter |
HOTP 非负安全整数,默认 0
|
新增成功返回 201,记录位于 data.secret,id 由服务器生成。服务名、账户、密钥重复返回 409;单条新增的该类 409 响应还包含 details.identical,表示现有记录的 OTP 类型、位数、算法及 TOTP 周期是否也一致,该标记不比较 HOTP 计数器。GET 返回裸数组,包含生成验证码所需的明文密钥及有效 HOTP 计数器,带 Cache-Control: no-store。
PUT 的路径 ID 必须 URL 编码。提交时仍需要 name 和 secret;省略或为 null 的可选字段按当前记录合并,再整体校验。已有非标准 TOTP 周期可以原样保留,以便修改名称等字段;这不代表新增时接受任意周期。变更 HOTP 密钥、位数或算法且省略 counter 时,计数器从 0 开始;切换类型也会重置相应的类型专用默认值。HOTP 生成参数保持不变时,显式提交低于当前有效值的 counter 会返回 409,不能用旧编辑表单回退计数器。常规更新没有通用版本号或 If-Match 冲突检查。
批量新增请求为 {"secrets":[...],"immediateBackup":true},单次 1~100 条,逐条验证并报告 successCount、failCount、totalCount、results。HTTP 200 不代表所有条目都成功。分片客户端可提供从 1 开始的 chunkIndex、chunkCount;只有满 100 条且确有后续片的中间片才跳过即时备份,最终片正常触发备份。
HOTP 递增必须提交从最近列表读取的快照,避免旧客户端覆盖其他设备的新计数器:
{
"expectedNamespace": "使用该记录的 hotpCounterNamespace;不存在时省略此字段",
"expectedCounter": 12,
"expectedSecret": "JBSWY3DPEHPK3PXP",
"expectedDigits": 6,
"expectedAlgorithm": "SHA1"
}调用 POST /api/secrets/{id}/counter。上面的 namespace 文本是说明占位值,实际请求必须使用当前记录值。成功结果在 data 中包含 secret、id、递增后的 counter、idempotent: false。生成参数或计数器不一致返回 409;刷新账户后再判断下一步,不要盲目重复递增。该接口不属于离线队列可重放操作。
当前实现将 HOTP 递增写入独立 sidecar 记录,GET 列表和服务端创建备份时读取合并后的有效值。POST /api/secrets/export 使用请求中的账户快照,导出前应重新读取列表以取得最新计数器。回滚到不认识 sidecar 的旧版本前,先停止所有设备写入并完成备份,然后在仍运行当前版本时调用:
const response = await fetch("/api/secrets/counters/compact", {
method: "POST",
credentials: "same-origin",
headers: { "X-Confirm-Maintenance": "compact-hotp-counters" },
});
if (!response.ok) throw new Error(`压实未成功:${response.status}`);成功响应的 data 含 compactedCount 和 secretCount。维护头不在跨源 CORS 预检允许列表中,应使用同源请求或受信任的服务端客户端。压实与 epoch 轮换不是一个原子事务,错误后需保持维护窗口并核查、重试。完整顺序见升级与回滚。
POST /api/secrets/export 接收 format 和非空 secrets 数组,生成 txt、json、csv 或 html 文件。这是格式转换接口,导出内容来自请求体,不能只发送 ID 来让服务器查找。可选 language、filenamePrefix、metadata;profile 默认 backup,兼容 bulk-export-legacy 但仍按备份规范输出。请求体上限 2 MiB,超出返回 413。成功响应含下载的 Content-Type、Content-Disposition 和 Content-Language。
POST /api/backup 无需业务请求体,为当前完整账户库创建备份,格式采用系统默认导出格式,并向启用的远程目标推送。响应包含 backupKey、count、timestamp、encrypted、format 等本地备份信息;远程同步结果需另查目标状态和日志,不能仅凭备份接口成功判断所有远程目标均已收到文件。
手动备份在协调层的操作名为 backup.create,按“不修改账户文档”的读取操作处理。已绑定 SECRETS_STORE 但与它通信失败时,此接口可以直接读取 KV 再创建备份;KV 可能仍是较旧视图,因此备份成功不保证已包含刚完成的账户修改或最新 HOTP 计数器。迁移或回滚前应确认协调服务正常,并核对备份内容。
GET /api/backup?limit=50&details=true 返回 backups、count、pagination。pagination.cursor 是不透明游标,后续请求直接传回 cursor。limit=all 或 limit=0 读取全部;数值分页最多 1000 条;details=false 减少详情读取。
GET /api/backup/export/{backupKey}?format=json 下载备份,backupKey 应使用列表返回的键;可选 format=txt|json|csv|html,默认 txt。不是直接返回 KV 中保存的加密字节。
恢复有两种来源,不能同时提供:
{ "backupKey": "从备份列表取得的实际键", "preview": true }{
"backupFileName": "原始备份文件名",
"backupContent": "备份文件的完整文本内容",
"preview": true
}先预览检查 data.count、data.partial、data.warnings、data.unsupportedCount 和 data.unsupportedWarnings,确认后将同一请求的 preview 改为 false。执行恢复的成功响应使用顶层 count、backupKey、format、source、unsupportedCount 等字段,不再包装于 data。文件名须符合项目备份命名规则;上传内容上限 10 MiB,整个 JSON 请求上限为 20 MiB 加 64 KiB。
恢复会替换整个账户库,并不会在覆盖前自动保存旧库。 必须先另行创建并验证当前备份。存在被丢弃无效条目的不完整备份禁止恢复;空备份禁止覆盖;可保留但暂不支持的 OTP 参数另列警告。详见备份与恢复。
界面按每种提供方最多 5 个目标管理。四种 GET /api/{provider}/config 都返回 destinations、count、maxAllowed: 5,其中 OneDrive / Google Drive 的新增 API 也强制检查数量;WebDAV / S3 当前保存 API 未强制执行这个数量上限。目标含配置与 status.lastSuccess / lastError;OAuth 提供方额外返回 oauthConfigured 与目标的 authorized。
| 提供方 |
POST /config 请求字段 |
|---|---|
webdav |
name、HTTPS url、username、首次必填 password;可选 id、path(默认 /) |
s3 |
name、HTTPS endpoint、bucket、accessKeyId、首次必填 secretAccessKey;可选 id、region(默认 auto)、prefix
|
onedrive |
name;可选 id、folderPath(默认 /2FA-Backups) |
gdrive |
name;可选 id、folderPath(默认 /2FA-Backups) |
POST 带 id 更新,不带则新建;名称最多 30 字符。WebDAV 和 S3 更新时空密码/空 Secret Access Key 保留已保存值;GET 用空值及 hasPassword / hasSecretKey 标记代替返回凭证。
删除方式为 DELETE /api/{provider}/config?id=<目标ID>。启停方式为 POST /api/{provider}/toggle,JSON 为 {"id":"目标ID","enabled":true}。OAuth 目标未经授权不能启用。WebDAV / S3 的 /test 使用对应配置请求体,允许凭已保存 ID 复用密码。
OneDrive / Google Drive 先保存目标,再向 /oauth/start 发送 {"id":"目标ID"},返回 authorizeUrl、callbackOrigin。通过浏览器打开授权地址,由提供商调用 /oauth/callback?code=...&state=...;回调以短期 state 校验而非登录 Cookie 为依据。它返回授权结果页面,并非普通配置 JSON。云盘 OAuth 环境变量、权限及回调设置见远程备份。
GET /api/time 返回 {"serverTimeMs":1790000000000} 形式的 Unix 毫秒值,带 Cache-Control: no-store,不依赖密码初始化或 KV。
GET /otp/JBSWY3DPEHPK3PXP?format=json
GET /otp/JBSWY3DPEHPK3PXP?format=json&preview=1&period=30
GET /otp/JBSWY3DPEHPK3PXP?format=json&type=HOTP&counter=0
OTP 查询参数支持 type=TOTP|HOTP、digits=6|8、period=30|60|120、algorithm=SHA1|SHA256|SHA512、HOTP 的非负安全整数 counter。默认返回 HTML;format=json 返回 {"token":"123456"}。TOTP 的 preview=1 额外返回 nextToken、followingToken、period、Unix 毫秒 validUntil 和 serverTime;HOTP 不预览下一时间段,也不修改账户库的计数器。
/otp/{secret} 不要求登录。密钥放入 URL 后可能出现在历史记录、代理日志、请求日志或分享内容中。 上述地址只用公开测试密钥演示。真实账户通常应在登录后的主界面管理,不要把含真实密钥的链接交给第三方。
| 状态 | 常见含义 | 客户端处理 |
|---|---|---|
| 200 / 201 | 成功 / 新增成功 | 同时检查批量结果和远程同步结果 |
| 202 | Service Worker 在断网时接受了本地变更队列 |
queued: true 不是服务器已保存 |
| 400 | 参数错误、备份不完整、维护头缺失 | 修正请求;恢复先预览 |
| 401 | 缺少、错误或过期凭证 | 登录后重新读取状态 |
| 403 | 登出来源不合格或 CORS 预检拒绝等 | 检查具体响应及来源 |
| 404 / 405 | 资源不存在 / 方法不支持 | 对照路由表 |
| 409 | 重复初始化、重复账户、HOTP 快照冲突 | 读取最新状态再决定;不自动推进 HOTP |
| 413 | 导出或恢复体积超限 | 减少导出范围或使用合适的导入流程 |
| 429 | 对应处理器的速率限制 | 遵守 Retry-After
|
| 500 | 存储、解密、配置或外部服务处理失败 | 查 Worker 日志,先排除配置与密钥问题 |
| 503 | 未绑定 KV、未设置密码、存储协调服务不可达或离线操作不支持 | 按响应定位;不能假定写入未发生 |
限流由各处理器采用不同预设,不是所有路由统一每分钟 30 次。登录默认 5 次/分钟,敏感操作通常 10 次/分钟,批量导入 20 次/5 分钟。登录、首次设置、退出、保存设置、修改密码、创建备份、删除账户、批量导入和导出按“操作:客户端 IP”分别计数,彼此互不占用额度,例如连续删除账户不会让登录返回 429。同一操作内的多个请求共用该操作的计数。
远程存储按“提供方:客户端 IP”计数:WebDAV、S3、OneDrive、Google Drive 四种提供方之间独立,也不占用上述其他操作的额度。同一提供方下,不同目标的保存、删除、启停及连接测试(WebDAV/S3)或授权启动(OneDrive/Google Drive)共用 10 次/分钟的额度,GET 配置查询不计入该额度。
限流响应包含 Retry-After、X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset(Unix 毫秒)等。
已绑定 SECRETS_STORE 但调用发生通信异常时,修改账户文档或 HOTP 状态的操作返回 503「无法确认本次修改是否已保存」,不会直接改走 KV 重试写入;应先刷新确认。账户读取可以回退 KV,但可能读到较旧数据。不能仅按 HTTP 方法判断:POST /api/backup 的 backup.create 也允许回退 KV 并创建可能较旧的备份,如上文所述。离线 202 和部分离线 503 来自浏览器 Service Worker,直接调用服务器时不会出现同样的本地队列行为。