セキュリティログプラグイン (Security Log Plugin)¶
概要¶
顧客のログイン試行を詳細に記録し、不正アクセスの検知やカスタマーサポートの迅速化を実現するプラグイン。AES-256-GCM による認証付き暗号化、IPベースの位置情報特定、およびログイン失敗時の管理者即時通知機能を提供します。
補足: ログイン入力パスワードを可逆暗号化で保持し復号表示を許容する仕様は、顧客要件として合意済みです。
参照:docs/specifications/2026-03-security-log-password-retention-requirement.md
パッケージ情報¶
- パッケージ名:
@ritsubi/plugins-security-log - パス:
/packages/plugins/security-log - エントリーポイント:
src/index.ts
実装機能¶
1. ログイン試行の自動記録¶
Shop API および Admin API の login ミューテーションを LoginLogInterceptor
が自動的にキャプチャし、以下の情報を LoginLog
エンティティとしてデータベースに保存します。Admin 管理者ログインも対象に含まれるため、不正アクセスの監視を顧客側・管理者側の双方で行えます。
| 項目 | 説明 | 備考 |
|---|---|---|
identifier |
試行されたメールアドレス | |
encryptedPassword |
入力されたパスワード | AES-256-GCMで暗号化 |
ipAddress |
クライアントのIPアドレス | |
location |
概算の位置情報 | geoip-lite による解析結果(JSON) |
success |
ログイン成否 | |
failureReason |
失敗理由 | エラーコード(例: INVALID_CREDENTIALS_ERROR) |
createdAt |
試行時刻 | 日本標準時 (JST) で表示可能 |
2. パスワードの安全な保存と復号¶
顧客が「パスワードが合っているはずなのにログインできない」と問い合わせた際、管理者が実際に入力された内容を確認できるように、可逆暗号化を採用しています。
この可逆暗号化運用は、一般的な「パスワード非保持」のベストプラクティスとの差分を認識したうえで、顧客業務要件として適用しています。
- 暗号化方式: AES-256-GCM (Authenticated Encryption)
- 鍵管理: 環境変数
SECURITY_LOG_ENCRYPTION_KEYを使用。 - 改ざん検知: GCMモードにより、保存データの改ざんを検知可能。
- 復号表示: 特定の権限を持つ管理者のみ、管理画面 API を通じて平文パスワード(
decryptedPassword)を確認できます。
3. 管理者向けメール通知(複数宛先対応)¶
ログイン失敗が発生した際、管理者に即座にアラートメールを送信します。
- トリガー: ログイン失敗(成功時は送信されません)
- 通知内容: 試行メールアドレス、IPアドレス、推定位置、失敗理由、発生時刻。
- テンプレート: MJML形式による日本語メールテンプレート (
login-failure)。 - 複数宛先: 通知先は React Dashboard の [設定] > [セキュリティログ] からチップ形式 UI で複数登録可能。SettingsStore key は
security.login-failure-notify-email。 - 文字列・配列・
{ notifyEmails: string[] }のいずれの保存形式も受け付け、SecurityLogService.extractNotifyEmailsがカンマ区切りに正規化してLoginFailedEvent.notifyEmailに渡す。 - 設定が空の場合は環境変数
LOGIN_FAILURE_NOTIFY_EMAIL、最後に@ritsubi/configのDEFAULT_LOGIN_FAILURE_NOTIFY_EMAILの順でフォールバックする。
4. 閲覧制限と権限管理¶
セキュリティログは機密性が高いため、アクセス権限を厳格に管理しています。
- 専用権限:
ReadSecurityLog - 閲覧可能者:
SuperAdminまたは上記権限を明示的に付与された「ロール」を持つ管理者のみ。 - スタッフへの制限: 通常のCSスタッフ用ロールにはこの権限を付与しないことで、ログの露出を防げます。
技術仕様¶
プラグイン初期化¶
import { SecurityLogPlugin, loginFailureHandler } from "@ritsubi/plugins";
// vendure-config.ts
export const config: VendureConfig = {
plugins: [
SecurityLogPlugin.init({}),
EmailPlugin.init({
handlers: [
// 通知先は LoginFailedEvent.notifyEmail(カンマ区切り)から動的に解決される。
// ハンドラ側で setRecipient(event => event.notifyEmail) を構成し、複数宛先を許容する。
loginFailureHandler.setRecipient((event) => event.notifyEmail),
],
// ...
}),
],
};
通知先文字列は
SecurityLogService.resolveNotifyEmail(ctx)が SettingsStore → env → デフォルトの順で解決し、LoginFailedEvent経由でハンドラへ渡されます。アプリ側でグローバル recipient を直書きしないでください。
データベースモデル (LoginLog)¶
- テーブル名:
login_log - カラム:
identifier,encryptedPassword,ipAddress,location,success,failureReason
設定・運用¶
環境変数¶
| 変数名 | 説明 | 推奨値 |
|---|---|---|
SECURITY_LOG_ENCRYPTION_KEY |
パスワード暗号化用の秘密鍵 | 32byte の鍵(推奨: openssl rand -base64 32) |
LOGIN_FAILURE_NOTIFY_EMAIL |
失敗通知の宛先 | alerts@ritsubi-platform.com |
※ SECURITY_LOG_ENCRYPTION_KEY
を変更すると、それ以前に保存された過去のログ(パスワード)は復号できなくなります(Decryption
failed と表示されます)。
管理画面での確認¶
- React Dashboard の [設定] > [セキュリティログ] ページにて、ログイン失敗通知の送信先メールアドレスをチップ形式 UI で複数登録・削除できます。
- ページへの導線は
ReadSecurityLog/ReadSettings/UpdateSettingsのいずれかで表示されます。 - 通知先メールの現在値確認には
ReadSettings、更新にはUpdateSettingsが必要です。 - ログイン試行履歴の閲覧を許可する場合は、[設定] > [ロール] から
ReadSecurityLog権限を付与してください。 - ログイン試行履歴の
channelIdフィルタは、Admin API ログインで channel が紐づかないケースを許容するため null 安全化されています(login-log.interceptor/login-log.resolver)。
セキュリティ考慮事項¶
- 生パスワードの非保持: データベース上には暗号文のみが存在します。
- ブルートフォース対策: このログを活用して、短期間に同一IPから多数の失敗がある場合の監視が可能です。
- GDPR/プライバシー: IPアドレスと位置情報を保持するため、プライバシーポリシーへの記載を検討してください。
- 業務要件による例外運用: 可逆暗号化・復号表示は顧客要件に基づく例外運用であり、
ReadSecurityLog権限と監査運用を必須とします。
開発ガイド¶
テストの実行¶
cd packages/plugins/security-log
pnpm test
すべてのロジック(暗号化・復号・インターセプター・イベント発行)はユニットテストで検証済みです。