# Mitglieder-System Phase 1 — Konto-System (Auth-Fundament) — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Echtes Login-/Konto-System (Laravel-Standard-Auth) mit Registrierung, E-Mail-Verifizierung, Passwort **und** passwortlosem Code-Login, Passwort-Reset, Konto-Seite und Admin-Übersicht — Fundament für Wissen-/Webtools-Gating (Phase 2+3).

**Architecture:** Native Laravel Session-Auth auf der bestehenden `users`-Tabelle (erweitert um Consent-Felder). Passwortloser Login über eine `login_codes`-Tabelle (6-stelliger Code, SHA-256-Hash, 15 Min, max 5 Versuche) — gespiegelt vom bewährten `LeadOtp`-Muster. Alle Mails sind schlichte Blade-Mailables wie im Lead-System. Admin-Bereich nutzt das bestehende separate Admin-Auth (getrennt von den Member-Accounts).

**Tech Stack:** Laravel 12, PHP 8.2, SQLite (Tests) / MySQL (prod), native `Auth`-Facade, `MustVerifyEmail`, `Password`-Broker.

**Wichtige Projekt-Regeln:**
- Tests IMMER via `composer test` (macht `config:clear` vorweg). NIE nacktes `php artisan test` (gecachte Config → env=production → migrate-confirm killt DB-Tests). Nach den Tests `php artisan config:cache` wiederherstellen.
- 54 vorbestehende Test-Fehler im Suite sind unrelated (i18n, Shop, AdminStatus) — nicht durch diesen Branch verursacht.
- PHP-Strings: einfache Anführungszeichen. Deutsche Route-Pfade.
- Branch: `feat/wissen-subdomain`.

**Referenz-Muster (bestehend, zum Spiegeln):**
- `app/Models/LeadOtp.php` — Code-Generierung/-Prüfung (`isValid()`, `checkCode()`, SHA-256, 15 Min, 5 Versuche)
- `app/Mail/LeadOtpMail.php` + `resources/views/mail/leads/otp.blade.php` — Blade-Mailable
- `routes/web.php` Zeilen 65-71 — throttle-Routen-Muster
- `app/Http/Middleware/LocalePersistence.php` — `SKIP_FIRST`-Konstante
- `tests/Feature/Leads/LeadSubscribeTest.php` — Test-Muster (`RefreshDatabase`, `Mail::fake()`)

---

## File Structure

**Migrationen:**
- Create `database/migrations/2026_07_02_000001_add_account_fields_and_login_codes.php` — `users` um Consent-Felder erweitern + `login_codes`-Tabelle

**Models:**
- Modify `app/Models/User.php` — `MustVerifyEmail`, neue fillable/casts, `sendPasswordResetNotification()`, `loginCodes()`
- Create `app/Models/LoginCode.php` — Code-Lifecycle (gespiegelt von `LeadOtp`)

**Mail:**
- Create `app/Mail/VerifyEmailMail.php` + `resources/views/mail/auth/verify.blade.php`
- Create `app/Mail/LoginCodeMail.php` + `resources/views/mail/auth/code.blade.php`
- Create `app/Mail/ResetPasswordMail.php` + `resources/views/mail/auth/reset.blade.php`

**Controller:**
- Create `app/Http/Controllers/Auth/RegisterController.php`
- Create `app/Http/Controllers/Auth/LoginController.php`
- Create `app/Http/Controllers/Auth/VerifyController.php`
- Create `app/Http/Controllers/Auth/PasswordResetController.php`
- Create `app/Http/Controllers/AccountController.php`
- Create `app/Http/Controllers/Admin/AdminUserController.php`

**Views:**
- Create `resources/views/auth/register.blade.php`, `auth/login.blade.php`, `auth/verify.blade.php`, `auth/passwort-vergessen.blade.php`, `auth/passwort-reset.blade.php`
- Create `resources/views/konto/index.blade.php`
- Create `resources/views/admin/users/index.blade.php`

**Wiring:**
- Modify `bootstrap/app.php` — Middleware-Aliase `verified` (+ `auth` prüfen)
- Modify `app/Http/Middleware/LocalePersistence.php` — Auth-Pfade in `SKIP_FIRST`
- Modify `routes/web.php` — alle Auth-Routen + Admin-User-Route
- Modify Admin-Sidebar-Partial — Link „Benutzer"
- Modify Datenschutz-Config/Controller — Abschnitt „Benutzerkonto"

---

## Task 1: Migration — Consent-Felder + login_codes

**Files:**
- Create: `database/migrations/2026_07_02_000001_add_account_fields_and_login_codes.php`

- [ ] **Step 1: Prüfe, dass sessions + password_reset_tokens existieren**

Run:
```
cd C:\xampp\htdocs\kotsch.tech\kotsch-tech && php -r "require 'vendor/autoload.php'; $f=glob('database/migrations/*create_users_table*'); echo implode(PHP_EOL,$f);"
```
Expected: eine Datei `0001_01_01_000000_create_users_table.php`. Öffne sie und bestätige: sie erstellt `users`, `password_reset_tokens` **und** `sessions`. (Laravel-12-Default tut das.) Falls `sessions` fehlt: in dieser Migration mit anlegen. Normalfall: vorhanden → nichts tun.

- [ ] **Step 2: Migration schreiben**

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->timestamp('consent_at')->nullable()->after('email_verified_at');
            $table->text('consent_text')->nullable()->after('consent_at');
            $table->boolean('newsletter_opt_in')->default(false)->after('consent_text');
        });

        Schema::create('login_codes', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->string('code_hash', 64);
            $table->timestamp('expires_at');
            $table->unsignedTinyInteger('attempts')->default(0);
            $table->timestamp('consumed_at')->nullable();
            $table->timestamp('created_at')->nullable();
            $table->index(['user_id', 'consumed_at']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('login_codes');
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn(['consent_at', 'consent_text', 'newsletter_opt_in']);
        });
    }
};
```

- [ ] **Step 3: Migration ausführen (Test-DB via composer test kommt später; hier prod-DB)**

Run:
```
cd C:\xampp\htdocs\kotsch.tech\kotsch-tech && php artisan migrate
```
Expected: `2026_07_02_000001_add_account_fields_and_login_codes ... DONE`

- [ ] **Step 4: Commit**

```
git add database/migrations/2026_07_02_000001_add_account_fields_and_login_codes.php
git commit -m "feat(konto): Migration — Consent-Felder + login_codes"
```

---

## Task 2: Models — User erweitern + LoginCode

**Files:**
- Modify: `app/Models/User.php`
- Create: `app/Models/LoginCode.php`
- Test: `tests/Feature/Auth/LoginCodeTest.php`

- [ ] **Step 1: Failing test schreiben**

`tests/Feature/Auth/LoginCodeTest.php`:
```php
<?php

namespace Tests\Feature\Auth;

use App\Models\LoginCode;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class LoginCodeTest extends TestCase
{
    use RefreshDatabase;

    public function test_issue_and_verify_code(): void
    {
        $user = User::create(['email' => 'a@example.com', 'password' => 'x']);
        $code = LoginCode::issueFor($user);

        $this->assertMatchesRegularExpression('/^\d{6}$/', $code);
        $this->assertFalse(LoginCode::verifyFor($user, '000000') && $code !== '000000');
        $this->assertTrue(LoginCode::verifyFor($user, $code));
    }

    public function test_code_is_single_use(): void
    {
        $user = User::create(['email' => 'b@example.com', 'password' => 'x']);
        $code = LoginCode::issueFor($user);

        $this->assertTrue(LoginCode::verifyFor($user, $code));
        $this->assertFalse(LoginCode::verifyFor($user, $code));
    }

    public function test_issuing_new_code_invalidates_old(): void
    {
        $user = User::create(['email' => 'c@example.com', 'password' => 'x']);
        $old = LoginCode::issueFor($user);
        $new = LoginCode::issueFor($user);

        $this->assertFalse(LoginCode::verifyFor($user, $old));
        $this->assertTrue(LoginCode::verifyFor($user, $new));
    }
}
```

- [ ] **Step 2: Test laufen lassen — muss fehlschlagen**

Run: `cd C:\xampp\htdocs\kotsch.tech\kotsch-tech && composer test -- --filter=LoginCodeTest`
Expected: FAIL — `Class "App\Models\LoginCode" not found`

- [ ] **Step 3: LoginCode-Model schreiben**

`app/Models/LoginCode.php`:
```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\DB;

class LoginCode extends Model
{
    public $timestamps = false;

    protected $fillable = ['user_id', 'code_hash', 'expires_at', 'attempts', 'consumed_at', 'created_at'];

