M3 Passwort-Reset und E-Mail-Verifikation ergänzen
- Auth-Token-Tabelle mit gehashten Single-Use-Tokens einführen - Passwort-Reset und E-Mail-Verifikation als Dev-Flow bauen - Token-Flow-Test, Smoke-Abdeckung und M3-Dokumentation aktualisieren
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/dev-db.php';
|
||||
require __DIR__ . '/../app/saas-auth.php';
|
||||
|
||||
function password_email_check_assert(string $label, bool $condition, array &$failures, int &$passes): void
|
||||
{
|
||||
if ($condition) {
|
||||
$passes++;
|
||||
echo "PASS {$label}\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$failures[] = $label;
|
||||
echo "FAIL {$label}\n";
|
||||
}
|
||||
|
||||
function password_email_check_cleanup(PDO $pdo, string $slug, string $emailNorm): void
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT id FROM tenants WHERE slug = ?');
|
||||
$stmt->execute([$slug]);
|
||||
$tenantId = $stmt->fetchColumn();
|
||||
|
||||
if ($tenantId !== false) {
|
||||
$tenantId = (int)$tenantId;
|
||||
$pdo->prepare('DELETE FROM participants WHERE tenant_id = ?')->execute([$tenantId]);
|
||||
$pdo->prepare('DELETE FROM tenant_memberships WHERE tenant_id = ?')->execute([$tenantId]);
|
||||
$pdo->prepare('DELETE FROM tenant_settings WHERE tenant_id = ?')->execute([$tenantId]);
|
||||
$pdo->prepare('DELETE FROM tenants WHERE id = ?')->execute([$tenantId]);
|
||||
}
|
||||
|
||||
$pdo->prepare('DELETE FROM users WHERE email_norm = ?')->execute([$emailNorm]);
|
||||
}
|
||||
|
||||
$pdo = dev_pdo();
|
||||
dev_apply_schema($pdo);
|
||||
|
||||
$slug = 'm3-password-email-check';
|
||||
$email = 'm3-password-email-check@test.local';
|
||||
$emailNorm = saas_email_norm($email);
|
||||
$password = 'M3-password-email-123';
|
||||
$newPassword = 'M3-password-email-456';
|
||||
$failures = [];
|
||||
$passes = 0;
|
||||
|
||||
password_email_check_cleanup($pdo, $slug, $emailNorm);
|
||||
|
||||
$registration = saas_register_tenant_owner($pdo, [
|
||||
'tenant_name' => 'M3 Password Email Check',
|
||||
'tenant_slug' => $slug,
|
||||
'display_name' => 'M3 Password Email',
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'password_confirm' => $password,
|
||||
]);
|
||||
|
||||
password_email_check_assert('registration succeeds', $registration['ok'] === true, $failures, $passes);
|
||||
password_email_check_assert('registration returns verification token', !empty($registration['email_verification_token']), $failures, $passes);
|
||||
|
||||
if ($registration['ok']) {
|
||||
$userId = (int)$registration['identity']['user_id'];
|
||||
|
||||
$stmt = $pdo->prepare('SELECT email_verified_at FROM users WHERE id = ?');
|
||||
$stmt->execute([$userId]);
|
||||
password_email_check_assert('email starts unverified', $stmt->fetchColumn() === null, $failures, $passes);
|
||||
|
||||
$verification = saas_verify_email_token($pdo, (string)$registration['email_verification_token']);
|
||||
password_email_check_assert('email verification succeeds', $verification['ok'] === true, $failures, $passes);
|
||||
|
||||
$stmt = $pdo->prepare('SELECT email_verified_at FROM users WHERE id = ?');
|
||||
$stmt->execute([$userId]);
|
||||
password_email_check_assert('email is verified', $stmt->fetchColumn() !== null, $failures, $passes);
|
||||
|
||||
$verificationAgain = saas_verify_email_token($pdo, (string)$registration['email_verification_token']);
|
||||
password_email_check_assert('email verification token is single use', $verificationAgain['ok'] === false, $failures, $passes);
|
||||
|
||||
$resetRequest = saas_request_password_reset($pdo, $email, $slug);
|
||||
password_email_check_assert('password reset request succeeds', $resetRequest['ok'] === true, $failures, $passes);
|
||||
password_email_check_assert('password reset returns token', !empty($resetRequest['token']), $failures, $passes);
|
||||
|
||||
$reset = saas_reset_password_with_token(
|
||||
$pdo,
|
||||
(string)$resetRequest['token'],
|
||||
$newPassword,
|
||||
$newPassword
|
||||
);
|
||||
password_email_check_assert('password reset succeeds', $reset['ok'] === true, $failures, $passes);
|
||||
|
||||
$oldLogin = saas_authenticate($pdo, $email, $password, $slug);
|
||||
password_email_check_assert('old password is rejected', $oldLogin['ok'] === false, $failures, $passes);
|
||||
|
||||
$newLogin = saas_authenticate($pdo, $email, $newPassword, $slug);
|
||||
password_email_check_assert('new password works', $newLogin['ok'] === true, $failures, $passes);
|
||||
|
||||
$reuse = saas_reset_password_with_token(
|
||||
$pdo,
|
||||
(string)$resetRequest['token'],
|
||||
'M3-password-email-789',
|
||||
'M3-password-email-789'
|
||||
);
|
||||
password_email_check_assert('password reset token is single use', $reuse['ok'] === false, $failures, $passes);
|
||||
}
|
||||
|
||||
$unknownRequest = saas_request_password_reset($pdo, 'unknown-password-email@test.local', $slug);
|
||||
password_email_check_assert('unknown reset request is generic success', $unknownRequest['ok'] === true, $failures, $passes);
|
||||
password_email_check_assert('unknown reset request has no token', empty($unknownRequest['token']), $failures, $passes);
|
||||
|
||||
password_email_check_cleanup($pdo, $slug, $emailNorm);
|
||||
|
||||
if ($failures !== []) {
|
||||
echo "\nM3 password/email flow check failed with " . count($failures) . " failure(s):\n";
|
||||
foreach ($failures as $failure) {
|
||||
echo "- {$failure}\n";
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "\nM3 password/email flow check passed with {$passes} assertions.\n";
|
||||
+16
-1
@@ -54,13 +54,28 @@ $checks = [
|
||||
[
|
||||
'label' => 'Login',
|
||||
'path' => 'login.php',
|
||||
'contains' => ['Login', 'Registrieren'],
|
||||
'contains' => ['Login', 'Registrieren', 'Passwort vergessen'],
|
||||
],
|
||||
[
|
||||
'label' => 'Registrierung',
|
||||
'path' => 'register.php',
|
||||
'contains' => ['Registrierung', 'Kundenkonto anlegen'],
|
||||
],
|
||||
[
|
||||
'label' => 'Passwort vergessen',
|
||||
'path' => 'passwort-vergessen.php',
|
||||
'contains' => ['Passwort vergessen', 'Link vorbereiten'],
|
||||
],
|
||||
[
|
||||
'label' => 'Passwort zuruecksetzen Invalid',
|
||||
'path' => 'passwort-zuruecksetzen.php',
|
||||
'contains' => ['Passwort zuruecksetzen', 'ungueltig oder abgelaufen'],
|
||||
],
|
||||
[
|
||||
'label' => 'E-Mail verifizieren Invalid',
|
||||
'path' => 'email-verifizieren.php',
|
||||
'contains' => ['E-Mail verifizieren', 'ungueltig oder abgelaufen'],
|
||||
],
|
||||
[
|
||||
'label' => 'Mandant-Einstellungen Login-Schutz',
|
||||
'path' => 'mandant-einstellungen.php',
|
||||
|
||||
Reference in New Issue
Block a user