Skip to content

Authentication and Roles

Joël Deffner edited this page Jul 17, 2026 · 1 revision

Authentication and Roles

FlightMeet uses CodeIgniter Shield for session-based authentication. There are no API tokens or JWTs: the browser carries a session cookie, and the SPA carries a CSRF token in memory.

Roles and permissions

Configured in app/Config/AuthGroups.php:

Group Permissions May ...
admin admin.*, users.* see the dashboard; create/edit/delete users; edit any meet or group; delete any chat message
moderator users.view base permissions only (currently no dashboard access)
user none log in and use the app as a pilot

Every registered account lands in group user (every account is a pilot by default). A pilot's public profile also reports the highest group as role (admin > moderator > user).

Where checks happen

Authorization is enforced twice, but only the backend is authoritative:

  1. Backend (authoritative)
    • ApiAuthFilter (apiauth) rejects sessionless requests to protected routes with 401.
    • AdminApiFilter (adminapi) additionally requires admin.access for everything under /api/admin/*; each admin action then checks its concrete permission (users.view / users.create / users.edit / users.delete).
    • Domain-level ownership rules live in the controllers: only the meet creator (or an admin) can edit a meet, only the group founder (or an admin) can edit a group, only the author (or an admin) can delete a chat message, group chat requires membership.
  2. Frontend (UX only)
    • RequireAuth and RequireAdmin (in frontend/src/components/) hide routes and redirect to the login page. isAdmin comes from user.permissions['admin.access'] in the /api/auth/me payload.

Session and CSRF flow

CSRF protection is globally enabled in session mode, so every mutating request (POST/PUT/DELETE) must carry the X-CSRF-TOKEN header.

  1. On app start, AuthProvider calls GET /api/auth/me. The response contains { authenticated, user, csrf: { header, token } }; the SPA stores the token in memory (frontend/src/lib/api.ts).
  2. POST /api/auth/login with { login, password, remember } logs in. Shield regenerates the session, so the response includes a fresh CSRF token, which the API wrapper picks up automatically (it captures a csrf field from any response).
  3. All subsequent non-GET requests send the token; the session cookie travels via credentials: 'same-origin'.
  4. POST /api/auth/logout ends the session; the SPA re-fetches /me to obtain a CSRF token for the new anonymous session.

Login accepts email or username: Shield's validFields includes username, enabled in app/Config/Auth.php.

Registration

POST /api/auth/register with { username, email, password, vorname?, nachname? } creates a Shield account in group user, logs the session in immediately, and returns the same { user, csrf } shape as login (201). Validation mirrors the admin create-user rules: unique username and email, Shield password rules.

User data model

Profile fields (vorname, nachname, strasse, plz, ort) are columns on Shield's users table (migration AddProfileFieldsToUsers); the legacy personen table stays untouched. A subscription_tier column (pilot / club / school, default pilot) was added by AddSubscriptionTierToUsers; switching tiers via PUT /api/profile/subscription is free (there is no payment flow, matching the landing page pricing section).

Test accounts (seed data)

Login (email) Username Password Group
admin@team11.local admin Admin123! admin
moderator@team11.local moderator Moderator123! moderator
pilot1..pilot6@example.com pilot1 .. pilot6 password123 user

PersonenSeeder additionally imports the 9999 legacy personen records as regular users: password password123, email <transliterated-username>@example.com (e.g. HerbertBürgersherbertburgers@example.com).

Admin safeguards

The admin API refuses to let admins lock themselves out: your own account cannot be deleted, deactivated, or removed from the admin group.

Clone this wiki locally