OAuth2 eerste login faalt met 403 "Only superusers can perform this action" — createRule blokkeert OAuth2-sign-up #22

Closed
opened 2026-07-12 13:10:03 +00:00 by rve · 0 comments
Owner

Symptoom

Site is online, maar een normale gebruiker die met Microsoft inlogt krijgt:

Only superusers can perform this action.

Console: POST /api/collections/team_members/auth-with-oauth2403 Forbidden. (De COOP-meldingen over window.close in de console zijn onschuldige popup-warnings, geen oorzaak.)

Root cause (lokaal gereproduceerd)

De team_members-collection is aangemaakt met createRule: null (migratie 1781000000_team_members_to_auth.js, met als aanname "Creation/deletion happen via OAuth2 / superuser only"). Die aanname klopt niet: PocketBase (v0.23+) past de createRule toe op de OAuth2-sign-up — het automatisch aanmaken van het auth-record bij een eerste login. createRule: null betekent "alleen superusers", dus elke eerste login faalt met exact deze 403.

Omdat de migratie de oude PIN-records bewust heeft gedropt, is iedere gebruiker een eerste login → niemand kan erin (behalve superusers via het dashboard).

Reproductie (PocketBase v0.30.4, mock-OIDC-provider lokaal, productie-migraties):

POST /api/collections/team_members/auth-with-oauth2  (nieuwe gebruiker)
→ {"data":{},"message":"Only superusers can perform this action.","status":403}

authRule: "" staat goed (bestaande records zouden kunnen inloggen) — het probleem zit uitsluitend op de create bij eerste login.

Fix

PocketBase heeft hiervoor een dedicated rule-context: @request.context = "oauth2". Daarmee mag record-creatie uitsluitend binnen de OAuth2-flow plaatsvinden — de anonieme REST-create (POST /api/collections/team_members/records) blijft superuser-only (belangrijk: anders kan iedereen records met bv. role: "admin" klaarzetten).

  1. pb_migrations/1781000002_allow_oauth2_signup.js (nieuw) — zet createRule = '@request.context = "oauth2"' op de bestaande collection (Labs is al gemigreerd; de originele migratie draait daar niet opnieuw).
  2. pb_migrations/1781000000_team_members_to_auth.js — zelfde rule voor verse omgevingen (bestandsnaam blijft gelijk; ledger-veilig).
  3. scripts/setup-pb-collections.mjs — fallback-entry gespiegeld (per PROTECTED.md-conventie).
  4. docs/auth-spec.md — ADR + rules-matrix bijgewerkt.

Verificatie (lokaal, mock-OIDC)

  • Repro zonder fix: eerste login → 403 "Only superusers can perform this action."
  • Met fix, upgrade-pad (DB waar 1781000000 al applied is → 1781000002 erbij): eerste login → 200, record aangemaakt met role uit allow-list, enrollment_status = "not_started", naam uit OIDC-claim
  • Tweede login zelfde gebruiker (bestaand record, authRule-pad): 200, geen duplicaat
  • Verse DB (volledige keten): zelfde resultaat
  • Anonieme REST-create blijft 403
  • E2E op Labs met echt Microsoft-account (menselijke check)

Follow-up (bewust buiten scope)

  • TeamManager-rules: updateRule: '@request.auth.id = id' en deleteRule: null betekenen dat admins via de app géén rollen van anderen kunnen wijzigen of leden verwijderen (alleen superusers via het dashboard). Als TeamManager dat moet kunnen, is een aparte rule-uitbreiding nodig (bv. @request.auth.role = "admin"); dat raakt de trust-boundary en verdient een eigen afweging.

Gerelateerd: #16 (feature), #18 (ledger/SSO-fix), #20 (Caddyfile-crash — PR #21 nog niet gemerged!).

## Symptoom Site is online, maar een normale gebruiker die met Microsoft inlogt krijgt: > **Only superusers can perform this action.** Console: `POST /api/collections/team_members/auth-with-oauth2` → **403 Forbidden**. (De COOP-meldingen over `window.close` in de console zijn onschuldige popup-warnings, geen oorzaak.) ## Root cause (lokaal gereproduceerd) De `team_members`-collection is aangemaakt met **`createRule: null`** (migratie `1781000000_team_members_to_auth.js`, met als aanname "Creation/deletion happen via OAuth2 / superuser only"). Die aanname klopt niet: **PocketBase (v0.23+) past de `createRule` toe op de OAuth2-sign-up** — het automatisch aanmaken van het auth-record bij een eerste login. `createRule: null` betekent "alleen superusers", dus elke *eerste* login faalt met exact deze 403. Omdat de migratie de oude PIN-records bewust heeft gedropt, is **iedere** gebruiker een eerste login → niemand kan erin (behalve superusers via het dashboard). Reproductie (PocketBase v0.30.4, mock-OIDC-provider lokaal, productie-migraties): ``` POST /api/collections/team_members/auth-with-oauth2 (nieuwe gebruiker) → {"data":{},"message":"Only superusers can perform this action.","status":403} ``` `authRule: ""` staat goed (bestaande records zouden kunnen inloggen) — het probleem zit uitsluitend op de create bij eerste login. ## Fix PocketBase heeft hiervoor een dedicated rule-context: **`@request.context = "oauth2"`**. Daarmee mag record-creatie uitsluitend binnen de OAuth2-flow plaatsvinden — de anonieme REST-create (`POST /api/collections/team_members/records`) blijft superuser-only (belangrijk: anders kan iedereen records met bv. `role: "admin"` klaarzetten). 1. **`pb_migrations/1781000002_allow_oauth2_signup.js`** (nieuw) — zet `createRule = '@request.context = "oauth2"'` op de bestaande collection (Labs is al gemigreerd; de originele migratie draait daar niet opnieuw). 2. **`pb_migrations/1781000000_team_members_to_auth.js`** — zelfde rule voor verse omgevingen (bestandsnaam blijft gelijk; ledger-veilig). 3. **`scripts/setup-pb-collections.mjs`** — fallback-entry gespiegeld (per PROTECTED.md-conventie). 4. **`docs/auth-spec.md`** — ADR + rules-matrix bijgewerkt. ## Verificatie (lokaal, mock-OIDC) - [x] Repro zonder fix: eerste login → 403 "Only superusers can perform this action." - [ ] Met fix, upgrade-pad (DB waar 1781000000 al applied is → 1781000002 erbij): eerste login → 200, record aangemaakt met `role` uit allow-list, `enrollment_status = "not_started"`, naam uit OIDC-claim - [ ] Tweede login zelfde gebruiker (bestaand record, authRule-pad): 200, geen duplicaat - [ ] Verse DB (volledige keten): zelfde resultaat - [ ] Anonieme REST-create blijft 403 - [ ] E2E op Labs met echt Microsoft-account (menselijke check) ## Follow-up (bewust buiten scope) - **TeamManager-rules**: `updateRule: '@request.auth.id = id'` en `deleteRule: null` betekenen dat admins via de app géén rollen van anderen kunnen wijzigen of leden verwijderen (alleen superusers via het dashboard). Als TeamManager dat moet kunnen, is een aparte rule-uitbreiding nodig (bv. `@request.auth.role = "admin"`); dat raakt de trust-boundary en verdient een eigen afweging. Gerelateerd: #16 (feature), #18 (ledger/SSO-fix), #20 (Caddyfile-crash — PR #21 nog niet gemerged!).
rve closed this issue 2026-07-12 15:11:25 +00:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rve/learning-platform#22