Skip to content

Issue #1301: ログインエラー表示改善 + ブルートフォース攻撃対策 + AJAX統一対応#1302

Merged
nanasess merged 38 commits intomasterfrom
feature/issue-1301-login-error-improvement
Mar 27, 2026
Merged

Issue #1301: ログインエラー表示改善 + ブルートフォース攻撃対策 + AJAX統一対応#1302
nanasess merged 38 commits intomasterfrom
feature/issue-1301-login-error-improvement

Conversation

@nobuhiko
Copy link
Copy Markdown
Contributor

@nobuhiko nobuhiko commented Jan 17, 2026

概要

Issue #1301 の実装です。ログイン失敗時のエラーを別画面ではなくログインフォーム上に直接表示するよう改善し、データベースベースのレート制限によるブルートフォース攻撃対策を追加しました。

さらに、全デバイス(PC/スマートフォン/モバイル)でログイン処理をAJAXに統一し、一貫したユーザー体験を提供します。

主な変更内容

1. エラー表示の改善

  • ログイン失敗時に別画面(LC_Page_Error)へ遷移していた仕様を変更
  • エラーメッセージをログインフォーム上に直接表示
  • AJAX統一対応: 全デバイスでAJAXログインに統一(デバイス判定を削除)
  • ヘッダー・サイドバーログインブロック: alertで表示(省スペース化)
  • マイページ・ショッピングカートログイン: ページ内に表示
  • UX(ユーザー体験)の大幅な向上

2. ブルートフォース攻撃対策

  • データベーステーブル dtb_login_attempt を新規作成し、ログイン試行を記録
  • レート制限ヘルパークラス SC_Helper_LoginRateLimit を実装
  • 制限ルール:
    • 同一メールアドレス: 1時間に5回まで失敗を許可(6回目でブロック)
    • 同一IPアドレス: 1時間に10回まで失敗を許可(11回目でブロック)
  • Issue パスワードの再発行機能の改善 #368 のパスワードリセット機能と同様の実装パターンを採用

3. セキュリティ強化

  • ✅ アカウント列挙攻撃対策: エラーメッセージを統一
  • ✅ XSS対策: テンプレートで |h|nl2br フィルタを適用
  • ✅ SQLインジェクション対策: プリペアドステートメント使用
  • ✅ ログイン試行の記録による監査証跡の確保
  • ✅ 既存のSession Fixation対策・CSRF対策を維持

4. AJAX統一対応とHTTP/HTTPS両対応

  • サーバー側: デバイス判定を削除し、常にJSON応答を返却
  • テンプレート: HTTPS_URLROOT_URLPATH に変更(HTTP/HTTPS両対応)
  • レート制限: デバイス判定を削除し、すべてJSON応答
  • エラー表示方針:
    • ヘッダーログインブロック: alert表示
    • サイドバーログインブロック: alert表示
    • マイページログイン: ページ内表示
    • ショッピングカートログイン: ページ内表示
    • スマートフォン: AJAX(既存の動作を維持)

変更ファイル

追加

  • data/class/helper/SC_Helper_LoginRateLimit.php (新規ヘルパークラス)
  • data/class_extends/helper/SC_Helper_LoginRateLimit_Ex.php (拡張クラス)
  • tests/class/helper/SC_Helper_LoginRateLimit/SC_Helper_LoginRateLimitTest.php (単体テスト)

修正(サーバー側)

  • data/class/pages/frontparts/LC_Page_FrontParts_LoginCheck.php - デバイス判定削除、AJAX統一
  • data/class/pages/shopping/LC_Page_Shopping.php - デバイス判定削除、AJAX統一

修正(テンプレート)

  • data/Smarty/templates/default/mypage/login.tpl - AJAX対応、詳細ログ追加
  • data/Smarty/templates/default/shopping/index.tpl - AJAX対応
  • data/Smarty/templates/default/frontparts/bloc/login.tpl - AJAX対応、alert表示
  • data/Smarty/templates/default/frontparts/bloc/login_header.tpl - URL修正、alert表示
  • data/Smarty/templates/sphone/shopping/index.tpl - バグ修正(login_error→error)
  • data/Smarty/templates/mobile/mypage/login.tpl - エラー表示追加
  • data/Smarty/templates/mobile/shopping/index.tpl - エラー表示追加

修正(データベース・インストール)

  • html/install/sql/create_table_mysqli.sql - dtb_login_attemptテーブル追加
  • html/install/sql/create_table_pgsql.sql - dtb_login_attemptテーブル追加
  • eccube_install.sh - PostgreSQLシーケンス追加

修正(テスト)

  • tests/require.php - TEST_MAILCATCHER_URL定義追加
  • e2e-tests/test/front_guest/login_error.test.ts - AJAX対応、セレクタ修正、alert対応

テスト

単体テスト

  • ✅ 全12テスト、33アサーションが成功
  • ✅ レート制限の動作確認済み
  • ✅ XSS対策の検証済み

