# 通知設計書 56-ssh_key_expired_email

## 概要

本ドキュメントは、GitLabにおけるSSHキー期限切れ通知（ssh_key_expired_email）の設計内容を記載する。

### 本通知の処理概要

**業務上の目的・背景**：SSHキーは、Gitリポジトリへのセキュアなアクセスに使用される認証情報である。SSHキーが期限切れになると、ユーザーはGit over SSHによるリポジトリへのアクセスができなくなる。本通知は、SSHキーが期限切れとなったことをユーザーに通知し、新しいキーの登録を促すことで、作業の中断を最小限に抑える。

**通知の送信タイミング**：定期的なcronジョブにより、当日期限切れとなったSSHキーを検出し、通知を送信する。Keys::ExpiryNotificationServiceを通じて処理される。

**通知の受信者**：期限切れSSHキーを所有するアクティブなユーザー本人。ユーザーが通知を受信できる権限（receive_notifications）を持っている必要がある。

**通知内容の概要**：期限切れとなったSSHキーのフィンガープリント一覧と、SSHキー設定画面へのリンクが含まれる。

**期待されるアクション**：受信者は通知を受け取った後、SSHキー設定画面にアクセスし、新しいSSHキーを登録して期限切れキーを置き換えることが期待される。

## 通知種別

メール

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（deliver_later） |
| 優先度 | 高 |
| リトライ | Sidekiq標準リトライ機構に依存 |

### 送信先決定ロジック

SSHキーを所有するユーザーの`notification_email_or_default`メソッドで取得されるメールアドレスに送信される。ユーザーが`can?(:receive_notifications)`を満たし、かつ`active?`である場合のみ送信が実行される。

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | GitLab設定に基づく |
| 送信元名称 | GitLab |
| 件名 | "Your SSH key has expired" |
| 形式 | テキスト |

### 本文テンプレート

```
Hi {username}!

SSH keys with the following fingerprints have expired and can no longer be used:

  - {fingerprint_1}
  - {fingerprint_2}
  ...

You can create a new one or check them in your SSH keys settings {ssh_key_link}.
```

### 添付ファイル

なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @user | 通知対象ユーザー | User | Yes |
| @fingerprints | 期限切れSSHキーのフィンガープリント配列 | Key.fingerprint | Yes |
| @target_url | SSHキー設定URL | user_settings_ssh_keys_url | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| スケジュール（Cron） | SSHキー期限切れチェック | SSHキーが当日期限切れ | 毎日実行されるワーカーで期限切れキーを検出 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| ユーザーが通知不可 | user.can?(:receive_notifications)がfalseの場合 |
| ユーザーが非アクティブ | user.active?がfalseの場合 |
| 既に通知済み | expiry_notification_delivered_atが設定済みの場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[Cronジョブ実行] --> B[期限切れユーザー取得]
    B --> C{ユーザー存在?}
    C -->|No| D[終了]
    C -->|Yes| E[Keys::ExpiryNotificationService.execute]
    E --> F{通知可能?}
    F -->|No| G[次のユーザーへ]
    F -->|Yes| H[trigger_expired_notification]
    H --> I[notification_service.ssh_key_expired]
    I --> J[Emails::Profile.ssh_key_expired_email]
    J --> K[deliver_later]
    K --> L[通知済みフラグ更新]
    L --> G
    G --> B
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| users | ユーザー情報取得 | |
| keys | SSHキー情報取得 | |

### テーブル別参照項目詳細

#### keys

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| fingerprint | キーのフィンガープリント | 通知メール本文に記載 |
| expires_at | 有効期限 | 期限切れ判定 |
| expiry_notification_delivered_at | 通知送信日時 | 送信済み判定 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| keys | UPDATE | 通知送信済みフラグを更新 |

#### keys更新

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| UPDATE | expiry_notification_delivered_at | Time.current | 通知送信完了時 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | メール送信エラー | Sidekiqリトライ |
| ユーザー非アクティブ | ユーザーが削除・無効化済み | 処理スキップ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiq標準（25回） |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | 一時的なエラー全般 |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | GitLab設定に依存 |
| 1日あたり上限 | 特になし |

### 配信時間帯

Cronジョブの実行スケジュールに依存。

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

- SSHキーのフィンガープリントのみを通知に含め、秘密鍵は含まれない
- 通知はキー所有者本人にのみ送信される

## 備考

- 同時にTodoも作成される（TodoService.ssh_key_expired）
- キーのフィンガープリントで識別を行う

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | key.rb | `app/models/key.rb` | SSHキーモデルの構造、expires_at、fingerprintを確認 |

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | expiry_notification_service.rb | `app/services/keys/expiry_notification_service.rb` | executeメソッドから処理フロー |

**主要処理フロー**:
1. **14-23行目**: executeメソッドでexpiring_soonかどうかで分岐
2. **38-42行目**: trigger_expired_notificationで通知サービス呼び出し
3. **41行目**: expiry_notification_delivered_atを更新

#### Step 3: NotificationServiceを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notification_service.rb | `app/services/notification_service.rb` | ssh_key_expiredメソッド（166-170行目） |

#### Step 4: メーラーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | profile.rb | `app/mailers/emails/profile.rb` | ssh_key_expired_emailメソッド（185-193行目） |

**主要処理フロー**:
- **186行目**: user.active?チェック
- **189行目**: @fingerprintsにフィンガープリント配列を設定
- **192行目**: 件名「Your SSH key has expired」

#### Step 5: テンプレートを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | ssh_key_expired_email.text.erb | `app/views/notify/ssh_key_expired_email.text.erb` | メール本文 |

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

```
SSHキー期限切れチェックワーカー
    │
    └─ Keys::ExpiryNotificationService#execute
           │
           ├─ allowed? (user.can?(:receive_notifications))
           │
           ├─ create_expired_todos (TodoService)
           │
           └─ trigger_expired_notification
                  ├─ NotificationService#ssh_key_expired
                  │      └─ Notify#ssh_key_expired_email
                  │             └─ email_with_layout
                  │
                  └─ keys.update_all(expiry_notification_delivered_at: ...)
```

### データフロー図

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

users                   ──▶ ExpiryNotificationService ──▶ メール送信
  (with_expired_keys)       ├─ 通知可能チェック             │
                            ├─ Todo作成                    ▼
keys                    ──▶ └─ NotificationService     ──▶ Sidekiq Job
  (fingerprint,                 └─ Emails::Profile         (deliver_later)
   expiry_notification_             └─ テンプレート展開
   delivered_at)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| expiry_notification_service.rb | `app/services/keys/expiry_notification_service.rb` | ソース | 通知サービス |
| notification_service.rb | `app/services/notification_service.rb` | ソース | 通知サービス |
| profile.rb | `app/mailers/emails/profile.rb` | ソース | メーラー |
| ssh_key_expired_email.text.erb | `app/views/notify/ssh_key_expired_email.text.erb` | テンプレート | メール本文 |
| key.rb | `app/models/key.rb` | ソース | キーモデル |
| todo_service.rb | `app/services/todo_service.rb` | ソース | Todo作成 |
