@einja/dev-cli 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/lib/file-system.js +1 -1
  2. package/dist/lib/file-system.js.map +1 -1
  3. package/package.json +1 -2
  4. package/scaffolds/cli/preset.yaml +110 -0
  5. package/scaffolds/example/README.md +35 -0
  6. package/scaffolds/example/specs/issues/issue999-example-task/design.md +879 -0
  7. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/README.md +150 -0
  8. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/phase1/1-1.md +268 -0
  9. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/phase1/1-2.md +179 -0
  10. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/phase1/1-3.md +392 -0
  11. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/phase1/evidence/.gitkeep +0 -0
  12. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/phase2/2-1.md +459 -0
  13. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/phase2/evidence/.gitkeep +0 -0
  14. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/scenarios.md +125 -0
  15. package/scaffolds/example/specs/issues/issue999-example-task/requirements.md +494 -0
  16. package/scaffolds/example/specs/issues/issue999-example-task/tasks.md +212 -0
  17. package/scaffolds/instructions/deployment-setup.md +458 -0
  18. package/scaffolds/instructions/environment-setup.md +509 -0
  19. package/scaffolds/instructions/local-server-environment-and-worktree.md +539 -0
  20. package/scaffolds/instructions/task-execute.md +649 -0
  21. package/scaffolds/instructions/task-vibe-kanban-loop.md +495 -0
  22. package/scaffolds/memory/archive/.gitkeep +0 -0
  23. package/scaffolds/memory/decisions.md +26 -0
  24. package/scaffolds/memory/patterns.md +28 -0
  25. package/scaffolds/CLAUDE.md.template +0 -386
  26. /package/{templates → scaffolds/templates}/README.md +0 -0
  27. /package/{templates → scaffolds/templates}/design-simple.md.template +0 -0
  28. /package/{templates → scaffolds/templates}/design.md.template +0 -0
  29. /package/{templates → scaffolds/templates}/qa-test.md.template +0 -0
  30. /package/{templates → scaffolds/templates}/requirements.md.template +0 -0