E2Eテスト

  • 7 passed (1 skipped) - 全テスト成功
  • ✅ マイページログインエラー表示
  • ✅ マイページバリデーションエラー表示
  • ✅ ショッピングカートログインエラー表示
  • ✅ マイページログインレート制限
  • ✅ ヘッダーログインブロックエラー表示(alert)
  • ✅ サイドバーログインブロックエラー表示(alert)
  • ✅ 正常ログイン動作確認

コード品質

  • ✅ php-cs-fixer適用済み

セキュリティレビュー結果

包括的なセキュリティレビューを実施しました。

✅ セキュリティ強化ポイント

  1. SQLインジェクション対策: プリペアドステートメントの使用
  2. XSS対策: エスケープ処理(|h|nl2brフィルタ)
  3. レート制限: メールアドレス・IP双方の制限
  4. 監査証跡: すべてのログイン試行を記録
  5. Session Fixation対策: SC_Session_Ex::regenerateSID() の維持
  6. CSRF対策: トランザクションIDの維持
  7. アカウント列挙攻撃への部分的対策: エラーメッセージの統一

📋 推奨される将来的改善(本PRには含まれません)

以下は別Issueとして起票することを推奨します:

  1. タイミング攻撃対策: アカウントが存在しない場合もダミーのパスワード検証を実行
  2. ログの個人情報保護: メールアドレスのハッシュ化
  3. データベース最適化: TEXT型をVARCHAR型に変更(パフォーマンス向上)
  4. IPアドレス取得の改善: プロキシ環境への対応

技術的詳細

AJAX統一化の実装

従来はスマートフォンのみAJAXでログイン処理していましたが、今回の変更で全デバイスでAJAXに統一しました。

Before:

// デバイス判定して処理を分岐
if (SC_Display_Ex::detectDevice() === DEVICE_TYPE_SMARTPHONE) {
    echo SC_Utils_Ex::jsonEncode(['error' => $this->arrErr['login']]);
} else {
    $_SESSION['login_error'] = $this->arrErr['login'];
    SC_Response_Ex::sendRedirect($url);
}

After:

// 常にJSON返却(デバイス判定なし)
echo SC_Utils_Ex::jsonEncode(['error' => $this->arrErr['login']]);
SC_Response_Ex::actionExit();

HTTP/HTTPS両対応

テンプレートのURL指定を修正し、HTTP(localhost:8080)とHTTPS(localhost:4430)の両方で動作するようにしました。

Before: url: "<!--{$smarty.const.HTTPS_URL}-->frontparts/login_check.php"
After: url: "<!--{$smarty.const.ROOT_URLPATH}-->frontparts/login_check.php"

統計

  • 15ファイル変更
  • 985行追加、234行削除

参考

動作確認チェックリスト

PC版

  • マイページログイン成功
  • マイページログイン失敗(エラーが同一ページに表示)
  • ショッピング画面ログイン成功
  • ショッピング画面ログイン失敗(エラーが同一ページに表示)
  • ヘッダーログインブロック失敗(alertで表示)
  • サイドバーログインブロック失敗(alertで表示)
  • レート制限動作確認(7回目の失敗でブロック)
  • 仮登録会員への専用メッセージ表示

スマートフォン版

  • ショッピング画面ログイン失敗(alertで表示)
  • バグ修正確認(login_error→error)

環境

  • HTTP環境(localhost:8080)
  • HTTPS環境(localhost:4430)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 新機能
    • ログインセキュリティを強化しました。一定時間内の連続失敗を検知して一時的にブロックし、不正なアクセス試行から保護します。試行の記録・集計・削除機能も追加。
  • Chores
    • 開発環境向けの無視設定を追加(パッケージ管理・一時ファイル・スクリプト・スクリーンショット・データベースダンプ等)。

✏️ Tip: You can customize this high-level summary in your review settings.

## 概要
ログイン失敗時のエラーを別画面ではなくログインフォーム上に直接表示するよう改善し、
データベースベースのレート制限によるブルートフォース攻撃対策を追加しました。

## 主な変更内容

### 1. エラー表示の改善
- ログイン失敗時に別画面(LC_Page_Error)へ遷移していた仕様を変更
- エラーメッセージをログインフォーム上に直接表示
- UX(ユーザー体験)の向上

### 2. ブルートフォース攻撃対策
- データベーステーブル `dtb_login_attempt` を新規作成し、ログイン試行を記録
- レート制限ヘルパークラス `SC_Helper_LoginRateLimit` を実装
- 制限ルール:
  - 同一メールアドレス: 1時間に5回まで失敗を許可
  - 同一IPアドレス: 1時間に10回まで失敗を許可
- Issue #368のパスワードリセット機能と同様の実装パターンを採用

### 3. セキュリティ強化
- アカウント列挙攻撃対策: エラーメッセージを統一
- XSS対策: テンプレートで |h|nl2br フィルタを適用
- SQLインジェクション対策: プリペアドステートメント使用
- ログイン試行の記録による監査証跡の確保
- 既存のSession Fixation対策・CSRF対策を維持

