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>
417 lines
8.8 KiB
Markdown
417 lines
8.8 KiB
Markdown
# @lilith/sso-client
|
|
|
|
Client library for integrating with lilith-platform SSO authentication service.
|
|
|
|
## Features
|
|
|
|
- Session-based authentication with HTTP-only cookies
|
|
- Popup-based login/register flows
|
|
- Automatic session checking and refresh
|
|
- React hooks for easy integration
|
|
- TypeScript support with full type definitions
|
|
- PostMessage communication for cross-origin auth
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
pnpm add @lilith/sso-client
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Vanilla JavaScript
|
|
|
|
```typescript
|
|
import { SSOClient } from '@lilith/sso-client';
|
|
|
|
const sso = new SSOClient({
|
|
ssoUrl: 'https://sso.lilithapps.com',
|
|
checkInterval: 300000, // Check session every 5 minutes
|
|
onAuthChange: (authenticated, user) => {
|
|
console.log('Auth state changed:', authenticated, user);
|
|
},
|
|
onError: (error) => {
|
|
console.error('SSO error:', error);
|
|
},
|
|
});
|
|
|
|
// Start automatic session checking
|
|
sso.startAutoCheck();
|
|
|
|
// Login
|
|
async function login() {
|
|
try {
|
|
const user = await sso.login();
|
|
console.log('Logged in:', user);
|
|
} catch (error) {
|
|
console.error('Login failed:', error);
|
|
}
|
|
}
|
|
|
|
// Register
|
|
async function register() {
|
|
try {
|
|
const user = await sso.register();
|
|
console.log('Registered:', user);
|
|
} catch (error) {
|
|
console.error('Registration failed:', error);
|
|
}
|
|
}
|
|
|
|
// Logout
|
|
async function logout() {
|
|
try {
|
|
await sso.logout();
|
|
console.log('Logged out');
|
|
} catch (error) {
|
|
console.error('Logout failed:', error);
|
|
}
|
|
}
|
|
|
|
// Check current session
|
|
async function checkAuth() {
|
|
const response = await sso.checkSession();
|
|
if (response.authenticated) {
|
|
console.log('User:', response.user);
|
|
} else {
|
|
console.log('Not authenticated');
|
|
}
|
|
}
|
|
|
|
// Make authenticated requests
|
|
async function fetchData() {
|
|
const response = await sso.authenticatedFetch('https://api.lilithapps.com/data');
|
|
const data = await response.json();
|
|
return data;
|
|
}
|
|
|
|
// Cleanup when done
|
|
sso.destroy();
|
|
```
|
|
|
|
### React Hooks
|
|
|
|
```typescript
|
|
import { useSSO } from '@lilith/sso-client/react';
|
|
|
|
function App() {
|
|
const {
|
|
authenticated,
|
|
user,
|
|
loading,
|
|
error,
|
|
login,
|
|
register,
|
|
logout,
|
|
authenticatedFetch,
|
|
} = useSSO({
|
|
ssoUrl: 'https://sso.lilithapps.com',
|
|
});
|
|
|
|
if (loading) {
|
|
return <div>Loading...</div>;
|
|
}
|
|
|
|
if (error) {
|
|
return <div>Error: {error.message}</div>;
|
|
}
|
|
|
|
if (!authenticated) {
|
|
return (
|
|
<div>
|
|
<h1>Please log in</h1>
|
|
<button onClick={() => login()}>Login</button>
|
|
<button onClick={() => register()}>Register</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<h1>Welcome, {user?.username}!</h1>
|
|
<button onClick={() => logout()}>Logout</button>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Custom Popup Options
|
|
|
|
```typescript
|
|
// Customize popup window size and title
|
|
await sso.login({
|
|
width: 600,
|
|
height: 700,
|
|
title: 'Login to lilith.platform',
|
|
});
|
|
|
|
await sso.register({
|
|
width: 600,
|
|
height: 800,
|
|
title: 'Create Account',
|
|
});
|
|
```
|
|
|
|
### Manual Session Management
|
|
|
|
```typescript
|
|
// Validate current session
|
|
const isValid = await sso.validateSession();
|
|
|
|
// Refresh session TTL
|
|
const refreshed = await sso.refreshSession();
|
|
|
|
// Get current user
|
|
const user = sso.getUser();
|
|
|
|
// Check authentication status
|
|
const authenticated = sso.isAuthenticated();
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### SSOClient
|
|
|
|
#### Constructor
|
|
|
|
```typescript
|
|
new SSOClient(config: SSOConfig)
|
|
```
|
|
|
|
**Config Options:**
|
|
- `ssoUrl` (required): URL of the SSO service
|
|
- `checkInterval` (optional): Session check interval in ms (default: 300000)
|
|
- `popupWidth` (optional): Default popup width (default: 500)
|
|
- `popupHeight` (optional): Default popup height (default: 600)
|
|
- `onAuthChange` (optional): Callback when auth state changes
|
|
- `onError` (optional): Callback for errors
|
|
|
|
#### Methods
|
|
|
|
**`login(options?: PopupOptions): Promise<User>`**
|
|
Opens login popup and returns authenticated user.
|
|
|
|
**`register(options?: PopupOptions): Promise<User>`**
|
|
Opens register popup and returns new user.
|
|
|
|
**`logout(): Promise<void>`**
|
|
Logs out the current user and clears session.
|
|
|
|
**`checkSession(): Promise<AuthResponse>`**
|
|
Checks current session status and updates internal state.
|
|
|
|
**`validateSession(): Promise<boolean>`**
|
|
Validates session cookie without updating state.
|
|
|
|
**`refreshSession(): Promise<boolean>`**
|
|
Refreshes session TTL.
|
|
|
|
**`startAutoCheck(): void`**
|
|
Starts automatic session checking at configured interval.
|
|
|
|
**`stopAutoCheck(): void`**
|
|
Stops automatic session checking.
|
|
|
|
**`getUser(): User | null`**
|
|
Returns current authenticated user.
|
|
|
|
**`isAuthenticated(): boolean`**
|
|
Returns current authentication status.
|
|
|
|
**`authenticatedFetch(url: string, options?: RequestInit): Promise<Response>`**
|
|
Makes fetch request with credentials included.
|
|
|
|
**`destroy(): void`**
|
|
Cleanup method - stops auto-check, removes listeners, closes popups.
|
|
|
|
### useSSO Hook
|
|
|
|
```typescript
|
|
const {
|
|
authenticated,
|
|
user,
|
|
loading,
|
|
error,
|
|
login,
|
|
register,
|
|
logout,
|
|
checkSession,
|
|
authenticatedFetch,
|
|
} = useSSO(config);
|
|
```
|
|
|
|
Returns:
|
|
- `authenticated`: Boolean indicating auth status
|
|
- `user`: Current user object or null
|
|
- `loading`: Boolean indicating loading state
|
|
- `error`: Error object or null
|
|
- `login`: Function to open login popup
|
|
- `register`: Function to open register popup
|
|
- `logout`: Function to logout
|
|
- `checkSession`: Function to manually check session
|
|
- `authenticatedFetch`: Function to make authenticated requests
|
|
|
|
## Types
|
|
|
|
```typescript
|
|
interface User {
|
|
id: string;
|
|
email: string;
|
|
username: string;
|
|
role: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
interface SSOConfig {
|
|
ssoUrl: string;
|
|
checkInterval?: number;
|
|
popupWidth?: number;
|
|
popupHeight?: number;
|
|
onAuthChange?: (authenticated: boolean, user: User | null) => void;
|
|
onError?: (error: Error) => void;
|
|
}
|
|
|
|
interface AuthState {
|
|
authenticated: boolean;
|
|
user: User | null;
|
|
loading: boolean;
|
|
error: Error | null;
|
|
}
|
|
|
|
interface PopupOptions {
|
|
width?: number;
|
|
height?: number;
|
|
title?: string;
|
|
}
|
|
```
|
|
|
|
## How It Works
|
|
|
|
1. **Popup-based authentication**: Login/register opens SSO service in popup window
|
|
2. **PostMessage communication**: Popup sends success/error messages to parent
|
|
3. **HTTP-only cookies**: Session token stored securely in cookie
|
|
4. **Automatic session checking**: Periodically validates session in background
|
|
5. **Credentials included**: All requests include session cookie automatically
|
|
|
|
## Security
|
|
|
|
- Session tokens stored in HTTP-only cookies (not accessible via JavaScript)
|
|
- Cookies are domain-scoped (`.lilithapps.com` in production)
|
|
- HTTPS enforced in production (`secure` flag)
|
|
- SameSite protection against CSRF
|
|
- PostMessage origin validation
|
|
|
|
## Development
|
|
|
|
```bash
|
|
# Install dependencies
|
|
pnpm install
|
|
|
|
# Build
|
|
pnpm build
|
|
|
|
# Watch mode
|
|
pnpm dev
|
|
|
|
# Clean
|
|
pnpm clean
|
|
```
|
|
|
|
## MFA (Two-Factor Authentication)
|
|
|
|
The SSO client includes full MFA support with two methods: TOTP and Email (no third-party SMS services for privacy).
|
|
|
|
### Checking MFA Status
|
|
|
|
```typescript
|
|
const status = await sso.getMfaStatus();
|
|
console.log('MFA enabled:', status.enabled);
|
|
console.log('Available methods:', status.methods);
|
|
```
|
|
|
|
### Setting Up TOTP (Authenticator App)
|
|
|
|
```typescript
|
|
// 1. Get setup data (secret + QR code)
|
|
const setup = await sso.setupTotp();
|
|
console.log('QR Code:', setup.qrCodeDataUrl);
|
|
console.log('Secret:', setup.secret);
|
|
|
|
// 2. Verify with code from authenticator app
|
|
const result = await sso.verifyTotpSetup(setup.secret, '123456');
|
|
console.log('Recovery codes:', result.codes);
|
|
```
|
|
|
|
### Setting Up Email MFA
|
|
|
|
```typescript
|
|
await sso.enableEmailMfa();
|
|
// Uses the user's registered email
|
|
```
|
|
|
|
### Login with MFA
|
|
|
|
```typescript
|
|
const result = await sso.loginWithCredentials(email, password);
|
|
|
|
if (result.mfaRequired) {
|
|
// MFA is required
|
|
const pending = sso.getMfaPendingSession();
|
|
console.log('Available methods:', pending.availableMethods);
|
|
|
|
// For email, send code first
|
|
await sso.sendMfaCode('email');
|
|
|
|
// Verify MFA code
|
|
const challenge = await sso.verifyMfaChallenge('email', '123456');
|
|
if (challenge.success) {
|
|
console.log('Logged in:', challenge.user);
|
|
}
|
|
} else {
|
|
// Direct login (no MFA)
|
|
console.log('Logged in:', result.user);
|
|
}
|
|
```
|
|
|
|
### Using Recovery Codes
|
|
|
|
```typescript
|
|
const result = await sso.verifyRecoveryCode('ABCD-1234');
|
|
if (result.success) {
|
|
console.log('Logged in with recovery code');
|
|
if (result.warning) {
|
|
console.log('Warning:', result.warning);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Managing MFA
|
|
|
|
```typescript
|
|
// Set preferred method
|
|
await sso.setPreferredMfaMethod('totp');
|
|
|
|
// Disable a method
|
|
await sso.disableMfaMethod('email', 'password123');
|
|
|
|
// Regenerate recovery codes
|
|
const codes = await sso.regenerateRecoveryCodes('password123');
|
|
console.log('New codes:', codes.codes);
|
|
```
|
|
|
|
### MFA Callback
|
|
|
|
```typescript
|
|
const sso = new SSOClient({
|
|
ssoUrl: 'https://sso.lilithapps.com',
|
|
onMfaRequired: (pending) => {
|
|
// Redirect to MFA challenge UI
|
|
console.log('MFA required:', pending.availableMethods);
|
|
},
|
|
});
|
|
```
|
|
|
|
## License
|
|
|
|
UNLICENSED
|