# 通知設計書 58-unknown_sign_in_email

## 概要

本ドキュメントは、GitLabにおける未知のサインイン通知（unknown_sign_in_email）の設計内容を記載する。

### 本通知の処理概要

**業務上の目的・背景**：アカウントセキュリティの観点から、ユーザーが通常とは異なる場所やデバイスからサインインした場合に通知することは重要である。本通知は、未知のIPアドレスまたはデバイスからのサインインを検知し、ユーザーに警告を発することで、不正アクセスの早期発見と対応を可能にする。

**通知の送信タイミング**：ユーザーがサインインした際、既知のデバイス（Cookie）または既知のIPアドレス（過去のセッションや最後のサインインIP）でない場合に即座に通知が送信される。

**通知の受信者**：サインインしたユーザー本人。ユーザーが通知を受信できる権限（receive_notifications）を持っている必要がある。

**通知内容の概要**：サインインが行われたIPアドレス、日時、可能であれば地理的位置（国、都市）、パスワード変更画面へのリンクが含まれる。

**期待されるアクション**：受信者は通知を受け取った後、自身によるサインインであれば無視し、心当たりがない場合は即座にパスワードを変更し、セッションを無効化する。

## 通知種別

メール

## 送信仕様

### 基本情報

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

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

サインインしたユーザーの`notification_email_or_default`メソッドで取得されるメールアドレスに送信される。ユーザーが`can?(:receive_notifications)`を満たす場合のみ送信が実行される。

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | GitLab設定に基づく |
| 送信元名称 | GitLab |
| 件名 | "{host} sign-in from new location" |
| 形式 | HTML/テキスト |

### 本文テンプレート

```
新しい場所からのサインインが検出されました。

IPアドレス: {ip}
日時: {time}
場所: {city}, {country}（利用可能な場合）

このサインインに心当たりがない場合は、すぐにパスワードを変更してください: {password_change_link}
```

### 添付ファイル

なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @user | 通知対象ユーザー | User | Yes |
| @ip | サインイン元IPアドレス | request.remote_ip | Yes |
| @time | サインイン日時 | current_sign_in_at | Yes |
| @city | 都市名 | Gitlab::Auth::VisitorLocation | No |
| @country | 国名 | Gitlab::Auth::VisitorLocation | No |
| @target_url | パスワード変更URL | edit_user_settings_password_url | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | ユーザーサインイン | 未知のデバイス/IPからのサインイン | サインイン成功時に検証 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| ユーザーが通知不可 | user.can?(:receive_notifications)がfalseの場合 |
| 既知のデバイス | known_sign_in Cookieが一致する場合 |
| 既知のIPアドレス | 過去のセッションまたは最後のサインインIPに一致する場合 |
| 機能が無効 | Gitlab::CurrentSettings.notify_on_unknown_sign_in?がfalseの場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ユーザーサインイン] --> B{機能有効?}
    B -->|No| C[処理終了]
    B -->|Yes| D{既知のデバイス?}
    D -->|Yes| E[Cookie更新]
    D -->|No| F{既知のIP?}
    F -->|Yes| E
    F -->|No| G[notify_user]
    G --> H[NotificationService.unknown_sign_in]
    H --> I{通知可能?}
    I -->|No| E
    I -->|Yes| J[Emails::Profile.unknown_sign_in_email]
    J --> K[deliver_later]
    K --> E
    E --> C
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| users | ユーザー情報取得 | last_sign_in_ip |
| active_sessions | セッション情報取得 | 過去のIPアドレス |

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

#### users

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| last_sign_in_ip | 最後のサインインIP | 既知IP判定 |
| current_sign_in_at | 現在のサインイン日時 | メール本文に記載 |

### 更新テーブル一覧

本通知処理では更新は行わない（Cookieの更新は別処理）

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | メール送信エラー | Sidekiqリトライ |
| 位置情報取得失敗 | GeoIPルックアップ失敗 | 位置情報なしで送信 |

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

制限なし（イベント発生時に即座に送信）

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

- セキュリティ通知のため、重要度が高い
- IPアドレスと位置情報は個人情報に該当するため、適切に取り扱う
- パスワード変更画面へのリンクを含め、迅速な対応を促す
- Cookieは暗号化され、HTTPOnly属性で保護

## 備考

- KNOWN_SIGN_IN_COOKIE_EXPIRY = 14日間
- Gitlab::Auth::VisitorLocationで地理情報を取得
- 管理設定でnotify_on_unknown_sign_in設定により有効/無効化可能

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | user.rb | `app/models/user.rb` | last_sign_in_ip、current_sign_in_atを確認 |
| 1-2 | active_session.rb | `app/models/active_session.rb` | セッション情報の構造を確認 |

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

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

**主要処理フロー**:
1. **12行目**: verify_known_sign_inメソッドが呼び出される
2. **13行目**: Gitlab::CurrentSettings.notify_on_unknown_sign_in?チェック
3. **15行目**: known_device? || known_remote_ip?で既知判定
4. **47-55行目**: notify_userでNotificationService呼び出し

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

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

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

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

**主要処理フロー**:
- **207-210行目**: @ip, @city, @country, @timeを設定
- **211行目**: @target_urlにパスワード変更URLを設定
- **215行目**: 件名に{host}を含める

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

```
SessionsController (サインイン処理)
    │
    └─ KnownSignIn#verify_known_sign_in (after_action)
           │
           ├─ Gitlab::CurrentSettings.notify_on_unknown_sign_in? チェック
           │
           ├─ known_device? (Cookie確認)
           │
           ├─ known_remote_ip? (IP確認)
           │      └─ sessions.map(&:ip_address)
           │      └─ current_user.last_sign_in_ip
           │
           └─ notify_user
                  ├─ Gitlab::Auth::VisitorLocation.new(request)
                  │
                  └─ NotificationService#unknown_sign_in
                         └─ Notify#unknown_sign_in_email
                                └─ email_with_layout
```

### データフロー図

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

サインインリクエスト    ──▶ SessionsController        ──▶ サインイン成功
  (IP, Cookie)              ├─ 認証処理                    │
                            └─ KnownSignIn concern         ▼
                                 ├─ 既知判定             ──▶ Cookie更新
ActiveSession           ──▶      │   ├─ Cookie確認
  (ip_address)                   │   └─ IP確認
                                 │
users                   ──▶      └─ notify_user         ──▶ メール送信
  (last_sign_in_ip)                  └─ NotificationService  (deliver_later)
                                          └─ Emails::Profile
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| known_sign_in.rb | `app/controllers/concerns/known_sign_in.rb` | ソース | サインイン検知 |
| notification_service.rb | `app/services/notification_service.rb` | ソース | 通知サービス |
| profile.rb | `app/mailers/emails/profile.rb` | ソース | メーラー |
| visitor_location.rb | `lib/gitlab/auth/visitor_location.rb` | ソース | 位置情報取得 |
| application_setting_implementation.rb | `app/models/application_setting_implementation.rb` | ソース | 設定 |
| notify_preview.rb | `app/mailers/previews/notify_preview.rb` | ソース | メールプレビュー |
