# 画面設計書 198-アクセストークン一覧

## 概要

本ドキュメントは、個人アクセストークン一覧画面の設計書である。ユーザーの個人アクセストークン（PAT）を管理する画面の仕様を定義する。

### 本画面の処理概要

ユーザーが自身のアカウントに発行された個人アクセストークンを一覧表示し、新規トークンの発行および既存トークンの失効を行う画面である。個人アクセストークンはAPIアクセスや Git over HTTPS での認証に使用される。

**業務上の目的・背景**：個人アクセストークンは、CI/CDパイプライン、スクリプト、外部アプリケーションからGitLab APIにアクセスするために必要な認証手段である。トークンにはスコープ（権限範囲）と有効期限を設定でき、セキュリティを確保しながら自動化を実現する。本画面は、トークンのライフサイクル管理（発行、確認、失効）を一元的に行うために必要である。

**画面へのアクセス方法**：
1. 右上のユーザーアバターをクリック
2. 「設定」を選択
3. 左サイドバーから「アクセストークン」を選択
4. または URL `/-/user_settings/personal_access_tokens` に直接アクセス

**主要な操作・処理内容**：
1. 発行済みトークンの一覧表示
2. 新規トークンの発行（名前、スコープ、有効期限の設定）
3. トークンの失効
4. DPoP（Demonstrating Proof of Possession）設定の切り替え

**画面遷移**：
- 遷移元: ユーザー設定メニュー
- 遷移先: 詳細設定トークン作成画面（granular_new）

**権限による表示制御**：
- ログイン必須
- `personal_access_tokens_disabled?` 設定で機能自体が無効化可能

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 81 | 個人アクセストークン | 主機能 | 個人アクセストークンの管理 |

## 画面種別

一覧 / 登録

## URL/ルーティング

```
GET  /-/user_settings/personal_access_tokens         # 一覧表示
POST /-/user_settings/personal_access_tokens         # トークン発行
PUT  /-/user_settings/personal_access_tokens/toggle_dpop  # DPoP切り替え
GET  /-/user_settings/personal_access_tokens.ics    # iCal形式（期限通知）
```

## 入出力項目

### 新規トークン発行フォーム

| 項目名 | 項目ID | 入出力 | 型 | 必須 | 説明 |
|--------|--------|--------|-----|------|------|
| トークン名 | personal_access_token[name] | 入力 | string | Yes | トークンの識別名 |
| 説明 | personal_access_token[description] | 入力 | string | No | トークンの説明 |
| 有効期限 | personal_access_token[expires_at] | 入力 | date | Yes* | YYYY-MM-DD形式 |
| スコープ | personal_access_token[scopes][] | 入力 | array | Yes | 権限スコープ |

*`require_personal_access_token_expiry?` 設定による

## 表示項目

### トークン一覧

| 項目名 | データソース | 説明 |
|--------|-------------|------|
| トークン名 | token.name | トークンの識別名 |
| スコープ | token.scopes | 付与されたスコープ一覧 |
| 作成日 | token.created_at | トークン発行日時 |
| 最終使用日 | token.last_used_at | 最後に使用された日時 |
| 有効期限 | token.expires_at | 有効期限日 |
| ステータス | token.revoked? / token.expired? | アクティブ/失効/期限切れ |

### 新規発行トークン（1回のみ表示）

| 項目名 | データソース | 説明 |
|--------|-------------|------|
| トークン値 | result.payload[:personal_access_token].token | 発行されたトークン文字列（コピー可） |

## イベント仕様

### 1-トークン発行

**トリガー**: 「Create personal access token」ボタン押下

**処理フロー**:
1. フォームデータのバリデーション
2. `PersonalAccessTokens::CreateService` を呼び出し
3. 成功時: トークン値を表示（この時のみ表示される）
4. 失敗時: エラーメッセージを表示

### 2-トークン失効

**トリガー**: 「Revoke」ボタン押下

**処理フロー**:
1. 失効確認ダイアログを表示
2. 確認後、トークンを失効状態に更新
3. 一覧を更新

### 3-DPoP設定切り替え

**トリガー**: DPoP設定トグル操作

**処理フロー**:
1. PUT `/toggle_dpop` へリクエスト
2. `UserPreferences::UpdateService` で設定を更新
3. 成功/失敗メッセージを表示

### 4-iCal出力

**トリガー**: `.ics` 形式でアクセス

**処理フロー**:
1. フィードトークンで認証
2. アクティブなトークンの有効期限をiCal形式で出力
3. カレンダーアプリでトークン期限を管理可能

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| トークン発行 | personal_access_tokens | INSERT | 新規トークンの発行 |
| トークン失効 | personal_access_tokens | UPDATE | revoked フラグを true に更新 |
| DPoP切り替え | user_preferences | UPDATE | dpop_enabled の更新 |

### テーブル別更新項目詳細

