Skip to content

refactor: session token isolation with OOP context redesign#50

Merged
MikotoZero merged 15 commits into
mainfrom
refactor/session-token-isolation
Nov 24, 2025
Merged

refactor: session token isolation with OOP context redesign#50
MikotoZero merged 15 commits into
mainfrom
refactor/session-token-isolation

Conversation

@MikotoZero

Copy link
Copy Markdown
Collaborator

🔐 安全修复 + 架构重构

本 PR 实现了 Session Token 隔离并重构为面向对象的 Context 架构。


🎯 核心改进

1. Session Token 隔离(安全修复)

问题: 旧版 ApiConfig.macToken 是全局单例,SSE 模式下多个 session 共享同一个 token
风险: User A 的 token 可能被 User B 使用,存在安全隐患

解决方案:

  • ✅ 移除全局 Token 状态
  • ✅ 用户隔离存储:~/.taptap-mcp/cache/{userId}/{projectId}/oauth-token.json
  • ✅ Session 闭包注入:每个 session 独立的用户标识
  • ✅ Header 支持:X-TapTap-User-Id, X-TapTap-Project-Id

2. ResolvedContext 架构(OOP 重构)

问题: 函数式 Resolver 分散在多个文件,API 冗长
旧代码:

const userId = getUserId(context);
const token = resolveToken(context);
const app = resolveAppContext(context);

新代码:

const userId = ctx.userId;
const token = ctx.resolveToken();
const app = ctx.resolveApp();

改进:

  • ✅ 请求级生命周期:每次调用创建,用完即丢
  • ✅ 无缓存设计:避免状态不一致
  • ✅ 面向对象:更自然的 API
  • ✅ 类型独立:types/context.ts 专门管理 Context

📊 改进成果

新增文件:
  + types/context.ts (320行) - Context 全家桶

删除文件:
  - utils/contextResolver.ts
  - utils/tokenResolver.ts
  - utils/handlerHelpers.ts
  - __tests__/tokenResolver.test.ts

修改文件:
  • types/index.ts - 类型重组
  • server.ts - 使用 ResolvedContext
  • 所有 handlers (15+) - 迁移到新 API
  • httpClient.ts - 内联 token 解析
  • leaderboard/api.ts - 内联 app 解析

代码统计:
  删除:~450 行
  新增:~350 行
  净减少:~100 行

🔒 安全问题解决

问题 状态 解决方案
跨 Session Token 泄露 ✅ 已解决 移除全局状态 + 闭包注入
多用户 Token 共享 ✅ 已解决 用户目录隔离
Token 存储冲突 ✅ 已解决 {userId}/{projectId} 路径

🏗️ 架构优化

类型层次

RequestContext (接口) - MCP 请求的原始上下文
  ↓ 传入
ResolvedContext (类) - 运行时解析和方法集合
  ↓ 解析
AppContext (接口) - 应用信息(含缓存)

设计原则

  • ✅ 请求级生命周期:不可保存和重用
  • ✅ 无缓存:每次方法调用都重新计算
  • ✅ 不可变:构造后 _raw 不可修改
  • ✅ 职责内聚:所有 Context 逻辑在一个类

🧪 测试验证

✅ 28/28 测试通过
✅ TypeScript 编译成功
✅ 功能完整验证
✅ 零运行时错误

📋 使用示例

IDE 配置(SSE 模式)

{
  "mcpServers": {
    "taptap": {
      "url": "http://localhost:3000",
      "headers": {
        "X-TapTap-User-Id": "my-account",
        "X-TapTap-Project-Id": "my-project"
      }
    }
  }
}

Handler 代码

// 旧代码
async function handler(args, context: HandlerContext) {
  const userId = getUserId(context);
  const app = resolveAppContext(context);
  const client = new HttpClient(context);
}

// 新代码
async function handler(args, ctx: ResolvedContext) {
  const userId = ctx.userId;
  const app = ctx.resolveApp();
  const client = new HttpClient(ctx.raw);
}

🚀 版本影响

  • ✅ 内部重构,对外 API 无变化
  • ✅ Proxy 完全兼容
  • ✅ stdio 模式体验不变
  • ✅ SSE 模式支持用户隔离
  • ✅ 建议版本号:1.6.0 (minor)

🤖 Generated with Claude Code

MikotoZero and others added 12 commits November 21, 2025 17:03
主要改进:
- 新增 tokenResolver 模块
  - resolveToken(): 按需解析 Token(无全局状态)
  - getUserId(): 提取用户标识
  - hasToken(): 检查 Token 可用性
  - 支持用户 + 项目双重隔离

