# 通知設計書 16-Login attempts

## 概要

本ドキュメントは、Wikipedia iOSアプリケーションにおける「Login attempts（ログイン試行）」通知の設計を定義する。この通知は、ユーザーのアカウントに対して不正なログイン試行が検出された際に、セキュリティ警告として配信される。

### 本通知の処理概要

本通知は、アカウントのセキュリティを保護するための重要な警告機能を提供する。

**業務上の目的・背景**：Wikipediaでは、ユーザーアカウントのセキュリティを保護するため、不正なログイン試行を検知した場合にユーザーに警告を送信する。これにより、不正アクセスの早期発見と対策（パスワード変更など）を促す。

**通知の送信タイミング**：以下の2つのケースでトリガーされる：
1. **loginFailUnknownDevice**：知らないデバイスからのログイン試行が失敗した場合
2. **loginFailKnownDevice**：既知のデバイスからの複数回のログイン試行が失敗した場合

**通知の受信者**：対象となるアカウントの所有者が受信者となる。

**通知内容の概要**：失敗したログイン試行の詳細（回数、デバイス情報など）と、パスワード変更を促すメッセージが表示される。

**期待されるアクション**：受信者は通知を確認し、心当たりがない場合はパスワードを変更することが強く推奨される。

## 通知種別

- プッシュ通知（iOS Push Notification）
- アプリ内通知（Notifications Center）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（MediaWiki Echo APIからのプル型） |
| 優先度 | active（0.85） |
| リトライ | APIフェッチ時に自動リトライあり |

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

MediaWiki Login Notify拡張機能がログイン失敗イベントを検知し、対象アカウントの所有者に対して通知を生成する。

## 通知テンプレート

### プッシュ通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元 | Wikipediaアプリ |
| タイトル | Login attempts |
| 本文 | ログイン試行に関する警告メッセージ |
| 割り込みレベル | active |
| 関連スコア | 0.85 |

### 本文テンプレート

```
// 知らないデバイスからの失敗
"There has been a failed attempt to log in to your account."

// 既知のデバイスからの複数回失敗
"There have been {count} failed attempts to log in to your account since the last time you logged in. If it wasn't you, please make sure your account has a strong password."
```

### アプリ内通知表示

| 項目 | 内容 |
|-----|------|
| ヘッダーテキスト（知らないデバイス） | "Failed log in attempt" |
| ヘッダーテキスト（既知デバイス） | "Multiple failed log in attempts" |
| サブヘッダーテキスト | "Alert" |
| ボディテキスト | messageHeader（警告メッセージ） |
| フッターテキスト | "Change password" |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| count | 失敗回数 | messageHeaderから抽出 | No |
| messageHeader | メッセージヘッダー | RemoteNotification.messageHeader | Yes |
| date | 通知日時 | RemoteNotification.date | Yes |
| wiki | プロジェクト識別子 | RemoteNotification.wiki | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| API | Echo API通知取得 | ユーザーがログイン中 | アプリがアクティブになった際にAPIから通知を取得 |
| プッシュ | APNs経由のプッシュ通知 | デバイス登録済み | MediaWikiサーバーからAPNs経由でプッシュ通知を受信 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| フィルター設定 | ユーザーがloginAttempts通知タイプをフィルターで除外している場合 |
| 未認証状態 | ユーザーがログインしていない場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ログイン試行失敗検知] --> B{失敗タイプ判定}
    B -->|知らないデバイス| C[loginFailUnknownDevice通知]
    B -->|既知デバイス複数回| D[loginFailKnownDevice通知]
    C --> E[Echo APIに通知登録]
    D --> E
    E --> F{プッシュ通知対応?}
    F -->|Yes| G[APNs経由でプッシュ送信]
    F -->|No| H[アプリ内通知のみ]
    G --> I[NotificationService Extension処理]
    I --> J[通知コンテンツ生成]
    J --> K[ユーザーに表示]
    H --> L[アプリ起動時にAPI取得]
    L --> M[CoreDataに保存]
    M --> N[Notifications Centerに表示]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| RemoteNotification | 通知データの保存・取得 | Core Data Entity |
| PushNotificationsCache | プッシュ通知キャッシュ | SharedContainerCache |

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