    protected $casts = [
        'expires_at'  => 'datetime',
        'consumed_at' => 'datetime',
        'created_at'  => 'datetime',
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function isValid(): bool
    {
        return $this->consumed_at === null
            && $this->expires_at !== null
            && $this->expires_at->isFuture()
            && $this->attempts < 5;
    }

    public function checkCode(string $code): bool
    {
        return hash_equals($this->code_hash, hash('sha256', $code));
    }

    public static function issueFor(User $user): string
    {
        $code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);

        DB::transaction(function () use ($user, $code) {
            self::where('user_id', $user->id)->whereNull('consumed_at')->update(['consumed_at' => now()]);
            self::create([
                'user_id'    => $user->id,
                'code_hash'  => hash('sha256', $code),
                'expires_at' => now()->addMinutes(15),
                'attempts'   => 0,
                'created_at' => now(),
            ]);
        });

        return $code;
    }

    public static function verifyFor(User $user, string $code): bool
    {
        $row = self::where('user_id', $user->id)->whereNull('consumed_at')->orderByDesc('id')->first();

        if (! $row || ! $row->isValid()) {
            return false;
        }

        $row->increment('attempts');

        if (! $row->checkCode($code)) {
            return false;
        }

        $row->forceFill(['consumed_at' => now()])->save();

        return true;
    }
}
```

- [ ] **Step 4: User-Model erweitern**

`app/Models/User.php` — ganze Datei ersetzen:
```php
<?php

namespace App\Models;

use App\Mail\ResetPasswordMail;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Mail;

class User extends Authenticatable implements MustVerifyEmail
{
    use HasFactory, Notifiable;

    protected $fillable = ['name', 'email', 'password', 'consent_at', 'consent_text', 'newsletter_opt_in'];

    protected $hidden = ['password', 'remember_token'];

    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'consent_at'        => 'datetime',
            'newsletter_opt_in' => 'boolean',
            'password'          => 'hashed',
        ];
    }

    public function loginCodes(): HasMany
    {
        return $this->hasMany(LoginCode::class);
    }

    public function sendPasswordResetNotification($token): void
    {
        $url = route('password.reset', ['token' => $token, 'email' => $this->email]);
        Mail::to($this->email)->send(new ResetPasswordMail($url));
    }
}
```

Note: `ResetPasswordMail` wird in Task 3 erstellt. `::class`-Referenz + `use` erzeugen keinen Fehler, solange die Methode nicht aufgerufen wird (erst in Task 8 getestet).

- [ ] **Step 5: Test laufen lassen — muss grün sein**

Run: `composer test -- --filter=LoginCodeTest`
Expected: PASS (3 Tests). Danach `php artisan config:cache`.

- [ ] **Step 6: Commit**

```
git add app/Models/User.php app/Models/LoginCode.php tests/Feature/Auth/LoginCodeTest.php
git commit -m "feat(konto): User erweitert + LoginCode-Model (3 Tests grün)"
```

---

## Task 3: Mailables + Templates

**Files:**
- Create: `app/Mail/VerifyEmailMail.php`, `app/Mail/LoginCodeMail.php`, `app/Mail/ResetPasswordMail.php`
- Create: `resources/views/mail/auth/verify.blade.php`, `code.blade.php`, `reset.blade.php`

- [ ] **Step 1: VerifyEmailMail**

`app/Mail/VerifyEmailMail.php`:
```php
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class VerifyEmailMail extends Mailable
{
    use Queueable, SerializesModels;

    public function __construct(public string $verifyUrl, public string $code) {}

    public function envelope(): Envelope
    {
        return new Envelope(subject: 'Bestätige deine E-Mail — kotsch.tech');
    }

    public function content(): Content
    {
        return new Content(view: 'mail.auth.verify');
    }
}
```

- [ ] **Step 2: LoginCodeMail**

`app/Mail/LoginCodeMail.php`:
```php
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class LoginCodeMail extends Mailable
{
    use Queueable, SerializesModels;

    public function __construct(public string $code) {}

    public function envelope(): Envelope
    {
        return new Envelope(subject: 'Dein Login-Code: ' . $this->code . ' — kotsch.tech');
    }

    public function content(): Content
    {
        return new Content(view: 'mail.auth.code');
    }
}
```

- [ ] **Step 3: ResetPasswordMail**

`app/Mail/ResetPasswordMail.php`:
```php
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class ResetPasswordMail extends Mailable
{
    use Queueable, SerializesModels;

    public function __construct(public string $resetUrl) {}

    public function envelope(): Envelope
    {
        return new Envelope(subject: 'Passwort zurücksetzen — kotsch.tech');
    }

    public function content(): Content
    {
        return new Content(view: 'mail.auth.reset');
    }
}
```

- [ ] **Step 4: Templates**

`resources/views/mail/auth/verify.blade.php`:
```blade
<!DOCTYPE html>
<html lang="de">
<body style="font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:#f8fafc;padding:24px;color:#0f172a;">
    <div style="max-width:520px;margin:0 auto;background:#fff;border-radius:16px;padding:32px;">
        <h1 style="font-size:1.4rem;margin:0 0 16px;">Willkommen bei kotsch.tech</h1>
        <p style="line-height:1.6;color:#475569;">Bestätige deine E-Mail-Adresse, um dein Konto zu aktivieren.</p>
        <p style="margin:24px 0;">
            <a href="{{ $verifyUrl }}" style="display:inline-block;background:#6D5AE6;color:#fff;text-decoration:none;padding:14px 28px;border-radius:10px;font-weight:700;">E-Mail bestätigen</a>
        </p>
        <p style="line-height:1.6;color:#475569;">Oder gib diesen Code auf der Bestätigungsseite ein:</p>
        <p style="font-family:monospace;font-size:2rem;font-weight:800;letter-spacing:.2em;color:#0f172a;margin:8px 0 24px;">{{ $code }}</p>
        <p style="font-size:.85rem;color:#94a3b8;">Der Code ist 15 Minuten gültig. Wenn du kein Konto angelegt hast, ignoriere diese E-Mail.</p>
    </div>
</body>
</html>
```

`resources/views/mail/auth/code.blade.php`:
```blade
<!DOCTYPE html>
<html lang="de">
<body style="font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:#f8fafc;padding:24px;color:#0f172a;">
    <div style="max-width:520px;margin:0 auto;background:#fff;border-radius:16px;padding:32px;">
        <h1 style="font-size:1.4rem;margin:0 0 16px;">Dein Login-Code</h1>
        <p style="line-height:1.6;color:#475569;">Gib diesen Code ein, um dich anzumelden:</p>
        <p style="font-family:monospace;font-size:2.4rem;font-weight:800;letter-spacing:.2em;color:#0f172a;margin:16px 0 24px;">{{ $code }}</p>
        <p style="font-size:.85rem;color:#94a3b8;">Der Code ist 15 Minuten gültig. Wenn du das nicht warst, ignoriere diese E-Mail.</p>
    </div>
</body>
</html>
```

`resources/views/mail/auth/reset.blade.php`:
```blade
<!DOCTYPE html>
<html lang="de">
<body style="font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:#f8fafc;padding:24px;color:#0f172a;">
    <div style="max-width:520px;margin:0 auto;background:#fff;border-radius:16px;padding:32px;">
        <h1 style="font-size:1.4rem;margin:0 0 16px;">Passwort zurücksetzen</h1>
        <p style="line-height:1.6;color:#475569;">Klicke auf den Button, um ein neues Passwort zu setzen:</p>
        <p style="margin:24px 0;">
            <a href="{{ $resetUrl }}" style="display:inline-block;background:#6D5AE6;color:#fff;text-decoration:none;padding:14px 28px;border-radius:10px;font-weight:700;">Neues Passwort setzen</a>
        </p>
        <p style="font-size:.85rem;color:#94a3b8;">Der Link ist 60 Minuten gültig. Wenn du das nicht angefragt hast, ignoriere diese E-Mail.</p>
    </div>
</body>
</html>
```

- [ ] **Step 5: Render-Smoke-Test**

Run:
```
cd C:\xampp\htdocs\kotsch.tech\kotsch-tech && php artisan tinker --execute="echo (new App\Mail\LoginCodeMail('123456'))->render();" 2>&1 | Select-String -Pattern '123456' -SimpleMatch
```
Expected: Ausgabe enthält `123456` (Template rendert fehlerfrei).

- [ ] **Step 6: Commit**

```
git add app/Mail/VerifyEmailMail.php app/Mail/LoginCodeMail.php app/Mail/ResetPasswordMail.php resources/views/mail/auth/
git commit -m "feat(konto): 3 Auth-Mailables + Templates"
```

---

## Task 4: Middleware-Aliase + LocalePersistence + Routen

**Files:**
- Modify: `bootstrap/app.php`
- Modify: `app/Http/Middleware/LocalePersistence.php`
- Modify: `routes/web.php`

- [ ] **Step 1: Middleware-Aliase sicherstellen**

Öffne `bootstrap/app.php`, finde den `->withMiddleware(function (Middleware $middleware) {` Block mit den `alias([...])`-Einträgen (dort stehen schon `admin.auth`, `admin.guard`). Ergänze im `alias([...])`-Array:
```php
            'auth'     => \Illuminate\Auth\Middleware\Authenticate::class,
            'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
```
(Falls `auth` bereits vorhanden ist, nur `verified` ergänzen.)

Im selben `withMiddleware`-Block sicherstellen, dass unauthentifizierte Nutzer auf `login` umgeleitet werden. Ergänze (falls nicht vorhanden):
```php
        $middleware->redirectGuestsTo(fn () => route('login'));
```

- [ ] **Step 2: LocalePersistence SKIP_FIRST erweitern**

In `app/Http/Middleware/LocalePersistence.php`, im `SKIP_FIRST`-Array die Auth-Pfade ergänzen (nach `'gratis', 'lead',`):
```php
        'login', 'logout', 'registrieren', 'konto', 'email', 'passwort',
```

- [ ] **Step 3: Auth-Routen einfügen**

In `routes/web.php`: oben bei den anderen `use`-Imports ergänzen:
```php
use App\Http\Controllers\Auth\RegisterController;
use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\Auth\VerifyController;
use App\Http\Controllers\Auth\PasswordResetController;
use App\Http\Controllers\AccountController;
```

Direkt **nach** dem bestehenden Lead-Routen-Block (nach Zeile ~71, `leads.page`) einfügen:
```php
// ---- Mitglieder-Konto (Auth) ----
Route::get('/registrieren',  [RegisterController::class, 'show'])->name('register');
Route::post('/registrieren', [RegisterController::class, 'store'])->middleware('throttle:10,10')->name('register.store');

Route::get('/login',                [LoginController::class, 'show'])->name('login');
Route::post('/login',               [LoginController::class, 'password'])->middleware('throttle:10,1')->name('login.password');
Route::post('/login/code-anfordern',[LoginController::class, 'requestCode'])->middleware('throttle:5,1')->name('login.code.request');
Route::post('/login/code',          [LoginController::class, 'code'])->middleware('throttle:10,1')->name('login.code');
Route::post('/logout',              [LoginController::class, 'logout'])->name('logout');

Route::get('/email/bestaetigen',       [VerifyController::class, 'notice'])->name('verification.notice');
Route::get('/email/verify/{id}/{hash}',[VerifyController::class, 'verify'])->middleware('signed')->name('email.verify');
Route::post('/email/code',             [VerifyController::class, 'code'])->middleware('throttle:10,1')->name('verification.code');
Route::post('/email/erneut',           [VerifyController::class, 'resend'])->middleware('throttle:5,1')->name('verification.send');

Route::get('/passwort/vergessen',   [PasswordResetController::class, 'request'])->name('password.request');
Route::post('/passwort/vergessen',  [PasswordResetController::class, 'email'])->middleware('throttle:5,1')->name('password.email');
Route::get('/passwort/reset/{token}',[PasswordResetController::class, 'reset'])->name('password.reset');
Route::post('/passwort/reset',      [PasswordResetController::class, 'update'])->middleware('throttle:5,1')->name('password.update');

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('/konto',            [AccountController::class, 'index'])->name('konto');
    Route::post('/konto/passwort',  [AccountController::class, 'password'])->name('konto.password');
    Route::post('/konto/newsletter',[AccountController::class, 'newsletter'])->name('konto.newsletter');
    Route::post('/konto/loeschen',  [AccountController::class, 'destroy'])->name('konto.destroy');
});
```

- [ ] **Step 4: Routen registrieren sich fehlerfrei**

Run: `cd C:\xampp\htdocs\kotsch.tech\kotsch-tech && php artisan route:list --path=login 2>&1`
Expected: Zeigt `login`, `login.password`, `login.code` etc. ohne Fehler. (Controller existieren noch nicht — das ist ok, `::class` ist nur ein String.)

- [ ] **Step 5: Commit**

```
git add bootstrap/app.php app/Http/Middleware/LocalePersistence.php routes/web.php
git commit -m "feat(konto): Middleware-Aliase + LocalePersistence + Auth-Routen"
```

---

## Task 5: RegisterController + View

**Files:**
- Create: `app/Http/Controllers/Auth/RegisterController.php`
- Create: `resources/views/auth/register.blade.php`
- Test: `tests/Feature/Auth/RegisterTest.php`

- [ ] **Step 1: Failing test**

`tests/Feature/Auth/RegisterTest.php`:
```php
<?php

namespace Tests\Feature\Auth;

use App\Mail\VerifyEmailMail;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;

class RegisterTest extends TestCase
{
    use RefreshDatabase;

    public function test_register_page_loads(): void
    {
        $this->get('/registrieren')->assertOk()->assertSee('Registrieren');
    }

    public function test_register_creates_unverified_user_and_sends_mail(): void
    {
        Mail::fake();

        $this->post('/registrieren', [
            'email'    => 'neu@example.com',
            'password' => 'geheim1234',
            'consent'  => '1',
        ])->assertRedirect(route('verification.notice'));

        $user = User::where('email', 'neu@example.com')->first();
        $this->assertNotNull($user);
        $this->assertNull($user->email_verified_at);
        $this->assertNotNull($user->consent_at);
        Mail::assertSent(VerifyEmailMail::class);
    }

    public function test_register_requires_consent(): void
    {
        $this->post('/registrieren', ['email' => 'x@example.com', 'password' => 'geheim1234'])
            ->assertSessionHasErrors('consent');
    }

    public function test_password_is_optional(): void
    {
        Mail::fake();
        $this->post('/registrieren', ['email' => 'ohnepw@example.com', 'consent' => '1'])
            ->assertRedirect(route('verification.notice'));
        $this->assertDatabaseHas('users', ['email' => 'ohnepw@example.com']);
    }
}
```

- [ ] **Step 2: Test läuft, schlägt fehl**

Run: `composer test -- --filter=RegisterTest`
Expected: FAIL — `Target class [App\Http\Controllers\Auth\RegisterController] does not exist.`

- [ ] **Step 3: Controller schreiben**

`app/Http/Controllers/Auth/RegisterController.php`:
```php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Mail\VerifyEmailMail;
use App\Models\LoginCode;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\URL;
use Illuminate\Validation\Rules\Password;

class RegisterController extends Controller
{
    public function show()
    {
        if (Auth::check()) {
            return redirect()->route('konto');
        }

        return view('auth.register');
    }

    public function store(Request $request)
    {
        $data = $request->validate([
            'email'      => ['required', 'email', 'max:255', 'unique:users,email'],
            'password'   => ['nullable', Password::min(8)],
            'name'       => ['nullable', 'string', 'max:120'],
            'consent'    => ['accepted'],
            'newsletter' => ['nullable'],
        ]);

        $user = User::create([
            'email'             => mb_strtolower(trim($data['email'])),
            'password'          => ! empty($data['password']) ? $data['password'] : null,
            'name'              => $data['name'] ?? null,
            'consent_at'        => now(),
            'consent_text'      => 'Ich akzeptiere die Datenschutzerklärung und AGB. (Konto-Registrierung ' . now()->toDateString() . ')',
            'newsletter_opt_in' => $request->boolean('newsletter'),
        ]);

        $this->sendVerification($user);

        $request->session()->put('auth.pending_user_id', $user->id);

        return redirect()->route('verification.notice')
            ->with('status', 'Wir haben dir einen Bestätigungslink und einen Code geschickt.');
    }

    private function sendVerification(User $user): void
    {
        $url = URL::temporarySignedRoute('email.verify', now()->addDays(2), [
            'id'   => $user->id,
            'hash' => sha1($user->email),
        ]);

        $code = LoginCode::issueFor($user);

        Mail::to($user->email)->send(new VerifyEmailMail($url, $code));
    }
}
```

- [ ] **Step 4: View schreiben**

`resources/views/auth/register.blade.php`:
```blade
@extends('layouts.app')
@section('title', 'Registrieren — kotsch.tech')
@section('content')
<div style="max-width:440px;margin:0 auto;padding:60px 20px;">
    <h1 style="font-size:2rem;font-weight:800;margin-bottom:8px;">Registrieren</h1>
    <p style="color:#475569;margin-bottom:28px;">Kostenloses Konto — schaltet alle Wissen-Artikel und Premium-Tools frei.</p>

    @if ($errors->any())
        <div style="background:#fef2f2;color:#b91c1c;padding:12px 16px;border-radius:10px;margin-bottom:16px;">
            {{ $errors->first() }}
        </div>
    @endif

    <form method="POST" action="{{ route('register.store') }}">
        @csrf
        <label style="display:block;font-weight:600;margin-bottom:6px;">E-Mail *</label>
        <input type="email" name="email" required value="{{ old('email') }}"
            style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:10px;margin-bottom:16px;">

        <label style="display:block;font-weight:600;margin-bottom:6px;">Passwort (optional)</label>
        <input type="password" name="password" autocomplete="new-password"
            style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:10px;margin-bottom:4px;">
        <p style="font-size:.8rem;color:#94a3b8;margin-bottom:16px;">Leer lassen = Login per Code an deine E-Mail.</p>

        <label style="display:flex;gap:8px;align-items:flex-start;margin-bottom:12px;font-size:.9rem;color:#475569;">
            <input type="checkbox" name="consent" value="1" style="margin-top:3px;">
            <span>Ich akzeptiere die <a href="/datenschutz">Datenschutzerklärung</a>. *</span>
        </label>
        <label style="display:flex;gap:8px;align-items:flex-start;margin-bottom:20px;font-size:.9rem;color:#475569;">
            <input type="checkbox" name="newsletter" value="1" style="margin-top:3px;">
            <span>Newsletter mit Tipps &amp; neuen Tools (jederzeit abbestellbar).</span>
        </label>

        <button type="submit" style="width:100%;background:#6D5AE6;color:#fff;border:none;padding:14px;border-radius:10px;font-weight:700;font-size:1rem;cursor:pointer;">Konto erstellen</button>
    </form>

    <p style="text-align:center;margin-top:20px;color:#475569;">Schon ein Konto? <a href="{{ route('login') }}">Anmelden</a></p>
</div>
@endsection
```

- [ ] **Step 5: Test grün**

Run: `composer test -- --filter=RegisterTest`
Expected: PASS (4 Tests). Danach `php artisan config:cache`.

- [ ] **Step 6: Commit**

```
git add app/Http/Controllers/Auth/RegisterController.php resources/views/auth/register.blade.php tests/Feature/Auth/RegisterTest.php
git commit -m "feat(konto): RegisterController + View (4 Tests grün)"
```

---

## Task 6: VerifyController + View

**Files:**
- Create: `app/Http/Controllers/Auth/VerifyController.php`
- Create: `resources/views/auth/verify.blade.php`
- Test: `tests/Feature/Auth/VerifyTest.php`

- [ ] **Step 1: Failing test**

`tests/Feature/Auth/VerifyTest.php`:
```php
<?php

namespace Tests\Feature\Auth;

use App\Models\LoginCode;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\URL;
use Tests\TestCase;

class VerifyTest extends TestCase
{
    use RefreshDatabase;

    public function test_signed_link_verifies_and_logs_in(): void
    {
        $user = User::create(['email' => 'v@example.com', 'password' => 'geheim1234']);

        $url = URL::temporarySignedRoute('email.verify', now()->addDays(2), [
            'id' => $user->id, 'hash' => sha1($user->email),
        ]);

        $this->get($url)->assertRedirect(route('konto'));
        $this->assertNotNull($user->fresh()->email_verified_at);
        $this->assertAuthenticatedAs($user->fresh());
    }

    public function test_code_verifies(): void
    {
        $user = User::create(['email' => 'v2@example.com', 'password' => 'geheim1234']);
        $code = LoginCode::issueFor($user);

        $this->withSession(['auth.pending_user_id' => $user->id])
            ->post('/email/code', ['code' => $code])
            ->assertRedirect(route('konto'));

        $this->assertNotNull($user->fresh()->email_verified_at);
    }

    public function test_newsletter_optin_creates_confirmed_lead_on_verify(): void
    {
        $user = User::create([
            'email' => 'nl@example.com', 'password' => 'geheim1234', 'newsletter_opt_in' => true,
        ]);

        $url = URL::temporarySignedRoute('email.verify', now()->addDays(2), [
            'id' => $user->id, 'hash' => sha1($user->email),
        ]);
        $this->get($url);

        $this->assertDatabaseHas('leads', ['email' => 'nl@example.com', 'status' => 'confirmed']);
    }
}
```

- [ ] **Step 2: Test fehlschlagen lassen**

Run: `composer test -- --filter=VerifyTest`
Expected: FAIL — `VerifyController does not exist`

- [ ] **Step 3: Controller**

`app/Http/Controllers/Auth/VerifyController.php`:
```php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Mail\VerifyEmailMail;
use App\Models\Lead;
use App\Models\LoginCode;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\URL;

class VerifyController extends Controller
{
    public function notice(Request $request)
    {
        if (Auth::check() && Auth::user()->hasVerifiedEmail()) {
            return redirect()->route('konto');
        }

        return view('auth.verify');
    }

    public function verify(Request $request, int $id, string $hash)
    {
        $user = User::findOrFail($id);
        abort_unless(hash_equals(sha1($user->email), (string) $hash), 403);

        if (! $user->hasVerifiedEmail()) {
            $user->markEmailAsVerified();
            $this->afterVerified($user);
        }

        Auth::login($user, true);

        return redirect()->intended(route('konto'))->with('status', 'E-Mail bestätigt. Willkommen!');
    }

    public function code(Request $request)
    {
        $request->validate(['code' => ['required', 'string']]);

        $userId = $request->session()->get('auth.pending_user_id');
        abort_unless($userId, 419);
        $user = User::findOrFail($userId);

        if (! LoginCode::verifyFor($user, $request->input('code'))) {
            return back()->withErrors(['code' => 'Code ungültig oder abgelaufen.']);
        }

        if (! $user->hasVerifiedEmail()) {
            $user->markEmailAsVerified();
            $this->afterVerified($user);
        }

        $request->session()->forget('auth.pending_user_id');
        Auth::login($user, true);

        return redirect()->intended(route('konto'))->with('status', 'E-Mail bestätigt. Willkommen!');
    }

    public function resend(Request $request)
    {
        $userId = $request->session()->get('auth.pending_user_id');
        abort_unless($userId, 419);
        $user = User::findOrFail($userId);

        if ($user->hasVerifiedEmail()) {
            return redirect()->route('konto');
        }

        $url = URL::temporarySignedRoute('email.verify', now()->addDays(2), [
            'id' => $user->id, 'hash' => sha1($user->email),
        ]);
        $code = LoginCode::issueFor($user);
        Mail::to($user->email)->send(new VerifyEmailMail($url, $code));

        return back()->with('status', 'Neue Bestätigung gesendet.');
    }

    private function afterVerified(User $user): void
    {
        if (! $user->newsletter_opt_in) {
            return;
        }

        $email = mb_strtolower(trim($user->email));
        if (Lead::where('email', $email)->exists()) {
            return;
        }

        Lead::create([
            'email'             => $email,
            'status'            => 'confirmed',
            'confirmed_at'      => now(),
            'unsubscribe_token' => Lead::generateUnsubscribeToken(),
            'source'            => 'account:register',
            'locale'            => 'de',
            'consent_version'   => now()->format('Y-m'),
            'consent_text'      => 'Newsletter-Opt-in bei Konto-Registrierung, E-Mail per Konto verifiziert.',
        ]);
    }
}
```

- [ ] **Step 4: View**

`resources/views/auth/verify.blade.php`:
```blade
@extends('layouts.app')
@section('title', 'E-Mail bestätigen — kotsch.tech')
@section('content')
<div style="max-width:440px;margin:0 auto;padding:60px 20px;">
    <h1 style="font-size:2rem;font-weight:800;margin-bottom:8px;">E-Mail bestätigen</h1>
    <p style="color:#475569;margin-bottom:24px;">Wir haben dir einen Link und einen 6-stelligen Code geschickt. Klicke den Link — oder gib den Code hier ein.</p>

    @if (session('status'))
        <div style="background:#f0fdf4;color:#15803d;padding:12px 16px;border-radius:10px;margin-bottom:16px;">{{ session('status') }}</div>
    @endif
    @if ($errors->any())
        <div style="background:#fef2f2;color:#b91c1c;padding:12px 16px;border-radius:10px;margin-bottom:16px;">{{ $errors->first() }}</div>
    @endif

    <form method="POST" action="{{ route('verification.code') }}">
        @csrf
        <input type="text" name="code" inputmode="numeric" pattern="\d{6}" maxlength="6" placeholder="123456" required
            style="width:100%;padding:14px;border:1px solid #cbd5e1;border-radius:10px;font-size:1.4rem;text-align:center;letter-spacing:.3em;font-family:monospace;margin-bottom:16px;">
        <button type="submit" style="width:100%;background:#6D5AE6;color:#fff;border:none;padding:14px;border-radius:10px;font-weight:700;cursor:pointer;">Bestätigen</button>
    </form>

    <form method="POST" action="{{ route('verification.send') }}" style="margin-top:16px;text-align:center;">
        @csrf
        <button type="submit" style="background:none;border:none;color:#6D5AE6;cursor:pointer;text-decoration:underline;">Code erneut senden</button>
    </form>
</div>
@endsection
```

- [ ] **Step 5: Test grün**

Run: `composer test -- --filter=VerifyTest`
Expected: PASS (3 Tests). Danach `php artisan config:cache`.

- [ ] **Step 6: Commit**

```
git add app/Http/Controllers/Auth/VerifyController.php resources/views/auth/verify.blade.php tests/Feature/Auth/VerifyTest.php
git commit -m "feat(konto): VerifyController (Link + Code + Newsletter-Bridge, 3 Tests grün)"
```

---

## Task 7: LoginController (Passwort + passwortlos) + View

**Files:**
- Create: `app/Http/Controllers/Auth/LoginController.php`
- Create: `resources/views/auth/login.blade.php`
- Test: `tests/Feature/Auth/LoginTest.php`

- [ ] **Step 1: Failing test**

`tests/Feature/Auth/LoginTest.php`:
```php
<?php