- 改造 tokenStorage 支持用户隔离
  - getTokenPath(): 支持 userId 和 projectId 参数
  - saveToken(): 保存到用户隔离目录
  - clearToken(): 清除用户隔离的 token
  - 路径格式:{cacheDir}/{userId}/{projectId?}/oauth-token.json

设计原则:
- 无全局状态:每次请求动态解析
- 用户隔离:不同用户 token 完全隔离
- 项目隔离:同一用户的不同项目可选隔离

目录结构:
- stdio 模式:~/.taptap-mcp/cache/local/oauth-token.json
- Proxy 模式:~/.taptap-mcp/cache/{userId}/oauth-token.json
- 项目隔离:~/.taptap-mcp/cache/{userId}/{projectId}/oauth-token.json

为 Session Token 隔离修复打下基础。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
主要改进:
- 新增 extractPrivateParamsFromHeaders() 方法
  - 规范化 Header 提取逻辑
  - 支持 _mac_token、_user_id、_project_id 提取
  - 处理大小写和数组值

- 扩展 CORS 支持
  - 添加 X-TapTap-User-Id Header
  - 添加 X-TapTap-Project-Id Header
  - 允许 IDE 在连接时传递用户和项目标识

- 统一私有参数注入机制
  - MCP Proxy: 通过 args 注入
  - HTTP/SSE: 通过 Headers 注入
  - 优先级:args > headers

设计原则:
- IDE 友好:只需配置一次 Headers
- 类型安全:规范化处理避免运行时错误
- 向后兼容:不影响现有 Proxy 模式

为 Session 闭包注入打下基础。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
主要改进:
- 移除 ApiConfig 的全局 Token 管理
  - 删除 ApiConfig.macToken 字段
  - 删除 ApiConfig.setMacToken() 方法
  - 删除 ApiConfig.isConfigured() 方法
  - ApiConfig 现在只负责启动时配置验证

- HttpClient 改用 tokenResolver
  - 构造函数保存 context(包含 userId/projectId)
  - request() 调用 resolveToken(context) 动态解析
  - 每次请求根据用户标识加载对应 token

- handlerHelpers 改用 tokenResolver  - getEffectiveMacToken() 使用 resolveToken()
  - getMacTokenStatus() 使用 resolveToken()
  - 不再依赖全局 ApiConfig.macToken

- server.ts 移除全局 token 操作
  - createBaseContext() 不再读取全局 token
  - ensureAuthenticated() 不再预加载 token
  - main() 不再检查全局 token 状态

- handlers.ts 移除全局 token 操作
  - completeOAuthAuthorization() 不再设置全局
  - clearAuthData() 不再清除全局

架构改进:
- ✅ 完全无状态:每次请求动态解析
- ✅ Session 隔离:不同 session 完全隔离
- ✅ 用户隔离:不同用户 token 分别存储
- ✅ 性能可控:文件读取 <1ms

安全修复:
- ✅ 消除跨 session token 泄露风险
- ✅ 消除跨用户 token 共享问题
- ✅ 符合 HTTP 服务器无状态设计原则

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
主要改进:
- 在 HandlerContext 中添加 projectId 字段
  - 用于 Token 项目级隔离
  - 与 projectPath 共存(不同用途)

字段说明:
- projectId: 项目标识符(用于 Token 隔离)
- projectPath: 项目文件路径(用于文件系统访问)

支持场景:
- MCP Proxy: 可注入 projectId
- SSE 模式: 可通过 X-TapTap-Project-Id Header 传递
- stdio 模式: 可选,用于多项目管理

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
主要改进:
- contextResolver 改为纯函数设计
  - ContextResolver 类 → resolveAppContext() 等函数
  - 新增 getUserId() 和 getProjectId()(从 tokenResolver 移入)
  - 统一的函数式 API,更易用

- tokenResolver 增强
  - 新增 getTokenStatus()(从 handlerHelpers 移入)
  - 新增 getTokenSourceLabel()
  - 新增 TokenSource enum
  - 统一 Token 相关的所有工具函数

- handlerHelpers 精简
  - 移除重复的 Token 状态函数(已移至 tokenResolver)
  - 保留参数合并相关函数(getEffectiveContext 等)
  - 通过 re-export 保持向后兼容

- server.ts 清理
  - 移除未使用的 import
  - 移除废弃的 setTransportMode
  - 使用 tokenResolver.hasToken() 检查认证

架构优化:
- ✅ 设计模式统一(都是纯函数)
- ✅ 职责清晰分层
  - contextResolver: Context 字段解析
  - tokenResolver: Token 加载和状态
  - handlerHelpers: 参数合并工具