#### RemoteNotification

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| categoryString | 通知カテゴリ判定 | "login-fail" |
| typeString | 通知タイプ判定 | "login-fail-new"（知らないデバイス）, "login-fail-known"（既知デバイス） |
| messageHeader | メッセージ表示 | - |
| isRead | 既読状態 | - |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| RemoteNotification | INSERT/UPDATE | 新規通知の保存・既読状態の更新 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| API取得失敗 | ネットワークエラー | リトライ処理、フォールバックテキスト表示 |
| 認証エラー | トークン期限切れ | 再認証フローへ遷移 |
| データベースエラー | Core Data保存失敗 | エラーログ出力、次回同期時に再取得 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 自動（OperationQueue管理） |
| リトライ間隔 | 30秒のデッドライン制御 |
| リトライ対象エラー | ネットワークエラー、一時的なサーバーエラー |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| リフレッシュ間隔 | 30秒以上 |
| バッチ取得 | APIから一括取得 |

### 配信時間帯

制限なし（24時間配信）- セキュリティ通知のため即時配信

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

- セキュリティ上重要な通知のため、ユーザーに確実に届ける必要がある
- 通知データはCore Dataに保存される
- APNs通信はTLS暗号化
- パスワード変更URLはmediawiki.org/wiki/Special:ChangeCredentialsを使用

## 備考

- categoryString: "login-fail"
- typeString: "login-fail-new"（知らないデバイス）, "login-fail-known"（既知デバイス）
- 画像アイコン: "notifications-type-login-notify"
- 背景色: theme.colors.error（エラーカラー）
- プライマリアクション: パスワード変更ページへのリンク
- セカンダリアクション: ログイン通知のヘルプページ

---

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

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

### 推奨読解順序

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

通知のデータモデルとタイプ定義を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | RemoteNotificationType.swift | `WMF Framework/Remote Notifications/Model/RemoteNotificationType.swift` | 19-20行目: `.loginFailUnknownDevice`と`.loginFailKnownDevice`タイプの定義、173-175行目: 優先度(.active, 0.85)の設定、57-58行目: 画像アイコン名、72-73行目: エラー背景色 |
| 1-2 | RemoteNotificationFilterType.swift | `WMF Framework/Remote Notifications/Model/RemoteNotificationFilterType.swift` | 15行目: `loginAttempts`フィルタータイプ（2つのタイプを統合）、130-131行目: カテゴリ文字列"login-fail"の定義、172-174行目: タイプ文字列 |
| 1-3 | RemoteNotification+CoreDataClass.swift | `WMF Framework/Remote Notifications/Model/RemoteNotification+CoreDataClass.swift` | 52-55行目: category/typeからの型変換ロジック |

**読解のコツ**: loginFailUnknownDeviceとloginFailKnownDeviceは別のenumケースだが、フィルターではloginAttemptsとして統合されている。

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

プッシュ通知受信とアプリ内表示の起点を特定。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | NotificationService.swift | `NotificationServiceExtension/NotificationService.swift` | 19-92行目: プッシュ通知受信時の処理フロー、70-73行目: 優先度設定 |
| 2-2 | RemoteNotificationsController.swift | `WMF Framework/Remote Notifications/RemoteNotificationsController.swift` | 125-163行目: loadNotifications関数 |

#### Step 3: ViewModel層を理解する

通知の表示ロジックを把握。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | NotificationsCenterCommonViewModel+TextExtensions.swift | `Wikipedia/Code/NotificationsCenterCommonViewModel+TextExtensions.swift` | 57-60行目: 2種類のサブヘッダーテキスト、86-89行目: messageHeaderの取得 |
| 3-2 | NotificationsCenterCellViewModel+TextExtensions.swift | `Wikipedia/Code/NotificationsCenterCellViewModel+TextExtensions.swift` | 29-34行目: サブヘッダー"Alert"、62-65行目: フッターテキスト"Change password" |
| 3-3 | NotificationsCenterDetailViewModel+ActionExtensions.swift | `Wikipedia/Code/NotificationsCenterDetailViewModel+ActionExtensions.swift` | 52-57行目: プライマリアクション（changePasswordAction）、109-112行目: セカンダリアクション（loginActions） |
| 3-4 | NotificationsCenterCellViewModel+SheetActionExtensions.swift | `Wikipedia/Code/NotificationsCenterCellViewModel+SheetActionExtensions.swift` | 44-47行目: loginアクション、259-271行目: loginActions関数 |

