コンテンツにスキップ

セキュリティログプラグイン (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/configDEFAULT_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 と表示されます)。

管理画面での確認

  1. React Dashboard の [設定] > [セキュリティログ] ページにて、ログイン失敗通知の送信先メールアドレスをチップ形式 UI で複数登録・削除できます。
  2. ページへの導線は ReadSecurityLog / ReadSettings / UpdateSettings のいずれかで表示されます。
  3. 通知先メールの現在値確認には ReadSettings、更新には UpdateSettings が必要です。
  4. ログイン試行履歴の閲覧を許可する場合は、[設定] > [ロール] から ReadSecurityLog 権限を付与してください。
  5. ログイン試行履歴の channelId フィルタは、Admin API ログインで channel が紐づかないケースを許容するため null 安全化されています(login-log.interceptor / login-log.resolver)。

セキュリティ考慮事項

  1. 生パスワードの非保持: データベース上には暗号文のみが存在します。
  2. ブルートフォース対策: このログを活用して、短期間に同一IPから多数の失敗がある場合の監視が可能です。
  3. GDPR/プライバシー: IPアドレスと位置情報を保持するため、プライバシーポリシーへの記載を検討してください。
  4. 業務要件による例外運用: 可逆暗号化・復号表示は顧客要件に基づく例外運用であり、ReadSecurityLog 権限と監査運用を必須とします。

開発ガイド

テストの実行

cd packages/plugins/security-log
pnpm test

すべてのロジック(暗号化・復号・インターセプター・イベント発行)はユニットテストで検証済みです。