# IC Coder 认证系统实现文档

## 概述

本文档详细说明了 IC Coder 插件如何集成 VSCode Authentication API，实现用户登录功能，并在 VSCode 左下角账户区域显示登录状态。

## 架构设计

### 核心组件

1. **ICCoderAuthenticationProvider** - 认证提供者
2. **VSCode Authentication API** - VSCode 官方认证接口
3. **本地 HTTP 服务器** - 处理登录回调
4. **ICViewProvider** - 侧边栏视图（根据登录状态显示不同按钮）

### 工作流程

```
用户点击登录
    ↓
调用 vscode.authentication.getSession()
    ↓
ICCoderAuthenticationProvider.createSession()
    ↓
启动本地 HTTP 服务器（动态端口）
    ↓
打开浏览器访问登录页面
    ↓
用户在网站完成登录
    ↓
网站重定向到 http://localhost:{port}/callback?token=xxx
    ↓
本地服务器接收 token
    ↓
创建 AuthenticationSession
    ↓
VSCode 左下角显示账户信息
```

## 详细实现

### 1. Authentication Provider 实现

文件：`src/services/icCoderAuthProvider.ts`

#### 1.1 类定义

```typescript
export class ICCoderAuthenticationProvider
  implements vscode.AuthenticationProvider
{
  private _onDidChangeSessions =
    new vscode.EventEmitter<vscode.AuthenticationProviderAuthenticationSessionsChangeEvent>();
  public readonly onDidChangeSessions = this._onDidChangeSessions.event;

  private _sessions: vscode.AuthenticationSession[] = [];
}
```

**关键点：**

- 实现 `vscode.AuthenticationProvider` 接口
- 使用 `EventEmitter` 通知会话变化
- 在内存中维护会话列表

#### 1.2 核心方法

##### getSessions() - 获取会话列表

```typescript
async getSessions(scopes?: readonly string[]): Promise<readonly vscode.AuthenticationSession[]> {
  return this._sessions;
}
```

##### createSession() - 创建会话（登录）

```typescript
async createSession(scopes: readonly string[]): Promise<vscode.AuthenticationSession> {
  const token = await this.login();

  const session: vscode.AuthenticationSession = {
    id: this.generateSessionId(),
    accessToken: token,
    account: {
      id: "iccoder-user",
      label: "IC Coder 用户",
    },
    scopes: [...scopes],
  };

  this._sessions.push(session);
  await this.saveSessions();

  this._onDidChangeSessions.fire({
    added: [session],
    removed: [],
    changed: [],
  });

  return session;
}
```

**关键点：**

- 调用 `login()` 方法获取 token
- 创建 `AuthenticationSession` 对象
- 保存到 `globalState`
- 触发 `onDidChangeSessions` 事件通知 VSCode

##### removeSession() - 删除会话（登出）

```typescript
async removeSession(sessionId: string): Promise<void> {
  const sessionIndex = this._sessions.findIndex((s) => s.id === sessionId);
  if (sessionIndex > -1) {
    const session = this._sessions[sessionIndex];
    this._sessions.splice(sessionIndex, 1);
    await this.saveSessions();

    this._onDidChangeSessions.fire({
      added: [],
      removed: [session],
      changed: [],
    });
  }
}
```

### 2. 本地 HTTP 服务器实现

#### 2.1 动态端口分配

```typescript
server.listen(0, () => {
  const address = server.address();
  const port = typeof address === "object" && address ? address.port : 3000;
  resolve({ server, port });
});
```

**关键点：**

- 使用端口 `0` 让系统自动分配可用端口
- 避免端口冲突问题
- 支持多个用户同时使用

#### 2.2 回调处理

```typescript
if (url.pathname === "/callback") {
  const token = url.searchParams.get("token");

  if (token) {
    // 返回成功页面
    res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
    res.end(this.getSuccessPage(iconBase64));

    // 关闭服务器
    server.close();

    // 返回 token
    if ((server as any)._loginResolve) {
      (server as any)._loginResolve(token);
    }
  }
}
```

### 3. package.json 配置

#### 3.1 注册 Authentication Provider

```json
{
  "contributes": {
    "authentication": [
      {
        "id": "iccoder",
        "label": "IC Coder"
      }
    ]
  }
}
```

**关键点：**

- `id` 必须与代码中使用的 ID 一致
- `label` 会显示在 VSCode 账户菜单中

#### 3.2 注册命令

```json
{
  "contributes": {
    "commands": [
      {
        "command": "ic-coder.login",
        "title": "IC Coder: 登录账户",
        "category": "IC Coder"
      },
      {
        "command": "ic-coder.logout",
        "title": "IC Coder: 退出登录",
        "category": "IC Coder"
      }
    ]
  }
}
```