@@ -0,0 +1,879 @@
1
+ # マジックリンク認証機能 設計文書
2
+
3
+ ## 概要
4
+
5
+ パスワードレス認証を実現するマジックリンク機能を実装し、ユーザーのログイン体験を向上させます。メールアドレスに送信される一時的なリンクをクリックすることで、パスワード入力なしに安全な認証を可能にします。本設計書では、要件定義書で定義された3つのユーザーストーリー(マジックリンクリクエスト、認証処理、セキュリティ通知)を実現するための技術的実装を詳細に定義します。
6
+
7
+ ## アーキテクチャ
8
+
9
+ ### システム構成図
10
+
11
+ ```mermaid
12
+ graph TB
13
+ User[ユーザー] --> WebApp[Webアプリケーション]
14
+ WebApp --> AuthAPI[認証API]
15
+ AuthAPI --> TokenService[トークンサービス]
16
+ AuthAPI --> EmailService[メール送信サービス]
17
+ TokenService --> DB[(PostgreSQL)]
18
+ TokenService --> Redis[(Redis Cache)]
19
+ EmailService --> SendGrid[SendGrid API]
20
+
21
+ AuthAPI --> SecurityService[セキュリティサービス]
22
+ SecurityService --> DeviceDetector[デバイス検知]
23
+ SecurityService --> RateLimit[レート制限]
24
+
25
+ subgraph 認証フロー
26
+ TokenService
27
+ SecurityService
28
+ RateLimit
29
+ end
30
+ ```
31
+
32
+ ### データフロー図(DFD)
33
+
34
+ ```mermaid
35
+ flowchart LR
36
+ %% 外部エンティティ
37
+ User[👤 ユーザー]
38
+ EmailProvider[📧 SendGrid]
39
+
40
+ %% プロセス
41
+ P1((1.0<br/>リンク要求))
42
+ P2((2.0<br/>トークン生成))
43
+ P3((3.0<br/>メール送信))
44
+ P4((4.0<br/>リンク検証))
45
+ P5((5.0<br/>セッション作成))
46
+
47
+ %% データストア
48
+ TokenDB[(トークンDB)]
49
+ SessionDB[(セッションDB)]
50
+ Cache[(キャッシュ)]
51
+
52
+ %% データフロー
53
+ User -->|メールアドレス| P1
54
+ P1 -->|検証済みアドレス| P2
55
+ P2 -->|トークン| TokenDB
56
+ P2 -->|送信データ| P3
57
+ P3 -->|メール| EmailProvider
58
+ EmailProvider -->|配信| User
59
+ User -->|マジックリンク| P4
60
+ P4 -->|トークン照合| TokenDB
61
+ P4 -->|認証成功| P5
62
+ P5 -->|セッション| SessionDB
63
+ P5 -->|一時保存| Cache
64
+ ```
65
+
66
+ ### データフロー説明
67
+
68
+ 1. **マジックリンクリクエストフロー**
69
+ - ユーザーがメールアドレスを入力
70
+ - メールアドレスの形式検証とレート制限チェック
71
+ - 暗号学的に安全なトークンの生成(256ビット)
72
+ - トークンのハッシュ化とデータベース保存
73
+ - メール送信キューへの登録
74
+
75
+ 2. **メール送信フロー**
76
+ - SendGrid APIを使用したメール送信
77
+ - HTMLとテキストのマルチパート形式
78
+ - リンクの有効期限(15分)を明記
79
+ - 配信ステータスの追跡
80
+
81
+ 3. **認証処理フロー**
82
+ - マジックリンクのトークン抽出
83
+ - トークンの有効性検証(期限、使用済み、ハッシュ照合)
84
+ - セッション生成と Cookie 設定
85
+ - トークンの即座の無効化
86
+ - ダッシュボードへのリダイレクト
87
+
88
+ ## シーケンス図
89
+
90
+ ### マジックリンク認証の全体フロー
91
+
92
+ ```mermaid
93
+ sequenceDiagram
94
+ participant U as ユーザー
95
+ participant W as Webアプリ
96
+ participant A as 認証API
97
+ participant T as トークンサービス
98
+ participant E as メールサービス
99
+ participant D as データベース
100
+ participant S as SendGrid
101
+
102
+ U->>W: メールアドレス入力
103
+ W->>A: POST /api/auth/magic-link
104
+ A->>A: レート制限チェック
105
+ A->>T: generateToken()
106
+ T->>T: 256ビットトークン生成
107
+ T->>D: トークン保存(ハッシュ化)
108
+ D-->>T: 保存完了
109
+ T-->>A: トークン返却
110
+ A->>E: sendMagicLink()
111
+ E->>S: メール送信API呼び出し
112
+ S-->>E: 送信完了
113
+ E-->>A: 送信ステータス
114
+ A-->>W: 成功レスポンス
115
+ W-->>U: 確認画面表示
116
+
117
+ Note over U: メール受信
118
+
119
+ U->>W: マジックリンククリック
120
+ W->>A: GET /api/auth/verify?token=xxx
121
+ A->>T: verifyToken()
122
+ T->>D: トークン検証
123
+ D-->>T: トークン情報
124
+ T->>T: 有効期限・使用済みチェック
125
+ T->>D: トークン無効化
126
+ T-->>A: 検証結果
127
+ A->>A: セッション生成
128
+ A->>D: セッション保存
129
+ A-->>W: 認証Cookie設定
130
+ W-->>U: ダッシュボードへリダイレクト
131
+ ```
132
+
133
+ ## コンポーネントとインターフェース
134
+
135
+ ### データベース設計
136
+
137
+ #### ERD
138
+
139
+ ```mermaid
140
+ erDiagram
141
+ User {
142
+ string id PK
143
+ string email UK
144
+ string name
145
+ datetime createdAt
146
+ datetime updatedAt
147
+ }
148
+
149
+ MagicLinkToken {
150
+ string id PK
151
+ string userId FK
152
+ string hashedToken UK
153
+ datetime expiresAt
154
+ boolean used
155
+ string ipAddress
156
+ string userAgent
157
+ datetime createdAt
158
+ }
159
+
160
+ Session {
161
+ string id PK
162
+ string userId FK
163
+ string deviceFingerprint
164
+ datetime expiresAt
165
+ datetime createdAt
166
+ datetime lastActivity
167
+ }
168
+
169
+ SecurityLog {
170
+ string id PK
171
+ string userId FK
172
+ string eventType
173
+ string ipAddress
174
+ string deviceInfo
175
+ json metadata
176
+ datetime createdAt
177
+ }
178
+
179
+ User ||--o{ MagicLinkToken : generates
180
+ User ||--o{ Session : has
181
+ User ||--o{ SecurityLog : logs
182
+ ```
183
+
184
+ #### Prismaスキーマ
185
+
186
+ ```prisma
187
+ model User {
188
+ id String @id @default(cuid())
189
+ email String @unique
190
+ name String?
191
+ createdAt DateTime @default(now())
192
+ updatedAt DateTime @updatedAt
193
+ magicLinkTokens MagicLinkToken[]
194
+ sessions Session[]
195
+ securityLogs SecurityLog[]
196
+
197
+ @@index([email])
198
+ @@map("users")
199
+ }
200
+
201
+ model MagicLinkToken {
202
+ id String @id @default(cuid())
203
+ userId String
204
+ hashedToken String @unique
205
+ expiresAt DateTime
206
+ used Boolean @default(false)
207
+ ipAddress String?
208
+ userAgent String?
209
+ createdAt DateTime @default(now())
210
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
211
+
212
+ // インデックス戦略
213
+ @@index([hashedToken]) // トークン検証の高速化
214
+ @@index([userId, createdAt]) // ユーザー別の履歴取得
215
+ @@index([expiresAt]) // 期限切れトークンのクリーンアップ
216
+ @@map("magic_link_tokens")
217
+ }
218
+
219
+ model Session {
220
+ id String @id @default(cuid())
221
+ userId String
222
+ deviceFingerprint String?
223
+ expiresAt DateTime
224
+ createdAt DateTime @default(now())
225
+ lastActivity DateTime @default(now())
226
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
227
+
228
+ @@index([userId])
229
+ @@index([expiresAt]) // セッション期限管理
230
+ @@map("sessions")
231
+ }
232
+
233
+ model SecurityLog {
234
+ id String @id @default(cuid())
235
+ userId String
236
+ eventType String // LOGIN, LOGOUT, TOKEN_REQUEST, etc.
237
+ ipAddress String?
238
+ deviceInfo String?
239
+ metadata Json?
240
+ createdAt DateTime @default(now())
241
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
242
+
243
+ @@index([userId, eventType]) // イベントタイプ別の監査
244
+ @@index([createdAt]) // 時系列での分析
245
+ @@map("security_logs")
246
+ }
247
+ ```
248
+
249
+ ### API エンドポイント
250
+
251
+ | メソッド | エンドポイント | 説明 | リクエスト | レスポンス |
252
+ |---------|---------------|------|------------|------------|
253
+ | POST | `/api/auth/magic-link` | マジックリンク送信 | `{email: string}` | `{success: boolean, message: string}` |
254
+ | GET | `/api/auth/verify` | トークン検証 | `?token=xxx` | リダイレクト or エラー |
255
+ | POST | `/api/auth/logout` | ログアウト | - | `{success: boolean}` |
256
+ | GET | `/api/auth/session` | セッション確認 | - | `{user: User \| null}` |
257
+ | POST | `/api/auth/resend` | リンク再送信 | `{email: string}` | `{success: boolean, message: string}` |
258
+
259
+ ### フロントエンドコンポーネント
260
+
261
+ ```typescript
262
+ // ディレクトリ構造
263
+ /components/auth/
264
+ ├── MagicLinkForm.tsx // メールアドレス入力フォーム
265
+ ├── VerificationMessage.tsx // 送信確認メッセージ
266
+ ├── TokenVerifying.tsx // トークン検証中の表示
267
+ └── SessionProvider.tsx // 認証状態管理
268
+
269
+ /hooks/auth/
270
+ ├── useMagicLink.ts // マジックリンク送信
271
+ ├── useSession.ts // セッション管理
272
+ └── useAuth.ts // 認証状態
273
+
274
+ /services/auth/
275
+ ├── authApi.ts // API通信
276
+ └── tokenStorage.ts // トークン管理
277
+ ```
278
+
279
+ ## 画面設計
280
+
281
+ ### 画面ワイヤーフレーム(Mermaid図)
282
+
283
+ #### 1. ログイン画面 (Login Screen)
284
+
285
+ ```mermaid
286
+ graph TB
287
+ subgraph "ログイン画面"
288
+ Header[ヘッダー: アプリケーション名/ロゴ]
289
+ Title[タイトル: マジックリンクでログイン]
290
+ Description[説明: メールアドレスを入力してログインリンクを受け取ります]
291
+ EmailField[入力フィールド: メールアドレス<br/>placeholder: your@email.com]
292
+ ValidationError[バリデーションエラー表示エリア<br/>条件付き表示: エラー時のみ赤文字]
293
+ SubmitBtn[ボタン: ログインリンクを送信<br/>状態: デフォルト/ローディング/無効化]
294
+ LoadingIndicator[ローディングスピナー<br/>条件付き表示: 送信中のみ]
295
+ PasswordLink[リンク: パスワードでログイン<br/>※並行運用期間中のみ]
296
+
297
+ Header --> Title
298
+ Title --> Description
299
+ Description --> EmailField
300
+ EmailField --> ValidationError
301
+ ValidationError --> SubmitBtn
302
+ SubmitBtn --> LoadingIndicator
303
+ LoadingIndicator --> PasswordLink
304
+ end
305
+ ```
306
+
307
+ **レイアウト詳細**:
308
+ - 中央寄せレイアウト、最大幅480px
309
+ - 各要素間の余白: 16px
310
+ - フィールド高さ: 48px
311
+ - ボタン高さ: 48px
312
+
313
+ **状態管理**:
314
+ - デフォルト: 全要素表示、バリデーションエラーは非表示
315
+ - 入力中: フィールドにフォーカスインジケーター表示
316
+ - 送信中: ボタン無効化、ローディングスピナー表示
317
+ - エラー: バリデーションエラーメッセージ表示、フィールド赤枠
318
+
319
+ #### 2. メール送信確認画面 (Email Sent Screen)
320
+
321
+ ```mermaid
322
+ graph TB
323
+ subgraph "メール送信確認画面"
324
+ Header[ヘッダー: アプリケーション名/ロゴ]
325
+ SuccessIcon[アイコン: メール送信成功✓]
326
+ Title[タイトル: メールを送信しました]
327
+ Message[メッセージ: {email}宛にログインリンクを送信しました<br/>メールを確認してリンクをクリックしてください]
328
+ ExpiryNote[注意書き: リンクは15分間有効です]
329
+ Divider[区切り線]
330
+ NoEmailTitle[サブタイトル: メールが届きませんか?]
331
+ CheckSpam[案内: 迷惑メールフォルダも確認してください]
332
+ ResendBtn[ボタン: 再送信<br/>状態: カウントダウン中は無効/1分後に有効化]
333
+ Countdown[カウントダウン表示: 00:XX<br/>条件付き表示: 1分間のみ]
334
+ TryAnotherLink[リンク: 別のメールアドレスを試す]
335
+
336
+ Header --> SuccessIcon
337
+ SuccessIcon --> Title
338
+ Title --> Message
339
+ Message --> ExpiryNote
340
+ ExpiryNote --> Divider
341
+ Divider --> NoEmailTitle
342
+ NoEmailTitle --> CheckSpam
343
+ CheckSpam --> ResendBtn
344
+ ResendBtn --> Countdown
345
+ Countdown --> TryAnotherLink
346
+ end
347
+ ```
348
+
349
+ **レイアウト詳細**:
350
+ - 中央寄せレイアウト、最大幅540px
351
+ - 成功アイコンサイズ: 64x64px
352
+ - 再送信ボタンは1分間カウントダウン表示
353
+
354
+ **状態管理**:
355
+ - 初期表示: 再送信ボタン無効、カウントダウン60秒開始
356
+ - カウントダウン中: ボタンに「再送信 (00:XX)」と表示
357
+ - カウントダウン完了: ボタン有効化、「再送信」のみ表示
358
+
359
+ #### 3. トークン検証画面 (Token Verification Screen)
360
+
361
+ ```mermaid
362
+ graph TB
363
+ subgraph "トークン検証画面"
364
+ Header[ヘッダー: アプリケーション名/ロゴ]
365
+ Spinner[ローディングスピナー: 大サイズ]
366
+ Message[メッセージ: 認証しています...<br/>しばらくお待ちください]
367
+ ProgressBar[プログレスバー: インジケーター<br/>条件付き表示: 3秒以上経過時]
368
+
369
+ Header --> Spinner
370
+ Spinner --> Message
371
+ Message --> ProgressBar
372
+ end
373
+ ```
374
+
375
+ **レイアウト詳細**:
376
+ - 中央寄せレイアウト、最小高さ400px
377
+ - スピナーサイズ: 48x48px
378
+ - 自動リダイレクト: 検証成功後3秒以内
379
+
380
+ **状態管理**:
381
+ - 検証中: スピナー表示、メッセージ表示
382
+ - 3秒以上: プログレスバー追加表示
383
+ - 検証完了: 自動リダイレクト
384
+
385
+ #### 4. エラー画面 (Error Screen)
386
+
387
+ ```mermaid
388
+ graph TB
389
+ subgraph "エラー画面"
390
+ Header[ヘッダー: アプリケーション名/ロゴ]
391
+ ErrorIcon[アイコン: エラーアイコン ⚠️]
392
+ ErrorTitle[タイトル: エラー種別により変動<br/>- リンクの有効期限が切れています<br/>- このリンクは既に使用されています<br/>- 無効なリンクです<br/>- リクエスト回数の上限に達しました]
393
+ ErrorMessage[メッセージ: エラー詳細と対処法]
394
+ ActionBtn[ボタン: リカバリーアクション<br/>- 新しいリンクを送信<br/>- ログインページに戻る<br/>- 時間経過後リトライ]
395
+ RetryAfter[リトライ可能時刻表示<br/>条件付き表示: レート制限エラー時のみ]
396
+ BackLink[リンク: ホームに戻る]
397
+
398
+ Header --> ErrorIcon
399
+ ErrorIcon --> ErrorTitle
400
+ ErrorTitle --> ErrorMessage
401
+ ErrorMessage --> ActionBtn
402
+ ActionBtn --> RetryAfter
403
+ RetryAfter --> BackLink
404
+ end
405
+ ```
406
+
407
+ **エラー種別ごとの表示内容**:
408
+
409
+ | エラー種別 | タイトル | ボタンラベル | 追加表示 |
410
+ |-----------|---------|------------|---------|
411
+ | TOKEN_EXPIRED | リンクの有効期限が切れています | 新しいリンクを送信 | - |
412
+ | TOKEN_USED | このリンクは既に使用されています | 新しいリンクを送信 | - |
413
+ | TOKEN_INVALID | 無効なリンクです | ログインページに戻る | - |
414
+ | RATE_LIMIT | リクエスト回数の上限に達しました | - | 次回リクエスト可能時刻 |
415
+
416
+ #### 5. セッション無効化確認画面 (Session Revocation Screen)
417
+
418
+ ```mermaid
419
+ graph TB
420
+ subgraph "セッション無効化確認画面"
421
+ Header[ヘッダー: アプリケーション名/ロゴ]
422
+ WarningIcon[アイコン: 警告アイコン ⚠️]
423
+ Title[タイトル: セッションの無効化]
424
+ Message[メッセージ: 以下のセッションを無効化しますか?]
425
+ SessionInfo[セッション情報カード<br/>- デバイス情報<br/>- IPアドレス<br/>- ログイン日時<br/>- 最終アクティビティ]
426
+ WarningNote[警告: この操作は取り消せません]
427
+ ButtonGroup[ボタングループ]
428
+ RevokeBtn[ボタン: 無効化する - 危険アクション]
429
+ CancelBtn[ボタン: キャンセル - 通常アクション]
430
+
431
+ Header --> WarningIcon
432
+ WarningIcon --> Title
433
+ Title --> Message
434
+ Message --> SessionInfo
435
+ SessionInfo --> WarningNote
436
+ WarningNote --> ButtonGroup
437
+ ButtonGroup --> RevokeBtn
438
+ ButtonGroup --> CancelBtn
439
+ end
440
+ ```
441
+
442
+ **レイアウト詳細**:
443
+ - 中央寄せレイアウト、最大幅480px
444
+ - ボタングループ: 横並び配置、間隔12px
445
+ - 無効化ボタン: 赤色系(危険アクション)
446
+ - キャンセルボタン: グレー系(通常アクション)
447
+
448
+ ### 画面遷移フロー図(詳細版)
449
+
450
+ ```mermaid
451
+ stateDiagram-v2
452
+ [*] --> LoginScreen: ユーザーアクセス
453
+
454
+ LoginScreen --> LoginScreen: バリデーションエラー
455
+ LoginScreen --> EmailSentScreen: メール送信成功
456
+
457
+ EmailSentScreen --> EmailSentScreen: 再送信(1分待機後)
458
+ EmailSentScreen --> RateLimitError: レート制限超過
459
+ EmailSentScreen --> LoginScreen: 別のアドレスを試す
460
+
461
+ RateLimitError --> LoginScreen: 時間経過後リトライ
462
+
463
+ note right of EmailSentScreen
464
+ メール受信
465
+
466
+ リンククリック
467
+ end note
468
+
469
+ EmailSentScreen --> VerificationScreen: リンククリック
470
+
471
+ VerificationScreen --> Dashboard: 検証成功
472
+ VerificationScreen --> ExpiredError: トークン期限切れ
473
+ VerificationScreen --> UsedError: トークン使用済み
474
+ VerificationScreen --> InvalidError: トークン無効
475
+
476
+ ExpiredError --> LoginScreen: 新しいリンクを送信
477
+ UsedError --> LoginScreen: 新しいリンクを送信
478
+ InvalidError --> LoginScreen: ログインページに戻る
479
+
480
+ Dashboard --> SessionRevocationScreen: セキュリティ通知から遷移
481
+ SessionRevocationScreen --> SessionRevoked: 無効化実行
482
+ SessionRevocationScreen --> Dashboard: キャンセル
483
+
484
+ SessionRevoked --> [*]: 完了
485
+ Dashboard --> [*]: ログアウト
486
+ ```
487
+
488
+ ## UIインタラクション設計
489
+
490
+ ### コンポーネント詳細仕様
491
+
492
+ #### MagicLinkForm.tsx
493
+
494
+ **Props**:
495
+ ```typescript
496
+ interface MagicLinkFormProps {
497
+ onSubmit: (email: string) => Promise<void>;
498
+ defaultEmail?: string; // エラー後の再表示用
499
+ isLoading?: boolean;
500
+ error?: string | null;
501
+ }
502
+ ```
503
+
504
+ **State**:
505
+ ```typescript
506
+ interface MagicLinkFormState {
507
+ email: string;
508
+ validationError: string | null;
509
+ isSubmitting: boolean;
510
+ focusedField: 'email' | null;
511
+ }
512
+ ```
513
+
514
+ **Events**:
515
+ - `onChange`: メールアドレス入力時
516
+ - `onBlur`: フィールドフォーカスアウト時、バリデーション実行
517
+ - `onSubmit`: フォーム送信時、バリデーション後API呼び出し
518
+
519
+ **バリデーションロジック**:
520
+ ```typescript
521
+ const validateEmail = (email: string): string | null => {
522
+ if (!email.trim()) return 'メールアドレスを入力してください';
523
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
524
+ if (!emailRegex.test(email)) return '有効なメールアドレスを入力してください';
525
+ return null;
526
+ };
527
+ ```
528
+
529
+ **インタラクション詳細**:
530
+ 1. **フィールドフォーカス**
531
+ - フォーカス時: 枠線色変更
532
+ - フォーカスアウト時: バリデーション実行
533
+
534
+ 2. **送信ボタンクリック**
535
+ - バリデーション実行
536
+ - エラーがあれば表示してreturn
537
+ - エラーなければボタン無効化、ローディング表示
538
+ - API呼び出し
539
+ - 成功時: 確認画面へ遷移
540
+ - 失敗時: エラー表示、ボタン再有効化
541
+
542
+ 3. **ローディング状態**
543
+ - ボタン内にスピナー表示
544
+ - 3秒以上かかる場合: 追加メッセージ表示
545
+
546
+ #### VerificationMessage.tsx
547
+
548
+ **Props**:
549
+ ```typescript
550
+ interface VerificationMessageProps {
551
+ email: string;
552
+ onResend: () => Promise<void>;
553
+ onChangeEmail: () => void;
554
+ expiryMinutes?: number; // デフォルト: 15
555
+ }
556
+ ```
557
+
558
+ **State**:
559
+ ```typescript
560
+ interface VerificationMessageState {
561
+ resendCountdown: number; // 秒単位
562
+ canResend: boolean;
563
+ isResending: boolean;
564
+ }
565
+ ```
566
+
567
+ **Events**:
568
+ - `onResend`: 再送信ボタンクリック時
569
+ - `onChangeEmail`: 別のアドレスを試すリンククリック時
570
+
571
+ **カウントダウンロジック**:
572
+ ```typescript
573
+ useEffect(() => {
574
+ const timer = setInterval(() => {
575
+ setCountdown(prev => {
576
+ if (prev <= 1) {
577
+ setCanResend(true);
578
+ return 0;
579
+ }
580
+ return prev - 1;
581
+ });
582
+ }, 1000);
583
+
584
+ return () => clearInterval(timer);
585
+ }, []);
586
+ ```
587
+
588
+ **インタラクション詳細**:
589
+ 1. **初期表示**
590
+ - カウントダウン60秒開始
591
+ - 再送信ボタン無効化
592
+ - カウントダウン表示: 「再送信 (00:XX)」
593
+
594
+ 2. **カウントダウン完了**
595
+ - 再送信ボタン有効化
596
+ - ボタンラベル: 「再送信」
597
+
598
+ 3. **再送信ボタンクリック**
599
+ - ボタン無効化、ローディング表示
600
+ - API呼び出し
601
+ - 成功時: カウントダウンリセット
602
+ - 失敗時: エラー表示
603
+
604
+ #### TokenVerifying.tsx
605
+
606
+ **Props**:
607
+ ```typescript
608
+ interface TokenVerifyingProps {
609
+ token: string;
610
+ onSuccess: (user: User) => void;
611
+ onError: (error: TokenVerificationError) => void;
612
+ }
613
+ ```
614
+
615
+ **State**:
616
+ ```typescript
617
+ interface TokenVerifyingState {
618
+ status: 'verifying' | 'success' | 'error';
619
+ elapsedTime: number; // 秒単位
620
+ showProgressBar: boolean;
621
+ }
622
+ ```
623
+
624
+ **Events**:
625
+ - `useEffect`: マウント時に自動的にトークン検証開始
626
+ - `onVerificationComplete`: 検証完了時
627
+
628
+ **検証フロー**:
629
+ ```typescript
630
+ useEffect(() => {
631
+ const verifyToken = async () => {
632
+ try {
633
+ const result = await authApi.verifyToken(token);
634
+ setStatus('success');
635
+ setTimeout(() => onSuccess(result.user), 3000);
636
+ } catch (error) {
637
+ setStatus('error');
638
+ onError(error);
639
+ }
640
+ };
641
+
642
+ verifyToken();
643
+ }, [token]);
644
+ ```
645
+
646
+ **インタラクション詳細**:
647
+ 1. **検証中**
648
+ - ローディングスピナー表示
649
+ - メッセージ: 「認証しています...」
650
+ - 3秒経過後: プログレスバー追加表示
651
+
652
+ 2. **検証成功**
653
+ - メッセージ変更: 「ログインしています...」
654
+ - 3秒以内に自動リダイレクト
655
+
656
+ 3. **検証失敗**
657
+ - エラー画面へ遷移
658
+ - エラー種別に応じたメッセージとアクション表示
659
+
660
+ ### フォームバリデーション設計
661
+
662
+ **バリデーションタイミング**:
663
+ - リアルタイムバリデーション: 実施しない(UX配慮)
664
+ - フォーカスアウト時: 実施する
665
+ - 送信時: 必ず実施する
666
+
667
+ **エラー表示ルール**:
668
+ - エラーメッセージ: フィールド直下に赤文字で表示
669
+ - フィールド枠: エラー時は赤色に変更
670
+ - エラー自動消去: しない(ユーザーが修正するまで表示)
671
+
672
+ ### ローディング状態管理
673
+
674
+ **ローディングインジケーター**:
675
+ - 短時間(< 3秒): ボタン内スピナーのみ
676
+ - 長時間(≥ 3秒): 追加メッセージ表示
677
+
678
+ **ローディング中のユーザー操作制限**:
679
+ - ボタン無効化
680
+ - フォーム入力フィールド無効化
681
+ - 戻るボタン/リロードの警告表示(検証画面)
682
+
683
+ ### エラーハンドリングUI
684
+
685
+ **エラー表示パターン**:
686
+
687
+ 1. **インラインエラー** (フォームバリデーション)
688
+ - 位置: フィールド直下
689
+ - 色: 赤色
690
+ - アイコン: エラーアイコン(小)
691
+
692
+ 2. **ページレベルエラー** (API エラー)
693
+ - 位置: 専用のエラー画面
694
+ - 色: 赤色系
695
+ - アイコン: エラーアイコン(大)
696
+ - リカバリーアクションボタン必須
697
+
698
+ 3. **通知バナーエラー** (セキュリティ通知)
699
+ - 位置: ページ上部
700
+ - 色: 黄色系(警告)または赤色系(重大)
701
+ - 閉じるボタン: あり
702
+
703
+ ## エラーハンドリング
704
+
705
+ ### エラー分類とコード体系
706
+
707
+ 認証システムで発生する可能性のあるエラーを以下のように分類します:
708
+
709
+ 1. **検証エラー (VALIDATION_ERROR)**
710
+ - 無効なメールアドレス形式
711
+ - 必須フィールドの欠落
712
+ - 不正な入力値
713
+
714
+ 2. **認証エラー (AUTH_ERROR)**
715
+ - 期限切れトークン
716
+ - 使用済みトークン
717
+ - 無効なトークン
718
+ - セッション期限切れ
719
+
720
+ 3. **レート制限エラー (RATE_LIMIT_ERROR)**
721
+ - 短時間での過剰なリクエスト
722
+ - 1日の送信上限超過
723
+
724
+ 4. **システムエラー (SYSTEM_ERROR)**
725
+ - データベース接続エラー
726
+ - メール送信サービスの障害
727
+ - 内部サーバーエラー
728
+
729
+ ### エラー処理戦略
730
+
731
+ - **ユーザー向けメッセージ**: 技術的詳細を含まない、理解しやすいメッセージを表示
732
+ - **開発者向けログ**: 詳細なスタックトレースとコンテキスト情報を記録
733
+ - **リトライ可能性の提示**: エラーの種類に応じて、再試行ボタンや代替アクションを提供
734
+ - **フォールバック処理**: メール送信失敗時は再送信オプションを提供
735
+
736
+ ## セキュリティ考慮事項
737
+
738
+ ### 認証・認可の実装
739
+
740
+ > **📖 権限マトリクスの詳細は [requirements.md「権限マトリクス」セクション](./requirements.md#権限マトリクス) を参照**
741
+
742
+ 1. **トークンのセキュリティ**
743
+ - 暗号学的に安全な256ビットのランダムトークン生成
744
+ - bcryptによるトークンのハッシュ化保存
745
+ - 使用後の即座の無効化
746
+
747
+ 2. **レート制限**
748
+ - IPアドレスベース: 1分あたり3回まで
749
+ - メールアドレスベース: 1分あたり1回まで
750
+ - 1日あたりの上限: 同一メールで20回まで
751
+
752
+ 3. **セッション管理**
753
+ - HTTPOnly Cookieでのセッション管理
754
+ - Secure フラグの設定(HTTPS環境)
755
+ - SameSite属性によるCSRF対策
756
+
757
+ 4. **権限制御**
758
+ - ロールベースアクセス制御(RBAC)を採用
759
+ - Guest / User / Admin / System の4階層
760
+ - API各エンドポイントで権限チェックを実施
761
+ - 詳細な操作可否は権限マトリクスに準拠
762
+
763
+ ### データ保護戦略
764
+
765
+ - **個人情報の暗号化**: メールアドレスなどの個人情報は暗号化して保存
766
+ - **トークンの安全な保存**: ハッシュ化により、データベース漏洩時もトークンを保護
767
+ - **通信の暗号化**: HTTPS必須、TLS 1.2以上
768
+ - **ログのサニタイゼーション**: 個人情報やトークンをログに含めない
769
+
770
+ ## パフォーマンス最適化
771
+
772
+ - **キャッシュ戦略**
773
+ - Redisによるセッション情報のキャッシュ
774
+ - レート制限カウンターのメモリキャッシュ
775
+ - 静的アセットのCDN配信
776
+
777
+ - **データベース最適化**
778
+ - 適切なインデックスによるクエリ高速化
779
+ - 期限切れトークンの定期削除バッチ
780
+ - コネクションプーリングの活用
781
+
782
+ - **非同期処理**
783
+ - メール送信のキューイング
784
+ - バックグラウンドでのセキュリティログ記録
785
+ - 非同期バリデーション処理
786
+
787
+ ## テスト設計
788
+
789
+ ### 単体テスト
790
+
791
+ #### 正常系テストケース
792
+
793
+ 1. **トークン生成処理**
794
+ - Given: 有効なメールアドレスが提供される
795
+ - When: トークン生成関数を実行する
796
+ - Then: 256ビットの暗号学的に安全なトークンが生成される
797
+
798
+ 2. **メール送信処理**
799
+ - Given: 有効なトークンとメールアドレスが存在する
800
+ - When: メール送信サービスを呼び出す
801
+ - Then: SendGrid APIが正しいパラメータで呼び出される
802
+
803
+ #### 異常系テストケース
804
+
805
+ 1. **無効なメールアドレス**
806
+ - Given: 不正な形式のメールアドレスが入力される
807
+ - When: バリデーション処理を実行する
808
+ - Then: VALIDATION_ERRORが返され、処理が中断される
809
+
810
+ 2. **レート制限超過**
811
+ - Given: 同一IPから1分以内に4回目のリクエストが来る
812
+ - When: レート制限チェックを実行する
813
+ - Then: RATE_LIMIT_ERRORが返され、429ステータスコードが返される
814
+
815
+ ### 統合テスト
816
+
817
+ - データベースとの連携確認
818
+ - メールサービスとの通信テスト
819
+ - キャッシュとセッションストアの動作確認
820
+ - 複数コンポーネント間のデータフロー検証
821
+
822
+ ### E2Eテスト
823
+
824
+ 1. **完全な認証フロー**
825
+ - Given: 新規ユーザーがログインページにアクセス
826
+ - When: メールアドレスを入力し、受信したリンクをクリック
827
+ - Then: ダッシュボードにログインでき、セッションが確立される
828
+
829
+ 2. **トークン期限切れシナリオ**
830
+ - Given: 15分以上経過したマジックリンクを持つユーザー
831
+ - When: そのリンクをクリックする
832
+ - Then: 期限切れエラーが表示され、再送信オプションが提示される
833
+
834
+ ## マイグレーション戦略
835
+
836
+ Prismaを使用しているため、通常のスキーマ変更は`prisma migrate dev`で自動処理されます。
837
+
838
+ ### 特別なデータ移行が必要なケース
839
+
840
+ 既存のパスワード認証システムからの移行時:
841
+ 1. 既存ユーザーのメールアドレスを保持
842
+ 2. パスワードフィールドを段階的に非推奨化
843
+ 3. 移行期間中は両方の認証方式を並行運用
844
+ 4. 全ユーザーの移行完了後、パスワード関連のコードを削除
845
+
846
+ ## モニタリングと分析
847
+
848
+ - **収集するメトリクス**
849
+ - マジックリンクのリクエスト数と成功率
850
+ - トークンの有効期限切れ率
851
+ - メール配信成功率
852
+ - 平均認証完了時間
853
+
854
+ - **アラート設定**
855
+ - メール送信失敗率が5%を超えた場合
856
+ - 認証成功率が90%を下回った場合
857
+ - レート制限エラーが急増した場合
858
+
859
+ ## 実装上の注意点
860
+
861
+ ### コード品質とセキュリティ
862
+
863
+ - トークン生成には必ず暗号学的に安全な乱数生成器を使用すること
864
+ - 環境変数で機密情報を管理し、ハードコーディングは絶対に避ける
865
+ - すべてのユーザー入力に対して適切なバリデーションとサニタイゼーションを実施
866
+ - エラーメッセージに機密情報や実装の詳細を含めない
867
+
868
+ ### パフォーマンスとスケーラビリティ
869
+
870
+ - データベースクエリは必要最小限に抑え、N+1問題を回避
871
+ - 非同期処理を活用し、ユーザーの待ち時間を最小化
872
+ - キャッシュを適切に活用するが、セキュリティ情報のキャッシュは慎重に
873
+
874
+ ### 保守性と拡張性
875
+
876
+ - 認証ロジックを独立したサービスとして実装し、疎結合を維持
877
+ - 設定値は環境変数やコンフィグファイルで管理し、変更を容易に
878
+ - ログは構造化形式で出力し、分析や監視を容易に
879
+ - テストカバレッジ80%以上を維持し、リグレッションを防止