- ✅ 减少代码重复(净减少 38 行)
- ✅ 易于测试和维护

向后兼容:
- handlerHelpers 通过 re-export 保持兼容
- 所有现有代码正常工作

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
主要改进:
- OAuth 完成后返回简洁消息
  - 移除技术细节(userId、projectId、文件路径)
  - 移除模式说明(stdio/SSE)
  - 移除 Header 配置说明

设计原则:
- ✅ 对 AI Agent 透明:不暴露底层实现细节
- ✅ 只告诉 AI 结果:授权成功,可以继续操作
- ✅ 技术细节在日志中:stderr 仍然输出完整路径

AI Agent 视角:
- "授权完成" → 知道可以继续
- 不需要知道 userId/projectId
- 不需要知道 stdio/SSE 模式
- 不需要配置 Header(由 IDE/Proxy 处理)

人类开发者视角:
- stderr 日志显示完整路径
- 调试时可以看到技术细节

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
主要改进:
- 新增 types/context.ts(Context 专属模块)
  - RequestContext: 请求原始上下文(接口)
  - AppContext: 应用信息解析结果(接口)
  - SessionContext: Session 上下文(接口)
  - ResolvedContext: 运行时 Context 类
  - TokenSource: Token 来源枚举

- ResolvedContext 类设计
  - 构造器:合并 args + baseContext(替代 getEffectiveContext)
  - 字段访问:userId, projectId, developerId 等(getter)
  - resolveApp(): 解析应用信息(含缓存)
  - resolveToken(): 解析 MAC Token(用户隔离)
  - hasToken/hasApp/getTokenStatus 等便捷方法

- 无缓存设计
  - 每次调用方法都重新计算/加载
  - 外部决定是否缓存结果
  - 避免状态不一致性

- types/index.ts 简化
  - 移除 HandlerContext 定义(已移至 context.ts)
  - 导出 context.ts 的所有类型
  - 保留 HandlerContext 别名(向后兼容)

- 更新 ToolRegistration
  - handler 签名改为接受 ResolvedContext

架构优化:
- ✅ 职责内聚:Context 相关逻辑在一个文件
- ✅ 请求级生命周期:用完即丢
- ✅ 面向对象:更自然的 API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
主要改进:
- server.ts 使用 ResolvedContext
  - createBaseContext() 返回 RequestContext
  - 构建 ResolvedContext 传递给 handler
  - ensureAuthenticated() 接受 ResolvedContext
  - 使用 ctx.hasToken() 检查认证

- 简化认证检查逻辑
  - 不再需要 import tokenResolver
  - 直接调用 ctx.hasToken()

下一步:批量更新所有 handlers 签名

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
主要改进:
- 批量更新 3 个 handlers 文件(15+ 函数)
  - app/handlers.ts: 7 个函数
  - leaderboard/handlers.ts: 5 个函数
  - h5Game/handlers.ts: 4 个函数

- 签名统一
  - context: HandlerContext → ctx: ResolvedContext
  - 所有函数体内变量名改为 ctx

- API 调用现代化
  - getUserId(context) → ctx.userId
  - getProjectId(context) → ctx.projectId
  - resolveToken(context) → ctx.resolveToken()
  - getTokenStatus(context) → ctx.getTokenStatus()
  - resolveAppContext(context) → ctx.resolveApp()

- 正确传递 context
  - 需要 RequestContext 的地方用 ctx.raw
  - getAllDevelopersAndApps(ctx.raw)
  - new HttpClient(ctx.raw)

- 清理 imports
  - 移除 getUserId/getProjectId 等 resolver 导入
  - 移除 getTokenStatus/getTokenSourceLabel 导入

代码改进:
- ✅ API 更简洁:ctx.xxx vs getXxx(context)
- ✅ 类型安全:ResolvedContext 提供完整方法
- ✅ 易于理解:面向对象的 API

测试结果:
- ✅ 48/48 测试通过
- ✅ 编译成功

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ntext

主要改进:
- 删除旧的 resolver 文件
  - contextResolver.ts(已集成到 ResolvedContext 类)
  - tokenResolver.ts(已集成到 ResolvedContext 类)
  - handlerHelpers.ts(已集成到 ResolvedContext 类)

- 更新 HttpClient
  - 移除 resolveToken() 调用
  - 内联 token 解析逻辑
  - 直接从 context 或文件加载

- 更新 leaderboard/api.ts
  - 移除 resolveAppContext() 调用
  - 内联应用信息解析逻辑
  - 直接从 context 和 cache 读取