### 4. extension.ts 注册

#### 4.1 注册 Authentication Provider

```typescript
export function activate(context: vscode.ExtensionContext) {
  // 注册 Authentication Provider
  const authProvider = new ICCoderAuthenticationProvider(context);
  context.subscriptions.push(
    vscode.authentication.registerAuthenticationProvider(
      "iccoder",
      "IC Coder",
      authProvider
    )
  );
}
```

#### 4.2 登录命令

```typescript
const loginCommand = vscode.commands.registerCommand(
  "ic-coder.login",
  async () => {
    try {
      await vscode.authentication.getSession("iccoder", [], {
        createIfNone: true,
      });
    } catch (error) {
      vscode.window.showErrorMessage(`登录失败: ${error}`);
    }
  }
);
```

**关键点：**

- `createIfNone: true` 会在没有会话时自动调用 `createSession()`
- VSCode 会自动处理 UI 交互

#### 4.3 登出命令

```typescript
const logoutCommand = vscode.commands.registerCommand(
  "ic-coder.logout",
  async () => {
    try {
      const session = await vscode.authentication.getSession("iccoder", [], {
        createIfNone: false,
      });
      if (session) {
        await vscode.authentication.getSession("iccoder", [], {
          clearSessionPreference: true,
          forceNewSession: true,
        });
        vscode.window.showInformationMessage("已退出登录");
      }
    } catch (error) {
      vscode.window.showInformationMessage("当前未登录");
    }
  }
);
```

### 5. ICViewProvider 集成

#### 5.1 检查登录状态

```typescript
private async checkLoginStatus(): Promise<boolean> {
  try {
    const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false });
    return !!session;
  } catch (error) {
    return false;
  }
}
```

#### 5.2 根据登录状态显示不同按钮

```typescript
resolveWebviewView(webviewView: vscode.WebviewView) {
  this.checkLoginStatus().then((isLoggedIn) => {
    webviewView.webview.html = this.getWebviewContent(
      webviewView.webview,
      isLoggedIn
    );
  });
}
```

```typescript
${isLoggedIn
  ? '<button class="btn" onclick="openChat()">开始创作</button>'
  : '<button class="btn" onclick="login()">登录账户</button>'
}
```

### 6. 网站前端配置

#### 6.1 检测插件登录请求

```javascript
// 在登录页面检测 redirect_uri 参数
const urlParams = new URLSearchParams(window.location.search);
const redirectUri = urlParams.get("redirect_uri");

if (redirectUri) {
  // 保存回调地址
  localStorage.setItem("plugin_redirect_uri", redirectUri);
}
```

#### 6.2 登录成功后重定向

```javascript
// 用户登录成功，拿到 token
const token = response.data.token;

// 检查是否需要重定向回插件
const redirectUri = localStorage.getItem("plugin_redirect_uri");

if (redirectUri) {
  // 重定向回插件，带上 token
  window.location.href = `${redirectUri}?token=${token}`;
  localStorage.removeItem("plugin_redirect_uri");
} else {
  // 正常登录流程
  router.push("/dashboard");
}
```

## 关键技术点

### 1. 动态端口分配

**问题：** 固定端口可能被占用，导致登录失败

**解决方案：** 使用端口 `0` 让系统自动分配可用端口

```typescript
server.listen(0, () => {
  const address = server.address();
  const port = typeof address === "object" && address ? address.port : 3000;
});
```

### 2. Promise 异步等待

**问题：** 需要等待浏览器登录完成后才能继续

**解决方案：** 使用 Promise 包装回调逻辑

```typescript
return new Promise((resolve, reject) => {
  (server as any)._loginResolve = resolve;
  (server as any)._loginReject = reject;
});
```

### 3. 会话持久化

**问题：** 重启 VSCode 后需要重新登录

**解决方案：** 使用 `globalState` 保存会话

```typescript
await this.context.globalState.update("icCoderSessions", this._sessions);
```

### 4. 事件通知机制

**问题：** VSCode 需要知道会话状态变化

**解决方案：** 使用 `EventEmitter` 触发事件

```typescript
this._onDidChangeSessions.fire({
  added: [session],
  removed: [],
  changed: [],
});
```

## 用户体验

### 登录流程

1. 用户点击侧边栏"登录账户"按钮
2. 浏览器自动打开登录页面
3. 用户在网站完成登录
4. 浏览器自动跳转到成功页面
5. VSCode 左下角显示"IC Coder 用户"
6. 侧边栏按钮变为"开始创作"

### 登出流程

1. 点击 VSCode 左下角账户图标
2. 选择"IC Coder"账户
3. 点击"退出"按钮
4. 或使用命令 `IC Coder: 退出登录`

