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>
143 lines
3.8 KiB
TypeScript
143 lines
3.8 KiB
TypeScript
'use client'
|
|
|
|
import {
|
|
createContext,
|
|
useContext,
|
|
useState,
|
|
useCallback,
|
|
useMemo,
|
|
type ReactNode,
|
|
} from 'react'
|
|
import { createDjangoCSRClient, Auth } from './index'
|
|
import type { BaseUser, AuthDetails, AuthRoutes } from './types'
|
|
|
|
/**
|
|
* Auth state provided by AuthContext.
|
|
*/
|
|
export interface AuthState<TUser extends BaseUser = BaseUser> {
|
|
/** Current user (null if not authenticated) */
|
|
user: TUser | null
|
|
/** Whether auth state is loading */
|
|
isLoading: boolean
|
|
/** Refresh user from server */
|
|
refresh: () => Promise<TUser | null>
|
|
}
|
|
|
|
const Context = createContext<AuthState | null>(null)
|
|
|
|
/**
|
|
* Default routes configuration.
|
|
*/
|
|
export const defaultRoutes: AuthRoutes = {
|
|
login: '/auth/login',
|
|
authenticated: '/dashboard',
|
|
}
|
|
|
|
const RoutesContext = createContext<AuthRoutes>(defaultRoutes)
|
|
|
|
export interface AuthContextProps<TUser extends BaseUser = BaseUser> {
|
|
children: ReactNode
|
|
/** Initial user from SSR hydration (null if not authenticated) */
|
|
user?: TUser | null
|
|
/** API endpoint to fetch user data (default: '/api/auth/me/') */
|
|
userEndpoint?: string
|
|
/** Route configuration for guards */
|
|
routes?: Partial<AuthRoutes>
|
|
}
|
|
|
|
/**
|
|
* Base auth context for Django-React apps.
|
|
*
|
|
* Provides user state from a simple /me endpoint.
|
|
* For allauth integration, use AllauthContext instead.
|
|
*/
|
|
// Create client once at module level (session auth, no dynamic config needed)
|
|
const client = createDjangoCSRClient(Auth.SESSION)
|
|
|
|
export function AuthContext<TUser extends BaseUser = BaseUser>({
|
|
children,
|
|
user: initialUser = null,
|
|
userEndpoint = '/api/auth/me/',
|
|
routes,
|
|
}: AuthContextProps<TUser>) {
|
|
const [user, setUser] = useState<TUser | null>(initialUser)
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
const refresh = useCallback(async (): Promise<TUser | null> => {
|
|
setIsLoading(true)
|
|
try {
|
|
const resp = await client.request('GET', userEndpoint)
|
|
if (resp.ok) {
|
|
const userData = await resp.json()
|
|
setUser(userData)
|
|
return userData
|
|
} else if (resp.status === 401 || resp.status === 403) {
|
|
setUser(null)
|
|
return null
|
|
}
|
|
throw new Error(`Failed to fetch user: ${resp.status}`)
|
|
} catch (e) {
|
|
console.error('[AuthContext] Failed to fetch user:', e)
|
|
return null
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}, [userEndpoint])
|
|
|
|
const authState = useMemo<AuthState<TUser>>(() => ({
|
|
user,
|
|
isLoading,
|
|
refresh,
|
|
}), [user, isLoading, refresh])
|
|
|
|
const routesValue = useMemo(() => ({
|
|
...defaultRoutes,
|
|
...routes,
|
|
}), [routes])
|
|
|
|
return (
|
|
<RoutesContext value={routesValue}>
|
|
<Context value={authState}>
|
|
{children}
|
|
</Context>
|
|
</RoutesContext>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Hook to access auth state.
|
|
* Throws if used outside AuthContext.
|
|
*/
|
|
export function useAuthState<TUser extends BaseUser = BaseUser>(): AuthState<TUser> {
|
|
const ctx = useContext(Context)
|
|
if (!ctx) throw new Error('useAuthState must be used within AuthContext')
|
|
return ctx as AuthState<TUser>
|
|
}
|
|
|
|
/**
|
|
* Hook to access current user.
|
|
* Returns null if not authenticated.
|
|
*/
|
|
export function useUser<TUser extends BaseUser = BaseUser>(): TUser | null {
|
|
return useAuthState<TUser>().user
|
|
}
|
|
|
|
/**
|
|
* Hook to access auth details (isAuthenticated, isStaff, etc.)
|
|
*/
|
|
export function useAuth(): AuthDetails {
|
|
const user = useUser()
|
|
return {
|
|
isAuthenticated: user !== null,
|
|
isStaff: user?.is_staff ?? false,
|
|
isSuperuser: user?.is_superuser ?? false,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook to access route configuration.
|
|
*/
|
|
export function useAuthRoutes(): AuthRoutes {
|
|
return useContext(RoutesContext)
|
|
}
|