Move allauth + auth UI to legacy/

allauth/ (44 files) is a django-allauth React UI — a separate concern
from the Mizan protocol. Moved to legacy/ pending extraction into a
standalone mizan-django-allauth package.

Also moved to legacy/:
- client/AuthContext.tsx — generic auth state from /me endpoint
- client/RouterContext.tsx — framework-agnostic router adapter
- client/routing.tsx — UserRoute/StaffRoute/AnonymousRoute guards
- client/nextjs.tsx — Next.js router adapter for auth

These are auth UI infrastructure, not Mizan protocol. The Mizan core
only needs JWT for auth header selection (jwt/ stays — MizanProvider
depends on useJWT() to decide between Bearer and session auth).

Cleaned up re-exports in client/react.ts and vitest aliases.

33 React tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 03:41:22 -04:00
parent 24ff0ae66d
commit 27c30d7e50
50 changed files with 0 additions and 8 deletions

View File

@@ -0,0 +1,137 @@
'use client'
import { useState } from 'react'
import { AuthenticatorType } from '../../defines'
import { useAllauthAPI } from '../../contexts/APIContext'
import { useStyles } from '../../contexts/StylesContext'
import { AuthCard } from '../AuthCard'
import { MFATOTPView } from './MFATOTPView'
import { MFAWebAuthnView } from './MFAWebAuthnView'
import { MFARecoveryCodesView } from './MFARecoveryCodesView'
const MFA_OPTIONS: Record<string, { label: string; description: string }> = {
[AuthenticatorType.WEBAUTHN]: {
label: 'Security Key / Passkey',
description: 'Use your registered security key or passkey',
},
[AuthenticatorType.TOTP]: {
label: 'Authenticator App',
description: 'Enter a code from your authenticator app',
},
[AuthenticatorType.RECOVERY_CODES]: {
label: 'Recovery Code',
description: 'Use one of your recovery codes',
},
}
interface MFAChooserViewProps {
types: string[]
onSuccess?: () => void
onCancel?: () => void
isReauth?: boolean
}
export function MFAChooserView({ types, onSuccess, onCancel, isReauth }: MFAChooserViewProps) {
const api = useAllauthAPI()
const styles = useStyles()
const [selectedType, setSelectedType] = useState<string | null>(null)
const [cancelling, setCancelling] = useState(false)
// Filter to only show options that are available
const availableOptions = types
.filter(type => MFA_OPTIONS[type])
.map(type => ({ type, ...MFA_OPTIONS[type] }))
const handleCancel = async () => {
setCancelling(true)
try {
await api.session.logout()
onCancel?.()
} catch {
setCancelling(false)
}
}
const handleBack = types.length > 1 ? () => setSelectedType(null) : undefined
// If a type is selected, show that method's view
if (selectedType === AuthenticatorType.TOTP) {
return (
<MFATOTPView
onSuccess={onSuccess}
onCancel={onCancel}
onBack={handleBack}
isReauth={isReauth}
/>
)
}
if (selectedType === AuthenticatorType.WEBAUTHN) {
return (
<MFAWebAuthnView
onSuccess={onSuccess}
onCancel={onCancel}
onBack={handleBack}
isReauth={isReauth}
/>
)
}
if (selectedType === AuthenticatorType.RECOVERY_CODES) {
return (
<MFARecoveryCodesView
onSuccess={onSuccess}
onCancel={onCancel}
onBack={handleBack}
isReauth={isReauth}
/>
)
}
// Show chooser
if (availableOptions.length === 0) {
return (
<AuthCard
title="Two-Factor Authentication"
subtitle="No authentication methods available."
footerLinks={onCancel ? [
{ label: 'Cancel and go back', onClick: handleCancel },
] : []}
/>
)
}
return (
<div className={styles.container}>
<div className={styles.card}>
<h1 className={styles.title}>Two-Factor Authentication</h1>
<p className={styles.subtitle}>Choose how you want to verify your identity.</p>
<div className={styles.form}>
{availableOptions.map(option => (
<button
key={option.type}
onClick={() => setSelectedType(option.type)}
className={styles.providerButton}
>
<div style={{ textAlign: 'left' }}>
<div style={{ fontWeight: 600 }}>{option.label}</div>
<div style={{ fontSize: '0.8125rem', opacity: 0.7, marginTop: '0.25rem' }}>
{option.description}
</div>
</div>
</button>
))}
</div>
{onCancel && (
<div className={styles.footer}>
<button onClick={handleCancel} disabled={cancelling} className={styles.link}>
{cancelling ? 'Cancelling...' : 'Cancel and go back'}
</button>
</div>
)}
</div>
</div>
)
}