Eerste Microsoft-logins faalden met 400 — email: Cannot be blank: Graph's /oidc/userinfo geeft géén email-claim voor accounts zonder mail-attribuut, terwijl team_members een e-mail vereist (die ook de ENTRA_ADMIN_EMAILS-rolmapping voedt). De door Raymond aangeleverde response-body matchte exact de "missing email"-fingerprint uit de analyse (#24).
De fix (twee lagen)
Claims uit het Entra id_token (userInfoURL: "" in de reconciler + migratie-fast-path). Het id_token bevat áltijd preferred_username (UPN) en email wanneer beschikbaar. De #18-reconciler flipt de al-gedeployede Labs-provider automatisch bij de eerstvolgende boot — geen migratie nodig.
UPN-fallback in onRecordAuthWithOAuth2Request: geen e-mailclaim → lowercased UPN als e-mail. Guest-UPN's (ext_user#EXT#@tenant...) worden expliciet uitgesloten — # is RFC-geldig in een e-mail-localpart, dus zowel een naïeve regex als PocketBase's eigen validatie accepteren ze (gevangen door test V5). De hook logt bovendien elke gefaalde OAuth2-poging mét onderliggende fout naar de containerlog.
Verificatie (mock-Entra met schakelbare claims, PocketBase v0.30.4)
#
Scenario
Resultaat
V1–V2
id_token zónder e-mailclaim
200 — e-mail uit UPN (lowercased), role=admin via allow-list
V3
Tweede login
Zelfde record, geen duplicaat
V4
id_token mét e-mailclaim
Claim wint, geen fallback
V5
Guest-UPN (#EXT#)
Nette validatiefout, geen rommel-e-mail
V6
Anonieme REST-create
Blijft geweigerd
V7–V8
Observability
Fallback én gefaalde pogingen zichtbaar in containerlog
Regressie: #22-matrix 9/9 (baseline gepind op 1a1351d nu #23 gemerged is), #18-harness 15/15, npm test112/112.
Na de merge/deploy: de smoke-report toont de reconciler-update (updated bij eerste boot — userInfoURL flipt), daarna E2E-login door Raymond.
Closes #24
## Wat dit oplost
Eerste Microsoft-logins faalden met `400 — email: Cannot be blank`: Graph's `/oidc/userinfo` geeft géén `email`-claim voor accounts zonder mail-attribuut, terwijl `team_members` een e-mail vereist (die ook de `ENTRA_ADMIN_EMAILS`-rolmapping voedt). De door Raymond aangeleverde response-body matchte exact de "missing email"-fingerprint uit de analyse (#24).
## De fix (twee lagen)
1. **Claims uit het Entra id_token** (`userInfoURL: ""` in de reconciler + migratie-fast-path). Het id_token bevat áltijd `preferred_username` (UPN) en `email` wanneer beschikbaar. De #18-reconciler flipt de al-gedeployede Labs-provider automatisch bij de eerstvolgende boot — geen migratie nodig.
2. **UPN-fallback in `onRecordAuthWithOAuth2Request`**: geen e-mailclaim → lowercased UPN als e-mail. Guest-UPN's (`ext_user#EXT#@tenant...`) worden expliciet uitgesloten — `#` is RFC-geldig in een e-mail-localpart, dus zowel een naïeve regex als PocketBase's eigen validatie accepteren ze (gevangen door test V5). De hook logt bovendien elke gefaalde OAuth2-poging mét onderliggende fout naar de containerlog.
## Verificatie (mock-Entra met schakelbare claims, PocketBase v0.30.4)
| # | Scenario | Resultaat |
|---|---|---|
| V1–V2 | id_token zónder e-mailclaim | 200 — e-mail uit UPN (lowercased), `role=admin` via allow-list |
| V3 | Tweede login | Zelfde record, geen duplicaat |
| V4 | id_token mét e-mailclaim | Claim wint, geen fallback |
| V5 | Guest-UPN (`#EXT#`) | Nette validatiefout, geen rommel-e-mail |
| V6 | Anonieme REST-create | Blijft geweigerd |
| V7–V8 | Observability | Fallback én gefaalde pogingen zichtbaar in containerlog |
Regressie: #22-matrix **9/9** (baseline gepind op `1a1351d` nu #23 gemerged is), #18-harness **15/15**, `npm test` **112/112**.
Na de merge/deploy: de smoke-report toont de reconciler-update (`updated` bij eerste boot — userInfoURL flipt), daarna E2E-login door Raymond.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
First Microsoft logins failed with 400 "email: Cannot be blank": Graph's
/oidc/userinfo endpoint omits the email claim for accounts without a
mail attribute, and team_members requires an e-mail (it also drives the
ENTRA_ADMIN_EMAILS role mapping). Reproduced against a mock Entra with
PocketBase v0.30.4; the user-supplied response body matched the
missing-email fingerprint exactly.
- provider config (reconciler + migration fast-path): userInfoURL is now
empty, so PocketBase reads the claims from the Entra id_token, which
always carries preferred_username (UPN) and email when available. The
#18 reconciler flips the already-deployed Labs provider automatically
on the next boot — no migration needed.
- pb_hooks/entra_oidc.pb.js: onRecordAuthWithOAuth2Request hook falls
back to the lowercased UPN when the email claim is absent. Guest UPNs
(ext_user#EXT#@tenant...) are excluded explicitly — "#" is RFC-valid
in an e-mail local part, so both a naive regex AND PocketBase's own
validation would accept them (caught by test V5). The hook also logs
every failed OAuth2 attempt with the underlying error to the container
log, which PocketBase otherwise only writes to its internal logs db.
Verified with a switchable mock-Entra matrix (8/8): no-email→UPN e-mail
+ allow-list role, email claim wins when present, no duplicate on
re-login, guest UPN yields a clean validation error, anonymous REST
create stays rejected, both log lines present. Regression: issue-22
matrix 9/9 (baseline pinned to 1a1351d now that #23 is merged), DoD
harness 15/15, npm test 112/112.
Closes#24
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live-rollout geslaagd (run #95): deploy-dev groen, PLAY RECAP failed=0, beide gates gepasseerd. Smoke-report: entra_oidc reconcile (bootstrap): in-sync — en omdat de reconciler-vergelijking userInfoURL omvat, bewijst dit dat de live provider nu op het id_token-pad staat (de flip is door de hook-hot-reload + cron al vóór de recreate uitgevoerd).
Lokale bewijsmatrix: 8/8 (UPN-fallback, claim-wint, geen duplicaat, guest-UPN nette fout, REST-create dicht, beide loglijnen). Regressie: #22-matrix 9/9, #18-harness 15/15, npm test 112/112.
Klaar voor de E2E: Microsoft-login op Labs zou nu moeten slagen — ook voor accounts zónder mail-attribuut (e-mail wordt dan de UPN).
**Live-rollout geslaagd (run #95):** deploy-dev groen, PLAY RECAP `failed=0`, beide gates gepasseerd. Smoke-report: `entra_oidc reconcile (bootstrap): in-sync` — en omdat de reconciler-vergelijking `userInfoURL` omvat, bewijst dit dat de live provider nu op het id_token-pad staat (de flip is door de hook-hot-reload + cron al vóór de recreate uitgevoerd).
Lokale bewijsmatrix: **8/8** (UPN-fallback, claim-wint, geen duplicaat, guest-UPN nette fout, REST-create dicht, beide loglijnen). Regressie: #22-matrix 9/9, #18-harness 15/15, `npm test` 112/112.
**Klaar voor de E2E:** Microsoft-login op Labs zou nu moeten slagen — ook voor accounts zónder mail-attribuut (e-mail wordt dan de UPN).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #24
Wat dit oplost
Eerste Microsoft-logins faalden met
400 — email: Cannot be blank: Graph's/oidc/userinfogeeft géénemail-claim voor accounts zonder mail-attribuut, terwijlteam_memberseen e-mail vereist (die ook deENTRA_ADMIN_EMAILS-rolmapping voedt). De door Raymond aangeleverde response-body matchte exact de "missing email"-fingerprint uit de analyse (#24).De fix (twee lagen)
userInfoURL: ""in de reconciler + migratie-fast-path). Het id_token bevat áltijdpreferred_username(UPN) enemailwanneer beschikbaar. De #18-reconciler flipt de al-gedeployede Labs-provider automatisch bij de eerstvolgende boot — geen migratie nodig.onRecordAuthWithOAuth2Request: geen e-mailclaim → lowercased UPN als e-mail. Guest-UPN's (ext_user#EXT#@tenant...) worden expliciet uitgesloten —#is RFC-geldig in een e-mail-localpart, dus zowel een naïeve regex als PocketBase's eigen validatie accepteren ze (gevangen door test V5). De hook logt bovendien elke gefaalde OAuth2-poging mét onderliggende fout naar de containerlog.Verificatie (mock-Entra met schakelbare claims, PocketBase v0.30.4)
role=adminvia allow-list#EXT#)Regressie: #22-matrix 9/9 (baseline gepind op
1a1351dnu #23 gemerged is), #18-harness 15/15,npm test112/112.Na de merge/deploy: de smoke-report toont de reconciler-update (
updatedbij eerste boot — userInfoURL flipt), daarna E2E-login door Raymond.🤖 Generated with Claude Code
Live-rollout geslaagd (run #95): deploy-dev groen, PLAY RECAP
failed=0, beide gates gepasseerd. Smoke-report:entra_oidc reconcile (bootstrap): in-sync— en omdat de reconciler-vergelijkinguserInfoURLomvat, bewijst dit dat de live provider nu op het id_token-pad staat (de flip is door de hook-hot-reload + cron al vóór de recreate uitgevoerd).Lokale bewijsmatrix: 8/8 (UPN-fallback, claim-wint, geen duplicaat, guest-UPN nette fout, REST-create dicht, beide loglijnen). Regressie: #22-matrix 9/9, #18-harness 15/15,
npm test112/112.Klaar voor de E2E: Microsoft-login op Labs zou nu moeten slagen — ook voor accounts zónder mail-attribuut (e-mail wordt dan de UPN).
Pull request closed