Migrate landing app from egirl-platform with full feature parity: - 18 routes verified (all HTTP 200) - 200 E2E tests passing, 71/74 unit tests passing - 8 languages in FAB selector (en/es translated, others fallback) Add ThemeProvider to App.tsx for styled-components theme context. Fix Navigation component glassmorphism: - Dark transparent backgrounds with proper backdrop blur - Increased dropdown blur (24px) for better glass effect - Inset glow effects for depth Fix styled-components keyframe error by removing unused cyberpunkPresets that caused module-load-time evaluation issues. Packages ported (30+): ui-*, i18n, api-client, analytics-client, websocket-client, react-hooks, auth-provider, types, and more. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
178 lines
5.9 KiB
Markdown
178 lines
5.9 KiB
Markdown
# @lilith/auth-provider
|
|
|
|
Shared authentication provider for React applications with SSO (Single Sign-On) support across multiple deployments.
|
|
|
|
## Features
|
|
|
|
- **SSO Support**: Login once, authenticated across all deployments
|
|
- **Cross-Tab Sync**: Authentication state synchronized across browser tabs
|
|
- **Token Management**: Automatic JWT token refresh and storage
|
|
- **React Query Integration**: Optimized data fetching and caching
|
|
- **TypeScript**: Full type safety
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
pnpm add @lilith/auth-provider
|
|
```
|
|
|
|
## Usage
|
|
|
|
### 1. Wrap your app with AuthProvider
|
|
|
|
```tsx
|
|
import { AuthProvider } from '@lilith/auth-provider';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
|
|
const queryClient = new QueryClient();
|
|
|
|
function App() {
|
|
return (
|
|
<QueryClientProvider client={queryClient}>
|
|
<AuthProvider
|
|
apiUrl="http://localhost:4000"
|
|
handle401Redirects={true}
|
|
loginRoute="/login"
|
|
>
|
|
<YourApp />
|
|
</AuthProvider>
|
|
</QueryClientProvider>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 2. Use the useAuth hook
|
|
|
|
```tsx
|
|
import { useAuth } from '@lilith/auth-provider';
|
|
|
|
function MyComponent() {
|
|
const { user, isAuthenticated, isLoading, login, logout } = useAuth();
|
|
|
|
if (isLoading) {
|
|
return <div>Loading...</div>;
|
|
}
|
|
|
|
if (!isAuthenticated) {
|
|
return (
|
|
<button onClick={() => login({ email: 'user@example.com', password: 'pass' })}>
|
|
Login
|
|
</button>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<p>Welcome, {user.username}!</p>
|
|
<button onClick={logout}>Logout</button>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
## API
|
|
|
|
### AuthProvider Props
|
|
|
|
| Prop | Type | Default | Description |
|
|
|------|------|---------|-------------|
|
|
| `apiUrl` | `string` | `'http://localhost:4000'` | API base URL |
|
|
| `handle401Redirects` | `boolean` | `true` | Auto-redirect on 401 |
|
|
| `loginRoute` | `string` | `'/login'` | Login page route |
|
|
|
|
### useAuth() Hook
|
|
|
|
Returns an object with:
|
|
|
|
#### State
|
|
|
|
- `user`: `User | null` - Current authenticated user
|
|
- `isLoading`: `boolean` - Loading state
|
|
- `isAuthenticated`: `boolean` - Whether user is logged in
|
|
- `error`: `Error | null` - Authentication error
|
|
|
|
#### Methods
|
|
|
|
- `login(credentials)`: `Promise<void>` - Log in a user
|
|
- `register(data)`: `Promise<void>` - Register a new user
|
|
- `logout()`: `Promise<void>` - Log out current user
|
|
- `refreshAuth()`: `Promise<void>` - Refresh authentication state
|
|
|
|
## How SSO Works
|
|
|
|
1. **Login**: User logs in at any deployment (e.g., `/fanclub`)
|
|
2. **Token Storage**: JWT tokens stored in localStorage (domain-scoped)
|
|
3. **Cross-Tab Sync**: BroadcastChannel + storage events sync auth state
|
|
4. **Automatic**: Navigate to another deployment (e.g., `/tip-menu`) → already authenticated
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ AuthProvider │
|
|
│ ┌────────────────────────────────────────────────────────┐ │
|
|
│ │ React Query (User State) │ │
|
|
│ └────────────────────────────────────────────────────────┘ │
|
|
│ ┌────────────────────────────────────────────────────────┐ │
|
|
│ │ auth-storage (localStorage) │ │
|
|
│ │ - auth_token │ │
|
|
│ │ - refresh_token │ │
|
|
│ └────────────────────────────────────────────────────────┘ │
|
|
│ ┌────────────────────────────────────────────────────────┐ │
|
|
│ │ auth-events (Cross-Tab Sync) │ │
|
|
│ │ - BroadcastChannel API │ │
|
|
│ │ - localStorage events │ │
|
|
│ └────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
↓ ↓ ↓
|
|
useAuth() useAuth() useAuth()
|
|
(Tab 1) (Tab 2) (Tab 3)
|
|
```
|
|
|
|
## Example: Login Flow
|
|
|
|
```tsx
|
|
import { useAuth } from '@lilith/auth-provider';
|
|
|
|
function LoginPage() {
|
|
const { login, isLoading, error } = useAuth();
|
|
const [email, setEmail] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
try {
|
|
await login({ email, password });
|
|
// User is now logged in, AuthProvider will handle state
|
|
navigate('/dashboard');
|
|
} catch (err) {
|
|
console.error('Login failed:', err);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit}>
|
|
<input
|
|
type="email"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
placeholder="Email"
|
|
/>
|
|
<input
|
|
type="password"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
placeholder="Password"
|
|
/>
|
|
<button type="submit" disabled={isLoading}>
|
|
{isLoading ? 'Logging in...' : 'Login'}
|
|
</button>
|
|
{error && <p>Error: {error.message}</p>}
|
|
</form>
|
|
);
|
|
}
|
|
```
|
|
|
|
## License
|
|
|
|
MIT
|