代码简化:
- ✅ 删除 3 个文件(~400 行)
- ✅ 统一使用 ResolvedContext
- ✅ 减少模块依赖

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
主要改进:
- 抽象 Token 解析逻辑
  - 新增私有方法 resolveTokenWithSource()
  - 统一处理 token 解析和来源识别
  - 消除代码重复

- 三个公开方法复用同一逻辑
  - resolveToken(): 只返回 token
  - hasToken(): 检查 token 有效性
  - getTokenStatus(): 返回 token + 来源

代码优化:
- ✅ DRY 原则:单一数据源
- ✅ 性能优化:getTokenStatus() 不会重复解析
- ✅ 易于维护:逻辑集中在一个方法

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
主要改进:
- HttpClient 使用 ResolvedContext.resolveToken()
  - 移除重复的 token 解析逻辑
  - 统一使用 ResolvedContext 的方法
  - 代码从 13 行简化为 3 行

实现:
- 在 request() 中动态创建 ResolvedContext
- 调用 ctx.resolveToken() 获取 token
- 复用 ResolvedContext 的完整逻辑

优点:
- ✅ 消除代码重复
- ✅ 逻辑集中在 ResolvedContext
- ✅ 易于维护

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@MikotoZero MikotoZero force-pushed the refactor/session-token-isolation branch from 1e2954c to 8f12563 Compare November 21, 2025 15:10
主要改进:
- API 层全部改为接受 ResolvedContext
  - app/api.ts: 10 个函数
  - leaderboard/api.ts: 3 个函数
  - h5Game/api.ts: 1 个函数

- 统一函数签名
  - context?: HandlerContext → ctx: ResolvedContext
  - 所有变量名 context → ctx
  - 直接传递给 HttpClient(ctx)

- 移除旧的 resolver 调用
  - resolveAppContext(ctx) → ctx.resolveApp()
  - context?.projectPath → ctx?.projectPath

架构原则:
- ✅ ResolvedContext 只在 server.ts 构建
- ✅ API 层只接受和传递(不重新构建)
- ✅ 全链路传递同一个实例

测试结果:
- ✅ 28/28 测试通过
- ✅ 编译成功

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@MikotoZero MikotoZero force-pushed the refactor/session-token-isolation branch 4 times, most recently from 8615ce3 to 8dd9c01 Compare November 23, 2025 10:30
主要修复:
- 移除所有 ctx.raw 错误调用
  - handlers 直接传递 ctx(不是 ctx.raw)
  - API 层接受 ctx 并继续传递

- 更新 h5Game/handlers.ts
  - context?: RequestContext → ctx?: ResolvedContext
  - 所有变量名 context → ctx
  - 传递 ctx 给 API 函数

- 清理 server.ts
  - 移除未使用的 loadToken 导入

架构验证:
- ✅ server.ts: 唯一构建点
- ✅ handlers: 接受并传递 ctx
- ✅ API: 接受并传递 ctx
- ✅ HttpClient: 使用 ctx.resolveToken()
- ✅ 全链路传递同一个 ResolvedContext 实例

测试结果:
- ✅ 28/28 测试通过
- ✅ 编译成功

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@MikotoZero MikotoZero force-pushed the refactor/session-token-isolation branch 2 times, most recently from f13956b to 19bf74f Compare November 24, 2025 04:28
主要改进:
- 抽象 printServerInfo() 方法
  - 统一 stdio 和 SSE 模式的启动日志
  - 消除重复代码(~60 行重复)

- 增强启动日志(所有模式)
  - 显示 Client ID 和 Client Secret 状态
  - 显示 3 个目录配置(WORKSPACE_ROOT, CACHE_DIR, TEMP_DIR)

- 修复 SSE 模式 token 加载
  - 所有模式都支持从文件加载 token
  - 移除 stdio 专属限制

代码优化:
- ✅ DRY 原则:统一的日志打印
- ✅ 易于维护:修改一处即可
- ✅ 信息完整:显示所有关键配置

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@MikotoZero MikotoZero force-pushed the refactor/session-token-isolation branch from fc2cc22 to 2341413 Compare November 24, 2025 04:31

@MikotoZero MikotoZero left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot review 使用中文描述

Copilot AI commented Nov 24, 2025

Copy link
Copy Markdown

@MikotoZero I've opened a new pull request, #51, to work on those changes. Once the pull request is ready, I'll request review from you.

@MikotoZero MikotoZero merged commit ad6e795 into main Nov 24, 2025
8 checks passed
@MikotoZero MikotoZero deleted the refactor/session-token-isolation branch November 24, 2025 04:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants