Commit Graph

225 Commits

Author SHA1 Message Date
vk
6aa05e47f3 News Design (vibe-kanban 220425f3)
Nutze das aktuelle pencil Design und setze die News Seite 100% genauso um. Validiere deine Ergebnisse mit playwright. Überschreibe tailwindcss und Flux UI Styles wenn nötig.
2026-02-13 16:02:03 +01:00
vk
ff7b9a3493 Die News Ansicht verschönern (vibe-kanban 7c9cbf57)
Nutze Livewire Flux UI um die Ansicht der News Items zu verschönern. Teste alles am besten auch eine Validierung mit playwright, ob alles passt und richtig in mobile und Desktop angezeigt wird.
2026-02-12 23:20:23 +01:00
vk
0c64fe55d7 Kleine Bilder anzeigen (vibe-kanban 9bcfc693)
Die Ladezeiten der Bilder ist zu hoch, weil die Original geladen werden.
Bei /association/project-support lade in der Übersicht und in der Einzel-Ansicht /association/project-support/badgebox-for-nostr-manage-your-badges nur die Conversions der Bilder, also die kleinere Versionen.
2026-02-12 22:53:56 +01:00
vk
d1b9dad35e [P2 Security] Laravel Authorization Policies für ProjectProposal, Vote, Election (vibe-kanban 85007440)
## Security Audit: Fehlende zentralisierte Autorisierung

### Problem
Die Anwendung hat **keine Laravel Policy-Klassen**. Autorisierungslogik ist verstreut in:

- **Blade Templates:** Inline `@if`-Checks gegen `config('einundzwanzig.config.current_board')`
- **Livewire Trait:** `app/Livewire/Traits/WithNostrAuth.php` setzt `$isAllowed` und `$canEdit` Booleans
- **Component-Mount-Methoden:** z.B. in `project-support/form/create.blade.php` (Zeile 41-47)

Die Board-Member-Autorisierung funktioniert über einen Vergleich mit hartkodierten npubs in `config/einundzwanzig/config.php`:
```php
'current_board' => [
    'npub1pt0kw36...', 'npub1gvqkjc...', // etc.
]
```

**Probleme:**
- Keine zentrale Stelle für Berechtigungsprüfungen
- Autorisierung kann leicht vergessen werden wenn neue Endpoints hinzukommen
- Board-Member-Check wird an vielen Stellen dupliziert
- Livewire-Actions können ggf. direkt aufgerufen werden ohne UI-seitige Prüfung

### Lösung

**1. Laravel Policies erstellen:**

```bash
php artisan make:policy ProjectProposalPolicy --model=ProjectProposal --no-interaction
php artisan make:policy VotePolicy --model=Vote --no-interaction
php artisan make:policy ElectionPolicy --model=Election --no-interaction
```

**2. Policy-Methoden implementieren:**

Für `ProjectProposalPolicy`:
- `viewAny()` – Jeder darf die Liste sehen
- `view()` – Jeder darf ein Proposal sehen
- `create()` – Nur authentifizierte User mit `association_status > 1` und bezahlter Mitgliedschaft im aktuellen Jahr
- `update()` – Nur der Ersteller ODER Board-Members
- `delete()` – Nur Board-Members
- `accept()` – Nur Board-Members (custom method für `accepted`-Flag)

Für `VotePolicy`:
- `create()` – Authentifizierte User, die noch nicht für dieses Proposal abgestimmt haben
- `update()` / `delete()` – Nur der eigene Vote

Für `ElectionPolicy`:
- `viewAny()` / `view()` – Jeder
- `create()` / `update()` / `delete()` – Nur Board-Members
- `vote()` – Authentifizierte Mitglieder mit gültigem Status

**3. Auth-Checks in der Nostr-Auth-Architektur:**

Die App nutzt eine Custom NostrAuth (`app/Support/NostrAuth.php`, `app/Auth/NostrUser.php`). Die Policies müssen mit `NostrUser` funktionieren. Prüfe ob `NostrUser` das `Authenticatable` Interface korrekt implementiert, damit `$this->authorize()` und `Gate::allows()` in Livewire-Components funktionieren.

**4. Policies in Livewire-Components nutzen:**

Ersetze die inline-Checks in den Components durch Policy-Aufrufe:
```php
// Vorher (verstreut in Blade/Component):
if (in_array($this->currentPleb->npub, config('einundzwanzig.config.current_board'), true)) { ... }

// Nachher (zentralisiert):
$this->authorize('update', $projectProposal);
```

