Commit Graph

13 Commits

Author SHA1 Message Date
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
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
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
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
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
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
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
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
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
31fb04caaa 🗑️ Remove outdated migration files for einundzwanzig_plebs and pulse tables, restructure directory, and update testing suite with new factories and Livewire tests. 2026-01-18 22:23:23 +01:00