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>
55 lines
1.7 KiB
JavaScript
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;
|