namespace Tests\Feature\Auth;

use App\Mail\LoginCodeMail;
use App\Models\LoginCode;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;

class LoginTest extends TestCase
{
    use RefreshDatabase;

    private function verifiedUser(array $attrs = []): User
    {
        return User::create(array_merge([
            'email' => 'log@example.com', 'password' => 'geheim1234', 'email_verified_at' => now(),
        ], $attrs));
    }

    public function test_login_page_loads(): void
    {
        $this->get('/login')->assertOk()->assertSee('Anmelden');
    }

    public function test_password_login_works(): void
    {
        $user = $this->verifiedUser();
        $this->post('/login', ['email' => 'log@example.com', 'password' => 'geheim1234'])
            ->assertRedirect(route('konto'));
        $this->assertAuthenticatedAs($user->fresh());
    }

    public function test_wrong_password_fails(): void
    {
        $this->verifiedUser();
        $this->post('/login', ['email' => 'log@example.com', 'password' => 'falsch'])
            ->assertSessionHasErrors('email');
        $this->assertGuest();
    }

    public function test_unverified_password_login_redirects_to_notice(): void
    {
        User::create(['email' => 'unv@example.com', 'password' => 'geheim1234']);
        $this->post('/login', ['email' => 'unv@example.com', 'password' => 'geheim1234'])
            ->assertRedirect(route('verification.notice'));
    }

    public function test_passwordless_request_sends_code(): void
    {
        Mail::fake();
        $this->verifiedUser(['email' => 'pl@example.com']);
        $this->post('/login/code-anfordern', ['email' => 'pl@example.com'])
            ->assertRedirect(route('login'));
        Mail::assertSent(LoginCodeMail::class);
    }

    public function test_passwordless_code_login_works(): void
    {
        $user = $this->verifiedUser(['email' => 'pl2@example.com']);
        $code = LoginCode::issueFor($user);
        $this->withSession(['auth.pending_user_id' => $user->id])
            ->post('/login/code', ['code' => $code])
            ->assertRedirect(route('konto'));
        $this->assertAuthenticatedAs($user->fresh());
    }

    public function test_logout(): void
    {
        $user = $this->verifiedUser();
        $this->actingAs($user)->post('/logout')->assertRedirect('/');
        $this->assertGuest();
    }
}
```

- [ ] **Step 2: Test fehlschlagen lassen**

Run: `composer test -- --filter=LoginTest`
Expected: FAIL — `LoginController does not exist`

- [ ] **Step 3: Controller**

`app/Http/Controllers/Auth/LoginController.php`:
```php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Mail\LoginCodeMail;
use App\Models\LoginCode;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Illuminate\Validation\ValidationException;

class LoginController extends Controller
{
    public function show()
    {
        if (Auth::check()) {
            return redirect()->route('konto');
        }

        return view('auth.login');
    }

    public function password(Request $request)
    {
        $data = $request->validate([
            'email'    => ['required', 'email'],
            'password' => ['required', 'string'],
            'remember' => ['nullable'],
        ]);

        $email = mb_strtolower(trim($data['email']));
        $user = User::where('email', $email)->first();

        if (! $user || $user->password === null || ! Auth::validate(['email' => $email, 'password' => $data['password']])) {
            throw ValidationException::withMessages(['email' => 'E-Mail oder Passwort ist falsch.']);
        }

        if (! $user->hasVerifiedEmail()) {
            $request->session()->put('auth.pending_user_id', $user->id);
            return redirect()->route('verification.notice')
                ->with('status', 'Bitte bestätige zuerst deine E-Mail.');
        }

        Auth::login($user, $request->boolean('remember'));
        $request->session()->regenerate();

        return redirect()->intended(route('konto'));
    }

    public function requestCode(Request $request)
    {
        $request->validate(['email' => ['required', 'email']]);
        $email = mb_strtolower(trim($request->input('email')));
        $user = User::where('email', $email)->first();

        if ($user) {
            $code = LoginCode::issueFor($user);
            Mail::to($user->email)->send(new LoginCodeMail($code));
            $request->session()->put('auth.pending_user_id', $user->id);
        }

        return redirect()->route('login')
            ->with('status', 'Falls ein Konto existiert, haben wir einen Code gesendet.')
            ->with('show_code_form', true);
    }

    public function code(Request $request)
    {
        $request->validate(['code' => ['required', 'string']]);

        $userId = $request->session()->get('auth.pending_user_id');
        abort_unless($userId, 419);
        $user = User::findOrFail($userId);

        if (! LoginCode::verifyFor($user, $request->input('code'))) {
            return back()->withErrors(['code' => 'Code ungültig oder abgelaufen.'])->with('show_code_form', true);
        }

        if (! $user->hasVerifiedEmail()) {
            $user->markEmailAsVerified();
        }

        $request->session()->forget('auth.pending_user_id');
        Auth::login($user, true);
        $request->session()->regenerate();

        return redirect()->intended(route('konto'));
    }

    public function logout(Request $request)
    {
        Auth::logout();
        $request->session()->invalidate();
        $request->session()->regenerateToken();

        return redirect('/');
    }
}
```

- [ ] **Step 4: View**

`resources/views/auth/login.blade.php`:
```blade
@extends('layouts.app')
@section('title', 'Anmelden — kotsch.tech')
@section('content')
<div style="max-width:440px;margin:0 auto;padding:60px 20px;" x-data="{ mode: '{{ session('show_code_form') ? 'code' : 'password' }}' }">
    <h1 style="font-size:2rem;font-weight:800;margin-bottom:8px;">Anmelden</h1>
    <p style="color:#475569;margin-bottom:24px;">Zugang zu allen Wissen-Artikeln und Premium-Tools.</p>

    @if (session('status'))
        <div style="background:#f0fdf4;color:#15803d;padding:12px 16px;border-radius:10px;margin-bottom:16px;">{{ session('status') }}</div>
    @endif
    @if ($errors->any())
        <div style="background:#fef2f2;color:#b91c1c;padding:12px 16px;border-radius:10px;margin-bottom:16px;">{{ $errors->first() }}</div>
    @endif

    <div style="display:flex;gap:8px;margin-bottom:20px;">
        <button type="button" @click="mode='password'" :style="mode==='password' ? 'background:#6D5AE6;color:#fff' : 'background:#f1f5f9;color:#475569'"
            style="flex:1;border:none;padding:10px;border-radius:8px;cursor:pointer;font-weight:600;">Mit Passwort</button>
        <button type="button" @click="mode='code'" :style="mode==='code' ? 'background:#6D5AE6;color:#fff' : 'background:#f1f5f9;color:#475569'"
            style="flex:1;border:none;padding:10px;border-radius:8px;cursor:pointer;font-weight:600;">Code per E-Mail</button>
    </div>

    <div x-show="mode==='password'">
        <form method="POST" action="{{ route('login.password') }}">
            @csrf
            <input type="email" name="email" placeholder="E-Mail" required value="{{ old('email') }}"
                style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:10px;margin-bottom:12px;">
            <input type="password" name="password" placeholder="Passwort" required autocomplete="current-password"
                style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:10px;margin-bottom:12px;">
            <label style="display:flex;gap:8px;align-items:center;font-size:.9rem;color:#475569;margin-bottom:16px;">
                <input type="checkbox" name="remember" value="1"> Eingeloggt bleiben
            </label>
            <button type="submit" style="width:100%;background:#6D5AE6;color:#fff;border:none;padding:14px;border-radius:10px;font-weight:700;cursor:pointer;">Anmelden</button>
        </form>
        <p style="text-align:center;margin-top:12px;"><a href="{{ route('password.request') }}" style="color:#6D5AE6;">Passwort vergessen?</a></p>
    </div>

    <div x-show="mode==='code'" style="display:none;">
        <form method="POST" action="{{ route('login.code.request') }}" style="margin-bottom:12px;">
            @csrf
            <input type="email" name="email" placeholder="E-Mail" required value="{{ old('email') }}"
                style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:10px;margin-bottom:12px;">
            <button type="submit" style="width:100%;background:#6D5AE6;color:#fff;border:none;padding:14px;border-radius:10px;font-weight:700;cursor:pointer;">Code anfordern</button>
        </form>
        <form method="POST" action="{{ route('login.code') }}">
            @csrf
            <input type="text" name="code" inputmode="numeric" pattern="\d{6}" maxlength="6" placeholder="6-stelliger Code"
                style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:10px;margin-bottom:12px;text-align:center;letter-spacing:.2em;font-family:monospace;">
            <button type="submit" style="width:100%;background:#0f172a;color:#fff;border:none;padding:14px;border-radius:10px;font-weight:700;cursor:pointer;">Einloggen</button>
        </form>
    </div>

    <p style="text-align:center;margin-top:24px;color:#475569;">Noch kein Konto? <a href="{{ route('register') }}" style="color:#6D5AE6;">Registrieren</a></p>
