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:
57
src/lib/__tests__/azureAuth.test.js
Normal file
57
src/lib/__tests__/azureAuth.test.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
OIDC_PROVIDER,
|
||||
parseAdminEmails,
|
||||
resolveRole,
|
||||
deriveName,
|
||||
} from '../azureAuth';
|
||||
|
||||
describe('azureAuth', () => {
|
||||
it('exposes the OIDC provider name used by authWithOAuth2', () => {
|
||||
expect(OIDC_PROVIDER).toBe('oidc');
|
||||
});
|
||||
|
||||
describe('parseAdminEmails', () => {
|
||||
it('returns [] for empty/undefined input', () => {
|
||||
expect(parseAdminEmails()).toEqual([]);
|
||||
expect(parseAdminEmails('')).toEqual([]);
|
||||
expect(parseAdminEmails(' ')).toEqual([]);
|
||||
});
|
||||
|
||||
it('lower-cases, trims, drops blanks and de-duplicates', () => {
|
||||
expect(parseAdminEmails(' RVE@respellion.nl , admin@respellion.nl ,, rve@respellion.nl'))
|
||||
.toEqual(['rve@respellion.nl', 'admin@respellion.nl']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveRole', () => {
|
||||
it('returns admin for an allow-listed e-mail (case-insensitive)', () => {
|
||||
expect(resolveRole('RVE@respellion.nl', 'rve@respellion.nl')).toBe('admin');
|
||||
});
|
||||
|
||||
it('returns user for a non-listed e-mail', () => {
|
||||
expect(resolveRole('jane@respellion.nl', 'rve@respellion.nl')).toBe('user');
|
||||
});
|
||||
|
||||
it('returns user when the allow-list is empty', () => {
|
||||
expect(resolveRole('rve@respellion.nl', '')).toBe('user');
|
||||
expect(resolveRole('rve@respellion.nl')).toBe('user');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deriveName', () => {
|
||||
it('prefers the OIDC name claim', () => {
|
||||
expect(deriveName({ name: 'Raymond Verhoef' }, 'rve@respellion.nl')).toBe('Raymond Verhoef');
|
||||
});
|
||||
|
||||
it('falls back to the e-mail prefix when no name claim', () => {
|
||||
expect(deriveName({}, 'rve@respellion.nl')).toBe('rve');
|
||||
expect(deriveName(undefined, 'jane.doe@respellion.nl')).toBe('jane.doe');
|
||||
});
|
||||
|
||||
it('falls back to "Onbekend" with no usable input', () => {
|
||||
expect(deriveName({}, '')).toBe('Onbekend');
|
||||
expect(deriveName(null, null)).toBe('Onbekend');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user