{"_id":"@agent-web-portal/auth","_rev":"2-047bd318c29f4dad9fb628f42e74b09b","name":"@agent-web-portal/auth","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@agent-web-portal/auth","version":"0.1.0","keywords":["mcp","ai-agent","agent-web-portal","awp","auth","oauth","hmac","api-key"],"author":{"name":"Agent Web Portal Contributors"},"license":"MIT","_id":"@agent-web-portal/auth@0.1.0","maintainers":[{"name":"shazhou.ww","email":"shazhou.ww@gmail.com"}],"homepage":"https://github.com/anthropics/agent-web-portal#readme","bugs":{"url":"https://github.com/anthropics/agent-web-portal/issues"},"dist":{"shasum":"632a9d6dbcfdf466d210811f7b48bd706e346737","tarball":"https://registry.npmjs.org/@agent-web-portal/auth/-/auth-0.1.0.tgz","fileCount":13,"integrity":"sha512-x1SJBEv2d0Ud+y6dWwgmsjrmRLi/2sMkW59jG9ymuQcVpdrvyv0OK1rRJCNSMb+pRWqh/vSyNfiOJf7x/WVo7w==","signatures":[{"sig":"MEUCIAgacDubzxYw9Ln6P5a+g7qm+LbYxB7r2Qztl5t5OC3VAiEA3xtgoOAbByRXwVRZpuz8eYJ2LJYUfoy0fzFAEuJBGNo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":56344},"main":"src/index.ts","type":"module","types":"src/index.ts","exports":{".":{"types":"./src/index.ts","import":"./src/index.ts"}},"gitHead":"775a13ba357a3164925dac6fd943f64188b54dd4","scripts":{"lint":"biome check .","check":"tsc --noEmit && biome check .","lint:fix":"biome check --write .","typecheck":"tsc --noEmit"},"_npmUser":{"name":"shazhou.ww","email":"shazhou.ww@gmail.com"},"repository":{"url":"git+https://github.com/anthropics/agent-web-portal.git","type":"git","directory":"packages/auth"},"_npmVersion":"11.6.2","description":"Authentication middleware for Agent Web Portal - supports OAuth 2.1, HMAC, and API Key","directories":{},"_nodeVersion":"24.12.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"bun-types":"^1.3.6","@types/bun":"latest"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/auth_0.1.0_1769099654151_0.924710304531013","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@agent-web-portal/auth","version":"0.2.0","description":"Authentication middleware for Agent Web Portal - supports OAuth 2.1, HMAC, and API Key","license":"MIT","author":{"name":"Agent Web Portal Contributors"},"repository":{"type":"git","url":"git+https://github.com/anthropics/agent-web-portal.git","directory":"packages/auth"},"homepage":"https://github.com/anthropics/agent-web-portal#readme","bugs":{"url":"https://github.com/anthropics/agent-web-portal/issues"},"publishConfig":{"access":"public"},"keywords":["mcp","ai-agent","agent-web-portal","awp","auth","oauth","hmac","api-key"],"type":"module","main":"src/index.ts","types":"src/index.ts","exports":{".":{"import":"./src/index.ts","types":"./src/index.ts"}},"scripts":{"clean":"rimraf .turbo dist","typecheck":"tsc --noEmit","lint":"biome check .","lint:fix":"biome check --write .","check":"tsc --noEmit && biome check ."},"dependencies":{},"devDependencies":{"@types/bun":"latest","bun-types":"^1.3.6"},"peerDependencies":{"typescript":"^5"},"gitHead":"ee9e9127b046b65772299a166ae6c03eb8009ea9","_id":"@agent-web-portal/auth@0.2.0","_nodeVersion":"24.12.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-5VMR8D74bef8UQe7m7pK/cH2DC91V3CJ4BJ8CuGEfcDp6G7equaX4twKhvlgXOEHFkiz1x7lT+YnkYpjOEnD6A==","shasum":"ba04ca3147de654675c6d155dac0c7223d83058d","tarball":"https://registry.npmjs.org/@agent-web-portal/auth/-/auth-0.2.0.tgz","fileCount":10,"unpackedSize":70721,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCEKEygeoQQOb/LpfHax+rxF0572vVWbU3jjPCBEEpejgIgOT3VLuY/VPSCOrNzOXDUfgNhg507+gYjzhXsxA2Xhgc="}]},"_npmUser":{"name":"shazhou.ww","email":"shazhou.ww@gmail.com"},"directories":{},"maintainers":[{"name":"shazhou.ww","email":"shazhou.ww@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/auth_0.2.0_1769334897808_0.5042144080235031"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-22T16:34:14.084Z","modified":"2026-01-25T09:54:58.111Z","0.1.0":"2026-01-22T16:34:14.338Z","0.2.0":"2026-01-25T09:54:57.967Z"},"bugs":{"url":"https://github.com/anthropics/agent-web-portal/issues"},"author":{"name":"Agent Web Portal Contributors"},"license":"MIT","homepage":"https://github.com/anthropics/agent-web-portal#readme","keywords":["mcp","ai-agent","agent-web-portal","awp","auth","oauth","hmac","api-key"],"repository":{"type":"git","url":"git+https://github.com/anthropics/agent-web-portal.git","directory":"packages/auth"},"description":"Authentication middleware for Agent Web Portal - supports OAuth 2.1, HMAC, and API Key","maintainers":[{"name":"shazhou.ww","email":"shazhou.ww@gmail.com"}],"readme":"# @agent-web-portal/auth\n\nAWP 认证中间件，基于 **ECDSA P-256 密钥对** 实现 Client 授权认证。\n\n## 概述\n\n`@agent-web-portal/auth` 提供 AWP 的服务端认证机制：\n\n- **ECDSA P-256 密钥对** - 客户端生成密钥对，服务端验证签名\n- **服务端验证码** - 防钓鱼保护，验证码由服务端生成\n- **请求签名** - 每个请求都使用私钥签名\n- **401 Challenge** - 未认证请求返回标准挑战响应\n\n## 安装\n\n```bash\nbun add @agent-web-portal/auth\n```\n\n## 快速开始\n\n```typescript\nimport {\n  createAwpAuthMiddleware,\n  routeAuthRequest,\n  MemoryPendingAuthStore,\n  MemoryPubkeyStore,\n} from \"@agent-web-portal/auth\";\n\n// 创建存储 (生产环境使用 DynamoDB/Redis)\nconst pendingAuthStore = new MemoryPendingAuthStore();\nconst pubkeyStore = new MemoryPubkeyStore();\n\n// 创建认证中间件\nconst authMiddleware = createAwpAuthMiddleware({\n  pendingAuthStore,\n  pubkeyStore,\n});\n\n// 在请求处理中使用\nBun.serve({\n  port: 3000,\n  fetch: async (req) => {\n    const authReq = {\n      method: req.method,\n      url: req.url,\n      headers: req.headers,\n      text: () => req.clone().text(),\n      clone: () => authReq,\n    };\n\n    // 处理认证端点 (/auth/init, /auth/status)\n    const authResponse = await routeAuthRequest(authReq, {\n      baseUrl: \"http://localhost:3000\",\n      pendingAuthStore,\n      pubkeyStore,\n    });\n    if (authResponse) return authResponse;\n\n    // 验证请求签名\n    const result = await authMiddleware(authReq);\n    if (!result.authorized) {\n      return result.challengeResponse!;\n    }\n\n    // 继续处理业务逻辑\n    // result.context 包含 { userId, pubkey, clientName }\n    return handleRequest(req, result.context);\n  },\n});\n```\n\n## 认证流程\n\n```\n1. Client 发起请求 (无认证)\n   → Server 返回 401 + auth_init_endpoint\n\n2. Client 生成密钥对，调用 POST /auth/init\n   { pubkey, client_name }\n   → Server 生成验证码，返回 { auth_url, verification_code, expires_in }\n\n3. Client 显示验证码给用户\n\n4. 用户访问 auth_url，登录后输入验证码\n\n5. Server 验证码匹配，存储 pubkey → userId 映射\n\n6. Client 轮询 /auth/status，获取授权状态\n\n7. 后续请求携带签名\n   X-AWP-Pubkey, X-AWP-Timestamp, X-AWP-Signature\n```\n\n## 请求签名\n\n每个认证请求需要携带以下 HTTP 头：\n\n| 头名称 | 说明 |\n|--------|------|\n| `X-AWP-Pubkey` | 公钥 (格式: `x.y`, base64url 编码) |\n| `X-AWP-Timestamp` | Unix 时间戳 (秒) |\n| `X-AWP-Signature` | 签名 (base64url 编码) |\n\n**签名算法**：\n\n```typescript\npayload = `${timestamp}.${METHOD}.${path}.${sha256(body)}`\nsignature = ECDSA-P256-SHA256(privateKey, payload)\n```\n\n## 401 Challenge Response\n\n未认证请求返回：\n\n```json\n{\n  \"error\": \"unauthorized\",\n  \"error_description\": \"Authentication required\",\n  \"auth_init_endpoint\": \"/auth/init\"\n}\n```\n\nHTTP 头：\n\n```\nHTTP/1.1 401 Unauthorized\nWWW-Authenticate: AWP realm=\"awp\"\nContent-Type: application/json\n```\n\n## Auth Init 端点\n\n**请求**：\n\n```\nPOST /auth/init\nContent-Type: application/json\n\n{\n  \"pubkey\": \"abc123...xyz.def456...uvw\",\n  \"client_name\": \"My AI Agent\"\n}\n```\n\n**响应**：\n\n```json\n{\n  \"auth_url\": \"https://example.com/auth?pubkey=abc123...\",\n  \"verification_code\": \"ABC-123\",\n  \"expires_in\": 600,\n  \"poll_interval\": 5\n}\n```\n\n## 配置选项\n\n```typescript\ninterface AwpAuthConfig {\n  // 必需\n  pendingAuthStore: PendingAuthStore;  // 待授权存储\n  pubkeyStore: PubkeyStore;            // 已授权公钥存储\n\n  // 可选\n  authInitPath?: string;        // 默认: \"/auth/init\"\n  authStatusPath?: string;      // 默认: \"/auth/status\"\n  authPagePath?: string;        // 默认: \"/auth\"\n  verificationCodeTTL?: number; // 默认: 600 (10分钟)\n  maxClockSkew?: number;        // 默认: 300 (5分钟)\n  excludePaths?: string[];      // 排除认证的路径\n}\n```\n\n## 存储接口\n\n### PendingAuthStore\n\n存储待授权请求：\n\n```typescript\ninterface PendingAuthStore {\n  create(auth: PendingAuth): Promise<void>;\n  get(pubkey: string): Promise<PendingAuth | null>;\n  delete(pubkey: string): Promise<void>;\n  validateCode(pubkey: string, code: string): Promise<boolean>;\n}\n```\n\n### PubkeyStore\n\n存储已授权公钥：\n\n```typescript\ninterface PubkeyStore {\n  lookup(pubkey: string): Promise<AuthorizedPubkey | null>;\n  store(auth: AuthorizedPubkey): Promise<void>;\n  revoke(pubkey: string): Promise<void>;\n  listByUser?(userId: string): Promise<AuthorizedPubkey[]>;\n}\n```\n\n### 内置实现\n\n- `MemoryPendingAuthStore` - 内存存储 (仅用于开发/测试)\n- `MemoryPubkeyStore` - 内存存储 (仅用于开发/测试)\n\n生产环境请使用 `@agent-web-portal/aws-lambda` 中的 DynamoDB 实现。\n\n## 授权完成处理\n\n当用户在授权页面输入验证码后，调用 `completeAuthorization`：\n\n```typescript\nimport { completeAuthorization } from \"@agent-web-portal/auth\";\n\n// 在授权页面的 POST 处理中\nasync function handleAuthPageSubmit(req: Request, userId: string) {\n  const { pubkey, verification_code } = await req.json();\n\n  const result = await completeAuthorization(pubkey, verification_code, userId, {\n    pendingAuthStore,\n    pubkeyStore,\n    authorizationTTL: 30 * 24 * 60 * 60, // 30天\n  });\n\n  if (result.success) {\n    return new Response(JSON.stringify({ success: true }));\n  } else {\n    return new Response(JSON.stringify({ error: result.error }), { status: 400 });\n  }\n}\n```\n\n## 排除路径\n\n以下路径默认不需要认证：\n\n- `/auth/init` - 认证初始化\n- `/auth/status` - 认证状态轮询\n- `/auth/` - 认证页面\n- `/health`, `/healthz`, `/ping` - 健康检查\n\n可通过 `excludePaths` 配置添加更多路径。\n\n## 测试\n\nE2E 测试覆盖以下场景：\n\n1. **Auth Init 测试**：验证码生成和返回\n2. **Auth Complete 测试**：验证码验证和授权完成\n3. **签名验证测试**：有效/无效签名处理\n4. **时间戳验证**：过期时间戳拒绝\n5. **路径排除测试**：认证端点和健康检查不需要认证\n\n## API 导出\n\n### 中间件\n\n- `createAwpAuthMiddleware(config)` - 创建认证中间件\n- `routeAuthRequest(request, options)` - 路由认证端点请求\n- `hasAwpAuthCredentials(request)` - 检查请求是否包含认证凭据\n\n### Auth Init\n\n- `handleAuthInit(request, options)` - 处理 /auth/init 请求\n- `handleAuthStatus(request, options)` - 处理 /auth/status 请求\n- `generateVerificationCode()` - 生成验证码\n- `MemoryPendingAuthStore` - 内存待授权存储\n\n### Auth Complete\n\n- `completeAuthorization(pubkey, code, userId, options)` - 完成授权\n- `handleAuthComplete(request, userId, options)` - 处理授权完成请求\n- `MemoryPubkeyStore` - 内存公钥存储\n\n### 低级工具\n\n- `verifyAwpAuth(request, pubkeyStore, maxClockSkew)` - 验证请求签名\n- `verifySignature(pubkey, payload, signature)` - 验证 ECDSA 签名\n- `validateTimestamp(timestamp, maxClockSkew)` - 验证时间戳\n- `buildChallengeResponse(authInitEndpoint)` - 构建 401 响应\n\n### 类型\n\n- `AwpAuthConfig` - 认证配置\n- `PendingAuth`, `PendingAuthStore` - 待授权相关\n- `AuthorizedPubkey`, `PubkeyStore` - 已授权相关\n- `AuthContext`, `AuthResult` - 认证结果\n- `AuthHttpRequest` - HTTP 请求接口\n- `AuthInitRequest`, `AuthInitResponse` - 初始化请求/响应\n- `AuthCompleteRequest`, `AuthStatusResponse` - 完成/状态请求/响应\n- `ChallengeBody` - 401 响应体\n- `AWP_AUTH_DEFAULTS`, `AWP_AUTH_HEADERS` - 常量\n\n## License\n\nMIT\n","readmeFilename":"README.md"}