/// // // Issue #18 — Reconcile the Entra (Azure) OIDC provider from the environment. // // The team_members→auth migration (pb_migrations/1781000000) is structure-only // and runs exactly once. Provider CONFIGURATION lives here instead, so that: // // * an environment whose migration applied while the ENTRA_* secrets were // absent gets its provider enabled on the next startup (no re-apply), // * rotating ENTRA_CLIENT_SECRET / changing the tenant only requires new env // values and a container restart, // * a fresh database gets its provider on the first cron tick right after // the migrations created the collection (onBootstrap fires BEFORE the // migrations run — there is no post-migration lifecycle hook in the JSVM, // hence the cron fallback). // // The reconciler compares before saving, so both hooks are cheap no-ops when // everything is already in sync. onBootstrap((e) => { e.next(); const { reconcileEntraOidc } = require(`${__hooks}/utils.js`); // reconcileEntraOidc logs a warn-once itself when the ENTRA_* env is absent. console.log("entra_oidc reconcile (bootstrap): " + reconcileEntraOidc(e.app)); }); cronAdd("entraOidcReconcile", "* * * * *", () => { const { reconcileEntraOidc } = require(`${__hooks}/utils.js`); const result = reconcileEntraOidc($app); // Only log state changes — this tick runs every minute. if (result === "updated") { console.log("entra_oidc reconcile (cron): OIDC provider (re)configured from ENTRA_* env"); } }); // Entra does not always supply an `email` claim: the id_token only carries it // when the account has a mail attribute (issue #24). The UPN in // `preferred_username` is always present and equals the primary e-mail for // Respellion organisation accounts, so fall back to it when it looks like a // real address. Guest UPNs ("user_ext#EXT#@tenant...") fail the regex on // purpose — those still get a clear validation error instead of a bogus email. // The catch also surfaces the underlying OAuth2 failure in the container log, // which PocketBase otherwise only writes to its internal logs database. onRecordAuthWithOAuth2Request((e) => { const u = e.oAuth2User; if (u && !u.email) { const upn = String((u.rawUser && u.rawUser.preferred_username) || u.username || "").trim().toLowerCase(); // `#` is RFC-valid in an e-mail local part, so guest UPNs // ("ext_user#EXT#@tenant.onmicrosoft.com") pass a naive e-mail check AND // PocketBase's own validation — exclude them explicitly. if (/^[^@\s#]+@[^@\s#]+\.[^@\s#]+$/.test(upn)) { u.email = upn; console.log("entra_oidc: email claim absent — falling back to UPN " + upn); } } try { e.next(); } catch (err) { console.log("entra_oidc auth-with-oauth2 FAILED:", String(err)); throw err; } }, "team_members");