**主要処理フロー**:
- **57-60行目**: verboseTitle - "Failed log in attempt" / "Multiple failed log in attempts"
- **62-65行目**: footerText - "Change password"
- **52-57行目**: primaryAction - changePasswordAction
- **259-271行目**: loginActions - changePassword, loginNotificationsHelp

#### Step 4: テストケースを確認する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | NotificationsCenterCellViewModelLoginIssuesTests.swift | `WikipediaUnitTests/Code/Notifications Tests/NotificationsCenterCellViewModelLoginIssuesTests.swift` | テストケース全体: ログイン失敗のテキスト、アイコン、アクション検証 |

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

```
NotificationService.didReceive (Push受信)
    │
    ├─ RemoteNotificationsAPIController.getUnreadPushNotifications
    │      └─ APIからJSON取得
    │
    └─ NotificationServiceHelper.determineNotificationsToDisplayAndCache
           └─ pushContentText生成

RemoteNotificationsController.loadNotifications (アプリ内)
    │
    ├─ RemoteNotificationsOperationsController.loadNotifications
    │      └─ RemoteNotificationsAPIController
    │
    └─ RemoteNotificationsModelController
           └─ Core Data保存
                  └─ typeFrom:
                         login-fail/login-fail-new → .loginFailUnknownDevice
                         login-fail/login-fail-known → .loginFailKnownDevice

NotificationsCenterViewController
    │
    └─ NotificationsCenterCellViewModel
           ├─ NotificationsCenterCommonViewModel (テキスト生成)
           ├─ footerText → "Change password"
           └─ sheetActions → loginActions
                  ├─ Change password
                  └─ Login notifications help

NotificationsCenterDetailViewModel
    │
    └─ primaryAction → changePasswordAction
```

### データフロー図

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

MediaWiki Echo API ──▶ NotificationService Extension ──▶ iOS Push通知表示
        │
        └──────────▶ RemoteNotificationsController ──▶ CoreData保存
                            │
                            └──▶ NotificationsCenterViewModel ──▶ UI表示
                                        │
                                        └──▶ sheetActions
                                               ├─ Change password
                                               │      └─ mediawiki.org/wiki/Special:ChangeCredentials
                                               └─ Login notifications
                                                      └─ mediawiki.org/wiki/Help:Login_notifications
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| RemoteNotificationType.swift | `WMF Framework/Remote Notifications/Model/RemoteNotificationType.swift` | ソース | 通知タイプ定義 |
| RemoteNotificationFilterType.swift | `WMF Framework/Remote Notifications/Model/RemoteNotificationFilterType.swift` | ソース | フィルタータイプ定義 |
| RemoteNotification+CoreDataClass.swift | `WMF Framework/Remote Notifications/Model/RemoteNotification+CoreDataClass.swift` | ソース | Core Dataエンティティ |
| NotificationService.swift | `NotificationServiceExtension/NotificationService.swift` | ソース | プッシュ通知処理 |
| RemoteNotificationsController.swift | `WMF Framework/Remote Notifications/RemoteNotificationsController.swift` | ソース | 通知コントローラー |
| NotificationsCenterCommonViewModel+TextExtensions.swift | `Wikipedia/Code/NotificationsCenterCommonViewModel+TextExtensions.swift` | ソース | テキスト生成 |
| NotificationsCenterCellViewModel+TextExtensions.swift | `Wikipedia/Code/NotificationsCenterCellViewModel+TextExtensions.swift` | ソース | セルテキスト生成 |
| NotificationsCenterDetailViewModel+ActionExtensions.swift | `Wikipedia/Code/NotificationsCenterDetailViewModel+ActionExtensions.swift` | ソース | アクション定義 |
| NotificationsCenterCellViewModel+SheetActionExtensions.swift | `Wikipedia/Code/NotificationsCenterCellViewModel+SheetActionExtensions.swift` | ソース | シートアクション定義 |
| NotificationsCenterCellViewModelLoginIssuesTests.swift | `WikipediaUnitTests/Code/Notifications Tests/NotificationsCenterCellViewModelLoginIssuesTests.swift` | テスト | Login issuesテストケース |
| CommonStrings.swift | `WMF Framework/CommonStrings.swift` | ソース | ローカライズ文字列 |