### Betroffene Dateien
- **Neue Dateien:** `app/Policies/ProjectProposalPolicy.php`, `app/Policies/VotePolicy.php`, `app/Policies/ElectionPolicy.php`
- **Anpassen:** `app/Livewire/Traits/WithNostrAuth.php` – Board-Check in Policy auslagern
- **Anpassen:** Livewire-Components in `app/Livewire/Association/ProjectSupport/` – `$this->authorize()` nutzen
- **Anpassen:** Livewire-Components in `app/Livewire/Association/Election/` – `$this->authorize()` nutzen
- **Prüfen:** `app/Auth/NostrUser.php` – Kompatibilität mit Policy-System
- **Prüfen:** `config/einundzwanzig/config.php` – Board-Member-Liste wird weiterhin als Datenquelle genutzt

### Vorgehen
1. `search-docs` nutzen: `queries: ['policies', 'authorization', 'gates']` und `packages: ['laravel/framework']`
2. Prüfe wie `NostrUser` mit Laravel's Authorization-System zusammenspielt
3. Policies mit `php artisan make:policy` erstellen
4. Policy-Methoden implementieren (Board-Check-Logik zentralisieren)
5. Livewire-Components auf Policy-Aufrufe umstellen
6. Blade-Templates: `@can` / `@cannot` Directives nutzen statt inline `@if`
7. Pest Feature-Tests für jede Policy-Methode schreiben
8. `vendor/bin/pint --dirty` und `php artisan test --compact`

### Akzeptanzkriterien
- 3 Policy-Klassen existieren und sind registriert
- Board-Member-Check ist an EINER Stelle definiert (in Policy oder Helper)
- Livewire-Components nutzen `$this->authorize()` statt inline-Checks
- Blade-Templates nutzen `@can` / `@cannot` Directives
- Pest-Tests decken alle Policy-Methoden ab (allow & deny)
- Bestehende Funktionalität bleibt erhalten
2026-02-11 23:49:53 +01:00
vk
0639c1a656 Fix all tests (vibe-kanban bba3e2c9)
Fixe alle tests. Frage mich, wenn du nicht weißt, was zu tun ist.
2026-02-11 23:15:49 +01:00
vk
9faae15212 [P1 Security] Rate Limiting für API-Routes und Livewire-Actions (vibe-kanban e1f85c61)
## Security Audit: Fehlendes Rate Limiting

### Problem
Die Anwendung hat **kein Rate Limiting** auf API-Routes oder Livewire-Actions. Das ermöglicht:
- Brute-Force-Angriffe auf Authentication-Endpoints
- Denial-of-Service durch massenhaftes Aufrufen von API-Endpoints
- Vote-Manipulation durch schnelle, wiederholte Requests
- Ressourcen-Erschöpfung durch unkontrollierte Datenbankabfragen

### Betroffene Endpoints

**API-Routes (`routes/api.php`):**
```php
Route::get('/nostr/profile/{key}', GetProfile::class);  // kein Rate Limit
Route::get('/members/{year}', GetPaidMembers::class);    // kein Rate Limit
```

**Kritische Livewire-Actions (kein Throttling):**
- Voting auf ProjectProposals (`association/project-support/show`)
- Login via Nostr (`handleNostrLogin`)
- ProjectProposal-Erstellung
- Election Voting

### Lösung

**1. API Rate Limiting in `bootstrap/app.php`:**
Nutze Laravel 12's Middleware-Konfiguration um Rate Limiting zu aktivieren:

```php
->withMiddleware(function (Middleware $middleware) {
    $middleware->api(prepend: [
        \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
    ]);
})
```

Und definiere den `api` Rate Limiter in `app/Providers/AppServiceProvider.php`:

```php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

public function boot(): void
{
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60)->by($request->ip());
    });
}
```

**2. Livewire Action Throttling:**
Nutze Livewire's `#[Throttle]` Attribut auf sensiblen Actions. Suche in der Livewire-Dokumentation nach der korrekten v4-Syntax mit `search-docs`:
- `queries: ['throttle', 'rate limiting']`
- `packages: ['livewire/livewire']`

Wende Throttling an auf:
- Vote-Submit-Methoden in den ProjectSupport-Components
- Login-Handler (`handleNostrLogin` in `WithNostrAuth` Trait)
- ProjectProposal Create/Update Actions

**3. Zusätzlicher Custom Rate Limiter für Voting:**

```php
RateLimiter::for('voting', function (Request $request) {
    return Limit::perMinute(10)->by($request->ip());
});
```

### Betroffene Dateien
- `bootstrap/app.php` – Middleware-Konfiguration (Rate Limit Middleware hinzufügen)
- `app/Providers/AppServiceProvider.php` – RateLimiter Definitionen
- `routes/api.php` – Rate Limit Middleware anwenden
- `app/Livewire/Traits/WithNostrAuth.php` – Throttle auf `handleNostrLogin`
- Livewire-Components in `app/Livewire/Association/ProjectSupport/` – Throttle auf Vote/Create Actions

### Vorgehen
1. `search-docs` nutzen für: `['rate limiting', 'throttle']` (Laravel) und `['throttle']` (Livewire)
2. Rate Limiter in AppServiceProvider definieren
3. API-Middleware in `bootstrap/app.php` konfigurieren
4. Livewire-Actions mit Throttle versehen
5. Pest-Tests schreiben, die verifizieren dass Rate Limiting greift (429 Response bei Überschreitung)
6. `vendor/bin/pint --dirty` und `php artisan test --compact`

### Akzeptanzkriterien
- API-Routes geben HTTP 429 nach 60 Requests/Minute zurück
- Voting-Actions sind auf max. 10/Minute limitiert
- Login-Attempts sind throttled
- Tests verifizieren Rate Limiting Verhalten
2026-02-11 21:13:36 +01:00
vk
90288ac20e [P0 Security] Mass Assignment Protection – $fillable für alle 18 Models (vibe-kanban 4a764a11)
## Security Audit: Mass Assignment Protection

### Problem
Alle 18 Eloquent Models verwenden `protected $guarded = [];` – das bedeutet **kein Schutz** gegen Mass Assignment. Ein Angreifer könnte über manipulierte Requests sensible Felder wie `accepted`, `sats_paid`, `association_status`, `paid` oder `created_by` direkt setzen.

### Betroffene Dateien und empfohlene Änderungen

Ersetze in **jedem** der folgenden Models `protected $guarded = [];` durch ein explizites `protected $fillable = [...]` Array. Hier die Analyse pro Model:

**Höchstes Risiko (Finanzen & Identity):**

1. **`app/Models/PaymentEvent.php`** – Finanz-kritisch!
   - Sensible Felder (NICHT fillable): `einundzwanzig_pleb_id`, `year`, `amount`, `event_id`, `paid`, `btc_pay_invoice`
   - `$fillable` sollte leer oder minimal sein – alle Felder werden programmatisch gesetzt

2. **`app/Models/EinundzwanzigPleb.php`**
   - Sensible Felder: `association_status`, `application_for`, `nip05_handle`
   - `$fillable = ['npub', 'pubkey', 'email', 'no_email', 'application_text', 'archived_application_text']`

3. **`app/Models/Vote.php`**
   - Sensible Felder: `einundzwanzig_pleb_id`, `project_proposal_id`, `value`
   - `$fillable = ['reason']` – alle anderen Felder müssen programmatisch gesetzt werden

4. **`app/Models/ProjectProposal.php`**
   - Sensible Felder: `einundzwanzig_pleb_id`, `accepted`, `sats_paid`, `slug`
   - `$fillable = ['name', 'support_in_sats', 'description', 'website']`

5. **`app/Models/Election.php`**
   - Sensible Felder: `year`, `candidates`, `end_time`
   - `$fillable` sollte leer sein – nur Admin-gesteuert

**Mittleres Risiko (mit `created_by` auto-fill in boot):**

6. **`app/Models/Venue.php`** – `$fillable = ['name']` (slug & created_by auto-generiert)
7. **`app/Models/MeetupEvent.php`** – `$fillable = ['start']` (meetup_id, created_by, attendees guarded)
8. **`app/Models/CourseEvent.php`** – `$fillable = ['from', 'to']` (course_id, venue_id, created_by guarded)
9. **`app/Models/Course.php`** – `$fillable = ['name', 'description']` (lecturer_id, created_by guarded)
10. **`app/Models/Meetup.php`** – `$fillable = ['name']` (city_id, created_by, slug, github_data, simplified_geojson guarded)
11. **`app/Models/Lecturer.php`** – `$fillable = ['name']` (active, created_by, slug guarded)
12. **`app/Models/City.php`** – `$fillable = ['name']` (country_id, created_by, slug, osm_relation, simplified_geojson guarded)

**Niedrigeres Risiko (Lookup/Reference-Daten):**

13. **`app/Models/Event.php`** – `$fillable = []` (alle Felder: event_id, parent_event_id, pubkey, json, type sind extern gesteuert)
14. **`app/Models/RenderedEvent.php`** – `$fillable = []` (event_id, html, profile_image, profile_name alle system-generiert)
15. **`app/Models/Profile.php`** – `$fillable = ['name', 'display_name', 'picture', 'banner', 'website', 'about']` (pubkey, deleted, nip05, lud16, lud06 guarded)
16. **`app/Models/Category.php`** – `$fillable = ['name']`
17. **`app/Models/Country.php`** – `$fillable = ['name']` (code, language_codes guarded)
18. **`app/Models/Notification.php`** – `$fillable = ['name', 'description']` (einundzwanzig_pleb_id, category guarded)

### Vorgehen
1. Jedes Model öffnen und `$guarded = []` durch das oben definierte `$fillable` Array ersetzen
2. Prüfen, ob bestehende `::create()` oder `::update()` Aufrufe noch funktionieren – ggf. müssen explizite Feld-Zuweisungen ergänzt werden
3. Für jedes geänderte Model einen Pest-Test schreiben, der verifiziert, dass Mass Assignment von sensiblen Feldern blockiert wird
4. `vendor/bin/pint --dirty` ausführen
5. Bestehende Tests laufen lassen: `php artisan test --compact`

### Akzeptanzkriterien
- Kein Model hat mehr `$guarded = []`
- Alle sensiblen Felder (status, paid, accepted, created_by, slug, IDs) sind NICHT in `$fillable`
- Bestehende Features funktionieren weiterhin (Tests grün)
- Neue Tests verifizieren Mass Assignment Protection
2026-02-11 21:13:36 +01:00
HolgerHatGarKeineNode
eb9285d601 Fix this (vibe-kanban 0064af70)
## Exception Summary

- Class: `TypeError`
- Message: `Cannot assign array to property Livewire\Component@anonymous::$fax of type string`
- Code: `0`
- File: `vendor/livewire/livewire/src/Mechanisms/HandleComponents/HandleComponents.php:555`
- Timestamp: `2026-02-11T11:10:02+00:00`
- Details: This exception was thrown during a HTTP Request.

## HTTP Request

- Method: `POST`
- URL: `https://verein.einundzwanzig.space/livewire-5c4c7b52/update`
- Route: `livewire.update`
- Status code: `500`
- IP address: `206.237.102.28`

### Request Headers

- `content-type: application/json`
- `content-length: 1484`
- `cookie: XSRF-TOKEN=[342 bytes redacted]; einundzwanzig_verein_session=[342 bytes redacted]`
- `sec-fetch-user: ?1`
- `sec-fetch-site: none`
- `sec-fetch-mode: navigate`
- `sec-fetch-dest: document`
- `upgrade-insecure-requests: 1`
- `connection: keep-alive`
- `accept-language: en-US,en;q=0.5`
- `accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8`
- `user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36`
- `accept-encoding: identity`
- `host: verein.einundzwanzig.space`

### Request Payload

```json
{
    "_token": "[40 bytes redacted]",
    "components": [
        {
            "snapshot": "{\"data\": {\"form\": [{\"reason\": \"\", \"check\": false, \"currentPleb\": null}, {\"class\": \"App\\\\Livewire\\\\Forms\\\\ApplicationForm\", \"s\": \"form\"}], \"profileForm\": [{\"email\": \"\", \"nip05Handle\": \"\", \"currentPleb\": null}, {\"class\": \"App\\\\Livewire\\\\Forms\\\\ProfileForm\", \"s\": \"form\"}], \"no\": false, \"showEmail\": true, \"fax\": \"\", \"nip05Verified\": false, \"nip05VerifiedHandle\": null, \"nip05HandleMismatch\": false, \"nip05VerifiedHandles\": [[], {\"s\": \"arr\"}], \"yearsPaid\": [[], {\"s\": \"arr\"}], \"events\": [[], {\"s\": \"arr\"}], \"payments\": null, \"invoiceStatus\": null, \"invoiceStatusLabel\": null, \"invoiceStatusMessage\": null, \"invoiceStatusVariant\": \"info\", \"invoiceExpiresAt\": null, \"invoiceExpiresAtDisplay\": null, \"invoiceExpiresIn\": null, \"amountToPay\": 21000, \"currentYearIsPaid\": false, \"currentPubkey\": null, \"currentPleb\": null, \"qrCode\": null}, \"memo\": {\"id\": \"w62UhlWfdP5hmH873cUk\", \"name\": \"association.profile\", \"path\": \"association/profile\", \"method\": \"GET\", \"release\": \"a-a-a\", \"attributes\": {\"_livewire_component\": \"association.profile\"}, \"children\": [], \"scripts\": [], \"assets\": [], \"errors\": [], \"locale\": \"en\", \"islands\": []}, \"checksum\": \"ef5141c41105681483f6be62494c4717a8d5a43c8c5a07173ea799a2a58d7005\"}",
            "updates": {
                "fax": []
            },
            "calls": []
        }
    ],
    "_nightwatch_files": []
}
```

### Authenticated User

- Not authenticated for this execution.

## Database Queries (before exception)

- Not captured

## Stack Trace (most recent call first)

- [0] Livewire\\Mechanisms\\HandleComponents\\HandleComponents->setComponentPropertyAwareOfTypes()

    at vendor/livewire/livewire/src/Mechanisms/HandleComponents/HandleComponents.php:555

- [1] Livewire\\Mechanisms\\HandleComponents\\HandleComponents->setComponentPropertyAwareOfTypes()

    at vendor/livewire/livewire/src/Mechanisms/HandleComponents/HandleComponents.php:455

- [2] Livewire\\Mechanisms\\HandleComponents\\HandleComponents->updateProperty()

    at vendor/livewire/livewire/src/Mechanisms/HandleComponents/HandleComponents.php:426

- [3] Livewire\\Mechanisms\\HandleComponents\\HandleComponents->updateProperties()

    at vendor/livewire/livewire/src/Mechanisms/HandleComponents/HandleComponents.php:188

- [4] Livewire\\Mechanisms\\HandleComponents\\HandleComponents->update()

    at vendor/livewire/livewire/src/LivewireManager.php:131

- [5] Livewire\\LivewireManager->update()

    at vendor/livewire/volt/src/LivewireManager.php:35

- [6] Livewire\\Volt\\LivewireManager->update()

    at vendor/livewire/livewire/src/Mechanisms/HandleRequests/HandleRequests.php:123

- [7] Livewire\\Mechanisms\\HandleRequests\\HandleRequests->handleUpdate()

    at vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php:46

- [8] Illuminate\\Routing\\ControllerDispatcher->dispatch()

    at vendor/laravel/framework/src/Illuminate/Routing/Route.php:265

- [9] Illuminate\\Routing\\Route->runController()

    at vendor/laravel/framework/src/Illuminate/Routing/Route.php:211

- [10] Illuminate\\Routing\\Route->run()

    at vendor/laravel/framework/src/Illuminate/Routing/Router.php:822

- [11] Illuminate\\Routing\\Router->{closure:Illuminate\\Routing\\Router::runRouteWithinStack():821}()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:180

- [12] Illuminate\\Pipeline\\Pipeline->{closure:Illuminate\\Pipeline\\Pipeline::prepareDestination():178}()

    at vendor/laravel/nightwatch/src/Hooks/RouteMiddleware.php:34

- [13] Laravel\\Nightwatch\\Hooks\\RouteMiddleware->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [14] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php:50

- [15] Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [16] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php:87

- [17] Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [18] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php:48

- [19] Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [20] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php:120

- [21] Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()

    at vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php:63

- [22] Illuminate\\Session\\Middleware\\StartSession->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [23] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php:36

- [24] Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [25] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php:74

- [26] Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [27] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:137

- [28] Illuminate\\Pipeline\\Pipeline->then()

    at vendor/laravel/framework/src/Illuminate/Routing/Router.php:821

- [29] Illuminate\\Routing\\Router->runRouteWithinStack()

    at vendor/laravel/framework/src/Illuminate/Routing/Router.php:800

- [30] Illuminate\\Routing\\Router->runRoute()

    at vendor/laravel/framework/src/Illuminate/Routing/Router.php:764

- [31] Illuminate\\Routing\\Router->dispatchToRoute()

    at vendor/laravel/framework/src/Illuminate/Routing/Router.php:753

- [32] Illuminate\\Routing\\Router->dispatch()

    at vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:200

- [33] Illuminate\\Foundation\\Http\\Kernel->{closure:Illuminate\\Foundation\\Http\\Kernel::dispatchToRouter():197}()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:180

- [34] Illuminate\\Pipeline\\Pipeline->{closure:Illuminate\\Pipeline\\Pipeline::prepareDestination():178}()

    at vendor/livewire/livewire/src/Features/SupportDisablingBackButtonCache/DisableBackButtonCacheMiddleware.php:19

- [35] Livewire\\Features\\SupportDisablingBackButtonCache\\DisableBackButtonCacheMiddleware->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [36] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php:21

- [37] Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()

    at vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php:31

- [38] Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [39] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php:21

- [40] Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()

    at vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php:51

- [41] Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [42] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Http/Middleware/ValidatePostSize.php:27

- [43] Illuminate\\Http\\Middleware\\ValidatePostSize->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [44] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php:109

- [45] Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [46] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php:48

- [47] Illuminate\\Http\\Middleware\\HandleCors->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [48] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php:58

- [49] Illuminate\\Http\\Middleware\\TrustProxies->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [50] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php:22

- [51] Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [52] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Http/Middleware/ValidatePathEncoding.php:26

- [53] Illuminate\\Http\\Middleware\\ValidatePathEncoding->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [54] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/nightwatch/src/Hooks/GlobalMiddleware.php:53

- [55] Laravel\\Nightwatch\\Hooks\\GlobalMiddleware->handle()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:219

- [56] Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}()

    at vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:137

- [57] Illuminate\\Pipeline\\Pipeline->then()

    at vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:175

- [58] Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()

    at vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:144

- [59] Illuminate\\Foundation\\Http\\Kernel->handle()

    at vendor/laravel/framework/src/Illuminate/Foundation/Application.php:1220

- [60] Illuminate\\Foundation\\Application->handleRequest()

    at public/index.php:17

## Code Context

- Not captured

## Occurrence Statistics

- First seen: `2026-02-02T01:38:48+00:00`
- Last seen: `2026-02-11T11:10:02+00:00`
- Occurrences (last 24 hours): `3`
- Occurrences (last 7 days): `3`
- Users affected: `0`
2026-02-11 14:37:05 +01:00
HolgerHatGarKeineNode
f5cb82566a 🔒 Remove #[Locked] attribute from nip05Handle in benefits view 2026-02-05 21:16:43 +01:00
HolgerHatGarKeineNode
064ed68638 🛠️ Add checks to prevent unauthenticated users from voting and hide voting buttons accordingly
 Add tests to ensure proper handling of unauthenticated users during voting interactions
2026-02-04 13:34:09 +01:00
HolgerHatGarKeineNode
2957e89c79 🔒 Add #[Locked] attribute to Livewire components to enhance security against client-side state tampering 2026-02-03 22:49:42 +01:00
HolgerHatGarKeineNode
71ce57ddd3 Add NIP-05 support and improve payment interaction handling 2026-02-03 20:32:04 +01:00
HolgerHatGarKeineNode
11399e7492 🔒 Handle UniqueConstraintViolationException in createPaymentEvent to prevent duplicate payment events creation 2026-02-02 13:37:06 +01:00
HolgerHatGarKeineNode
88a6623503 🔗 Add unique pleb+year constraint to payment_events and ensure migration handles duplicates
- 🧹 Prune duplicate `payment_events` before adding the unique index in migration
-  Add tests to verify invoice management, expiration handling, and payment status updates
- ⚙️ Refactor invoice management flow with `resolveCurrentPaymentEvent` and status syncing logic
- 🎨 Enhance UI for invoice status with dynamic messages, labels, and expiration info
2026-01-31 11:03:47 +01:00
HolgerHatGarKeineNode
7bad86dcb9 🔗 Update news view to use media.signed route for temporary signed URLs 2026-01-25 19:52:09 +01:00
HolgerHatGarKeineNode
1391808793 🎨 Add dynamic category-based filtering for news and improve UI interactions
- 🆕 Introduce `selectedCategory` state with URL binding for category filtering
- 🪄 Add computed `filteredNews` property to handle filtered results efficiently
- 🎛️ Implement category toggle buttons and "Clear Filter" functionality in UI
- 🌟 Improve category display with badges and contextual feedback for empty states
- 🔄 Refactor repeated news loading into a single `loadNews` method
2026-01-25 19:25:22 +01:00
HolgerHatGarKeineNode
10dac9d02b 🔒 Implement signed media URLs and migrate media storage to private disk
-  Introduce `getSignedMediaUrl` in models for temporary signed URLs
- 🗂️ Migrate media collections to private disk for added security
- 🔧 Add `media:move-to-private` command to streamline migration
- ⚙️ Update views and components to use signed media URLs
- ✏️ Adjust route `media.signed` for signed file access handling
2026-01-25 19:14:49 +01:00
HolgerHatGarKeineNode
4fcbeb9ca6 📂 Add MIME type restrictions for 'main' media collection in ProjectProposal 2026-01-25 18:19:57 +01:00
HolgerHatGarKeineNode
fe92418dbb 🎨 Modularize and refactor CSS: restructure styles into theme.css, base.css, utilities.css, and component-specific files (flux-overrides.css, custom.css, leaflet.css) to improve maintainability and align with the Einundzwanzig Design System. 2026-01-23 23:16:09 +01:00
HolgerHatGarKeineNode
b30fec150c 🎨 Add new Flux icons: implement multiple reusable icon components (e.g., hand-raised, hand-thumb-up, heart, hashtag, home) with variant support for improved UI consistency. 2026-01-23 23:00:02 +01:00
HolgerHatGarKeineNode
578e4f13fc 🧹 Migrate Yarn registry URLs to npm registry: update yarn.lock dependencies for consistency and clean up unused entries. 2026-01-23 20:02:21 +01:00
HolgerHatGarKeineNode
a2bce07520 ✏️ Simplify NIP-05 verified handles text: update messaging in benefits and profile views for clarity and consistency. 2026-01-23 17:14:23 +01:00
HolgerHatGarKeineNode
4b9ad0f6ab Refactor NIP-05 verification: extract handle-fetching logic into reusable NostrFetcherTrait and enhance UI to display all verified handles with improved feedback. 2026-01-23 17:09:00 +01:00
HolgerHatGarKeineNode
dfb1c3fa0f Add membership benefits page: implement Nostr Relay, NIP-05 verification, and Lightning Watchtower features with interactive UI and backend logic. 2026-01-23 16:51:31 +01:00
HolgerHatGarKeineNode
0a9498676c Add Lightning Watchtower feature: introduce Watchtower details, usage instructions, and clipboard copying functionality in association profile.
♻️ Update authentication buttons: replace `flux:navbar.item` and `flux:sidebar.item` with `flux:button` for improved UX and consistency.
🗑️ Update `.gitignore`: include additional configuration files (`.opencode`, `.switch-omo-config*`).
2026-01-23 15:52:52 +01:00
HolgerHatGarKeineNode
d3a75d656b Fix namespace usage in profile Blade template: qualify AssociationStatus with its full path for consistent referencing. 2026-01-21 11:41:58 +01:00
HolgerHatGarKeineNode
ec9b5a0d29 🎨 Enhance profile app cards layout: improve grid responsiveness, adjust card structure, and refine badge styles. Add screenshots gallery with responsive design and hover effects for better visual presentation. 🖼️ Include new assets for gallery. 2026-01-20 19:37:56 +01:00
HolgerHatGarKeineNode
d053a2c901 Update project card component: use flux:link for submitter's Nostr profile links and remove redundant link 2026-01-20 17:27:53 +01:00
HolgerHatGarKeineNode
7372b7f54f ✏️ Fix typo in project card component: correct "Anstragssteller" to "Antragssteller". 2026-01-20 17:24:46 +01:00
HolgerHatGarKeineNode
ad09b3804a Update project card component: simplify link styles, add applicant link, and improve badge usage 2026-01-20 17:22:22 +01:00
HolgerHatGarKeineNode
63ca98d258 Add conditional check for premium relay access: ensure relay instructions only display for eligible users. 2026-01-20 17:04:21 +01:00
HolgerHatGarKeineNode
b2987e201d 🎨 Improve text wrapping in profile view: add line break for better handle display readability. 2026-01-20 17:01:09 +01:00
HolgerHatGarKeineNode
e585721157 Add copyRelayUrl method: enable copying relay URL to clipboard and enhance profile benefits section with detailed relay instructions. 2026-01-20 17:00:16 +01:00
HolgerHatGarKeineNode
631e19b64a 🎨 Refactor project card component: adjust responsive layout, enhance button grouping, improve badge usage, and streamline footer structure for better readability and consistency. 2026-01-20 16:51:48 +01:00
HolgerHatGarKeineNode
8b78a85646 🎨 Refactor Blade template: improve formatting, standardize indentation, enhance grid layouts, and apply break-all for better text wrapping. 🛠️ Adjust conditional logic spacing for code consistency. 2026-01-20 16:34:18 +01:00
HolgerHatGarKeineNode
5e5e211244 Add file upload support: enable image uploads, implement file validation, and integrate media handling in project-support forms. 🛠 Update Blade templates and Livewire components for improved UX. 2026-01-20 15:57:13 +01:00
HolgerHatGarKeineNode
4a425da923 🎨 Update color palette: replace gray with zinc across Blade templates for improved design consistency and accessibility.
🛠 Refactor forms: rename NostrAuth method for clarity and enhance Flux button usage for cleaner and reusable components.
 Add `WithNostrAuth` trait: refactor `show` template logic, streamline project-support handling, and improve layout readability.
2026-01-20 14:58:02 +01:00
HolgerHatGarKeineNode
34f8d949d5 Add NIP-05 handle management: Introduce migration, API route, and Livewire updates to support NIP-05 handle verification.
 Enhance Nostr fetcher: Refactor profile data merging logic for improved efficiency and accuracy.
🛠
2026-01-20 13:56:50 +01:00
HolgerHatGarKeineNode
a857e54d61 📌 Update Blade layout: make flux:header sticky for improved UI/UX in the app layout. 2026-01-20 00:59:24 +01:00
HolgerHatGarKeineNode
e670e99a7c Update profile view: add no_email property handling for better email visibility management. 2026-01-20 00:58:23 +01:00
HolgerHatGarKeineNode
efb65b226e 🛠️ Refactor Blade templates: improve HTML structure, standardize indentation, replace custom div with Flux components, and enhance layout for consistency and readability. 2026-01-20 00:55:55 +01:00
HolgerHatGarKeineNode
a6c8fb6435 Refactor project-support forms: add admin-only fields, improve Flux form components, and enhance layout for consistency. 🛠️ Remove redundant NostrAuth methods and streamline authorization logic. 2026-01-20 00:49:28 +01:00
HolgerHatGarKeineNode
28b755575f Refactor Blade template: replace custom button with Flux component, improve layout consistency, and enhance voting section structure for better readability and maintainability. 2026-01-19 23:46:59 +01:00
HolgerHatGarKeineNode
714de411a6 🛠️ Refactor delete confirmation logic with projectToDelete property, enhance project voting features in Livewire, and update Blade templates for consistency and improved UX. 2026-01-19 23:40:42 +01:00
HolgerHatGarKeineNode
39835c3a24 🗑️ Remove obsolete pulse migration file, clean up unused directives in Blade templates, and streamline delete confirmation logic with Flux modals for improved UX and maintainability. 2026-01-19 23:17:39 +01:00
HolgerHatGarKeineNode
c5793872af 🛠️ Refactor Blade templates: improve HTML structure, standardize indentation, and enhance readability in layouts and component definitions. 2026-01-19 22:14:49 +01:00
HolgerHatGarKeineNode
bf31b9f001 Integrate Flux modals for member acceptance and rejection actions, refactor related Livewire methods, and improve Blade table structure for consistency and UX. 2026-01-18 23:28:27 +01:00
HolgerHatGarKeineNode
6edcf014a6 🗑️ Remove unused and outdated Blade views, refactor access restriction messages with Flux callout components, and update related Livewire tests for improved maintainability and UX. 2026-01-18 23:10:37 +01:00
HolgerHatGarKeineNode
22d3e6aac1 🛠️ Add Eloquent factories for ProjectProposal and Election models, integrate HasFactory trait, and update tests with NostrAuth for authentication validation. 2026-01-18 22:33:35 +01:00
HolgerHatGarKeineNode
7db9c04505 🛠️ Refactor Blade templates: improve formatting, streamline HTML structure, and update modal implementation for better maintainability. 2026-01-18 21:11:36 +01:00