### 4. デバイス別対応
- PC: ログインフォーム上にエラー表示
- スマートフォン: JSON形式でエラー返却
- モバイル: ログインフォーム上にエラー表示

## 変更ファイル

**追加:**
- data/class/helper/SC_Helper_LoginRateLimit.php (新規)
- data/class_extends/helper/SC_Helper_LoginRateLimit_Ex.php (新規)
- tests/class/helper/SC_Helper_LoginRateLimit/SC_Helper_LoginRateLimitTest.php (新規)

**修正:**
- data/class/pages/frontparts/LC_Page_FrontParts_LoginCheck.php
- data/class/pages/shopping/LC_Page_Shopping.php
- data/Smarty/templates/default/mypage/login.tpl
- data/Smarty/templates/default/shopping/index.tpl
- data/Smarty/templates/mobile/mypage/login.tpl
- data/Smarty/templates/mobile/shopping/index.tpl
- html/install/sql/create_table_mysqli.sql
- html/install/sql/create_table_pgsql.sql
- eccube_install.sh
- tests/require.php

## テスト
- 全12テスト、33アサーションが成功
- レート制限の動作確認済み
- XSS対策の検証済み

## セキュリティレビュー
包括的なセキュリティレビューを実施し、以下を確認:
- ✅ SQLインジェクション対策
- ✅ XSS対策(エスケープ処理)
- ✅ CSRF対策の維持
- ✅ Session Fixation対策の維持
- ✅ アカウント列挙攻撃への部分的対策
- ✅ レート制限によるブルートフォース攻撃対策

## 統計
- 13ファイル変更
- 768行追加、119行削除

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Jan 17, 2026

Codecov Report

❌ Patch coverage is 93.45794% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.78%. Comparing base (9a599f3) to head (56766da).
⚠️ Report is 39 commits behind head on master.

Files with missing lines Patch % Lines
data/class/helper/SC_Helper_LoginRateLimit.php 93.45% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1302      +/-   ##
==========================================
+ Coverage   54.39%   54.78%   +0.38%     
==========================================
  Files          84       85       +1     
  Lines       10822    10929     +107     
