# 機能設計書 68-CORSプロキシ

## 概要

本ドキュメントは、Actualアプリケーションにおけるメモリプロキシ機能の設計仕様を記載したものである。外部APIへのアクセスを中継するプロキシ機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：Webブラウザからは、CORSポリシーにより外部サイト（GitHub等）に直接アクセスできない場合がある。CORSプロキシ機能は、サーバーサイドで外部URLにアクセスし、その結果をクライアントに返すことで、この制限を回避する。主にプラグインシステムで使用され、許可リストに登録されたリポジトリのみアクセス可能な安全な設計となっている。

**機能の利用シーン**：
- プラグインストアからプラグイン情報を取得する場合
- GitHubリポジトリからプラグインマニフェストを取得する場合
- GitHubリリースからプラグインをダウンロードする場合

**主要な処理内容**：
1. URLパラメータの取得と検証
2. セッション認証の検証
3. 許可リスト（allowlist）の取得とキャッシュ
4. URL許可チェック（プライベートIP除外、allowlist照合）
5. 外部URLへのリクエスト転送
6. レスポンスの変換と返却

**関連システム・外部連携**：
- GitHub API（api.github.com）
- GitHub Raw（raw.githubusercontent.com）
- GitHub Releases（github.com/releases）
- プラグインストア（plugins.json）

**権限による制御**：
- セッション認証必須
- 許可リストに登録されたURLのみアクセス可能
- プライベート/ローカルIPアドレスへのアクセスは禁止

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | プラグイン管理（内部API） | 主機能 | プラグイン情報の取得 |

## 機能種別

データ連携 / プロキシ

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| url | string | Yes | 転送先URL | 有効なURL形式、allowlist内 |
| method | string | No | HTTPメソッド | GET/HEADのみ |
| headers | object | No | カスタムヘッダー | - |
| body | any | No | リクエストボディ | GET/HEADでは無視 |

### 入力データソース

- HTTPクエリパラメータ（url）
- HTTPリクエストボディ（method, headers, body）
- セッショントークン（X-Actual-Token）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| response | any | 転送先からのレスポンス |
| data | number[] | バイナリデータの場合の配列 |
| contentType | string | コンテンツタイプ |
| isBinary | boolean | バイナリかどうか |

### 出力先

- HTTPレスポンス（JSON / テキスト / バイナリ）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ Express middleware（JSON parse、ロギング、レート制限）

2. CORSプリフライト処理
   └─ OPTIONSメソッドの場合は204を返却

3. URLパラメータ検証
   └─ urlクエリパラメータの存在確認

4. セッション認証
   └─ validateSessionでトークン検証

5. 許可リスト取得
   └─ fetchAllowlist()でキャッシュまたは取得

6. URL許可チェック
   └─ isUrlAllowed()でプライベートIP除外、allowlist照合

7. 外部リクエスト実行
   └─ fetch()で転送先にリクエスト

8. レスポンス変換
   └─ JSON/テキスト/バイナリに応じて変換

9. レスポンス返却
   └─ 適切なContent-Typeで返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B{OPTIONSメソッド?}
    B -->|Yes| C[CORSヘッダー設定 + 204返却]
    B -->|No| D{urlパラメータあり?}
    D -->|No| E[400 Missing url parameter]
    D -->|Yes| F[セッション検証]
    F --> G{有効なセッション?}
    G -->|No| H[認証エラー]
    G -->|Yes| I[URL形式検証]
    I --> J{有効なURL?}
    J -->|No| K[400 Invalid url parameter]
    J -->|Yes| L[fetchAllowlist]
    L --> M[isUrlAllowed]
    M --> N{許可されたURL?}
    N -->|No| O[403 URL not allowed]
    N -->|Yes| P[外部URLにfetch]
    P --> Q{レスポンスタイプ?}
    Q -->|JSON| R[JSONとして返却]
    Q -->|テキスト| S[テキストとして返却]
    Q -->|バイナリ| T[バイナリ配列として返却]
    R --> U[終了]
    S --> U
    T --> U
    C --> U
    E --> U
    H --> U
    K --> U
    O --> U
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-68-001 | レート制限 | 60秒あたり25リクエストまで | 全リクエスト |
| BR-68-002 | プライベートIP禁止 | private, loopback, linkLocal等のIPはブロック | isUrlAllowed |
| BR-68-003 | allowlistキャッシュ | 5分間キャッシュ | fetchAllowlist |
| BR-68-004 | HTTPメソッド制限 | GET, HEADのみ許可 | リクエスト処理時 |
| BR-68-005 | プラグインストアURL常時許可 | plugins.jsonのURLは常に許可 | isUrlAllowed |
| BR-68-006 | GitHub認証 | github.tokenが設定されていればGitHubリクエストに認証ヘッダー追加 | GitHub URL時 |

