# 機能設計書 67-シークレット管理

## 概要

本ドキュメントは、Actualアプリケーションにおけるシークレット管理機能の設計仕様を記載したものである。APIキーなどの機密情報の管理機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：Actualは銀行連携（GoCardless、SimpleFIN、PluggyAI）やGitHub連携などの外部サービスと連携する。これらの連携にはAPIキーやトークンなどの機密情報（シークレット）が必要となる。シークレット管理機能は、これらの機密情報を安全に保存・取得するためのAPIを提供する。

**機能の利用シーン**：
- 銀行連携用のAPIキーを設定する場合
- GitHubトークンを設定する場合
- 設定されたシークレットの存在確認を行う場合
- SimpleFINアクセストークンを保存する場合

**主要な処理内容**：
1. シークレットの保存（POST /）
2. シークレットの存在確認（GET /:name）

**関連システム・外部連携**：
- secretsService（シークレット保存サービス）
- 認証システム（セッション検証）
- OpenID認証時の管理者権限チェック

**権限による制御**：
- OpenID認証使用時は管理者のみがシークレットを設定可能
- パスワード認証時は認証済みユーザーが設定可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 設定画面（銀行連携） | 主機能 | APIキーの設定 |

## 機能種別

CRUD操作 / セキュリティ管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| name | string | Yes | シークレット名 | - |
| value | string | Yes（保存時） | シークレット値 | - |

### 入力データソース

- HTTPリクエストボディ（POST）
- URLパラメータ（GET）
- セッショントークン

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| status | string | 処理結果（'ok' / 'error'） |

### 出力先

- HTTPレスポンス（JSON / ステータスコード）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ Express middleware（JSON parse、ロギング、セッション検証）

2. 認証方式確認
   └─ authテーブルからアクティブな認証方式を取得

3. 権限チェック（OpenID認証時のみ）
   └─ isAdmin()で管理者権限を確認

4. シークレット操作
   └─ secretsService.set() または secretsService.exists()

5. レスポンス返却
   └─ ステータスコードとJSONを返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[リクエスト受信]
    B --> C[セッション検証]
    C --> D{有効なセッション?}
    D -->|No| E[401 Unauthorized]
    D -->|Yes| F{POST or GET?}
    F -->|POST| G[認証方式確認]
    G --> H{OpenID認証?}
    H -->|Yes| I{管理者?}
    H -->|No| J[secretsService.set実行]
    I -->|No| K[403 Forbidden]
    I -->|Yes| J
    J --> L[200 OK]
    F -->|GET| M[secretsService.exists確認]
    M --> N{存在する?}
    N -->|Yes| O[204 No Content]
    N -->|No| P[404 Not Found]
    E --> Q[終了]
    K --> Q
    L --> Q
    O --> Q
    P --> Q
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-67-001 | OpenID管理者制限 | OpenID認証時は管理者のみがシークレットを設定可能 | POST /（OpenID認証時） |
| BR-67-002 | 存在確認のみ | シークレットの値自体は取得不可、存在確認のみ可能 | GET /:name |
| BR-67-003 | セッション必須 | 全操作にセッション認証が必要 | 全エンドポイント |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 認証方式取得 | auth | SELECT | アクティブな認証方式を取得 |
| シークレット保存 | secrets | INSERT/UPDATE | シークレットの保存 |
| シークレット存在確認 | secrets | SELECT | シークレットの存在確認 |

### テーブル別操作詳細

#### auth

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | method | WHERE active = 1 | 認証方式取得 |

#### secrets（secretsService経由）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | name | シークレット名 | キー |
| INSERT/UPDATE | value | シークレット値 | 暗号化保存 |
| SELECT | name | 存在確認 | 値は返さない |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | セッション無効 | ログイン要求 |
| 403 | not-admin | OpenID認証時に非管理者がアクセス | 権限エラー表示 |
| 404 | key not found | 指定されたシークレットが存在しない | エラー表示 |
| 500 | database-error | 認証方式取得失敗 | エラー表示 |

### リトライ仕様

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

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

シークレットの保存は単一操作で完結。

