リクエスト詳細
🐛 バグ報告
対応完了
対象アプリ: AttendanceCore
mypage.php でGETパラメータym変更時にセッション認証がリセットされ再ログインを強制される
## 1. 不具合の内容
マイページ(pages/mypage.php)で月切り替えフォームを送信すると、セッションに`mypage_worker_id`が保存されているにもかかわらず、条件によっては`$worker`がnullになりログイン画面へ戻されることがある。
具体的には、月切り替えフォームはGETリクエスト(`method="get"`)で送信されるが、フォームに`<input type="hidden" name="page" value="mypage">`しか含まれておらず、`ym`パラメータのみ付与される。
この状態でページがリロードされると、コードの先頭で:
```php
$workerId = (int)($_SESSION['mypage_worker_id'] ?? 0);
if ($workerId > 0) {
$worker = worker_find($workerId);
}
```
とセッションから復元しようとするが、**`pages/mypage.php`はGETリクエストでも`$_SERVER['REQUEST_METHOD'] === 'POST'`のブロックを通過したあと、`if (!$worker)`でログインフォームを表示するかどうか判定している**。
ここで問題となるのは、月切り替えGETフォームには`page=mypage`のhiddenがあるが、**実際のフォームのHTMLを確認すると`<input type="hidden" name="page" value="mypage">`が存在し`ym`も送られるため通常は問題ない**ように見えるが、**`worker_find()`がDBエラーや削除済み作業者を返す際に`null`を返すケースで、セッション上のIDが残ったままログイン画面が表示され、かつ`$_SESSION['mypage_worker_id']`がクリアされないため無限ループ状態になる**点が本質的な不具合である。
さらに致命的な問題として、**ログインPOST処理内でPIN照合失敗時に`$workerId`ローカル変数を上書きしてしまう**:
```php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['worker_id'], $_POST['pin'])) {
csrf_check();
$workerId = (int)$_POST['worker_id']; // セッション由来の$workerIdを上書き
```
これにより、セッションで認証済みだった作業者IDが`$_POST['worker_id']`で上書きされ、その後`worker_find($workerId)`が呼ばれることなく(POSTブロック内でのみfetchされ、ブロック外の初期取得は上書き前の変数を使う)、**意図しない作業者のデータが参照されるか、あるいは`$worker`がnullのままログイン画面へ戻る**可能性がある。
## 2. 根拠・発生しそうな条件
- `pages/mypage.php` 冒頭でセッションから`$workerId`を取得し`$worker`を設定するが、直後のPOSTブロックで`$workerId = (int)$_POST['worker_id']`と**同名変数を再代入**している(コード上確認可能)
- ログインPOSTで誤ったPINを入力した場合:`$worker = null`にセットされ`$_SESSION['mypage_worker_id']`はクリアされないが、次回GETアクセス時はセッションから復元されるため、**一度セッション認証済みの状態で別作業者のIDをPOSTすると`$workerId`変数が汚染される**
- `worker_find()`が`null`を返した場合(作業者削除後など)、セッションに古いIDが残り続けるがループ脱出手段がない
## 3. 期待動作
- セッションに有効な`mypage_worker_id`がある場合、GETアクセスでも正しく認証済み状態を維持してマイページを表示する
- POSTブロック内の変数は別名(例:`$postWorkerId`)を使い、セッション由来の`$workerId`を汚染しない
- `worker_find()`がnullを返した場合はセッションをクリア(`unset($_SESSION['mypage_worker_id'])`)してからログインフォームへ誘導する
## 4. 修正方針
**pages/mypage.php を以下のように修正:**
```php
// 修正前
$workerId = (int)($_SESSION['mypage_worker_id'] ?? 0);
if ($workerId > 0) {
$worker = worker_find($workerId);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['worker_id'], $_POST['pin'])) {
csrf_check();
$workerId = (int)$_POST['worker_id']; // ← ここが汚染元
$pin = (string)$_POST['pin'];
...
}
// 修正後
$sessionWorkerId = (int)($_SESSION['mypage_worker_id'] ?? 0);
if ($sessionWorkerId > 0) {
$worker = worker_find($sessionWorkerId);
if (!$worker) {
// 作業者が削除されていた場合はセッションをクリア
unset($_SESSION['mypage_worker_id'], $_SESSION['clock_worker_id']);
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['worker_id'], $_POST['pin'])) {
csrf_check();
$postWorkerId = (int)$_POST['worker_id']; // ← 別名変数を使用
$pin = (string)$_POST['pin'];
$candidate = worker_find($postWorkerId);
if ($candidate && worker_pin_verify($candidate, $pin)) {
worker_pin_rehash_if_needed($candidate, $pin);
$worker = $candidate;
$_SESSION['mypage_worker_id'] = (int)$candidate['id'];
$_SESSION['clock_worker_id'] = (int)$candidate['id'];
} else {
$worker = null;
$error = '作業者IDまたはPINが違います。';
}
}
```
この修正により、変数汚染・セッション残留・削除済み作業者によるループの3つの問題をすべて解消できる。既存の認証フロー・打刻ページとのセッション共有(`clock_worker_id` / `mypage_worker_id`)は維持される。
マイページ(pages/mypage.php)で月切り替えフォームを送信すると、セッションに`mypage_worker_id`が保存されているにもかかわらず、条件によっては`$worker`がnullになりログイン画面へ戻されることがある。
具体的には、月切り替えフォームはGETリクエスト(`method="get"`)で送信されるが、フォームに`<input type="hidden" name="page" value="mypage">`しか含まれておらず、`ym`パラメータのみ付与される。
この状態でページがリロードされると、コードの先頭で:
```php
$workerId = (int)($_SESSION['mypage_worker_id'] ?? 0);
if ($workerId > 0) {
$worker = worker_find($workerId);
}
```
とセッションから復元しようとするが、**`pages/mypage.php`はGETリクエストでも`$_SERVER['REQUEST_METHOD'] === 'POST'`のブロックを通過したあと、`if (!$worker)`でログインフォームを表示するかどうか判定している**。
ここで問題となるのは、月切り替えGETフォームには`page=mypage`のhiddenがあるが、**実際のフォームのHTMLを確認すると`<input type="hidden" name="page" value="mypage">`が存在し`ym`も送られるため通常は問題ない**ように見えるが、**`worker_find()`がDBエラーや削除済み作業者を返す際に`null`を返すケースで、セッション上のIDが残ったままログイン画面が表示され、かつ`$_SESSION['mypage_worker_id']`がクリアされないため無限ループ状態になる**点が本質的な不具合である。
さらに致命的な問題として、**ログインPOST処理内でPIN照合失敗時に`$workerId`ローカル変数を上書きしてしまう**:
```php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['worker_id'], $_POST['pin'])) {
csrf_check();
$workerId = (int)$_POST['worker_id']; // セッション由来の$workerIdを上書き
```
これにより、セッションで認証済みだった作業者IDが`$_POST['worker_id']`で上書きされ、その後`worker_find($workerId)`が呼ばれることなく(POSTブロック内でのみfetchされ、ブロック外の初期取得は上書き前の変数を使う)、**意図しない作業者のデータが参照されるか、あるいは`$worker`がnullのままログイン画面へ戻る**可能性がある。
## 2. 根拠・発生しそうな条件
- `pages/mypage.php` 冒頭でセッションから`$workerId`を取得し`$worker`を設定するが、直後のPOSTブロックで`$workerId = (int)$_POST['worker_id']`と**同名変数を再代入**している(コード上確認可能)
- ログインPOSTで誤ったPINを入力した場合:`$worker = null`にセットされ`$_SESSION['mypage_worker_id']`はクリアされないが、次回GETアクセス時はセッションから復元されるため、**一度セッション認証済みの状態で別作業者のIDをPOSTすると`$workerId`変数が汚染される**
- `worker_find()`が`null`を返した場合(作業者削除後など)、セッションに古いIDが残り続けるがループ脱出手段がない
## 3. 期待動作
- セッションに有効な`mypage_worker_id`がある場合、GETアクセスでも正しく認証済み状態を維持してマイページを表示する
- POSTブロック内の変数は別名(例:`$postWorkerId`)を使い、セッション由来の`$workerId`を汚染しない
- `worker_find()`がnullを返した場合はセッションをクリア(`unset($_SESSION['mypage_worker_id'])`)してからログインフォームへ誘導する
## 4. 修正方針
**pages/mypage.php を以下のように修正:**
```php
// 修正前
$workerId = (int)($_SESSION['mypage_worker_id'] ?? 0);
if ($workerId > 0) {
$worker = worker_find($workerId);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['worker_id'], $_POST['pin'])) {
csrf_check();
$workerId = (int)$_POST['worker_id']; // ← ここが汚染元
$pin = (string)$_POST['pin'];
...
}
// 修正後
$sessionWorkerId = (int)($_SESSION['mypage_worker_id'] ?? 0);
if ($sessionWorkerId > 0) {
$worker = worker_find($sessionWorkerId);
if (!$worker) {
// 作業者が削除されていた場合はセッションをクリア
unset($_SESSION['mypage_worker_id'], $_SESSION['clock_worker_id']);
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['worker_id'], $_POST['pin'])) {
csrf_check();
$postWorkerId = (int)$_POST['worker_id']; // ← 別名変数を使用
$pin = (string)$_POST['pin'];
$candidate = worker_find($postWorkerId);
if ($candidate && worker_pin_verify($candidate, $pin)) {
worker_pin_rehash_if_needed($candidate, $pin);
$worker = $candidate;
$_SESSION['mypage_worker_id'] = (int)$candidate['id'];
$_SESSION['clock_worker_id'] = (int)$candidate['id'];
} else {
$worker = null;
$error = '作業者IDまたはPINが違います。';
}
}
```
この修正により、変数汚染・セッション残留・削除済み作業者によるループの3つの問題をすべて解消できる。既存の認証フロー・打刻ページとのセッション共有(`clock_worker_id` / `mypage_worker_id`)は維持される。
💬 返信 (3)
🛠 開発を開始しました (バグ修正 (attendance-core))
ご要望ありがとうございます。AI 開発ワーカーが実装を開始します。
通常 5〜30 分で Pull Request を作成し、レビュー後にリリースされます。
ご要望ありがとうございます。AI 開発ワーカーが実装を開始します。
通常 5〜30 分で Pull Request を作成し、レビュー後にリリースされます。
📝 開発が完了しました
ご要望いただいた内容の実装が完了し、最終チェック段階に入りました。
レビュー (自動) → リリース、の流れで進みます。
もう少々お待ちください。
ご要望いただいた内容の実装が完了し、最終チェック段階に入りました。
レビュー (自動) → リリース、の流れで進みます。
もう少々お待ちください。
✅ リリース完了のお知らせ
ご要望いただいた「AttendanceCore」を実装し、リリースいたしました。
【ご利用方法】
ダッシュボード: https://www.aiapps.jp/?action=dashboard
アプリ詳細: https://www.aiapps.jp/apps/show.php?slug=attendance-core
デモ環境は 1 時間以内に自動構築されます:
https://www.aiapps.jp/demo/attendance-core/
ご利用ありがとうございます!
ご要望いただいた「AttendanceCore」を実装し、リリースいたしました。
【ご利用方法】
ダッシュボード: https://www.aiapps.jp/?action=dashboard
アプリ詳細: https://www.aiapps.jp/apps/show.php?slug=attendance-core
デモ環境は 1 時間以内に自動構築されます:
https://www.aiapps.jp/demo/attendance-core/
ご利用ありがとうございます!
Echo
Iris