</div>
@endsection
```

Note: `x-data`/`x-show`/`@click` sind Alpine.js — im Projekt bereits eingebunden (wird im Layout geladen). Falls Alpine fehlt: die beiden `<div>`-Blöcke rendern trotzdem beide (kein JS-Fehler), Funktionalität bleibt, nur ohne Tab-Umschaltung. Prüfe im Test nur die Formular-Existenz, nicht das Toggling.

- [ ] **Step 5: Test grün**

Run: `composer test -- --filter=LoginTest`
Expected: PASS (7 Tests). Danach `php artisan config:cache`.

- [ ] **Step 6: Commit**

```
git add app/Http/Controllers/Auth/LoginController.php resources/views/auth/login.blade.php tests/Feature/Auth/LoginTest.php
git commit -m "feat(konto): LoginController Passwort + passwortlos (7 Tests grün)"
```

---

## Task 8: PasswordResetController + Views

**Files:**
- Create: `app/Http/Controllers/Auth/PasswordResetController.php`
- Create: `resources/views/auth/passwort-vergessen.blade.php`, `resources/views/auth/passwort-reset.blade.php`
- Test: `tests/Feature/Auth/PasswordResetTest.php`

- [ ] **Step 1: Failing test**

`tests/Feature/Auth/PasswordResetTest.php`:
```php
<?php

namespace Tests\Feature\Auth;

use App\Mail\ResetPasswordMail;
use App\Models\User;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Password;
use Tests\TestCase;

class PasswordResetTest extends TestCase
{
    use RefreshDatabase;

    public function test_request_page_loads(): void
    {
        $this->get('/passwort/vergessen')->assertOk()->assertSee('Passwort');
    }

    public function test_reset_link_is_sent(): void
    {
        Mail::fake();
        User::create(['email' => 'r@example.com', 'password' => 'geheim1234', 'email_verified_at' => now()]);

        $this->post('/passwort/vergessen', ['email' => 'r@example.com'])->assertSessionHas('status');
        Mail::assertSent(ResetPasswordMail::class);
    }

    public function test_password_can_be_reset(): void
    {
        $user = User::create(['email' => 'r2@example.com', 'password' => 'altesPasswort1', 'email_verified_at' => now()]);
        $token = Password::createToken($user);

        $this->post('/passwort/reset', [
            'token' => $token,
            'email' => 'r2@example.com',
            'password' => 'neuesPasswort9',
            'password_confirmation' => 'neuesPasswort9',
        ])->assertRedirect(route('login'));

        $this->assertTrue(\Illuminate\Support\Facades\Hash::check('neuesPasswort9', $user->fresh()->password));
    }
}
```

- [ ] **Step 2: Test fehlschlagen lassen**

Run: `composer test -- --filter=PasswordResetTest`
Expected: FAIL — `PasswordResetController does not exist`

- [ ] **Step 3: Controller**

`app/Http/Controllers/Auth/PasswordResetController.php`:
```php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Password as PasswordRule;

class PasswordResetController extends Controller
{
    public function request()
    {
        return view('auth.passwort-vergessen');
    }

    public function email(Request $request)
    {
        $request->validate(['email' => ['required', 'email']]);

        Password::sendResetLink(['email' => mb_strtolower(trim($request->input('email')))]);

        // Immer generische Erfolgsmeldung (kein User-Enumeration).
        return back()->with('status', 'Falls ein Konto existiert, haben wir dir einen Link geschickt.');
    }

    public function reset(Request $request, string $token)
    {
        return view('auth.passwort-reset', ['token' => $token, 'email' => $request->query('email')]);
    }

    public function update(Request $request)
    {
        $request->validate([
            'token'    => ['required'],
            'email'    => ['required', 'email'],
            'password' => ['required', 'confirmed', PasswordRule::min(8)],
        ]);

        $status = Password::reset(
            $request->only('email', 'password', 'password_confirmation', 'token'),
            function ($user, $password) {
                $user->forceFill(['password' => $password, 'remember_token' => Str::random(60)])->save();
            }
        );

        if ($status === Password::PASSWORD_RESET) {
            return redirect()->route('login')->with('status', 'Passwort geändert. Du kannst dich jetzt anmelden.');
        }

        return back()->withErrors(['email' => 'Der Link ist ungültig oder abgelaufen.']);
    }
}
```

- [ ] **Step 4: Views**

`resources/views/auth/passwort-vergessen.blade.php`:
```blade
@extends('layouts.app')
@section('title', 'Passwort vergessen — kotsch.tech')
@section('content')
<div style="max-width:440px;margin:0 auto;padding:60px 20px;">
    <h1 style="font-size:2rem;font-weight:800;margin-bottom:8px;">Passwort vergessen</h1>
    <p style="color:#475569;margin-bottom:24px;">Wir schicken dir einen Link zum Zurücksetzen.</p>

    @if (session('status'))
        <div style="background:#f0fdf4;color:#15803d;padding:12px 16px;border-radius:10px;margin-bottom:16px;">{{ session('status') }}</div>
    @endif

    <form method="POST" action="{{ route('password.email') }}">
        @csrf
        <input type="email" name="email" placeholder="E-Mail" required value="{{ old('email') }}"
            style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:10px;margin-bottom:16px;">
        <button type="submit" style="width:100%;background:#6D5AE6;color:#fff;border:none;padding:14px;border-radius:10px;font-weight:700;cursor:pointer;">Link senden</button>
    </form>
    <p style="text-align:center;margin-top:16px;"><a href="{{ route('login') }}" style="color:#6D5AE6;">Zurück zum Login</a></p>
</div>
@endsection
```

`resources/views/auth/passwort-reset.blade.php`:
```blade
@extends('layouts.app')
@section('title', 'Neues Passwort — kotsch.tech')
@section('content')
<div style="max-width:440px;margin:0 auto;padding:60px 20px;">
    <h1 style="font-size:2rem;font-weight:800;margin-bottom:24px;">Neues Passwort</h1>

    @if ($errors->any())
        <div style="background:#fef2f2;color:#b91c1c;padding:12px 16px;border-radius:10px;margin-bottom:16px;">{{ $errors->first() }}</div>
    @endif

    <form method="POST" action="{{ route('password.update') }}">
        @csrf
        <input type="hidden" name="token" value="{{ $token }}">
        <input type="email" name="email" value="{{ $email }}" required
            style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:10px;margin-bottom:12px;">
        <input type="password" name="password" placeholder="Neues Passwort" required autocomplete="new-password"
            style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:10px;margin-bottom:12px;">
        <input type="password" name="password_confirmation" placeholder="Passwort wiederholen" required autocomplete="new-password"
            style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:10px;margin-bottom:16px;">
        <button type="submit" style="width:100%;background:#6D5AE6;color:#fff;border:none;padding:14px;border-radius:10px;font-weight:700;cursor:pointer;">Passwort setzen</button>
    </form>
</div>
@endsection
```

- [ ] **Step 5: Test grün**

Run: `composer test -- --filter=PasswordResetTest`
Expected: PASS (3 Tests). Danach `php artisan config:cache`.

- [ ] **Step 6: Commit**

```
git add app/Http/Controllers/Auth/PasswordResetController.php resources/views/auth/passwort-vergessen.blade.php resources/views/auth/passwort-reset.blade.php tests/Feature/Auth/PasswordResetTest.php
git commit -m "feat(konto): Passwort-Reset (Broker + branded Mail, 3 Tests grün)"
```

---

## Task 9: AccountController (/konto) + View

**Files:**
- Create: `app/Http/Controllers/AccountController.php`
- Create: `resources/views/konto/index.blade.php`
- Test: `tests/Feature/Auth/AccountTest.php`

- [ ] **Step 1: Failing test**

`tests/Feature/Auth/AccountTest.php`:
```php
<?php

namespace Tests\Feature\Auth;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;

class AccountTest extends TestCase
{
    use RefreshDatabase;

    private function member(array $a = []): User
    {
        return User::create(array_merge(['email' => 'm@example.com', 'password' => 'geheim1234', 'email_verified_at' => now()], $a));
    }

    public function test_guest_cannot_access_account(): void
    {
        $this->get('/konto')->assertRedirect(route('login'));
    }

    public function test_member_sees_account(): void
    {
        $this->actingAs($this->member())->get('/konto')->assertOk()->assertSee('Mein Konto');
    }

    public function test_member_can_set_password(): void
    {
        $user = $this->member(['password' => null]);
        $this->actingAs($user)->post('/konto/passwort', [
            'password' => 'neuesPasswort9', 'password_confirmation' => 'neuesPasswort9',
        ])->assertRedirect(route('konto'));
        $this->assertTrue(Hash::check('neuesPasswort9', $user->fresh()->password));
    }