## パフォーマンス要件

- API応答時間: 200ms以内

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

- シークレットの値はGET APIでは取得不可（存在確認のみ）
- OpenID認証時は管理者権限が必要
- セッション認証必須
- secretsServiceで暗号化保存（実装依存）

## 備考

- シークレット名の例: `simplefin_access_token`, `github_token`
- secretsServiceはシークレットの暗号化保存を担当

---

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

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

### 推奨読解順序

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

まず、シークレット保存の仕組みを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | app-secrets.js | `packages/sync-server/src/app-secrets.js` | Express Router構成 |
| 1-2 | secrets-service.ts | `packages/sync-server/src/services/secrets-service.ts` | secretsService実装 |

**読解のコツ**: シークレット管理はシンプルなKey-Value形式。値の取得は不可で、存在確認のみ可能なセキュアな設計。

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

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

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

**主要処理フロー**:
1. **10-16行目**: Expressアプリ初期化、ミドルウェア設定
2. **17-51行目**: POST / - シークレット保存
3. **53-61行目**: GET /:name - シークレット存在確認

**主要処理フロー（POST /）**:
- **18-31行目**: 認証方式確認（authテーブルからmethod取得）
- **32-33行目**: リクエストボディからname, valueを取得
- **34-45行目**: OpenID認証時の管理者チェック
- **47-49行目**: secretsService.setでシークレット保存

**主要処理フロー（GET /:name）**:
- **54行目**: URLパラメータからnameを取得
- **55行目**: secretsService.existsで存在確認
- **56-60行目**: 存在する場合204、しない場合404を返却

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

```
Express Router (app-secrets.js)
    │
    ├─ Middleware
    │      ├─ express.json()
    │      ├─ requestLoggerMiddleware
    │      └─ validateSessionMiddleware
    │
    ├─ POST /
    │      │
    │      ├─ getAccountDb().first(...)
    │      │      └─ 認証方式取得 (auth.method WHERE active=1)
    │      │
    │      ├─ isAdmin(res.locals.user_id)
    │      │      └─ OpenID認証時のみ
    │      │
    │      └─ secretsService.set(name, value)
    │             └─ シークレット保存
    │
    └─ GET /:name
           │
           └─ secretsService.exists(name)
                  └─ 存在確認
```

### データフロー図

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

HTTPリクエスト ───────┐
(POST: name, value   │
 GET: name)          │
                     ▼
              ┌─────────────────┐
              │ Express Router  │
              │ app-secrets.js  │
              └────────┬────────┘
                       │
                       ▼
              ┌─────────────────┐
              │ validateSession │
              │ Middleware      │
              └────────┬────────┘
                       │
                       ▼
              ┌─────────────────┐
              │ 認証方式確認    │
              │ auth.method     │
              │ WHERE active=1  │
              └────────┬────────┘
                       │
        ┌──────────────┼──────────────┐
        │              │              │
        ▼              ▼              ▼
   OpenID認証     パスワード認証    GET
        │              │              │
        ▼              │              │
   ┌─────────┐         │              │
   │isAdmin? │         │              │
   └────┬────┘         │              │
        │              │              │
        ▼              ▼              ▼
   ┌─────────────────────────┐  ┌─────────────┐
   │ secretsService.set      │  │ secretsService│
   │ (name, value)           │  │ .exists(name)│
   └───────────┬─────────────┘  └─────┬───────┘
               │                      │
               ▼                      ▼
         ┌──────────┐           ┌──────────┐
         │200 OK    │           │204 or 404│
         │{status:ok}│          │          │
         └──────────┘           └──────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| app-secrets.js | `packages/sync-server/src/app-secrets.js` | ソース | メインルーター |
| secrets-service.ts | `packages/sync-server/src/services/secrets-service.ts` | ソース | シークレット保存サービス |
| account-db.js | `packages/sync-server/src/account-db.js` | ソース | DB接続・isAdmin関数・getAccountDb関数 |
| middlewares.ts | `packages/sync-server/src/util/middlewares.ts` | ソース | Express middleware |
