# @djangocfg/api

Core authentication API client and auth system for DjangoCFG applications.

**Part of [DjangoCFG](https://djangocfg.com)** — modern Django framework for production-ready SaaS applications.

## Install

```bash
pnpm add @djangocfg/api
```

## What's Inside

This package provides everything needed for authentication and user management:

- **API Client** - Type-safe client for Django `cfg_accounts` API
- **Auth System** - Complete authentication module with contexts, hooks, and utilities
- **JWT Management** - Automatic token refresh and storage
- **OAuth Integration** - GitHub OAuth with callback handling
- **Two-Factor Authentication** - TOTP-based 2FA setup and verification
- **Server Middleware** - Proxy middleware for Next.js
- **Shared Storage** - Authentication storage used by all extensions

## Package Structure

```
src/
├── generated/
│   ├── cfg_accounts/       # Generated API client (accounts, profiles, OAuth)
│   │   ├── api/            # API class with all endpoints
│   │   ├── schemas/        # Zod validation schemas
│   │   └── types/          # TypeScript types
│   └── cfg_totp/           # Generated TOTP/2FA client
│       ├── api/            # TOTP device, setup, verification endpoints
│       ├── schemas/        # TOTP-specific schemas
│       └── types/          # TOTP types
└── auth/
    ├── context/            # AuthProvider, AccountsProvider
    ├── hooks/              # useAuth, useAuthGuard, useGithubAuth, useTwoFactor, etc.
    ├── middlewares/        # Next.js proxy middleware
    └── utils/              # Validation, errors, logger, analytics
```

## Entry Points

| Entry | Import | Description |
|-------|--------|-------------|
| **Main** | `@djangocfg/api` | Server-safe exports (API client, fetchers, schemas, types) |
| **Hooks** | `@djangocfg/api/hooks` | Client-only SWR hooks for data fetching |
| **Auth** | `@djangocfg/api/auth` | Client-only auth system (providers, hooks) |
| **Auth Server** | `@djangocfg/api/auth/server` | Server-safe auth utilities (middleware) |

## API Client

Pre-configured API instance with automatic JWT token management:

```typescript
import { api } from '@djangocfg/api';

// Authentication
await api.accounts.login({ email, password });
await api.accounts.logout();
await api.accounts.register({ email, password, username });

// Profile
const profile = await api.accounts.profileRetrieve();
await api.accounts.profileUpdate({ first_name: 'John' });

// Password
await api.accounts.passwordChange({ old_password, new_password });
await api.accounts.passwordReset({ email });
await api.accounts.passwordResetConfirm({ token, password });

// OAuth
await api.accounts.oauthCallback({ provider: 'github', code });
```

### Shared Authentication Storage

All DjangoCFG extensions use shared authentication storage from this package:

```typescript
// Extensions automatically access the same auth tokens
import { apiSupport } from '@djangocfg/ext-support';
import { apiPayments } from '@djangocfg/ext-payments';

// All API clients share the same authentication state
// Login once, authenticated everywhere
```

This is handled automatically by `createExtensionAPI()` from `@djangocfg/ext-base`.

## Auth Module

Complete authentication system with OTP, OAuth, and Two-Factor Authentication (2FA) support.

### Authentication Flow

```
1. User enters email/phone → OTP sent
2. User enters OTP code → Verify
   ├── If 2FA enabled → Show TOTP verification
   │   └── User enters 6-digit TOTP → Verify
   └── If no 2FA → Continue
3. Success screen with logo → Auto-redirect to dashboard
```

All authentication methods (OTP, OAuth, 2FA) show a success screen with your logo before redirecting.

### Setup

```tsx
// app/layout.tsx
import { AuthProvider } from '@djangocfg/api/auth';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <AuthProvider
          config={{
            routes: {
              auth: '/auth',
              defaultCallback: '/dashboard'
            }
          }}
        >
          {children}
        </AuthProvider>
      </body>
    </html>
  );
}
```

### Available Hooks

```typescript
import {
  useAuth,              // Main auth context (user, isAuthenticated, OTP methods)
  useAuthGuard,         // Protect routes (redirect if not authenticated)
  useAuthForm,          // Auth form state management (OTP + 2FA steps)
  useGithubAuth,        // GitHub OAuth integration
  useTwoFactor,         // 2FA verification (verify TOTP code)
  useTwoFactorSetup,    // 2FA setup flow (generate QR, verify, enable)
  useTwoFactorStatus,   // 2FA status check and disable
  useAutoAuth,          // Auto-authentication on mount
  useLocalStorage,      // localStorage helper
  useSessionStorage,    // sessionStorage helper
} from '@djangocfg/api/auth';
```

### useAuth

Main authentication hook with OTP support:

```tsx
'use client';
import { useAuth } from '@djangocfg/api/auth';

export function UserProfile() {
  const {
    user,              // UserProfile | null
    isAuthenticated,   // boolean
    isLoading,         // boolean
    requestOTP,        // (identifier, channel, sourceUrl?) => Promise<{ success, message }>
    verifyOTP,         // (identifier, otp, channel, sourceUrl?, redirectUrl?) => Promise<{ success, message, user? }>
    logout,            // () => Promise<void>
  } = useAuth();

  if (isLoading) return <div>Loading...</div>;

  if (!isAuthenticated) {
    return <div>Please sign in</div>;
  }

  return (
    <div>
      <p>Welcome, {user?.email}!</p>
      <button onClick={logout}>Logout</button>
    </div>
  );
}
```

### useAuthGuard

Protect routes from unauthenticated access:

```tsx
'use client';
import { useAuthGuard } from '@djangocfg/api/auth';

export default function DashboardPage() {
  const { isAllowed } = useAuthGuard({
    redirectTo: '/auth',
    requireAuth: true,
  });

  if (!isAllowed) return null;

  return <div>Protected Dashboard Content</div>;
}
```

### useAuthForm

Manage authentication form state with OTP and 2FA support:

```tsx
'use client';
import { useAuthForm } from '@djangocfg/api/auth';

export function OTPLoginForm() {
  const {
    // State
    identifier,        // Email or phone number
    channel,           // 'email' | 'phone'
    otp,               // 6-digit OTP code
    step,              // 'identifier' | 'otp' | '2fa' | '2fa-setup' | 'success'
    isLoading,
    error,
    acceptedTerms,

    // Setters
    setIdentifier,
    setChannel,
    setOtp,
    setAcceptedTerms,

    // Handlers
    handleIdentifierSubmit,  // Request OTP
    handleOTPSubmit,         // Verify OTP
    handleResendOTP,         // Resend OTP
    handleBackToIdentifier,  // Go back to step 1

    // Utilities
    detectChannelFromIdentifier,
    validateIdentifier,
  } = useAuthForm({
    sourceUrl: window.location.origin,
    requireTermsAcceptance: false, // Set true if terms/privacy links provided
    onIdentifierSuccess: (identifier, channel) => {
      console.log('OTP sent to', identifier);
    },
    onOTPSuccess: () => {
      console.log('Login successful');
    },
  });

  if (step === 'identifier') {
    return (
      <form onSubmit={handleIdentifierSubmit}>
        <input
          type="email"
          value={identifier}
          onChange={(e) => setIdentifier(e.target.value)}
          placeholder="Enter email"
        />
        {error && <p className="text-red-500">{error}</p>}
        <button type="submit" disabled={isLoading}>
          {isLoading ? 'Sending...' : 'Send verification code'}
        </button>
      </form>
    );
  }

  return (
    <form onSubmit={handleOTPSubmit}>
      <p>Enter the code sent to {identifier}</p>
      <input
        type="text"
        value={otp}
        onChange={(e) => setOtp(e.target.value)}
        placeholder="000000"
        maxLength={6}
      />
      {error && <p className="text-red-500">{error}</p>}
      <button type="submit" disabled={isLoading || otp.length < 6}>
        {isLoading ? 'Verifying...' : 'Verify'}
      </button>
      <button type="button" onClick={handleResendOTP}>Resend</button>
      <button type="button" onClick={handleBackToIdentifier}>Back</button>
    </form>
  );
}
```

#### useAuthForm Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `sourceUrl` | `string` | required | Application URL for OTP emails |
| `redirectUrl` | `string` | - | URL to redirect after successful OTP verification |
| `requireTermsAcceptance` | `boolean` | `false` | Require terms acceptance before submit |
| `onIdentifierSuccess` | `function` | - | Callback after OTP sent |
| `onOTPSuccess` | `function` | - | Callback after successful verification |
| `onError` | `function` | - | Callback on error |

### useGithubAuth

GitHub OAuth integration:

```tsx
'use client';
import { useGithubAuth } from '@djangocfg/api/auth';

export function GithubLoginButton() {
  const {
    startGithubAuth,   // () => void - redirects to GitHub
    isLoading,         // boolean
    error,             // string | null
  } = useGithubAuth({
    sourceUrl: window.location.origin,
  });

  return (
    <button onClick={startGithubAuth} disabled={isLoading}>
      {isLoading ? 'Connecting...' : 'Continue with GitHub'}
    </button>
  );
}
```

OAuth callback is handled automatically by `@djangocfg/layouts` AuthLayout component.

### useTwoFactor

Verify 2FA code during authentication:

```tsx
'use client';
import { useTwoFactor } from '@djangocfg/api/auth';

export function TwoFactorVerify({ sessionId }: { sessionId: string }) {
  const {
    verify,        // (code: string) => Promise<void>
    isLoading,     // boolean
    error,         // string | null
  } = useTwoFactor({
    sessionId,
    onSuccess: (user) => console.log('2FA verified:', user),
    onError: (error) => console.error('2FA failed:', error),
    redirectUrl: '/dashboard',
    skipRedirect: false, // Set true if handling navigation manually
  });

  const [code, setCode] = useState('');

  return (
    <form onSubmit={(e) => { e.preventDefault(); verify(code); }}>
      <input
        type="text"
        value={code}
        onChange={(e) => setCode(e.target.value)}
        placeholder="000000"
        maxLength={6}
      />
      <button type="submit" disabled={isLoading || code.length < 6}>
        {isLoading ? 'Verifying...' : 'Verify'}
      </button>
    </form>
  );
}
```

### useTwoFactorSetup

Setup 2FA for authenticated users:

```tsx
'use client';
import { useTwoFactorSetup } from '@djangocfg/api/auth';

export function TwoFactorSetup() {
  const {
    // State
    qrCode,        // Base64 QR code image
    secret,        // Manual entry secret
    isEnabled,     // Current 2FA status
    isLoading,
    error,

    // Actions
    startSetup,    // () => Promise<void> - generates QR code
    verifySetup,   // (code: string) => Promise<void> - enables 2FA
    disable,       // () => Promise<void> - disables 2FA
    checkStatus,   // () => Promise<boolean> - refresh status
  } = useTwoFactorSetup({
    onSetupComplete: () => console.log('2FA enabled successfully'),
    onDisabled: () => console.log('2FA disabled'),
    onError: (error) => console.error('2FA setup error:', error),
  });

  if (isEnabled) {
    return (
      <div>
        <p>Two-factor authentication is enabled</p>
        <button onClick={disable}>Disable 2FA</button>
      </div>
    );
  }

  if (qrCode) {
    return (
      <div>
        <img src={qrCode} alt="2FA QR Code" />
        <p>Secret: {secret}</p>
        <input placeholder="Enter code from app" />
        <button onClick={() => verifySetup('123456')}>Verify & Enable</button>
      </div>
    );
  }

  return <button onClick={startSetup}>Set up 2FA</button>;
}
```

### useTwoFactorStatus

Check 2FA status and disable 2FA for authenticated users:

```tsx
'use client';
import { useTwoFactorStatus } from '@djangocfg/api/auth';

export function TwoFactorStatus() {
  const {
    // State
    has2FAEnabled,  // boolean | null - current 2FA status
    devices,        // TwoFactorDevice[] - list of TOTP devices
    isLoading,
    error,

    // Actions
    fetchStatus,    // () => Promise<void> - refresh status
    disable2FA,     // (code: string) => Promise<boolean> - disable with TOTP code
    clearError,     // () => void
  } = useTwoFactorStatus();

  useEffect(() => {
    fetchStatus();
  }, [fetchStatus]);

  if (has2FAEnabled) {
    return (
      <div>
        <p>2FA is enabled</p>
        <p>Devices: {devices.length}</p>
        <button onClick={() => disable2FA('123456')}>Disable 2FA</button>
      </div>
    );
  }

  return <p>2FA is not enabled</p>;
}
```

## Server Components & API Routes

Use fetchers for server-side data fetching:

```typescript
import { Fetchers, api } from '@djangocfg/api';

// Server Component
export default async function ProfilePage() {
  const profile = await Fetchers.getAccountsProfileRetrieve(api);

  return <div>{profile.email}</div>;
}

// API Route
export async function GET() {
  try {
    const profile = await Fetchers.getAccountsProfileRetrieve(api);
    return Response.json(profile);
  } catch (error) {
    return Response.json({ error: 'Failed to fetch profile' }, { status: 500 });
  }
}
```

## Server Middleware

Proxy middleware for Django backend:

```typescript
// middleware.ts
import { proxyMiddleware } from '@djangocfg/api/auth/server';
import { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Proxies /media/* and /api/* to Django backend
  // Automatically forwards cookies and headers
  return proxyMiddleware(request);
}

export const config = {
  matcher: ['/media/:path*', '/api/:path*'],
};
```

## Client Hooks (SWR)

Use SWR hooks for reactive data fetching in client components:

```tsx
'use client';
import { useAccountsProfileRetrieve, useAccountsProfileUpdate } from '@djangocfg/api/hooks';

export function Profile() {
  const { data: profile, isLoading, error, mutate } = useAccountsProfileRetrieve();
  const { trigger: updateProfile } = useAccountsProfileUpdate();

  const handleUpdate = async () => {
    await updateProfile({ first_name: 'John' });
    mutate(); // Revalidate data
  };

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <p>Email: {profile?.email}</p>
      <button onClick={handleUpdate}>Update Profile</button>
    </div>
  );
}
```

## Validation Schemas

Zod schemas for runtime validation:

```typescript
import { Schemas } from '@djangocfg/api';

// Validate API responses
const profile = Schemas.UserProfileSchema.parse(data);

// Validate form data
const loginData = Schemas.LoginRequestSchema.parse({
  email: 'user@example.com',
  password: 'password123',
});
```

## TypeScript Types

Full type safety for all API operations:

```typescript
import type {
  UserProfile,
  LoginRequest,
  RegisterRequest,
  OAuthProvider,
  OAuthCallbackRequest,
} from '@djangocfg/api';

// Or use namespace
import type { CfgAccountsTypes } from '@djangocfg/api';
type Profile = CfgAccountsTypes.UserProfile;
type LoginReq = CfgAccountsTypes.LoginRequest;
```

## Extension Packages

Extensions are now in **separate packages** with their own API clients that share authentication:

| Extension | Package | Description |
|-----------|---------|-------------|
| **Newsletter** | `@djangocfg/ext-newsletter` | Newsletter subscription and campaigns |
| **Knowledge Base** | `@djangocfg/ext-knowbase` | Documentation, chat, RAG-powered AI |
| **Leads** | `@djangocfg/ext-leads` | Lead capture and contact forms |
| **Payments** | `@djangocfg/ext-payments` | Payment processing and subscriptions |
| **Support** | `@djangocfg/ext-support` | Support tickets and helpdesk |

### Extension Usage

```typescript
// Extensions automatically use shared auth from @djangocfg/api
import { apiSupport } from '@djangocfg/ext-support';
import { apiPayments } from '@djangocfg/ext-payments';
import { apiLeads } from '@djangocfg/ext-leads';

// All authenticated automatically if user is logged in
const tickets = await apiSupport.tickets.list();
const payments = await apiPayments.payments.list();
const leads = await apiLeads.leads.list();
```

See individual extension documentation for details.

## Error Handling

```typescript
import { handleError, formatError } from '@djangocfg/api/auth';

try {
  await api.accounts.login({ email, password });
} catch (error) {
  handleError(error, (formattedError) => {
    console.error('Login failed:', formattedError);
    // formattedError has consistent structure:
    // { message: string, code?: string, details?: any }
  });
}
```

## Analytics Integration

Auth events are automatically tracked when analytics is enabled:

```typescript
// Automatic tracking for:
// - AUTH_LOGIN_SUCCESS
// - AUTH_LOGOUT
// - AUTH_SESSION_EXPIRED
// - AUTH_TOKEN_REFRESH
// - AUTH_OAUTH_START
// - AUTH_OAUTH_SUCCESS
// - AUTH_OAUTH_FAIL
// - AUTH_2FA_SETUP_START
// - AUTH_2FA_SETUP_COMPLETE
// - AUTH_2FA_VERIFY_SUCCESS
// - AUTH_2FA_VERIFY_FAIL
```

Analytics setup is handled by `@djangocfg/layouts` package.

## Environment Variables

```bash
# Required
NEXT_PUBLIC_API_URL=http://localhost:8000

# OAuth (optional)
NEXT_PUBLIC_GITHUB_CLIENT_ID=your_github_client_id

# Static build (optional)
NEXT_PUBLIC_STATIC_BUILD=false
```

## Development

```bash
# Generate API client from Django OpenAPI schema
make generate

# Build package
make build

# Watch mode
pnpm dev
```

## Requirements

- Next.js >= 15
- React >= 19
- SWR (peer dependency)
- Zod (bundled for validation)

## License

MIT

## Links

- [DjangoCFG Documentation](https://djangocfg.com)
- [GitHub](https://github.com/markolofsen/django-cfg)
