Skip to content

test(auth): giris akisi sozlesme regresyon testi (E2E, gercek isleyici) - #7

Open
ersinkoc wants to merge 1 commit into
CodeByPinar:mainfrom
ersinkoc:test/login-flow-contract
Open

test(auth): giris akisi sozlesme regresyon testi (E2E, gercek isleyici) #7
ersinkoc wants to merge 1 commit into
CodeByPinar:mainfrom
ersinkoc:test/login-flow-contract

Conversation

@ersinkoc

@ersinkoc ersinkoc commented Sep 11, 2026

Copy link
Copy Markdown

Amaç

Yalnızca test — üretim koduna dokunulmadı. Giriş akışının belgelenmiş sözleşmesine uçtan uca regresyon kapsamı: round-7 E2E avının (giriş yolunda defect bulunamadı) kalıcılaştırılmış hali.

Yöntem

Test, gerçek auth/authenticate.php işleyicisini çocuk süreçlerde çalıştırır:

  • web ortamı yalnızca $_SERVER/$_POST ile simüle edilir (sabit test IP'si 127.0.0.7 — sayaç izolasyonu),
  • gerçek PHP oturumu (dosya handler'ı) + CSRF token'ı ile csrf_require/auth_start/flash üretimdeki gibi çalışır; redirect() exit(0), app_abort STDERR'da ABORT <kod> + exit(1),
  • işçi script çalışma anında repo dışında üretilir — dosya tamamen kendi kendine yeter,
  • users (parola hash'i dahil — işleyici geçerli girişte hash'i PASSWORD_DEFAULT'a yeniden hesaplayabilir) / login_attempts / audit_logs anlık görüntüsü alınır, sonda geri yüklenir; bölüm 5 geri yüklemeyi kendini doğrular.

Kapsanan sözleşme

  1. Geçersiz CSRF → 403 ABORT, hiçbir DB yan etkisi yok.
  2. Geçerli giriş → users.last_login_at güncellenir + başarılı deneme satırı + login audit.
  3. Yanlış parola → başarısız satır + login_failed audit.
  4. Ardından başarılı giriş → o e-postanın başarısız satırları silinir (kilit kalkar).
  5. max_login_attempts (ayarlardan okunur, sabit kodlanmaz) başarısız denemeden sonra doğru parola bile reddedilir; rate-limit yolu yeni deneme satırı yazmaz.
  6. Pasif hesap → login_disabled_account + başarısız satır.

Doğrulama

  • php tools/login_flow_test.php9 OK / 0 FAIL (geri yükleme kontrolleri dahil).
  • php -l temiz.
  • Bağımlılık: yerel MariaDB (README kurulumundaki gibi; geliştirmede docker run -d --name riskops-mariadb ... mariadb:10.11 + config/database.php). Sunucu yoksa test açıkça başarısız olur — sessiz geçiş yok.

Kapsam çiti

Bu PR yalnızca tools/login_flow_test.php dosyasını ekler. Dal doğrudan main üzerindendir; açık diğer PR'lere bağımlılığı yoktur.


Summary by cubic

Adds a login-flow contract regression test that runs the real auth/authenticate.php in child processes with real sessions, CSRF tokens, and database state. Test-only; no production code changes.

  • The worker script is generated at runtime in a temp directory outside the repo, making the test file self-contained.
  • Covers the documented flow contract: invalid CSRF, valid login, wrong password, failed-row cleanup after success, brute-force rate limiting, and disabled accounts.
  • Reads max_login_attempts from settings and verifies the rate-limit path writes no new attempt row.
  • Snapshots users (password hashes included, since the handler may rehash), login_attempts, and audit_logs, restores them afterward, and validates the restore.
  • Requires local MariaDB and fails with exit 1 when unreachable. Run with php tools/login_flow_test.php: 9 OK / 0 FAIL and clean php -l.

Written for commit 3d22ec5. Summary will update on new commits.

Review in cubic

Test-only degisiklik: uretim koduna dokunulmadi.

round-7 E2E avindan kalicilastirilan giris akisi sozlesme testi.
Cocuk sureclerde GERCEK auth/authenticate.php calisir (gercek oturum +
CSRF + veritabani; web ortami yalnizca $_SERVER/$_POST ile simule
edilir); isci script calisma aninda repo disinda uretilir, dosya
tamamen kendi kendine yeter.

Kapsanan sozlesme:
- gecersiz CSRF -> 403 ABORT, hicbir DB yan etkisi yok
- gecerli giris -> last_login_at + basarili deneme satiri + login audit
- yanlis parola -> basarisiz satir + login_failed
- ardan basarili giris -> e-postanin basarisiz satirlari silinir
- max_login_attempts (ayarlardan okunur) sonrasi DOGRU parola bile
  reddedilir; rate-limit yolu YENI deneme satiri yazmaz
- pasif hesap -> login_disabled_account + basarisiz satir

users (parola hash'i dahil - isleyici giriste yeniden hashleyebilir) /
login_attempts / audit_logs anlik goruntusu alinir ve sonda geri
yuklenir; bolum 5 geri yuklemeyi kendini dogrular.

Dogrulama: 9 OK / 0 FAIL; php -l temiz. Yerel MariaDB ister
(docker run -d --name riskops-mariadb ... mariadb:10.11 +
config/database.php); sunucu yoksa test acikca basarisiz olur.
@ersinkoc ersinkoc changed the title test(auth): giris akisi sozlesme regresyon testi (E2E, gercek isleyici) test(auth): giris akisi sozlesme regresyon testi (E2E, gercek isleyici) Sep 11, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

4 issues found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tools/login_flow_test.php">

<violation number="1" location="tools/login_flow_test.php:107">
P2: The test ignores `config/database.php` and always connects with the hard-coded `riskops_local_dev` password. It therefore fails on the documented installation unless the local database happens to use these exact credentials; load the application database configuration instead.</violation>

<violation number="2" location="tools/login_flow_test.php:122">
P2: If the admin hash needs rehashing, running this test permanently changes `password_changed_at` despite the advertised full restoration. Snapshot and restore `password_changed_at` together with the password.</violation>

<violation number="3" location="tools/login_flow_test.php:131">
P1: When the application is used concurrently, this cleanup deletes unrelated audit and login-attempt records and overwrites concurrent user changes. Run against an isolated database or tag and restore only records created by this test.</violation>

<violation number="4" location="tools/login_flow_test.php:207">
P1: The section-1 valid-login check asserts `$successRows === 1`, but `$successRows` counts every successful `login_attempts` row ever written for `admin@riskops.local` (no `id` or `ip_address` filter). Any dev DB where the admin has logged in before — via the web UI, another test IP, or a prior run — already has successful rows, so this check always fails and the whole test reports false FAIL on a non-fresh database. Restrict the count to rows created by this run, e.g. `AND id > {$laMax}` (or `AND ip_address = '127.0.0.7'`), consistent with the snapshot/restore boundary.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread tools/login_flow_test.php
$pdo->exec("DELETE FROM users WHERE email LIKE 'login-flow-test.%@riskops.local'");
$pdo->prepare('DELETE FROM login_attempts WHERE id > :m OR ip_address = :ip')
->execute([':m' => $laMax, ':ip' => '127.0.0.7']);
$pdo->prepare('DELETE FROM audit_logs WHERE id > :m')->execute([':m' => $auMax]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: When the application is used concurrently, this cleanup deletes unrelated audit and login-attempt records and overwrites concurrent user changes. Run against an isolated database or tag and restore only records created by this test.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/login_flow_test.php, line 131:

<comment>When the application is used concurrently, this cleanup deletes unrelated audit and login-attempt records and overwrites concurrent user changes. Run against an isolated database or tag and restore only records created by this test.</comment>

<file context>
@@ -0,0 +1,309 @@
+    $pdo->exec("DELETE FROM users WHERE email LIKE 'login-flow-test.%@riskops.local'");
+    $pdo->prepare('DELETE FROM login_attempts WHERE id > :m OR ip_address = :ip')
+        ->execute([':m' => $laMax, ':ip' => '127.0.0.7']);
+    $pdo->prepare('DELETE FROM audit_logs WHERE id > :m')->execute([':m' => $auMax]);
+    $upd = $pdo->prepare(
+        'UPDATE users SET password = :p, status = :s, must_change_password = :mcp,
</file context>

Comment thread tools/login_flow_test.php
Comment on lines +207 to +208
$code === 0 && last_login_of($pdo, $adminEmail) >= $t0 && $successRows === 1
&& in_array('login', audits_since($pdo, $auMax), true),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: The section-1 valid-login check asserts $successRows === 1, but $successRows counts every successful login_attempts row ever written for admin@riskops.local (no id or ip_address filter). Any dev DB where the admin has logged in before — via the web UI, another test IP, or a prior run — already has successful rows, so this check always fails and the whole test reports false FAIL on a non-fresh database. Restrict the count to rows created by this run, e.g. AND id > {$laMax} (or AND ip_address = '127.0.0.7'), consistent with the snapshot/restore boundary.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/login_flow_test.php, line 207:

<comment>The section-1 valid-login check asserts `$successRows === 1`, but `$successRows` counts every successful `login_attempts` row ever written for `admin@riskops.local` (no `id` or `ip_address` filter). Any dev DB where the admin has logged in before — via the web UI, another test IP, or a prior run — already has successful rows, so this check always fails and the whole test reports false FAIL on a non-fresh database. Restrict the count to rows created by this run, e.g. `AND id > {$laMax}` (or `AND ip_address = '127.0.0.7'`), consistent with the snapshot/restore boundary.</comment>

<file context>
@@ -0,0 +1,309 @@
+        "SELECT COUNT(*) FROM login_attempts WHERE success = 1 AND email = '{$adminEmail}'"
+    )->fetchColumn();
+    check('gecerli giris: last_login_at + basarili satir + login audit',
+        $code === 0 && last_login_of($pdo, $adminEmail) >= $t0 && $successRows === 1
+        && in_array('login', audits_since($pdo, $auMax), true),
+        'll=' . last_login_of($pdo, $adminEmail) . ' ok_rows=' . $successRows);
</file context>
Suggested change
$code === 0 && last_login_of($pdo, $adminEmail) >= $t0 && $successRows === 1
&& in_array('login', audits_since($pdo, $auMax), true),
$successRows = (int)$pdo->query(
"SELECT COUNT(*) FROM login_attempts WHERE success = 1 AND email = '{$adminEmail}' AND id > {$laMax}"
)->fetchColumn();

Comment thread tools/login_flow_test.php
/* --- Snapshot ---------------------------------------------------------- */

$usersSnap = $pdo->query(
'SELECT id, password, status, must_change_password, last_login_at FROM users ORDER BY id'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: If the admin hash needs rehashing, running this test permanently changes password_changed_at despite the advertised full restoration. Snapshot and restore password_changed_at together with the password.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/login_flow_test.php, line 122:

<comment>If the admin hash needs rehashing, running this test permanently changes `password_changed_at` despite the advertised full restoration. Snapshot and restore `password_changed_at` together with the password.</comment>

<file context>
@@ -0,0 +1,309 @@
+/* --- Snapshot ---------------------------------------------------------- */
+
+$usersSnap = $pdo->query(
+    'SELECT id, password, status, must_change_password, last_login_at FROM users ORDER BY id'
+)->fetchAll();
+$laMax = (int)$pdo->query('SELECT COALESCE(MAX(id),0) FROM login_attempts')->fetchColumn();
</file context>

Comment thread tools/login_flow_test.php
$pdo = new PDO(
'mysql:host=127.0.0.1;port=3306;dbname=riskops;charset=utf8mb4',
'riskops_user',
'riskops_local_dev',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The test ignores config/database.php and always connects with the hard-coded riskops_local_dev password. It therefore fails on the documented installation unless the local database happens to use these exact credentials; load the application database configuration instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/login_flow_test.php, line 107:

<comment>The test ignores `config/database.php` and always connects with the hard-coded `riskops_local_dev` password. It therefore fails on the documented installation unless the local database happens to use these exact credentials; load the application database configuration instead.</comment>

<file context>
@@ -0,0 +1,309 @@
+    $pdo = new PDO(
+        'mysql:host=127.0.0.1;port=3306;dbname=riskops;charset=utf8mb4',
+        'riskops_user',
+        'riskops_local_dev',
+        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+         PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
</file context>

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant