# Authorization Matrix — ALGA ARENA Esports Platform

**Date:** 2026-07-20  
**Mechanism:** Laravel Gates + Policies (no Spatie package)  
**Middleware:** `auth`, `verified`, `active`, `admin`

## Roles

| Role | Value | Admin access |
|------|-------|--------------|
| User | `user` | No |
| Admin | `admin` | Yes (`isAdmin()`) |
| Super Admin | `super_admin` | Yes + `manage-settings` gate |

Defined in `App\Enums\UserRole`.

## Middleware

| Alias | Class | Behavior |
|-------|-------|----------|
| `active` | `EnsureUserIsActive` | Logs out suspended users |
| `admin` | `EnsureAdmin` | Aborts 403 if not admin |
| `locale` | `SetLocale` | Sets app locale from query/session/user |

Applied in `bootstrap/app.php` and route groups.

---

## Gates (`AppServiceProvider`)

| Gate | Allowed |
|------|---------|
| `access-admin` | Admin or Super Admin |
| `manage-users` | Admin+ |
| `suspend-users` | Admin+ |
| `manage-games` | Admin+ |
| `manage-events` | Admin+ |
| `publish-events` | Admin+ |
| `review-registrations` | Admin+ |
| `review-payment-proofs` | Admin+ |
| `access-payment-proof` | Admin+ **or** proof owner (registration user) |
| `moderate-records` | Admin+ |
| `moderate-videos` | Admin+ |
| `export-participants` | Admin+ |
| `manage-settings` | **Super Admin only** |
| `view-audit-logs` | Admin+ |

---

## Policy Matrix

### `UserPolicy`

| Ability | User | Admin | Super Admin |
|---------|------|-------|-------------|
| `viewAny` | — | ✓ | ✓ |
| `view` (self) | ✓ | ✓ | ✓ |
| `view` (other) | — | ✓ | ✓ |
| `suspend` | — | ✓ (not self) | ✓ (not self) |

### `PlayerProfilePolicy`

| Ability | Guest | Owner | Admin |
|---------|-------|-------|-------|
| `view` | ✓ | ✓ | ✓ |
| `update` | — | ✓ | — |

### `PlayerGameProfilePolicy`

| Ability | Owner | Admin |
|---------|-------|-------|
| `view` | ✓ | ✓ |
| `update` | ✓ | — |
| `delete` | ✓ | — |

### `PlayerRecordPolicy`

| Ability | Owner | Admin |
|---------|-------|-------|
| `viewAny` | ✓ (auth) | ✓ |
| `view` | ✓ | ✓ |
| `create` | ✓ (requires profile) | — |
| `delete` | ✓ | ✓ |

No `update` policy — records are create/delete only.

### `PlayerVideoPolicy`

Same pattern as `PlayerRecordPolicy`.

### `GamePolicy`

| Ability | Guest | Admin |
|---------|-------|-------|
| `viewAny`, `view` | ✓ | ✓ |
| `manage` | — | ✓ |

### `EventPolicy`

| Ability | Guest | User | Admin |
|---------|-------|------|-------|
| `viewAny` | ✓ | ✓ | ✓ |
| `view` (public status) | ✓ | ✓ | ✓ |
| `view` (draft/archived) | — | — | ✓ |
| `create`, `update`, `publish` | — | — | ✓ |

Public visibility: all statuses except `draft` and `archived` (`EventStatus::isPubliclyVisible()`).

### `EventRegistrationPolicy`

| Ability | Owner | Admin |
|---------|-------|-------|
| `view` | ✓ | ✓ |
| `create` | ✓ (requires profile) | — |
| `cancel` | ✓ (active status only) | — |
| `review` | — | ✓ |

### `EventPaymentProofPolicy`

| Ability | Owner | Admin |
|---------|-------|-------|
| `view` | ✓ | ✓ |
| `create` | ✓ (auth user) | — |

Review actions use `review-payment-proofs` gate in controllers.

---

## Route-Level Authorization

### Public (no auth)

- `GET /`, `/games`, `/games/{slug}`, `/events`, `/events/{slug}`
- `GET /players/{playerProfile}` — profile view
- `GET /preview/*` — mock preview pages
- Auth guest routes: register, login, password reset

### Authenticated (`auth`, `verified`, `active`)

| Route prefix | Controller | Policy/Gate |
|--------------|------------|-------------|
| `/dashboard` | DashboardController | middleware only |
| `/settings/*` | SettingsController | `update` on profile |
| `/profile/edit` | ProfileController | `update` on profile |
| `/profile/games/*` | PlayerGameProfileController | PlayerGameProfilePolicy |
| `/profile/records/*` | PlayerRecordController | PlayerRecordPolicy |
| `/profile/videos/*` | PlayerVideoController | PlayerVideoPolicy |
| `/my-events` | MyEventsController | auth |
| `/events/{slug}/register` | EventRegistrationController | `view` event, `create` registration |
| `/event-registrations/*` | EventRegistrationController | EventRegistrationPolicy |
| `/payment-proofs/{proof}` | PaymentProofController | `view` proof |
| `/notifications/*` | NotificationController | auth |

### Admin (`auth`, `verified`, `active`, `admin`)

Prefix `/admin`, name `admin.*`. All routes require admin role via middleware; individual actions also call `$this->authorize()` on gates/policies.

| Area | Gate/Policy used |
|------|------------------|
| Users | `suspend` on UserPolicy |
| Games CRUD | `manage-games` (implicit via admin middleware) |
| Events | `publish` on EventPolicy |
| Registrations | `review` on EventRegistrationPolicy |
| Export | `export-participants` |
| Moderation | `moderate-records`, `moderate-videos` |
| Payment proofs | `review-payment-proofs` |
| Audit logs | `view-audit-logs` |
| Settings | `manage-settings` (super_admin) |

---

## User Status Enforcement

Suspended users (`UserStatus::Suspended`):

- Cannot stay authenticated — `EnsureUserIsActive` logs them out on any request
- Cannot access protected routes after logout

---

## Content Visibility (beyond policies)

| Content | Public display rule |
|---------|---------------------|
| Player records | `moderation_status = approved` AND `visibility = public` (enforced in queries/views) |
| Player videos | Same moderation + visibility rules |
| Payment proofs | Never public URL; download via authorized route only |
| Event drafts | Admin-only via EventPolicy |

---

## Authorization Gaps / Notes

1. **Record/video public profile display** — Policies allow owners/admins to `view`; public profile page should filter by moderation status in ViewModels (verify in frontend integration).
2. **`EventPaymentProofPolicy::create`** returns `true` for any authenticated user; business rules enforced in `SubmitPaymentProofAction` (ownership + registration status).
3. **Preview routes** — No authorization; serve mock data without DB.