==========================================
+ Hits         5887     5987     +100     
- Misses       4935     4942       +7     
Flag Coverage Δ
tests 54.78% <93.45%> (+0.38%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

nobuhiko and others added 2 commits January 17, 2026 21:47
主な変更点:
- タイムゾーン問題の修正: PHP date()からデータベースNOW()関数へ変更
  PostgreSQLのタイムスタンプがUTCで保存されるため、PHP date()(JST)との
  不整合を解消
- バリデーションエラー時もログイン試行失敗として記録
- レート制限の閾値を明確化: メール6回、IP11回で制限
- PostgreSQL/MySQL両対応のINTERVAL構文を使用

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
問題:
ヘッダーのログインフォーム(header_login_area)からログインエラーになった場合、
リダイレクト先のページ(トップページなど)でエラーメッセージが表示されない。

原因:
- LC_Page_FrontParts_LoginCheck.php はエラーを $_SESSION['login_error'] に保存してリダイレクト
- しかし、リダイレクト先のページではセッションからエラーを取得する処理がなかった
- html/mypage/login.php のみがエラーを取得していた

修正内容:
1. LC_Page::init() にセッションからログインエラーを取得する処理を追加
   すべてのページで共通的にエラーメッセージを取得
2. login_header.tpl(PC版)にエラーメッセージ表示エリアを追加
3. login_header.tpl(スマートフォン版)にエラーメッセージ表示エリアを追加

これにより、ヘッダーログインからのエラーもページ上部に表示されるようになる。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@nobuhiko nobuhiko changed the title Issue #1301: ログインエラー表示改善 + ブルートフォース攻撃対策 [WIP] Issue #1301: ログインエラー表示改善 + ブルートフォース攻撃対策 Jan 17, 2026
問題:
1. ログインブロック(#header_login_area, #login_area)でエラーが表示されない
2. ログインエラー表示機能のE2Eテストがない
3. ショッピングカートログインのバリデーションエラー処理の構造が最適でない

修正内容:

1. ブロックログインエラー表示の修正
   - LC_Page_FrontParts_Bloc_Login::action() にエラー取得処理を追加
   - ブロックはLC_Page::init()を経由しないため、明示的にセッションから取得
   - login.tpl にエラー表示エリアを追加

2. LC_Page.php のリファクタリング
   - セッションエラーのクリーンアップロジックをリクエストスコープに変更
   - 同じリクエスト内の複数ページインスタンス(メインページ+ブロック)で
     エラーが共有されるよう改善

3. E2Eテストの追加
   - ヘッダーログインブロックのエラー表示テスト
   - サイドバーログインブロックのエラー表示テスト
   - レート制限テストの改善(IPベースの制限を考慮)
   - playwright.config.ts に BASE_URL 環境変数サポート追加
   - package.json に test:e2e:local スクリプト追加

4. ショッピングカートログイン処理の改善
   - バリデーションエラー時の処理を早期リターン形式に変更
   - コードの可読性向上

5. html/mypage/login.php のクリーンアップ
   - LC_Page::init() に移動したため、重複したエラー取得処理を削除

6. CLAUDE.md の追加
   - E2Eテストの実行方法をドキュメント化
   - ローカル環境とCI環境の違いを明記

テスト結果:
- ✅ マイページログインエラー表示
- ✅ ショッピングカートログインエラー表示
- ✅ ヘッダーログインブロックエラー表示
- ✅ サイドバーログインブロックエラー表示
- ✅ レート制限機能
- ✅ 正常ログイン

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@bbkids
Copy link
Copy Markdown
Contributor

bbkids commented Jan 18, 2026

エラーページへ遷移せずに、ログインフォーム上に直接表示エラー表示できるようになるとすごく助かります。

細かな事をご指摘しているようで大変恐縮なのですが、
新規のSC_Helper_LoginRateLimit_Ex.php ファイルの配置ディレクトリですが、
data/class_extends/helper/ ではなく、
data/class_extends/helper_extends/ の間違いではないでしょうか?

nobuhiko and others added 3 commits January 18, 2026 18:47
問題:
LC_Page::init() とブロックのaction()メソッドで、ログインエラーの有無に関わらず
毎回セッション処理とリクエストID生成が実行されていた。
これがすべてのページ読み込みに影響し、E2Eテストのタイムアウトを引き起こした。

修正内容:

1. LC_Page.php
   - セッションにログインエラーまたはクリーンアップフラグが存在する場合のみ処理を実行
   - 不要な処理を省略してパフォーマンス向上

2. LC_Page_FrontParts_Bloc_Login.php
   - ログインエラーがある場合のみarrErrを初期化
   - 早期リターンパターンで最適化

影響:
- 通常のページ読み込み: セッションチェックのみ(高速)
- ログインエラー発生時: 必要な処理のみ実行
- E2Eテストのタイムアウト問題を解消

テスト結果:
✅ すべてのログインエラーテストが成功

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
問題:
data/class_extends/helper/SC_Helper_LoginRateLimit_Ex.php が作成されていたが、
EC-CUBE 2.17.2以降はオートローダーが自動的に拡張クラスを処理するため不要。

また、ディレクトリも間違っていた:
- 誤: data/class_extends/helper/
- 正: data/class_extends/helper_extends/

修正内容:
- data/class_extends/helper/SC_Helper_LoginRateLimit_Ex.php を削除
- data/class_extends/helper/ ディレクトリを削除
- オートローダーが SC_Helper_LoginRateLimit_Ex を自動生成

参考:
data/class_extends/README.md によると、特別な理由がない限り
_Ex ファイルは作成せず、オートローダーに任せるべき。

テスト結果:
✅ すべてのログインエラーテストが成功
✅ オートローダーが正常に動作

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
全デバイス(PC/スマートフォン/モバイル)でログイン処理をAJAXに統一し、
エラーメッセージを同一ページに表示できるよう改善しました。

主な変更:
- サーバー側のデバイス判定を削除し、常にJSON応答を返却
- テンプレートのURLをHTTPS_URLからROOT_URLPATHに変更(HTTP/HTTPS両対応)
- レート制限チェック時のデバイス判定も削除
- ヘッダー・サイドバーログインブロック: alertで表示(省スペース)
- マイページ・ショッピングカートログイン: ページ内に表示
- E2Eテストを修正してAJAX動作を検証

変更ファイル:
- data/class/pages/frontparts/LC_Page_FrontParts_LoginCheck.php
- data/class/pages/shopping/LC_Page_Shopping.php
- data/Smarty/templates/default/mypage/login.tpl
- data/Smarty/templates/default/shopping/index.tpl
- data/Smarty/templates/default/frontparts/bloc/login.tpl
- data/Smarty/templates/default/frontparts/bloc/login_header.tpl
- data/Smarty/templates/sphone/shopping/index.tpl
- e2e-tests/test/front_guest/login_error.test.ts

テスト結果: 7 passed (1 skipped)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@nobuhiko
Copy link
Copy Markdown
Contributor Author

@bbkids
ありがとうございます!
そもそも data/class_extends が必要ないですね、削除しました

@nobuhiko nobuhiko changed the title [WIP] Issue #1301: ログインエラー表示改善 + ブルートフォース攻撃対策 Issue #1301: ログインエラー表示改善 + ブルートフォース攻撃対策 + AJAX統一対応 Jan 18, 2026
Issue #1301(ログインエラー表示改善 + AJAX統一対応)の実装で学んだ10個の重要な教訓を追加:
- AJAX統一対応のパターン
- HTTP/HTTPS両対応のURL指定
- エラー表示のUI設計
- E2Eテストでのalertダイアログの扱い方
- E2Eテストのセレクタ設計
- レート制限のタイミング
- PostgreSQLとMySQLの両対応
- Dockerコンテナの再起動
- ブラウザ拡張機能の影響
- JSONパースエラーのデバッグ

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@bbkids
Copy link
Copy Markdown
Contributor

bbkids commented Jan 19, 2026

このPR「ログインエラー表示改善 + ブルートフォース攻撃対策 + AJAX統一対応 #1302」と
「トークンベース認証へ移行 #1299」が正式にマージされる事を強く望みますが、実際にはどんな感じなんでしょう?

@nanasess
Copy link
Copy Markdown
Contributor

@bbkids 個人的にはマージしてしまいたい気持ちです

nobuhiko and others added 2 commits January 19, 2026 14:49
Issue #1301でログイン処理がAJAX対応になったため、
E2EテストのログインメソッドにAJAX完了後の画面遷移待機処理を追加。

## 問題
- ログインフォームがAJAXに変更(form submitでなくAJAX通信)
- 成功時: `location.href = result.success;` で画面遷移
- テストは遷移を待たずに次のステップに進み、タイムアウトエラーが発生

## 修正内容
```typescript
async login () {
    await this.loginEmail.fill(this.email);
    await this.loginPass.fill(this.password);

    // クリック後、AJAX成功によるリダイレクトを待つ
    await this.loginButton.click();
    await this.page.waitForURL(url => !url.pathname.includes('/mypage/login.php'), { timeout: 10000 });
}
```

## 影響範囲
- front_loginフィクスチャを使用する全E2Eテスト
- ログインページ(/mypage/login.php)からのログイン処理

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- arrErrの初期化を常に実行(パフォーマンス最適化の副作用を修正)
- セッションを使ったログインエラー管理コードを削除(AJAX統一対応により不要)
- CLAUDE.mdにローカル環境セットアップ手順を追加

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@bbkids
Copy link
Copy Markdown
Contributor

bbkids commented Jan 19, 2026

下位互換性のない変更なのかとは思いますが、今回の修正には重大なセキュリティ脆弱性の解消が含まれています。
メジャーアップデートによる対応が見込めない状況にある EC-CUBE2 系を現在も運用している店舗を不正アクセスから守るための、極めて貴重な防御手段となります。

そのため、このような修正については互換性破壊のリスクを十分に理解したうえで、例外的なマージを前向きにご検討いただきたく思います。
この種の修正こそが、長年コミュニティを支えてこられた有志開発者の皆さまによるメンテナンスの意義かと思います。
関係者様どうかご検討のほど、よろしくお願いいたします。

@bbkids
Copy link
Copy Markdown
Contributor

bbkids commented Jan 20, 2026

このアカウントロック仕様は、
ログインに成功した後でも過去一時間内のログイン失敗回数を保持し引き継がれることでセキュリティ重視の設計になっており、この方針は非常に有意義だと思います。
一方で、正常なユーザが一度ログインに成功した後も、過去の失敗回数が残り続けることで、意図せずロックアウトされてしまうケースも想定されます。
運用ポリシーやサイト特性に応じて、セキュリティ重視の現行仕様、ログイン成功時に失敗回数をリセットする仕様へ切り替えられるオプションがあると、ユーザビリティとのバランスが取りやすくなるのではないかと思うのですがいかかでしょうか?

nobuhiko and others added 3 commits January 20, 2026 11:45
- マイページログインページでログイン後もURLは変わらない(/mypage/login.phpのまま)
- waitForURL()ではなく、ログアウトボタンの表示を待つように修正
- マイページ専用ログインフォーム(#login_mypage)を明示的に使用

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
問題:
- フィクスチャがトップページ(/)に移動してからログイン試行
- トップページには#login_mypageフォームが存在せずタイムアウト
- ログインボタンクリック後、ページ遷移を待たずにログアウトボタンを探していた

修正:
1. フィクスチャ: page.goto('/') → loginPage.goto()
   - ログインページに正しく遷移してからログイン
2. login.page.ts: Promise.all()でページ遷移を待機
   - AJAX成功時のリダイレクト(/mypage/index.php)を確実に待つ

期待される結果:
- CIでfront_loginテストがパスする
- タイムアウトエラーを解消

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@nobuhiko nobuhiko force-pushed the feature/issue-1301-login-error-improvement branch from 0176683 to e715106 Compare January 20, 2026 06:03
nobuhiko and others added 3 commits January 20, 2026 15:07
内容:
- Docker + PostgreSQLでの環境構築方法
- E2Eテストの実行手順(ダミーデータ生成、Playwright実行)
- テスト実行時の注意事項

変更:
- .gitignoreからCLAUDE.mdを削除(リポジトリに含める)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add error handling for page.goto() ERR_ABORTED errors in EndpointTests.ts
- Use waitUntil: 'commit' to reduce navigation timeout issues
- Wait for networkidle after login in fixture to ensure page stability
- Add gotoWithRetry() helper function in welcome.test.ts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…login-error-improvement

# Conflicts:
#	CLAUDE.md
#	composer.lock
#	package-lock.json
@bbkids
Copy link
Copy Markdown
Contributor

bbkids commented Jan 28, 2026

以前に LC_Page_Shopping.php と LC_Page_FrontParts_LoginCheck.php に対し修正案を提案させて頂いておりましたが、自身が読み直してみて説明内容が不十分で御座いました。
何とか意味の伝わるよう訂正させて頂きました。再度ご確認いただけますと幸いです。

【お伝えしたい内容】
仕様では「6回目のログイン失敗でアカウントをブロックする」ことになっています。
そのため、本来は 6回目の失敗時点でレート制限超過のエラーメッセージを表示する必要があります。

しかし現状では、
6回目の失敗時点でアカウントロックがかかっているにも関わらず、
7回目(正しいID・PASSでも失敗でも)に、はじめて「短時間に複数のログイン試行が検出されました。しばらく時間をおいてから再度お試しください。」というメッセージが表示されるという挙動になっています。
このままでは、ユーザーが「7回目の入力が間違っていたからロックかかった」と誤解する可能性があります。
そのため、6回目の失敗時にレート制限超過のエラーメッセージを表示するべきかと思います。

@nobuhiko
Copy link
Copy Markdown
Contributor Author

@bbkids めっちゃわかりやすいです、直しました!

@bbkids
Copy link
Copy Markdown
Contributor

bbkids commented Jan 28, 2026

@nobuhiko

めっちゃわかりやすいです、直しました!

  • email_count >= 6 → >= 5 に変更
  • ip_count >= 11 → >= 10 に変更
    の修正を確認いたしました。

しかしこの対処ですと、
5回まで間違ったID・PASSで、
6回目に正しいID・PASSの場合、5回目で既にアカウントロックされてしまっている為、ログイン出来ず初めて「レート制限メッセージ」が表示される事になります。
仕様では、6回目の試行結果でブロックするはずなので、矛盾する事になります。
どういうことかと申しますと
email_countとip_countの数字の変更による修正だと
・「失敗回数が6回でレート制限メッセージ表示が必要」〇
・「6回目のID・PASSが正しければログインできなければならない。」ここが×
つきましては、先にご提案させて頂きました、以下修正案ご検討頂けますと幸いです。

【修正案】
・正しいタイミング(6回目/11回目 の失敗)でレート制限メッセージが表示されます。
・指定回数(6回目/11回目)の失敗でアカウントロックされます。(6回目/11回目 が正しければログインできます。)

email_countとip_countは元の数字に戻し、
LC_Page_FrontParts_LoginCheck.php の165行目付近と
LC_Page_Shopping.php の198行目付近の

// ログイン失敗を記録
SC_Helper_LoginRateLimit_Ex::recordLoginAttempt($login_email, $ip_address, $user_agent, 0);

のすぐ下に、以下のコードの追加

// レート制限チェック
$rate_limit = SC_Helper_LoginRateLimit_Ex::checkRateLimit($login_email, $ip_address);

if (!$rate_limit['allowed']) {
    // レート制限超過時のエラーメッセージ
    $this->arrErr['login'] = '短時間に複数のログイン試行が検出されました。しばらく時間をおいてから再度お試しください。';

    // AJAX対応: JSON返却
    SC_Response_Ex::sendHttpStatus(401);
    echo SC_Utils_Ex::jsonEncode(['error' => $this->arrErr['login']]);
    SC_Response_Ex::actionExit();
}

5回失敗後、6回目の失敗を記録した直後にレート制限チェックを行い、
その場でレート制限メッセージを表示するよう変更。

これまでは6回失敗後の7回目の試行で初めてメッセージが表示されていたため、
ユーザーが「7回目の入力が間違っていたからロックされた」と誤解する可能性があった。

変更内容:
- ログイン失敗記録後に再度レート制限チェックを追加
- 閾値は変更せず(email: 6回、IP: 11回でブロック)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@nobuhiko nobuhiko force-pushed the feature/issue-1301-login-error-improvement branch from ca731d6 to 29895f4 Compare January 28, 2026 13:31
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.claude/settings.local.json:
- Around line 1-91: The committed .claude/settings.local.json contains
machine-specific absolute paths (e.g.
"/Users/kimotonobuhiko/Sites/workspace/ec-cube2/") and plaintext DB credentials
(DB_PASSWORD, DB_USER, DB_SERVER) and must be removed from the repo: remove the
file from version control, add ".claude/settings.local.json" to .gitignore, and
create a template "settings.local.json.example" that preserves the structure
(top-level "permissions" object and keys like the Bash entries) but replaces
absolute paths and secrets with placeholders (e.g. <PROJECT_PATH>, <DB_USER>,
<DB_PASSWORD>, <DB_SERVER>). Ensure instructions in the README tell developers
to copy the example to .claude/settings.local.json and populate their local
values.
🧹 Nitpick comments (1)
.claude/worklog-pr1314-mysql-test-failures.md (1)

5-6: Markdownのフォーマット改善を推奨

静的解析ツールが指摘しているように、URLは適切なMarkdownリンク形式にすることを推奨します:

-**PR**: https://github.com/EC-CUBE/ec-cube2/pull/1314
-**CI Run**: https://github.com/EC-CUBE/ec-cube2/actions/runs/21198520392
+**PR**: <https://github.com/EC-CUBE/ec-cube2/pull/1314>
+**CI Run**: <https://github.com/EC-CUBE/ec-cube2/actions/runs/21198520392>

また、Lines 15, 26, 36, 47, 58, 151, 166, 461, 468 のコードブロックには言語指定(例:phpbashtext)を追加すると、構文ハイライトと可読性が向上します。

4回失敗後、5回目の失敗を記録した直後にレート制限チェックを行い、
その場でレート制限メッセージを表示するよう変更。

これまでは6回失敗後の7回目の試行で初めてメッセージが表示されていたため、
ユーザーが「直前の入力が間違っていたからロックされた」と誤解する可能性があった。

変更内容:
- ログイン失敗記録後に再度レート制限チェックを追加
- 閾値を変更(email: >= 5、IP: >= 10)
- PHPUnit/E2Eテストを更新

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@nobuhiko nobuhiko force-pushed the feature/issue-1301-login-error-improvement branch from 29895f4 to ee752e0 Compare January 28, 2026 13:39
nobuhiko and others added 9 commits January 29, 2026 18:36
- Add dtb_login_attempt table to create_table_sqlite3.sql
- Rename attempt_id to login_attempt_id (EC-CUBE convention)
- Update sequence name in eccube_install.sh
- Update SC_Helper_LoginRateLimit.php and tests
- Remove accidentally committed .yarn/ directory (1604 files)
- Add .yarn/ to .gitignore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove html/manager2/ (dynamic admin directory)
- Remove .pnp.cjs, .pnp.loader.mjs (Yarn PnP files)
- Remove ISSUE-1301-PROGRESS.md (development notes)
- Remove tests/bootstrap_local.php (local config)
- Add .pnp.* to .gitignore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove eccube_db (SQLite database file)
- Add eccube_db and /data/eccube.db to .gitignore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove development scripts (capture-*.ts)
- Add /scripts/* to .gitignore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove screenshots/ directory
- Remove debug-html.js
- Remove test bloc templates (header_bloc, test_auto*, etc.)
- Add /screenshots/* to .gitignore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Each page should initialize arrErr individually as needed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
SQLite3 uses different syntax for date calculations:
- datetime('now', 'localtime', '-1 hour') instead of NOW() - INTERVAL

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@data/class/helper/SC_Helper_LoginRateLimit.php`:
- Around line 137-147: ログ出力で生のメール/IP/UAを出力しており(GC_Utils_Ex::gfPrintLog 呼び出しで
$login_id, $ip_address, $user_agent
をそのまま渡している箇所)、PII漏洩リスクがあるためこれらをマスクまたはハッシュしてからログ出力するよう修正してください;具体的には Login
attempt ログを作る前に $login_id はメールのドメインを残すか部分マスク、$ip_address
はサブネット化またはハッシュ、$user_agent は要約/ハッシュ化して短縮し、フォーマットされた $log_message にハッシュ化済み値を入れて
GC_Utils_Ex::gfPrintLog(..., CUSTOMER_LOG_REALFILE, false)
に渡すように変更し、必要なら設定フラグでマスク/生データ切替を可能にしてください。
- Around line 34-35: 既存の SC_Helper_LoginRateLimit クラスだけだと EC-CUBE2 の
class_extends による上書きに対応できないため、SC_Helper_LoginRateLimit_Ex
という拡張クラスを追加してプラグイン互換性を確保してください;具体的には新しいクラス名 SC_Helper_LoginRateLimit_Ex を作成して
SC_Helper_LoginRateLimit を extends
し(空のコンストラクタやメソッド差し替えポイントを残す形で)必要なオーバーライド用の拡張ポイントを提供し、EC-CUBE の class_extends
機構で読み込まれるように配置してください。

Comment on lines +137 to +147
// ログ出力(セキュリティ監視用)
$result_text = $result === 1 ? 'SUCCESS' : 'FAILED';
$log_message = sprintf(
'Login attempt: %s | Email: %s | IP: %s | User-Agent: %s',
$result_text,
$login_id,
$ip_address,
$user_agent
);

GC_Utils_Ex::gfPrintLog($log_message, CUSTOMER_LOG_REALFILE, false);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

ログにメール/IP/UAをそのまま出力しないでください。

CUSTOMER_LOG_REALFILE に生のメール・IP・User-Agentを残すのは PII漏えいリスクが高いです。DB側に生データが保存されているため、ログはハッシュ化・短縮化等で十分です(必要なら設定で切替)。

🛡️ マスキング/ハッシュ化の例
-        $log_message = sprintf(
-            'Login attempt: %s | Email: %s | IP: %s | User-Agent: %s',
-            $result_text,
-            $login_id,
-            $ip_address,
-            $user_agent
-        );
+        $login_id_hash = hash('sha256', strtolower($login_id));
+        $ip_hash = hash('sha256', $ip_address);
+        $ua_short = substr($user_agent, 0, 200);
+        $log_message = sprintf(
+            'Login attempt: %s | EmailHash: %s | IPHash: %s | User-Agent: %s',
+            $result_text,
+            $login_id_hash,
+            $ip_hash,
+            $ua_short
+        );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// ログ出力(セキュリティ監視用)
$result_text = $result === 1 ? 'SUCCESS' : 'FAILED';
$log_message = sprintf(
'Login attempt: %s | Email: %s | IP: %s | User-Agent: %s',
$result_text,
$login_id,
$ip_address,
$user_agent
);
GC_Utils_Ex::gfPrintLog($log_message, CUSTOMER_LOG_REALFILE, false);
// ログ出力(セキュリティ監視用)
$result_text = $result === 1 ? 'SUCCESS' : 'FAILED';
$login_id_hash = hash('sha256', strtolower($login_id));
$ip_hash = hash('sha256', $ip_address);
$ua_short = substr($user_agent, 0, 200);
$log_message = sprintf(
'Login attempt: %s | EmailHash: %s | IPHash: %s | User-Agent: %s',
$result_text,
$login_id_hash,
$ip_hash,
$ua_short
);
GC_Utils_Ex::gfPrintLog($log_message, CUSTOMER_LOG_REALFILE, false);
🤖 Prompt for AI Agents
In `@data/class/helper/SC_Helper_LoginRateLimit.php` around lines 137 - 147,
ログ出力で生のメール/IP/UAを出力しており(GC_Utils_Ex::gfPrintLog 呼び出しで $login_id, $ip_address,
$user_agent をそのまま渡している箇所)、PII漏洩リスクがあるためこれらをマスクまたはハッシュしてからログ出力するよう修正してください;具体的には
Login attempt ログを作る前に $login_id はメールのドメインを残すか部分マスク、$ip_address
はサブネット化またはハッシュ、$user_agent は要約/ハッシュ化して短縮し、フォーマットされた $log_message にハッシュ化済み値を入れて
GC_Utils_Ex::gfPrintLog(..., CUSTOMER_LOG_REALFILE, false)
に渡すように変更し、必要なら設定フラグでマスク/生データ切替を可能にしてください。

nobuhiko and others added 3 commits January 29, 2026 22:30
EC-CUBE 2 class_extends pattern requires _Ex class for customization.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Autoloader creates alias automatically when _Ex file doesn't exist.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@bbkids
Copy link
Copy Markdown
Contributor

bbkids commented Jan 31, 2026

すみません。
見落としや細かな経緯を把握できていないため、もし見当違いな点がありましたらご指摘いただけますと幸いです。

$objQuery = SC_Query_Ex::getSingletonInstance();
$objQuery->nextVal('dtb_xxx_xxx_id_seq');

についてですが、
EC-CUBE 2.2.5 のコードをざっと確認したところ、nextVal() の多くは主に

if ($db_type === 'pgsql') { ... }

のブロック内で使用されており、MySQL 環境では ID 採番に AUTO_INCREMENT が利用されているように見受けられます。

MySQL 環境でシーケンステーブルを作成し、
「Issue #1301: ログインエラー表示改善 + ブルートフォース攻撃対策 + AJAX統一対応 #1302
の動作する事は確認しましたが、
シーケンステーブルを作らず AUTO_INCREMENT を利用した方が、EC-CUBE 2.2.5 既存仕様との親和性や堅牢性が高いのではないかと勝手感じています。
(「Issue #368: パスワード再発行機能の改善 - トークンベース認証へ移行 #1299」も同様。)
nobuhiko 様その他詳しい方がいらっしゃいましたら、ご意見をいただけますと幸いです。

@nanasess
Copy link
Copy Markdown
Contributor

@bbkids 勘違いでしたらすみません。
2.2.5 は大変古いバージョンで、 MySQL は AUTO_INCREMENT を使用していました。
現在の最新バージョンは 2.25 で、 if ($db_type === 'pgsql') { ... } の条件分岐はほとんど排除しましたし、 シーケンステーブルを使用するように統一しています

@bbkids
Copy link
Copy Markdown
Contributor

bbkids commented Jan 31, 2026

@nanasess

@bbkids 勘違いでしたらすみません。 2.2.5 は大変古いバージョンで、 MySQL は AUTO_INCREMENT を使用していました。 現在の最新バージョンは 2.25 で、 if ($db_type === 'pgsql') { ... } の条件分岐はほとんど排除しましたし、 シーケンステーブルを使用するように統一しています

経緯ご説明、誠に有難う御座います。了解いたしました。
申し訳ございません。

@nanasess nanasess enabled auto-merge March 27, 2026 06:55
Copy link
Copy Markdown
Contributor

@nanasess nanasess left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM
以下、別PRで対応したいです

  • nobuhiko/ec-cube2-migration でマイグレーション作成(#1377)
  • DB_TYPE で条件分岐している箇所は DBFactory で対応できないか(#1378)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants