@travelclw/proof-protocol 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (7) hide show
  1. package/README.md +81 -65
  2. package/browser.d.ts +42 -40
  3. package/browser.js +342 -267
  4. package/core.js +221 -219
  5. package/node.d.ts +80 -76
  6. package/package.json +1 -10
  7. package/server.js +316 -288
package/README.md CHANGED
@@ -1,65 +1,81 @@
1
- # @travelclw/proof-protocol
2
-
3
- 无框架依赖的请求 Proof 协议实现。公共包只负责协议规范化、浏览器密钥存储与签名、Node.js 服务端验证;Axios、Alova、Nest Guard、业务路由、登录页面和 UI 由使用方实现。
4
-
5
- 公共包不包含任何项目业务路径,不判断某个业务接口是否需要 Proof,也不包含或生成服务端密钥。
6
-
7
- ## 安装
8
-
9
- ```bash
10
- pnpm add @travelclw/proof-protocol
11
- ```
12
-
13
- Node.js 服务端验证还需要安装兼容的 `jose`:
14
-
15
- ```bash
16
- pnpm add jose@^5
17
- ```
18
-
19
- ## 入口
20
-
21
- - `@travelclw/proof-protocol`:协议常量、规范化、错误码和模式解析。
22
- - `@travelclw/proof-protocol/browser`:IndexedDB P-256 Key Store 和 Proof 签名。
23
- - `@travelclw/proof-protocol/node`:JWK 校验、Proof 验证和服务端配置解析。
24
-
25
- ## 浏览器配置
26
-
27
- 浏览器构建只读取:
28
-
29
- - `PROOF_REQUIRED=0|1`
30
-
31
- 浏览器端不读取也不应获得任何服务端密钥。使用方需要为每个浏览器应用提供独立的 IndexedDB 名称。
32
-
33
- ## Node.js 环境变量
34
-
35
- - `AUTH_PROOF_MODE=0|1|2` 或 `off|shadow|enforce`
36
- - `AUTH_PROOF_DEVICE_SESSION_SECRET`
37
- - `AUTH_PROOF_DEVICE_SESSION_TTL_SECONDS`
38
- - `AUTH_PROOF_TIME_TOLERANCE_SECONDS`
39
-
40
- 服务端应在启动阶段调用 `resolveProofServerConfig()`:
41
-
42
- ```js
43
- const { resolveProofServerConfig } = require('@travelclw/proof-protocol/node')
44
-
45
- const proofConfig = resolveProofServerConfig()
46
- ```
47
-
48
- `AUTH_PROOF_MODE` 缺失时按 `enforce` 处理;只要模式不是显式 `off`,就必须由部署环境注入至少 32 个字符的独立 `AUTH_PROOF_DEVICE_SESSION_SECRET`,否则 `resolveProofServerConfig()` 立即抛错。它不读取通用 `SECRET`,也没有内置、示例或回退密钥。
49
-
50
- 环境变量中的非空值优先于调用方代码配置。缺少 TTL、时间窗口或 shadow 开关时使用协议默认值;显式配置非法值会直接报错,不会静默回退。
51
-
52
- ## 安全边界
53
-
54
- - 不要把 `.env`、真实密钥、Token、Cookie、私钥或生产配置提交到源码或发布到 npm。
55
- - `AUTH_PROOF_DEVICE_SESSION_SECRET` 只用于服务端设备会话摘要,不得暴露给浏览器。
56
- - 浏览器 P-256 私钥由 WebCrypto 创建并以不可导出 `CryptoKey` 保存到 IndexedDB
57
- - 包只提供协议能力;会话存储、Redis 原子操作、Guard 策略和业务路由由使用方负责。
58
-
59
- ## 发布内容
60
-
61
- npm 包只发布 `core`、`browser`、`node` 三个入口的 JavaScript、类型声明、README 和 LICENSE。测试、脚本、`.env`、`.npmrc` 与 `node_modules` 不进入发布包。
62
-
63
- ## License
64
-
65
- MIT
1
+ # @travelclw/proof-protocol
2
+
3
+ 无框架依赖的请求 Proof 协议实现。公共包只负责协议规范化、浏览器密钥存储与签名、Node.js 服务端验证;Axios、Alova、Nest Guard、业务路由、登录页面和 UI 由使用方实现。
4
+
5
+ 公共包不包含任何项目业务路径,不判断某个业务接口是否需要 Proof,也不包含或生成服务端密钥。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ pnpm add @travelclw/proof-protocol@0.2.0
11
+ ```
12
+
13
+ Node.js 服务端验证还需要安装兼容的 `jose`:
14
+
15
+ ```bash
16
+ pnpm add jose@^5
17
+ ```
18
+
19
+ ## 入口
20
+
21
+ - `@travelclw/proof-protocol`:协议常量、规范化、错误码和模式解析。
22
+ - `@travelclw/proof-protocol/browser`:IndexedDB P-256 Key Store 和 Proof 签名。
23
+ - `@travelclw/proof-protocol/node`:JWK 校验、Proof 验证和服务端配置解析。
24
+
25
+ ## 浏览器配置
26
+
27
+ 浏览器构建只读取:
28
+
29
+ - `PROOF_REQUIRED=0|1`
30
+
31
+ 浏览器端不读取也不应获得任何服务端密钥。使用方需要为每个浏览器应用提供独立的 IndexedDB 名称。
32
+
33
+ ## Node.js 环境变量
34
+
35
+ - `AUTH_PROOF_MODE=0|1|2` 或 `off|shadow|enforce`
36
+ - `AUTH_PROOF_DEVICE_SESSION_SECRET`
37
+ - `AUTH_PROOF_DEVICE_SESSION_TTL_SECONDS`
38
+ - `AUTH_PROOF_TIME_TOLERANCE_SECONDS`
39
+
40
+ 服务端应在启动阶段调用 `resolveProofServerConfig()`:
41
+
42
+ ```js
43
+ const { resolveProofServerConfig } = require('@travelclw/proof-protocol/node')
44
+
45
+ const proofConfig = resolveProofServerConfig()
46
+ ```
47
+
48
+ `AUTH_PROOF_MODE` 缺失时按 `enforce` 处理;只要模式不是显式 `off`,就必须由部署环境注入至少 32 个字符的独立 `AUTH_PROOF_DEVICE_SESSION_SECRET`,否则 `resolveProofServerConfig()` 立即抛错。它不读取通用 `SECRET`,也没有内置、示例或回退密钥。
49
+
50
+ 环境变量中的非空值优先于调用方代码配置。缺少 TTL、时间窗口或 shadow 开关时使用协议默认值;显式配置非法值会直接报错,不会静默回退。
51
+
52
+ `verifyRequestProof()` 只负责验签、请求绑定和时间计算,过期/未来时间通过 `timeWarning` 返回;是否在 `enforce` 模式拦截、如何在 `shadow` 模式审计,以及如何用 Redis `SET NX EX` 原子消费 `jti`,由服务端适配层负责。
53
+
54
+ ## 安全边界
55
+
56
+ - 不要把 `.env`、真实密钥、Token、Cookie、私钥或生产配置提交到源码或发布到 npm
57
+ - `AUTH_PROOF_DEVICE_SESSION_SECRET` 只用于服务端设备会话摘要,不得暴露给浏览器。
58
+ - 浏览器 P-256 私钥由 WebCrypto 创建并以不可导出 `CryptoKey` 保存到 IndexedDB。
59
+ - 同一浏览器应用的多个标签页共享同一个 IndexedDB 记录;`reset()` 会通过 `BroadcastChannel` 通知其它标签页清空内存中的进行中操作,并在每次读取/签名前重新核对 IndexedDB 当前 `kid`。不支持 `BroadcastChannel` 时仍以 IndexedDB 核对为准。
60
+ - `proofKeyPromise` 只缓存进行中的密钥创建操作,不缓存已完成的密钥 Promise;标签页休眠或漏收广播时,下一次签名仍会从 IndexedDB 发现 Key 已轮换。
61
+ - 包只提供协议能力;会话存储、Redis 原子操作、Guard 策略和业务路由由使用方负责。
62
+
63
+ ## 发布内容
64
+
65
+ npm 包只发布 `core`、`browser`、`node` 三个入口的 JavaScript、类型声明、README 和 LICENSE。测试、脚本、`.env`、`.npmrc` 与 `node_modules` 不进入发布包。
66
+
67
+ ## License
68
+
69
+ MIT
70
+
71
+ ## V2 Cookie 会话协议(2026-09-12,协调升级)
72
+
73
+ 调用方显式传入 `purpose: 'session' | 'ticket'` 后使用 `typ: ctx-proof+jwt`、`ver: 2`。session 从已签发 JWE 的受认证保护头读取 `psid`;服务端以已验证 sid 调用 `deriveProofSessionId(sid)` 比较。ticket 使用 ST 摘要 `sth`,浏览器必须提供此前注册的 `expectedKeyId`。
74
+
75
+ V2 不使用 ath/qsh/bth/rid,不自动创建丢失的签名 key,不允许 PROOF_REQUIRED=0 静默跳过。普通受保护路由必须显式指定 V2 purpose,时间警告必须拒绝,jti 由服务端原子消费。省略 purpose 的历史 API 仅保留库级兼容,不作为 V2 服务端降级路径。
76
+
77
+ 前端应在登录授权前取得候选 key;授权服务器须将目标 origin/key 写入 ST,在验签前检查目标,在资格复核后比较原值并原子消费 ST,绑定成功后才返回 Cookie。
78
+
79
+ `0.2.0` 包含 V2 协议实现。此前使用 `0.1.2` 配合 pnpm patch 的项目升级时,应移除对应补丁并锁定正式版本。前后端必须协调升级,旧登录凭据需要重新登录;不可将旧 Proof 作为 V2 校验失败后的回退。
80
+
81
+ 独立浏览器验收运行 `node tools/browser-v2-check.cjs`,打开输出的本机地址,点击运行;只使用测试 IndexedDB 和合成会话,结束后关闭进程。
package/browser.d.ts CHANGED
@@ -1,40 +1,42 @@
1
- export type RequestProofPublicKey = {
2
- kty: 'EC'
3
- crv: 'P-256'
4
- x: string
5
- y: string
6
- }
7
-
8
- export type RequestProofRegistration = {
9
- keyId: string
10
- publicKey: RequestProofPublicKey
11
- }
12
-
13
- export type BrowserProofInput = {
14
- token: string
15
- audience: string
16
- method: string
17
- path: string
18
- requestUri: string
19
- body?: unknown
20
- contentType?: unknown
21
- hasBody?: boolean
22
- requestId?: unknown
23
- }
24
-
25
- export type BrowserProofClient = {
26
- reset(): Promise<void>
27
- getRegistration(): Promise<RequestProofRegistration>
28
- tryGetRegistration(): Promise<RequestProofRegistration | null>
29
- createProof(input: BrowserProofInput): Promise<string>
30
- tryCreateProof(input: BrowserProofInput): Promise<string>
31
- }
32
-
33
- export function calculateKeyId(publicKey: JsonWebKey): Promise<string>
34
- export function createProofRequestId(): string
35
- export function createBrowserProofClient(options: {
36
- databaseName: string
37
- storeName?: string
38
- recordId?: string
39
- required: () => unknown
40
- }): BrowserProofClient
1
+ export type RequestProofPublicKey = {
2
+ kty: 'EC'
3
+ crv: 'P-256'
4
+ x: string
5
+ y: string
6
+ }
7
+
8
+ export type RequestProofRegistration = {
9
+ keyId: string
10
+ publicKey: RequestProofPublicKey
11
+ }
12
+
13
+ export type BrowserProofInput = {
14
+ purpose?: 'session' | 'ticket'
15
+ expectedKeyId?: string
16
+ token: string
17
+ audience: string
18
+ method: string
19
+ path: string
20
+ requestUri: string
21
+ body?: unknown
22
+ contentType?: unknown
23
+ hasBody?: boolean
24
+ requestId?: unknown
25
+ }
26
+
27
+ export type BrowserProofClient = {
28
+ reset(): Promise<void>
29
+ getRegistration(): Promise<RequestProofRegistration>
30
+ tryGetRegistration(): Promise<RequestProofRegistration | null>
31
+ createProof(input: BrowserProofInput): Promise<string>
32
+ tryCreateProof(input: BrowserProofInput): Promise<string>
33
+ }
34
+
35
+ export function calculateKeyId(publicKey: JsonWebKey): Promise<string>
36
+ export function createProofRequestId(): string
37
+ export function createBrowserProofClient(options: {
38
+ databaseName: string
39
+ storeName?: string
40
+ recordId?: string
41
+ required: () => unknown
42
+ }): BrowserProofClient