    public function test_member_can_delete_account(): void
    {
        $user = $this->member();
        $this->actingAs($user)->post('/konto/loeschen')->assertRedirect('/');
        $this->assertDatabaseMissing('users', ['id' => $user->id]);
    }
}
```

- [ ] **Step 2: Test fehlschlagen lassen**

Run: `composer test -- --filter=AccountTest`
Expected: FAIL — `AccountController does not exist`

- [ ] **Step 3: Controller**

`app/Http/Controllers/AccountController.php`:
```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rules\Password as PasswordRule;

class AccountController extends Controller
{
    public function index()
    {
        return view('konto.index', ['user' => Auth::user()]);
    }

    public function password(Request $request)
    {
        $request->validate(['password' => ['required', 'confirmed', PasswordRule::min(8)]]);

        $user = Auth::user();
        $user->forceFill(['password' => $request->input('password')])->save();

        return redirect()->route('konto')->with('status', 'Passwort gespeichert.');
    }

    public function newsletter(Request $request)
    {
        $user = Auth::user();
        $user->forceFill(['newsletter_opt_in' => $request->boolean('newsletter')])->save();

        return redirect()->route('konto')->with('status', 'Newsletter-Einstellung gespeichert.');
    }

    public function destroy(Request $request)
    {
        $user = Auth::user();
        Auth::logout();
        $user->delete();
        $request->session()->invalidate();
        $request->session()->regenerateToken();

        return redirect('/')->with('status', 'Dein Konto wurde gelöscht.');
    }
}
```

- [ ] **Step 4: View**

`resources/views/konto/index.blade.php`:
```blade
@extends('layouts.app')
@section('title', 'Mein Konto — kotsch.tech')
@section('content')
<div style="max-width:520px;margin:0 auto;padding:60px 20px;">
    <h1 style="font-size:2rem;font-weight:800;margin-bottom:24px;">Mein Konto</h1>

    @if (session('status'))
        <div style="background:#f0fdf4;color:#15803d;padding:12px 16px;border-radius:10px;margin-bottom:16px;">{{ session('status') }}</div>
    @endif
    @if ($errors->any())
        <div style="background:#fef2f2;color:#b91c1c;padding:12px 16px;border-radius:10px;margin-bottom:16px;">{{ $errors->first() }}</div>
    @endif

    <div style="background:#f8fafc;border-radius:12px;padding:20px;margin-bottom:24px;">
        <p style="margin:0;color:#475569;">Angemeldet als</p>
        <p style="margin:4px 0 0;font-weight:700;font-size:1.1rem;">{{ $user->email }}</p>
    </div>

    <h2 style="font-size:1.2rem;font-weight:700;margin-bottom:12px;">{{ $user->password ? 'Passwort ändern' : 'Passwort setzen' }}</h2>
    <form method="POST" action="{{ route('konto.password') }}" style="margin-bottom:32px;">
        @csrf
        <input type="password" name="password" placeholder="Neues Passwort" required autocomplete="new-password"
            style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:10px;margin-bottom:12px;">
        <input type="password" name="password_confirmation" placeholder="Wiederholen" required autocomplete="new-password"
            style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:10px;margin-bottom:12px;">
        <button type="submit" style="background:#6D5AE6;color:#fff;border:none;padding:12px 24px;border-radius:10px;font-weight:700;cursor:pointer;">Speichern</button>
    </form>

    <h2 style="font-size:1.2rem;font-weight:700;margin-bottom:12px;">Newsletter</h2>
    <form method="POST" action="{{ route('konto.newsletter') }}" style="margin-bottom:32px;">
        @csrf
        <label style="display:flex;gap:8px;align-items:center;margin-bottom:12px;">
            <input type="checkbox" name="newsletter" value="1" @checked($user->newsletter_opt_in)> Newsletter erhalten
        </label>
        <button type="submit" style="background:#0f172a;color:#fff;border:none;padding:12px 24px;border-radius:10px;font-weight:700;cursor:pointer;">Speichern</button>
    </form>

    <form method="POST" action="{{ route('logout') }}" style="margin-bottom:24px;">
        @csrf
        <button type="submit" style="background:#f1f5f9;color:#475569;border:none;padding:12px 24px;border-radius:10px;font-weight:600;cursor:pointer;">Abmelden</button>
    </form>

    <hr style="border:none;border-top:1px solid #e2e8f0;margin:24px 0;">
    <h2 style="font-size:1.05rem;font-weight:700;margin-bottom:8px;color:#b91c1c;">Konto löschen</h2>
    <p style="color:#94a3b8;font-size:.9rem;margin-bottom:12px;">Löscht dein Konto und alle Zugangsdaten unwiderruflich.</p>
    <form method="POST" action="{{ route('konto.destroy') }}" onsubmit="return confirm('Konto wirklich unwiderruflich löschen?');">
        @csrf
        <button type="submit" style="background:#fef2f2;color:#b91c1c;border:1px solid #fecaca;padding:12px 24px;border-radius:10px;font-weight:700;cursor:pointer;">Konto löschen</button>
    </form>
</div>
@endsection
```

- [ ] **Step 5: Test grün**

Run: `composer test -- --filter=AccountTest`
Expected: PASS (4 Tests). Danach `php artisan config:cache`.

- [ ] **Step 6: Commit**

```
git add app/Http/Controllers/AccountController.php resources/views/konto/index.blade.php tests/Feature/Auth/AccountTest.php
git commit -m "feat(konto): Konto-Seite (Passwort/Newsletter/Löschen, 4 Tests grün)"
```

---

## Task 10: Admin-User-Übersicht

**Files:**
- Create: `app/Http/Controllers/Admin/AdminUserController.php`
- Create: `resources/views/admin/users/index.blade.php`
- Modify: `routes/web.php` (admin.panel.-Gruppe)
- Modify: Admin-Sidebar-Partial (Pfad in Step 1 ermitteln)
- Test: `tests/Feature/Auth/AdminUserTest.php`

- [ ] **Step 1: Admin-Muster + Sidebar-Pfad ermitteln**

Run:
```
cd C:\xampp\htdocs\kotsch.tech\kotsch-tech && php artisan route:list --path=admin 2>&1 | Select-String -Pattern 'admin.panel' | Select-Object -First 5
```
Und Sidebar finden:
```
Get-ChildItem -Recurse resources/views/admin -Filter '*.blade.php' | Select-String -Pattern 'admin.panel.' -List | Select-Object Path -Unique
```
Notiere: die Datei mit den Sidebar-Links (enthält viele `route('admin.panel.…')`-Links) und wie ein bestehender `AdminController`-Test die Admin-Session aufbaut. **Wichtig:** `admin_sessions`-Insert braucht `token_hash`, `email`, `expires_at` (siehe bestehende Admin-Feature-Tests, z.B. `tests/Feature/…Admin…Test.php` — dort das `actingAsAdmin()`/Session-Setup kopieren).

- [ ] **Step 2: Failing test**

`tests/Feature/Auth/AdminUserTest.php` — nutze das in Step 1 gefundene Admin-Session-Setup. Vorlage (Session-Insert ggf. an bestehendes Muster anpassen):
```php
<?php

namespace Tests\Feature\Auth;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;

class AdminUserTest extends TestCase
{
    use RefreshDatabase;

    private function actingAsAdmin(): void
    {
        $token = bin2hex(random_bytes(32));
        DB::table('admin_sessions')->insert([
            'token_hash' => hash('sha256', $token),
            'email'      => 'admin@kotsch.tech',
            'ip_hash'    => hash('sha256', '127.0.0.1'),
            'expires_at' => now()->addDays(7),
            'created_at' => now(),
            'updated_at' => now(),
        ]);
        $this->withCookie('admin_auth_token', $token);
    }

    public function test_guest_cannot_see_admin_users(): void
    {
        $this->get('/admin/users')->assertRedirect();
    }

    public function test_admin_sees_user_list(): void
    {
        User::create(['email' => 'listed@example.com', 'password' => 'geheim1234', 'email_verified_at' => now()]);
        $this->actingAsAdmin();
        $this->get('/admin/users')->assertOk()->assertSee('listed@example.com');
    }

    public function test_admin_can_delete_user(): void
    {
        $user = User::create(['email' => 'del@example.com', 'password' => 'geheim1234']);
        $this->actingAsAdmin();
        $this->post('/admin/users/' . $user->id . '/loeschen')->assertRedirect(route('admin.panel.users'));
        $this->assertDatabaseMissing('users', ['id' => $user->id]);
    }
}
```

- [ ] **Step 3: Test fehlschlagen lassen**

Run: `composer test -- --filter=AdminUserTest`
Expected: FAIL — Route/Controller fehlt.

- [ ] **Step 4: Controller**

`app/Http/Controllers/Admin/AdminUserController.php`:
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;

class AdminUserController extends Controller
{
    public function index(Request $request)
    {
        $q = trim((string) $request->query('q', ''));

        $users = User::query()
            ->when($q !== '', fn ($query) => $query->where('email', 'like', '%' . $q . '%'))
            ->orderByDesc('id')
            ->paginate(50)
            ->withQueryString();

        $stats = [
            'total'    => User::count(),
            'verified' => User::whereNotNull('email_verified_at')->count(),
            'newsletter' => User::where('newsletter_opt_in', true)->count(),
        ];

        return view('admin.users.index', compact('users', 'stats', 'q'));
    }

    public function destroy(User $user)
    {
        $user->delete();

        return redirect()->route('admin.panel.users')->with('status', 'Benutzer gelöscht.');
    }
}
```

