feat: Azure (Entra ID) login as user login (#16)
Replaces the client-side PIN login with real authentication against Azure Entra ID via PocketBase's built-in OAuth2 (OIDC) flow. Every Azure user is auto-provisioned as a team_member on first login. - pb_migrations: team_members becomes a PocketBase auth collection (OIDC provider configured from env); all collections require @request.auth.id - pb_hooks: provisioning of role (ENTRA_ADMIN_EMAILS allow-list) and enrollment_status, with admin-role re-sync on every login - frontend: "Sign in with Microsoft" login, pb.authStore-based session, TeamManager manages roster/roles (no PIN/manual create) - infra: Entra env wiring + pb_hooks mount for dev (Labs) and prod - src/lib/azureAuth.js + tests for the canonical role/allow-list logic - docs/auth-spec.md Knowledge base, generated tests and micro-learnings are left untouched. Existing PIN users are dropped (no migration required, per sign-off). Closes #16 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
55
pb_hooks/team_members.pb.js
Normal file
55
pb_hooks/team_members.pb.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
//
|
||||
// Issue #16 — Auto-provisioning & role assignment for Azure (Entra ID) users.
|
||||
//
|
||||
// PocketBase auto-creates a `team_members` auth record on the first OIDC login
|
||||
// (name is filled from the OIDC `name` claim via the collection's mappedFields).
|
||||
// These hooks add the application-specific bits:
|
||||
//
|
||||
// * default `role` from an admin allow-list (env ENTRA_ADMIN_EMAILS),
|
||||
// * default `enrollment_status = "not_started"` so new users go through the
|
||||
// existing onboarding screen,
|
||||
// * re-sync the admin role on every login so allow-list changes take effect
|
||||
// without manual edits.
|
||||
//
|
||||
// The allow-list is a comma-separated list of e-mail addresses, e.g.:
|
||||
// ENTRA_ADMIN_EMAILS=rve@respellion.nl,admin@respellion.nl
|
||||
//
|
||||
// Keep this logic in sync with src/lib/azureAuth.js (resolveRole / parseAdminEmails),
|
||||
// which carries the canonical, unit-tested version for the frontend/tests.
|
||||
|
||||
function resolveRole(email) {
|
||||
const raw = ($os.getenv("ENTRA_ADMIN_EMAILS") || "").toLowerCase();
|
||||
const allow = raw.split(",").map(function (s) { return s.trim(); }).filter(Boolean);
|
||||
return allow.indexOf((email || "").toLowerCase()) !== -1 ? "admin" : "user";
|
||||
}
|
||||
|
||||
// On creation (first login): set defaults before the record is persisted.
|
||||
onRecordCreate(function (e) {
|
||||
const email = e.record.get("email") || "";
|
||||
|
||||
e.record.set("role", resolveRole(email));
|
||||
|
||||
if (!e.record.get("enrollment_status")) {
|
||||
e.record.set("enrollment_status", "not_started");
|
||||
}
|
||||
|
||||
// Fallback name when the IdP supplied no `name` claim.
|
||||
if (!e.record.get("name")) {
|
||||
e.record.set("name", email ? email.split("@")[0] : "Onbekend");
|
||||
}
|
||||
|
||||
e.next();
|
||||
}, "team_members");
|
||||
|
||||
// On every successful auth (incl. OIDC): keep the admin role in sync with the
|
||||
// allow-list, so promoting/demoting an admin only requires an env change.
|
||||
onRecordAuthRequest(function (e) {
|
||||
const email = e.record.get("email") || "";
|
||||
const want = resolveRole(email);
|
||||
if (e.record.get("role") !== want) {
|
||||
e.record.set("role", want);
|
||||
e.app.save(e.record);
|
||||
}
|
||||
e.next();
|
||||
}, "team_members");
|
||||
Reference in New Issue
Block a user