First commit

This commit is contained in:
CyberRex
2026-05-23 17:03:05 +09:00
commit 40e7953ee5
52 changed files with 13004 additions and 0 deletions

372
AGENTS.md Normal file
View File

@@ -0,0 +1,372 @@
# CertRemind 開発ガイド
## 概要
- アプリケーション名: CertRemind
- 目的: ユーザーが登録した Web サイトの TLS/SSL 証明書の有効期限を監視し、期限切れ前にアプリ内アラート、Webhook、Push 通知で知らせる。
- 現在の実装状況: `development_plan.md` のフェーズ 8 までの機能を実装済み。MVP の主要機能に加え、基本的な品質向上、テスト、運用準備も整備済み。
## 技術スタック
- Node.js v22
- pnpm
- Hono.js
- React.js / Vite
- Radix UI
- lucide-react
- PostgreSQL
- Docker Compose
- OpenSSL
- Argon2id
- TOTP: `otplib`
- Push 通知: `web-push`
- QR コード: `qrcode.react`
- テスト: Vitest
- Lint / Format: ESLint / Prettier
## 主要コマンド
```text
pnpm install
docker compose up -d postgres
pnpm dev
pnpm monitor:once
pnpm monitor:worker
pnpm lint
pnpm test
pnpm exec vite build
pnpm format
```
開発サーバー:
```text
Frontend: http://127.0.0.1:5173/
API: http://127.0.0.1:3000
```
## ディレクトリ構成
```text
db/schema.sql
README.md
public/push-sw.js
src/client
src/client/api/client.js
src/client/components
src/client/components/Toast.jsx
src/client/routes
src/client/styles/app.css
src/server
src/server/app.js
src/server/index.js
src/server/config/env.js
src/server/db/pool.js
src/server/middleware
src/server/modules
src/server/jobs/monitorCertificates.js
src/server/jobs/monitorWorker.js
src/server/utils/logger.js
tests/apiSecurity.test.js
tests/monitoring.test.js
tests/urlPolicy.test.js
```
## 環境変数
`.env.example` を参照すること。
主な項目:
- `NODE_ENV`
- `PORT`
- `DATABASE_URL`
- `COOKIE_SECRET`
- `VAPID_PUBLIC_KEY`
- `VAPID_PRIVATE_KEY`
- `VAPID_SUBJECT`
- `OPENSSL_PATH`
補足:
- `OPENSSL_PATH` 未設定時は `openssl` を使う。
- Windows では Git 付属の `openssl.exe` を自動検出する実装がある。
- VAPID private key が未設定の場合、Push 実送信は失敗として `delivery_result` に記録される。
## データベース
DDL は `db/schema.sql` に保管している。
実装済みテーブル:
- `users`
- `user_totp`
- `sessions`
- `sites`
- `notification_methods`
- `site_alert_conditions`
- `alert_history`
設計メモ:
- 主キーは UUID。
- 全テーブルに `created_at` / `updated_at` を持つ。
- `updated_at` はトリガーで更新する。
- `sites` は最新の証明書期限、確認日時、取得失敗内容を保持する。
- ユーザー関連データは `users` 削除を起点に CASCADE で削除する。
- サイト削除時、通知条件は CASCADE で削除する。
- アラート履歴の `site_id` はサイト削除時に `NULL` へ変更する。
- アラート重複抑制は `alert_history.dedupe_key` を使う。
注意:
- `docker-compose.yml` は初回 DB 起動時に `db/schema.sql` を読み込む。既存 volume がある場合、schema の変更は自動再適用されない。
## 実装済み API
```text
GET /api/health
GET /api/auth/csrf
POST /api/auth/register
POST /api/auth/login
POST /api/auth/logout
GET /api/auth/me
GET /api/sites
POST /api/sites
GET /api/sites/:siteId
PATCH /api/sites/:siteId
DELETE /api/sites/:siteId
GET /api/sites/:siteId/settings
PUT /api/sites/:siteId/settings
DELETE /api/sites/:siteId/settings
GET /api/alerts
PATCH /api/alerts/:alertId/read
DELETE /api/alerts/:alertId
GET /api/notification-methods
POST /api/notification-methods/webhooks
PATCH /api/notification-methods/webhooks/:methodId
DELETE /api/notification-methods/webhooks/:methodId
POST /api/notification-methods/push-subscriptions
GET /api/account
PATCH /api/account/profile
PATCH /api/account/password
POST /api/account/totp/setup
POST /api/account/totp/verify
DELETE /api/account/totp
DELETE /api/account
```
## 実装済み画面
認証前:
- 登録画面
- ログイン画面
- 2 段階認証コード入力
認証後:
- URL ルーティング
- `/sites`
- `/sites/:siteId/settings`
- `/alerts`
- `/notifications`
- `/account`
- `/login`
- `/register`
- 直リンク、初期読込み、ブラウザバック / フォワードに対応
- 未認証で保護画面 URL を開いた場合はログイン画面を表示し、ログイン後に元の画面へ復帰する
- 左サイドメニュー
- PC 幅では展開表示
- 720px 以下ではアイコンのみの畳み表示
- サイト一覧、アラート履歴、通知方法、アカウント、ログアウトに対応
- 共通トースト通知
- 認証後画面の正常メッセージ、エラーメッセージを右上に表示
- 5 秒後の自動非表示と手動クローズに対応
- ログイン / 登録画面のエラーはインライン表示を維持
- サイト一覧
- サイト追加
- 最新監視結果に基づく証明書期限表示
- 確認ダイアログ付きサイト削除
- サイト設定
- エイリアス編集
- 通知タイミング設定
- 時間 / 日 / 週間の単位指定
- 複数タイミングの追加
- 確認ダイアログ付き通知タイミング削除
- アプリ内アラート必須表示
- Webhook 選択
- Push 通知フラグ設定
- 確認ダイアログ付き設定削除
- アラート一覧
- サイト絞り込み
- アラート種類絞り込み
- 開始日時 / 終了日時絞り込み
- 既読更新
- 確認ダイアログ付き履歴削除
- 通知方法管理
- Webhook 登録
- Webhook 編集
- 確認ダイアログ付き Webhook 削除
- ブラウザ Push 通知の許可状態表示
- VAPID public key がある場合の Push 購読登録
- アカウント設定
- 表示名更新
- ダイアログでのパスワード更新
- パスワード更新後の全セッション無効化
- ステップ式ポップアップでの 2 段階認証セットアップ
- 2 段階認証 QR コード表示
- シークレット文字列表示
- 確認ダイアログ付き 2 段階認証解除
- 確認ダイアログ付きアカウント削除
## 入力検証
サーバー側:
- Zod でリクエストを検証する。
- クライアントから送られる `user_id` は信用しない。
- 認証済み API はセッションのユーザー ID を使う。
- SQL はプレースホルダを使う。
クライアント側:
- 登録 / ログイン、サイト、Webhook、アラート絞り込み、アカウント設定で入力必須チェックと形式チェックを実装済み。
- クライアント側検証は UX 用であり、サーバー側検証を省略してはいけない。
## 認証・セッション
- パスワードは Argon2id でハッシュ化する。
- セッションは `sessions` テーブルで管理する。
- セッション Cookie は HttpOnly / SameSite=Lax。
- 本番環境では Secure Cookie を有効化する。
- CSRF トークンを状態変更 API で必須にする。
- パスワード更新時は対象ユーザーの全セッションを削除し、現在の Cookie も削除する。
- TOTP が有効なユーザーはログイン時に OTP 検証が必須。
## サイト管理
- サイト URL は HTTPS のみ許可する。
- URL は `normalizeHttpsUrl` で正規化する。
- `localhost`、private IPv4、loopback IPv4 は拒否する。
- サイト API は常にセッションユーザーの所有データのみ扱う。
- サイト設定の通知タイミングは内部的に時間単位で保存する。
- 通知タイミングは 1 時間以上、17520 時間以内。
- 同じサイトに同じ通知タイミングを重複登録できない。
## アラート履歴
- アラート履歴は `alert_history` に保存する。
- アラート一覧はログインユーザーの履歴のみ返す。
- 既読更新、削除もログインユーザーの履歴のみ対象にする。
- 絞り込み条件:
- サイト
- アラート種類
- 開始日時
- 終了日時
- アラート重複抑制には `dedupe_key` を使う。
## 通知方法
Webhook:
- HTTPS のみ許可する。
- `normalizeHttpsUrl` を通し、localhost / private IPv4 / loopback IPv4 を拒否する。
- Slack 互換 Webhook として送信する。
- 更新・削除はログインユーザーの通知方法のみ対象。
Push:
- ブラウザ Push 購読情報を `notification_methods` に保存する。
- endpoint は HTTPS のみ許可する。
- Service Worker は `public/push-sw.js`
- VAPID key は環境変数で管理する。
## 証明書監視ジョブ
一回実行:
```text
pnpm monitor:once
```
定期実行 worker:
```text
pnpm monitor:worker
```
処理内容:
- 登録サイトを取得する。
- OpenSSL で証明書期限を取得する。
- 成功時は `sites.certificate_expires_at` / `certificate_checked_at` を更新し、取得失敗内容をクリアする。
- 失敗時は `sites.certificate_checked_at` / `certificate_check_error` を更新し、既存の期限値は残す。
- サイトごとの通知条件を評価する。
- 条件一致時にアラート履歴を作成する。
- Webhook / Push 通知を送信する。
- 証明書取得失敗も `certificate_check_failed` としてアラート化する。
- サイト単位の失敗で全体を止めない。
- 外部通信を伴う監視処理は並列数を制限する。
- Webhook / Push の送信失敗は `delivery_result` に記録する。
- 監視ジョブの開始、終了、失敗は構造化ログで出力する。
- `pnpm monitor:worker` は 1 時間ごとに監視ジョブを実行する長時間起動プロセス。
注意:
- サンドボックス内では外部 TLS 接続が `Permission denied` になる場合がある。その場合は権限付き実行が必要。
- 定期実行は `pnpm monitor:worker` で提供済み。運用要件に応じて cron やタスクスケジューラから `pnpm monitor:once` を実行する構成も選択できる。
## セキュリティ方針
- リクエスト改ざんに注意する。
- クライアントから送信されてきたデータを信頼しない。
- CSRF トークンを使用する。
- パスワードを平文保存しない。
- SQL インジェクションを避けるためプレースホルダを使う。
- 認可はセッションユーザーを基準に判定する。
- URL ルーティングは表示状態の復元用であり、認可は必ず API 側のセッションユーザー基準で判定する。
- 未認証時の復帰先 URL はアプリ内の許可済みパスのみ扱い、外部 URL へのリダイレクトに使わない。
- 1 リクエストがサーバー全体の処理をブロックしないようにする。
- 外部通信はタイムアウトや並列数制限を設定する。
- 削除操作には確認ダイアログを置く。
- API の未捕捉エラーは構造化ログで記録する。
## テスト・運用準備
- `tests/urlPolicy.test.js` で URL 正規化と基本的な SSRF 対策を検証する。
- `tests/apiSecurity.test.js` で CSRF、認証必須、セッションユーザー基準の認可、Webhook URL 検証、通知条件の所有者確認を検証する。
- `tests/monitoring.test.js` で証明書監視成功 / 失敗時の DB 更新、アラート作成、通知呼び出しを検証する。
- `README.md` に開発環境起動手順、検証コマンド、DB 注意点、環境変数一覧を記載済み。
- サンドボックス環境では Vite / Vitest の設定解決で権限付き実行が必要になる場合がある。
## 既知の強化候補
- API 単体テスト / 統合テストをさらに増やす。
- ログイン試行回数制限を追加する。
- SSRF 対策として DNS 解決後の IP チェックを追加する。
- IPv6 private / link-local / unique local address の検証を追加する。
- セッションローテーションを追加する。
- E2E テストを追加する。
## 開発時の注意
- `development_status.md` は作業後に更新する。
- DB 変更を行う場合は `db/schema.sql` も更新する。
- API を追加したら、認証、CSRF、認可、入力検証を確認する。
- UI の削除操作には確認ダイアログを追加する。
- 既存の左サイドメニューとレスポンシブ挙動を崩さない。
- フロントエンドの入力チェックを追加しても、サーバー側検証を必ず維持する。
- 変更後は少なくとも以下を実行する。
```text
pnpm lint
pnpm test
pnpm exec vite build
```