- [ ] **Step 5: Routen in admin.panel.-Gruppe**

In `routes/web.php`, innerhalb der bestehenden `Route::prefix('admin')->name('admin.panel.')->middleware('admin.auth')->group(function () { … })`-Gruppe ergänzen:
```php
    Route::get('/users', [\App\Http\Controllers\Admin\AdminUserController::class, 'index'])->name('users');
    Route::post('/users/{user}/loeschen', [\App\Http\Controllers\Admin\AdminUserController::class, 'destroy'])->name('users.destroy');
```

- [ ] **Step 6: View**

`resources/views/admin/users/index.blade.php` — nutze das Layout, das andere Admin-Views nutzen (in Step 1 gesehen, z.B. `@extends('admin.layout')` oder `layouts.admin`). Falls unklar, prüfe den Kopf einer bestehenden Admin-View und spiegle ihn. Inhalt:
```blade
@extends('admin.layout')
@section('admin_content')
<div style="padding:24px;">
    <h1 style="font-size:1.6rem;font-weight:800;margin-bottom:16px;">Benutzer ({{ $stats['total'] }})</h1>
    <p style="color:#64748b;margin-bottom:20px;">Verifiziert: {{ $stats['verified'] }} · Newsletter: {{ $stats['newsletter'] }}</p>

    @if (session('status'))
        <div style="background:#f0fdf4;color:#15803d;padding:10px 14px;border-radius:8px;margin-bottom:16px;">{{ session('status') }}</div>
    @endif

    <form method="GET" style="margin-bottom:16px;">
        <input type="text" name="q" value="{{ $q }}" placeholder="E-Mail suchen…"
            style="padding:10px 14px;border:1px solid #cbd5e1;border-radius:8px;width:280px;">
        <button type="submit" style="padding:10px 18px;border:none;background:#6D5AE6;color:#fff;border-radius:8px;cursor:pointer;">Suchen</button>
    </form>

    <table style="width:100%;border-collapse:collapse;">
        <thead>
            <tr style="text-align:left;border-bottom:2px solid #e2e8f0;">
                <th style="padding:8px;">E-Mail</th><th style="padding:8px;">Verifiziert</th>
                <th style="padding:8px;">Newsletter</th><th style="padding:8px;">Registriert</th><th></th>
            </tr>
        </thead>
        <tbody>
        @foreach ($users as $u)
            <tr style="border-bottom:1px solid #f1f5f9;">
                <td style="padding:8px;">{{ $u->email }}</td>
                <td style="padding:8px;">{{ $u->email_verified_at ? '✓' : '—' }}</td>
                <td style="padding:8px;">{{ $u->newsletter_opt_in ? '✓' : '—' }}</td>
                <td style="padding:8px;">{{ $u->created_at?->format('d.m.Y') }}</td>
                <td style="padding:8px;">
                    <form method="POST" action="{{ route('admin.panel.users.destroy', $u) }}" onsubmit="return confirm('Benutzer löschen?');">
                        @csrf
                        <button type="submit" style="background:none;border:none;color:#b91c1c;cursor:pointer;">Löschen</button>
                    </form>
                </td>
            </tr>
        @endforeach
        </tbody>
    </table>

    <div style="margin-top:20px;">{{ $users->links() }}</div>
</div>
@endsection
```
Note: Falls das Admin-Layout andere Section-/Extends-Namen nutzt (in Step 1 ermittelt), passe `@extends`/`@section` entsprechend an.

- [ ] **Step 7: Sidebar-Link**

In der in Step 1 gefundenen Sidebar-Datei einen Link ergänzen (neben den anderen `admin.panel.*`-Links):
```blade
<a href="{{ route('admin.panel.users') }}">Benutzer</a>
```
(Styling/Markup an die bestehenden Sidebar-Links anpassen.)

- [ ] **Step 8: Test grün**

Run: `composer test -- --filter=AdminUserTest`
Expected: PASS (3 Tests). Danach `php artisan config:cache`.

- [ ] **Step 9: Commit**

```
git add app/Http/Controllers/Admin/AdminUserController.php resources/views/admin/users/index.blade.php routes/web.php
git add resources/views/admin/  # Sidebar-Datei
git commit -m "feat(konto): Admin-Benutzerübersicht (Liste/Suche/Löschen, 3 Tests grün)"
```

---

## Task 11: Datenschutz-Abschnitt + Gesamt-Testlauf

**Files:**
- Modify: `config/datenschutz.php` (oder `app/Http/Controllers/DatenschutzController.php` — in Step 1 ermitteln)
- Verify: gesamte Suite

- [ ] **Step 1: Datenschutz-Struktur prüfen**

Run:
```
cd C:\xampp\htdocs\kotsch.tech\kotsch-tech && Get-Content config/datenschutz.php | Select-Object -First 60
```
Finde, wie Abschnitte definiert werden (Array-Struktur pro Bereich). Ergänze einen Abschnitt „Benutzerkonto":
- Welche Daten: E-Mail, optional Name, Passwort-Hash, Verifizierungs-/Login-Codes, Einwilligungs-Zeitpunkt.
- Zweck: Zugang zu geschützten Inhalten (Wissen, Premium-Tools).
- Speicherdauer: bis zur Konto-Löschung durch den Nutzer.
- Rechtsgrundlage: Vertragserfüllung (Art. 6 (1) b DSGVO), Einwilligung für Newsletter.
- Löschung: jederzeit selbst unter `/konto` möglich.

Halte dich exakt an das bestehende Array-/Struktur-Muster der Datei.

- [ ] **Step 2: Datenschutz-Seite lädt fehlerfrei**

Run: `php artisan route:list --path=datenschutz 2>&1 | Select-Object -First 3`
Und Smoke: die Seite darf keinen 500 werfen (im Gesamt-Testlauf abgedeckt, falls ein Datenschutz-Test existiert).

- [ ] **Step 3: Gesamte Auth-Suite grün**

Run: `cd C:\xampp\htdocs\kotsch.tech\kotsch-tech && composer test -- --filter=Auth`
Expected: alle Auth-Tests grün (LoginCode 3 + Register 4 + Verify 3 + Login 7 + PasswordReset 3 + Account 4 + AdminUser 3 = **27 Tests**).

- [ ] **Step 4: Voller Testlauf — keine neuen Regressionen**

Run: `cd C:\xampp\htdocs\kotsch.tech\kotsch-tech && composer test 2>&1 | Select-Object -Last 20`
Expected: Die neuen 27 Auth-Tests grün. Vorbestehende ~54 Fehler (i18n/Shop/AdminStatus) unverändert — **keine neuen** Fehler durch Phase 1. Falls neue Fehler: Ursache beheben.

- [ ] **Step 5: config:cache wiederherstellen**

Run: `cd C:\xampp\htdocs\kotsch.tech\kotsch-tech && php artisan config:cache`
Expected: `Configuration cached successfully.`

- [ ] **Step 6: Commit**

```
git add config/datenschutz.php
git commit -m "feat(konto): Datenschutz-Abschnitt Benutzerkonto + Phase-1-Suite grün (27 Tests)"
```

---

## Self-Review-Checkliste (vom Autor bereits geprüft)

- **Spec-Abdeckung:** users-Tabelle+Consent (T1), login_codes (T1/T2), Register mit/ohne PW (T5), Verify Link+Code (T6), Newsletter-Bridge (T6), Login Passwort+passwortlos (T7), Remember (T7), Passwort-Reset (T8), /konto inkl. Löschen (T9), Admin-Users (T10), DSGVO-Abschnitt (T11), Throttling (T4-Routen), Middleware-Aliase (T4). ✓
- **Typ-Konsistenz:** `LoginCode::issueFor()`/`verifyFor()` einheitlich in T2/T5/T6/T7. `auth.pending_user_id` Session-Key einheitlich. Route-Namen (`konto`, `login`, `verification.notice`, `email.verify`, `password.reset`) konsistent zwischen Routen (T4) und Controllern. ✓
- **Keine Platzhalter:** vollständiger Code je Step. Zwei bewusste Ermittlungs-Steps (Admin-Sidebar-Pfad T10-S1, Datenschutz-Struktur T11-S1) sind projektspezifische Discovery mit klarem Ziel — kein „TODO". ✓