## 常见问题

### Q1: 为什么不直接使用 globalState 存储 token？

**A:** 使用 VSCode Authentication API 的优势：

- ✅ 统一的用户体验（左下角账户区域）
- ✅ VSCode 自动管理会话生命周期
- ✅ 支持多账户切换
- ✅ 更好的安全性（VSCode 负责加密存储）

### Q2: 如何处理 token 过期？

**A:** 可以在 API 请求失败时：

1. 检测 401 错误
2. 调用 `removeSession()` 清除过期会话
3. 提示用户重新登录

### Q3: 如何支持多个账户？

**A:** 修改 `account` 对象：

```typescript
account: {
  id: userInfo.id,
  label: userInfo.username,
}
```

### Q4: 登录页面如何获取用户信息？

**A:** 可以在登录成功后，通过 API 获取用户信息：

```typescript
const userInfo = await fetch("https://api.iccoder.com/user/info", {
  headers: { Authorization: `Bearer ${token}` },
});

const session: vscode.AuthenticationSession = {
  account: {
    id: userInfo.id,
    label: userInfo.username,
  },
  // ...
};
```

## 安全考虑

### 1. Token 存储

- ✅ 使用 VSCode `globalState` 加密存储
- ✅ 不在代码中硬编码敏感信息
- ✅ Token 仅在内存和加密存储中传递

### 2. 本地服务器

- ✅ 仅监听 `localhost`，不暴露到外网
- ✅ 使用动态端口，避免固定端口被劫持
- ✅ 接收到 token 后立即关闭服务器
- ✅ 设置 5 分钟超时，防止服务器长期运行

### 3. HTTPS 考虑

**当前实现：** 使用 HTTP 本地回调

**生产环境建议：**

- 网站使用 HTTPS
- 本地回调使用 HTTP（localhost 不受浏览器限制）
- 或使用 `vscode://` 协议（需要网站支持）

## 测试指南

### 1. 本地测试

```bash
# 启动调试模式
按 F5

# 测试登录
1. 打开侧边栏
2. 点击"登录账户"
3. 在浏览器完成登录
4. 检查左下角是否显示账户

# 测试登出
1. 点击左下角账户
2. 选择"IC Coder"
3. 点击"退出"
```

### 2. 调试技巧

```typescript
// 在 ICCoderAuthenticationProvider 中添加日志
console.log("🔐 创建会话:", session);
console.log("🔑 Token:", token);

// 在 ICViewProvider 中添加日志
console.log("🔍 登录状态:", isLoggedIn);
```

### 3. 常见错误排查

| 错误                            | 原因            | 解决方案                           |
| ------------------------------- | --------------- | ---------------------------------- |
| `getSessions is not a function` | VSCode 版本过低 | 升级到 1.63.0+                     |
| 端口被占用                      | 固定端口冲突    | 使用动态端口（已实现）             |
| 登录后未显示账户                | 未触发事件      | 检查 `_onDidChangeSessions.fire()` |
| 重启后需要重新登录              | 未保存会话      | 检查 `saveSessions()` 调用         |

## 文件结构

```
ic-coder/
├── src/
│   ├── services/
│   │   └── icCoderAuthProvider.ts    # Authentication Provider 实现
│   ├── views/
│   │   └── ICViewProvider.ts         # 侧边栏视图（集成登录状态）
│   └── extension.ts                  # 注册 Provider 和命令
├── package.json                      # 配置 authentication 和 commands
└── docs/
    └── authentication-implementation.md  # 本文档
```

## 参考资料

- [VSCode Authentication API](https://code.visualstudio.com/api/references/vscode-api#authentication)
- [Authentication Provider Sample](https://github.com/microsoft/vscode-extension-samples/tree/main/authentication-sample)
- [VSCode Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines)

## 总结

本实现通过以下步骤完成了 VSCode Authentication API 的集成：

1. ✅ 创建 `ICCoderAuthenticationProvider` 类实现认证逻辑
2. ✅ 在 `package.json` 中注册 authentication provider
3. ✅ 在 `extension.ts` 中注册 provider 和命令
4. ✅ 实现本地 HTTP 服务器处理登录回调
5. ✅ 使用动态端口避免冲突
6. ✅ 集成到侧边栏视图，根据登录状态显示不同按钮
7. ✅ 配置网站前端支持插件登录重定向

**最终效果：**

- 用户登录后，VSCode 左下角显示"IC Coder 用户"
- 侧边栏根据登录状态显示"登录账户"或"开始创作"按钮
- 支持通过账户菜单或命令进行登录/登出操作

---

**文档版本：** 1.0  
**最后更新：** 2025-12-29  
**作者：** Roe-xin