### 計算ロジック

**isUrlAllowed判定ロジック**:
1. IPアドレスのレンジ判定（ipaddr.js使用）
2. プラグインストアURLとの完全一致
3. allowlist内のリポジトリURLとの照合（複数パターン）
   - 完全一致
   - プレフィックス一致（repoUrl/）
   - api.github.com/repos/{owner}/{repo}
   - raw.githubusercontent.com/{owner}/{repo}/
   - github.com/{owner}/{repo}/releases/

## データベース操作仕様

### 操作別データベース影響一覧

特になし（データベースアクセスなし）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 400 | Missing url parameter | urlパラメータなし | エラー表示 |
| 400 | Invalid url parameter | 無効なURL形式 | エラー表示 |
| 401 | Unauthorized | セッション無効 | ログイン要求 |
| 403 | URL not allowed | 許可リスト外のURL | エラー表示 |
| 405 | Method not allowed | GET/HEAD以外のメソッド | エラー表示 |
| 500 | Error proxying request | 転送先へのリクエスト失敗 | エラーログ出力 |

### リトライ仕様

特になし。エラー発生時は即座にエラーレスポンスを返却。

## トランザクション仕様

特になし（ステートレスなプロキシ処理）

## パフォーマンス要件

- API応答時間: 外部サービスの応答時間に依存
- 許可リストキャッシュ: 5分間

## セキュリティ考慮事項

- プライベート/ローカルIPアドレスへのアクセス禁止（SSRF対策）
- 許可リストによるURL制限
- レート制限（express-rate-limit）
- セッション認証必須
- ヘッダーの安全な処理（X-Actual-Token, cookie等を除外）
- GitHub認証トークンの安全な注入

## 備考

- ALLOWLIST_CACHE_TTL = 5分
- レート制限: 60秒間に25リクエスト
- clearAllowlistCache関数はテスト用にエクスポート

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、許可リストとキャッシュの仕組みを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | app-cors-proxy.js | `packages/sync-server/src/app-cors-proxy.js` | allowlistedRepos配列、キャッシュ変数 |

**読解のコツ**: allowlistedReposはプラグインストアのplugins.jsonから取得されるリポジトリURLの配列。5分間キャッシュされる。

#### Step 2: エントリーポイントを理解する

処理の起点となるExpressルートを特定。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | app-cors-proxy.js | `packages/sync-server/src/app-cors-proxy.js` | Expressアプリケーション定義、メインルーター |

**主要処理フロー**:
1. **9-20行目**: Expressアプリ初期化、レート制限設定
2. **22-31行目**: キャッシュ変数とclearAllowlistCache関数
3. **33-60行目**: fetchAllowlist関数（許可リスト取得）
4. **62-127行目**: isUrlAllowed関数（URL許可判定）
5. **129-277行目**: メインルーター（プロキシ処理）

**主要処理フロー（メインルーター）**:
- **129-137行目**: CORSプリフライト処理
- **139-143行目**: urlパラメータ取得と検証
- **145-149行目**: セッション検証
- **151-156行目**: URL形式検証
- **158-167行目**: 許可リスト取得
- **169-177行目**: URL許可チェック
- **179-228行目**: 外部リクエスト実行
- **230-271行目**: レスポンス変換と返却

#### Step 3: URL許可判定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | app-cors-proxy.js | `packages/sync-server/src/app-cors-proxy.js` | isUrlAllowed関数 |

