Files
learning-platform/src/pages/Login.jsx
RaymondVerhoef 3af105bccd 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>
2026-06-23 11:41:08 +02:00

55 lines
1.7 KiB
JavaScript

import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useApp } from '../store/AppContext';
import Card from '../components/ui/Card';
import Button from '../components/ui/Button';
const Login = () => {
const { loginWithAzure } = useApp();
const navigate = useNavigate();
const [error, setError] = useState('');
const [busy, setBusy] = useState(false);
const handleAzure = async () => {
setError('');
setBusy(true);
try {
await loginWithAzure();
navigate('/');
} catch (e) {
// PocketBase throws when the popup is closed or the token exchange fails.
setError(e?.message || 'Signing in with Microsoft failed. Please try again.');
setBusy(false);
}
};
return (
<div className="min-h-screen bg-bg flex items-center justify-center p-4">
<div className="w-full max-w-md">
<div className="text-center mb-8">
<img src="/images/icon.png" alt="Respellion Icon" className="h-24 mx-auto mb-4" />
<h2 className="text-2xl text-teal font-bold tracking-tight">Respellion</h2>
<p className="text-fg-muted mt-2">Learning Platform</p>
</div>
<Card className="border border-bg-warm">
<div className="flex flex-col gap-5">
<p className="text-fg-muted text-sm text-center">
Sign in with your Respellion Microsoft account.
</p>
{error && <div className="text-red-500 text-sm font-medium text-center">{error}</div>}
<Button onClick={handleAzure} disabled={busy} className="w-full">
{busy ? 'Signing in…' : 'Sign in with Microsoft'}
</Button>
</div>
</Card>
</div>
</div>
);
};
export default Login;