diff --git a/docs/auth-spec.md b/docs/auth-spec.md index 7785b41..d152845 100644 --- a/docs/auth-spec.md +++ b/docs/auth-spec.md @@ -44,6 +44,7 @@ Browser (SPA) PocketBase (auth) Entra ID | 006 | Baseline-ledger-sync migratie voor out-of-band geprovisionede DB's | Labs/prod draaide historisch zonder `--migrationsDir`; replay van de historie crashte PB (issue #18). De vroegst-sorterende migratie markeert de historie als applied wanneer schema bestaat maar de ledger leeg is | | 007 | Migratie = structuur, hook = configuratie | De OIDC-provider wordt bij elke start (en per cron-minuut) gereconcilieerd vanuit `ENTRA_*` env door `pb_hooks/entra_oidc.pb.js`; een one-shot migratie die van deploy-time env afhangt kan OAuth2 anders permanent uitschakelen | | 008 | Hook-helpers via `require(`${__hooks}/utils.js`)` | De JSVM draait elke hook-callback als geïsoleerd programma; top-level functies zijn níet in scope op call-time | +| 009 | `createRule: '@request.context = "oauth2"'` op `team_members` | PocketBase past de createRule toe op de OAuth2-sign-up (eerste login maakt het record aan); `null` blokkeerde élke eerste login met 403 (issue #22). De context-rule staat alléén de OAuth2-flow toe — anonieme REST-creates blijven geweigerd | ## Bestanden @@ -52,6 +53,7 @@ Browser (SPA) PocketBase (auth) Entra ID | `pb_migrations/1000000000_baseline_ledger_sync.js` | Detecteert een out-of-band geprovisionede DB (schema bestaat, `_migrations`-ledger leeg) en markeert de historische migraties als applied, zodat de replay niet crasht (issue #18). | | `pb_migrations/1781000000_team_members_to_auth.js` | Converteert relation-velden naar text (data blijft behouden), dropt de oude PIN-collection en maakt `team_members` als auth-collection. Idempotent; provider wordt alleen als fast-path meegenomen als de env al aanwezig is. | | `pb_migrations/1781000001_tighten_api_rules.js` | Zet alle niet-systeem-collecties op `@request.auth.id != ""`. | +| `pb_migrations/1781000002_allow_oauth2_signup.js` | Zet `createRule = '@request.context = "oauth2"'` op reeds-gemigreerde omgevingen (issue #22). | | `pb_hooks/entra_oidc.pb.js` | Reconcilieert de OIDC-provider vanuit `ENTRA_*` env bij elke start + cron-tick (compare-before-save). | | `pb_hooks/utils.js` | Gedeelde helpers (`resolveRole`, `reconcileEntraOidc`) — per callback binnenhalen via `require()`. | | `pb_hooks/team_members.pb.js` | Auto-provisioning (`role`, `enrollment_status`, naam-fallback) + admin-rol re-sync. | diff --git a/pb_migrations/1781000000_team_members_to_auth.js b/pb_migrations/1781000000_team_members_to_auth.js index 5d98ac9..4d02978 100644 --- a/pb_migrations/1781000000_team_members_to_auth.js +++ b/pb_migrations/1781000000_team_members_to_auth.js @@ -142,10 +142,14 @@ migrate((app) => { // Access rules: every legitimate user is authenticated (Azure-gated + // OIDC). Profiles are readable by any authenticated user (leaderboard, // dashboard); a user may only update their own record (onboarding flips - // enrollment_status). Creation/deletion happen via OAuth2 / superuser only. + // enrollment_status). Record creation is ONLY allowed from within the + // OAuth2 sign-up flow: PocketBase applies the createRule to the automatic + // record creation on a first OIDC login, so `null` would make every first + // login fail with 403 "Only superusers can perform this action" (issue + // #22). Plain REST creates keep being rejected for non-superusers. listRule: '@request.auth.id != ""', viewRule: '@request.auth.id != ""', - createRule: null, + createRule: '@request.context = "oauth2"', updateRule: '@request.auth.id = id', deleteRule: null, authRule: "", diff --git a/pb_migrations/1781000002_allow_oauth2_signup.js b/pb_migrations/1781000002_allow_oauth2_signup.js new file mode 100644 index 0000000..4e7d4e2 --- /dev/null +++ b/pb_migrations/1781000002_allow_oauth2_signup.js @@ -0,0 +1,46 @@ +/// +// +// Issue #22 — Allow OAuth2 sign-up on team_members. +// +// The collection was created with `createRule: null` on the assumption that +// the OAuth2 flow bypasses it. It does not: PocketBase applies the createRule +// to the automatic record creation on a first OIDC login, so every first +// login failed with 403 "Only superusers can perform this action". Since the +// pre-Azure records were intentionally dropped, EVERY user was a first login +// and nobody could sign in. +// +// `@request.context = "oauth2"` scopes record creation to the OAuth2 flow +// only — plain REST creates (e.g. anonymous POST /records, which could +// otherwise pre-seed rogue admin profiles) keep being rejected. +// +// Environments that have not applied 1781000000 yet get this rule directly +// from that (updated) migration; this follow-up exists for databases where it +// already ran with the old `null` rule (Labs). Applying it twice is harmless. +migrate((app) => { + let col; + try { + col = app.findCollectionByNameOrId("team_members"); + } catch (_) { + console.log("allow_oauth2_signup: team_members does not exist — skipping."); + return; + } + if (col.type !== "auth") { + console.log("allow_oauth2_signup: team_members is not an auth collection — skipping."); + return; + } + col.createRule = '@request.context = "oauth2"'; + app.save(col); +}, (app) => { + // Down: restore the (broken) superuser-only rule. + let col; + try { + col = app.findCollectionByNameOrId("team_members"); + } catch (_) { + return; + } + if (col.type !== "auth") { + return; + } + col.createRule = null; + app.save(col); +}); diff --git a/scripts/setup-pb-collections.mjs b/scripts/setup-pb-collections.mjs index 3f8fb30..4fce69b 100644 --- a/scripts/setup-pb-collections.mjs +++ b/scripts/setup-pb-collections.mjs @@ -111,6 +111,9 @@ const COLLECTIONS = [ name: 'team_members', type: 'auth', ...OPEN_RULES, + // Mirror of pb_migrations/1781000002: creation only via the OAuth2 + // sign-up flow (issue #22). + createRule: '@request.context = "oauth2"', passwordAuth: { enabled: false, identityFields: ['email'] }, fields: [ { name: 'name', type: 'text', required: false },