**主要処理フロー（isUrlAllowed）**:
- **66-86行目**: プライベートIPアドレスのブロック
- **87-93行目**: プラグインストアURLの常時許可
- **95-120行目**: allowlistとの照合（複数パターン）
- **122-127行目**: 許可されていない場合false

### プログラム呼び出し階層図

```
Express Router (app-cors-proxy.js)
    │
    ├─ Middleware
    │      ├─ express.json()
    │      ├─ requestLoggerMiddleware
    │      └─ rateLimit (60秒/25リクエスト)
    │
    ├─ OPTIONS (CORSプリフライト)
    │      └─ 204 No Content + CORSヘッダー
    │
    └─ GET/HEAD/POST (メインルート)
           │
           ├─ urlパラメータ取得
           │
           ├─ validateSession(req, res)
           │
           ├─ URL形式検証
           │      └─ new URL(targetUrlString)
           │
           ├─ fetchAllowlist()
           │      │
           │      ├─ キャッシュ確認
           │      │      └─ ALLOWLIST_CACHE_TTL (5分)
           │      │
           │      └─ fetch(plugins.json)
           │             └─ allowlistedRepos更新
           │
           ├─ isUrlAllowed(url.href)
           │      │
           │      ├─ ipaddr.parse(hostname)
           │      │      └─ プライベートIP判定
           │      │
           │      ├─ プラグインストアURL判定
           │      │
           │      └─ allowlistedReposとの照合
           │             ├─ 完全一致
           │             ├─ api.github.com/repos/
           │             ├─ raw.githubusercontent.com/
           │             └─ github.com/.../releases/
           │
           ├─ fetch(url.href, options)
           │      │
           │      ├─ ヘッダー処理
           │      │      ├─ 除外: X-Actual-Token, cookie等
           │      │      └─ 追加: GitHub Authorization (設定時)
           │      │
           │      └─ リクエスト実行
           │
           └─ レスポンス変換
                  ├─ JSON → res.json()
                  ├─ テキスト → res.send()
                  └─ バイナリ → {data, contentType, isBinary}
```

### データフロー図

```
[入力]                          [処理]                           [出力]

HTTPリクエスト ───────┐
(url, method,        │
 headers, body)      │
                     ▼
              ┌─────────────────┐
              │ Express Router  │
              │ レート制限      │
              │ (25req/60sec)   │
              └────────┬────────┘
                       │
                       ▼
              ┌─────────────────┐
              │ validateSession │
              └────────┬────────┘
                       │
                       ▼
              ┌─────────────────┐
              │ fetchAllowlist  │
              │ (キャッシュ5分) │
              └────────┬────────┘
                       │
                       ▼
              ┌─────────────────┐
              │ isUrlAllowed    │
              │ ・プライベートIP│
              │ ・allowlist照合 │
              └────────┬────────┘
                       │
        ┌──────────────┼──────────────┐
        │              │              │
        ▼              ▼              ▼
   403 Forbidden  fetch(url)    405 Method
                       │         Not Allowed
                       ▼
              ┌─────────────────┐
              │ ヘッダー処理    │
              │ ・X-Actual-Token除外│
              │ ・GitHub認証追加│
              └────────┬────────┘
                       │
                       ▼
              ┌─────────────────┐
              │ 外部サーバー    │
              │ GitHub / Plugin │
              │ Store           │
              └────────┬────────┘
                       │
                       ▼
              ┌─────────────────┐
              │ レスポンス変換  │
              │ JSON/Text/Binary│
              └────────┬────────┘
                       │
                       ▼
              ┌─────────────────┐
              │ HTTPレスポンス  │
              │ + CORSヘッダー  │
              └─────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| app-cors-proxy.js | `packages/sync-server/src/app-cors-proxy.js` | ソース | メインルーター |
| app-cors-proxy.test.js | `packages/sync-server/src/app-cors-proxy.test.js` | テスト | ユニットテスト |
| load-config.js | `packages/sync-server/src/load-config.js` | ソース | 設定読み込み（github.token） |
| validate-user.ts | `packages/sync-server/src/util/validate-user.ts` | ソース | セッション検証 |
| middlewares.ts | `packages/sync-server/src/util/middlewares.ts` | ソース | Express middleware |