#### personal_access_tokens

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | user_id | current_user.id | トークン所有者 |
| INSERT | organization_id | Current.organization.id | 組織ID |
| INSERT | name | フォーム入力値 | トークン名 |
| INSERT | description | フォーム入力値 | 説明 |
| INSERT | scopes | フォーム入力値 | スコープ配列 |
| INSERT | expires_at | フォーム入力値 | 有効期限 |
| INSERT | token_digest | SHA256ハッシュ | トークンハッシュ |
| INSERT | created_at | 現在日時 | 作成日時 |
| UPDATE (失効) | revoked | true | 失効フラグ |
| UPDATE (失効) | updated_at | 現在日時 | 更新日時 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|---------|
| MSG-001 | 成功 | (発行されたトークン値) | トークン発行成功時 |
| MSG-002 | 成功 | DPoP preference updated. | DPoP設定更新成功時 |
| MSG-003 | 警告 | Unable to update DPoP preference. | DPoP設定更新失敗時 |
| MSG-004 | 情報 | Token '%{name}' expires today | iCal イベントタイトル |

## 例外処理

| 例外 | 発生条件 | 対応処理 |
|-----|---------|---------|
| 未認証 | ログインしていない | ログイン画面へリダイレクト |
| 404 Not Found | personal_access_tokens_disabled? が true | 404ページ表示 |
| バリデーションエラー | 必須項目未入力、有効期限超過等 | エラーメッセージを表示 |

## 備考

- トークン値は発行直後に1回だけ表示され、以後は確認不可
- Feature Flag `granular_personal_access_tokens` が有効な場合、詳細設定版のフォームが表示される
- Feature Flag `dpop_authentication` が有効な場合、DPoP設定が表示される
- トークンの最大有効期限はインスタンス設定（365日または400日）で制限される
- `glpat-` プレフィックスがトークンに付与される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | personal_access_token.rb | `app/models/personal_access_token.rb` | トークンモデルの属性・スコープ・バリデーションを確認 |

**読解のコツ**: `token` 属性は `TokenAuthenticatable` により暗号化される。`token_digest` にハッシュが保存され、平文は発行時のみ取得可能。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | personal_access_tokens_controller.rb | `app/controllers/user_settings/personal_access_tokens_controller.rb` | index/create/toggle_dpopアクションを確認 |

**主要処理フロー**:
1. **12-44行目**: `index` アクション - トークン一覧取得とパラメータ処理
2. **50-63行目**: `create` アクション - PersonalAccessTokens::CreateService によるトークン発行
3. **66-81行目**: `toggle_dpop` アクション - DPoP設定の切り替え
4. **110-112行目**: `check_personal_access_tokens_enabled` - 機能有効チェック

#### Step 3: ビューレイヤーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.html.haml | `app/views/user_settings/personal_access_tokens/index.html.haml` | 全体構造を確認 |

**主要処理フロー**:
- **7-8行目**: Feature Flag による Vue.js コンポーネント切り替え
- **14行目**: トークン一覧表示用 Vue.js コンポーネント

#### Step 4: サービスレイヤーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | create_service.rb | `app/services/personal_access_tokens/create_service.rb` | トークン発行ロジック |

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

```
UserSettings::PersonalAccessTokensController#index
    │
    ├─ check_personal_access_tokens_enabled
    │      └─ Gitlab::CurrentSettings.personal_access_tokens_disabled?
    │
    └─ @access_token_params (name, description, scopes)
           └─ personal_access_token_data(@access_token_params)

UserSettings::PersonalAccessTokensController#create
    │
    └─ PersonalAccessTokens::CreateService#execute
           ├─ current_user
           ├─ target_user: current_user
           ├─ organization_id: Current.organization.id
           └─ params: personal_access_token_params
                  │
                  └─ [成功時] result.payload[:personal_access_token].token
                         └─ JSON レスポンス

UserSettings::PersonalAccessTokensController#toggle_dpop
    │
    └─ UserPreferences::UpdateService#execute
           └─ dpop_params[:dpop_enabled]
```

### データフロー図

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

フォームデータ ───▶ CreateService ───▶ PersonalAccessToken
    │                      │
    │                      ├─ add_authentication_token_field :token
    │                      │      └─ token_digest (SHA256)
    │                      │
    │                      └─ validate scopes, expires_at
    │
    └───────────────────────────────────────────▶ { token: "glpat-xxx" }

iCal要求 ───▶ expiry_ics ───▶ Icalendar::Calendar
    │                │
    │                └─ PersonalAccessTokensFinder
    │                       └─ active, expires_asc
    │
    └───────────────────────────────────────────▶ text/calendar
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| personal_access_tokens_controller.rb | `app/controllers/user_settings/personal_access_tokens_controller.rb` | コントローラー | リクエスト処理 |
| index.html.haml | `app/views/user_settings/personal_access_tokens/index.html.haml` | テンプレート | 一覧画面 |
| personal_access_token.rb | `app/models/personal_access_token.rb` | モデル | トークンデータ定義 |
| create_service.rb | `app/services/personal_access_tokens/create_service.rb` | サービス | トークン発行ロジック |
| user_settings.rb | `config/routes/user_settings.rb` | 設定 | ルーティング定義 |
| personal_access_tokens_finder.rb | `app/finders/personal_access_tokens_finder.rb` | Finder | トークン検索 |
