# @mounaji_npm/auth — Full API Reference

Authentication layer for Mounaji applications. Adapter-driven auth context, React hooks, role-based guards, and a complete drop-in auth UI (sign in, sign up, forgot password).

---

## Table of Contents

1. [Installation](#installation)
2. [Architecture & adapter pattern](#architecture--adapter-pattern)
3. [Adapters](#adapters)
   - [Firebase](#firebase-adapter-mounaji_npmauthfirebase)
   - [JWT / REST](#jwt--rest-createjwtadapter)
   - [NextAuth.js](#nextauthjs-createnextauthadapter)
   - [Custom adapter](#custom-adapter)
4. [AuthProvider](#authprovider)
5. [useAuth](#useauth)
6. [useRole](#userole)
7. [Guards](#guards)
   - [RoleGate / GuestGate](#rolegate--guestgate)
   - [AuthGuard / withAuthGuard](#authguard--withauthguard)
8. [Components](#components)
   - [LoginPage](#loginpage)
   - [LoginButton](#loginbutton)
   - [PublicNavbar](#publicnavbar)
9. [CSS theming](#css-theming)
10. [Changelog](#changelog)

---

## Installation

```bash
npm install @mounaji_npm/auth

# Firebase:
npm install firebase

# NextAuth.js:
npm install next-auth
```

React 17+ is required as a peer dependency.

---

## Architecture & adapter pattern

`AuthProvider` is decoupled from any specific backend via the **MounajiAdapter** interface. Pick your backend once; all hooks, guards, and UI components work identically.

```
MounajiAdapter interface
  login({ method, email?, password? })         → Promise<MounajiUser>
  register({ name?, email, password })         → Promise<MounajiUser>   (optional)
  resetPassword(email)                         → Promise<void>          (optional)
  logout()                                     → Promise<void>
  getToken(forceRefresh?)                      → Promise<string|null>
  onAuthChange(callback)                       → unsubscribe()
  provider                                     — string identifier
```

`register` and `resetPassword` are optional. When an adapter does not implement them, `useAuth()` returns `undefined` for those properties and `LoginPage` hides the related UI automatically.

### Normalized user shape

```ts
interface MounajiUser {
  id:     string
  name:   string | null
  email:  string | null
  avatar: string | null
  roles:  Record<string, boolean>   // e.g. { admin: true, editor: true }
  raw:    any                       // original provider object
}
```

---

## Adapters

### Firebase adapter (`@mounaji_npm/auth/firebase`)

Wraps Firebase Auth. Supports all standard Firebase OAuth providers plus email/password sign-in, registration, and password reset.

#### Setup

```js
import { createFirebaseAdapter } from '@mounaji_npm/auth/firebase';
import { initializeApp } from 'firebase/app';
import {
  getAuth,
  GoogleAuthProvider,
  GithubAuthProvider,
  FacebookAuthProvider,
  TwitterAuthProvider,
  OAuthProvider,
} from 'firebase/auth';

const auth = getAuth(initializeApp({
  apiKey:            process.env.FIREBASE_API_KEY,
  authDomain:        process.env.FIREBASE_AUTH_DOMAIN,
  projectId:         process.env.FIREBASE_PROJECT_ID,
  storageBucket:     process.env.FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
  appId:             process.env.FIREBASE_APP_ID,
}));

const adapter = createFirebaseAdapter({
  auth,

  // Pass any subset of providers you want to enable
  providers: {
    google:    new GoogleAuthProvider(),
    github:    new GithubAuthProvider(),
    facebook:  new FacebookAuthProvider(),
    twitter:   new TwitterAuthProvider(),
    microsoft: new OAuthProvider('microsoft.com'),
    apple:     new OAuthProvider('apple.com'),
  },
});
```

**Backward compatibility:** the old `googleProvider` prop still works if you only need Google sign-in:

```js
createFirebaseAdapter({ auth, googleProvider: new GoogleAuthProvider() });
```

#### createFirebaseAdapter options

| Option | Type | Description |
|---|---|---|
| `auth` | `Auth` | Firebase Auth instance. Required. |
| `providers` | `Record<string, AuthProvider>` | Map of method name → Firebase provider instance. |
| `googleProvider` | `GoogleAuthProvider` | Deprecated shorthand. Use `providers.google` instead. |

#### Supported methods

| `login({ method })` | Firebase class |
|---|---|
| `'google'` | `GoogleAuthProvider` |
| `'github'` | `GithubAuthProvider` |
| `'facebook'` | `FacebookAuthProvider` |
| `'twitter'` | `TwitterAuthProvider` |
| `'microsoft'` | `new OAuthProvider('microsoft.com')` |
| `'apple'` | `new OAuthProvider('apple.com')` |
| `'email'` | `signInWithEmailAndPassword` |

`register({ name?, email, password })` — calls `createUserWithEmailAndPassword`, then `updateProfile` if `name` is provided.

`resetPassword(email)` — calls `sendPasswordResetEmail`.

#### Roles from Firebase custom claims

Set boolean claims server-side with the Firebase Admin SDK:

```js
await admin.auth().setCustomUserClaims(uid, { admin: true, editor: true });
```

They are surfaced automatically on `user.roles` after the next token refresh (`onIdTokenChanged`). Any non-Firebase reserved claim key with a `boolean` value is treated as a role.

---

### JWT / REST (`createJwtAdapter`)

Calls your own REST API for all auth operations. No Firebase dependency.

```js
import { createJwtAdapter } from '@mounaji_npm/auth';

const adapter = createJwtAdapter({
  loginUrl:   '/api/auth/login',   // POST { method, email?, password? } → { token, user }
  logoutUrl:  '/api/auth/logout',  // POST — optional
  meUrl:      '/api/auth/me',      // GET  → { user, roles }
  refreshUrl: '/api/auth/refresh', // POST → { token } — optional
  storage:    'localStorage',      // 'localStorage' | 'sessionStorage' | 'memory'
  tokenKey:   'mn_token',          // storage key
});
```

**Expected API contract**

```
POST /api/auth/login
  body:  { method: string, email?: string, password?: string }
  resp:  { token: string, user: { id, name, email, avatar, roles } }

GET  /api/auth/me
  headers: Authorization: Bearer <token>
  resp:    { user: { id, name, email, avatar, roles } }

POST /api/auth/refresh
  resp: { token: string }
```

---

### NextAuth.js (`createNextAuthAdapter`)

Full OAuth support for Next.js via [next-auth](https://next-auth.js.org/).

#### 1 — Create the NextAuth API route

```ts
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import GitHubProvider from 'next-auth/providers/github';

const handler = NextAuth({
  providers: [
    GoogleProvider({
      clientId:     process.env.NEXTAUTH_GOOGLE_CLIENT_ID!,
      clientSecret: process.env.NEXTAUTH_GOOGLE_CLIENT_SECRET!,
    }),
    GitHubProvider({
      clientId:     process.env.NEXTAUTH_GITHUB_CLIENT_ID!,
      clientSecret: process.env.NEXTAUTH_GITHUB_CLIENT_SECRET!,
    }),
  ],
  callbacks: {
    async jwt({ token, account, user }) {
      if (account?.access_token) token.accessToken = account.access_token;
      if (user?.roles)            token.roles       = user.roles;
      return token;
    },
    async session({ session, token }) {
      session.accessToken = token.accessToken ?? null;
      session.user.id     = token.sub ?? '';
      session.user.roles  = (token.roles as Record<string, boolean>) ?? {};
      return session;
    },
  },
  pages:  { signIn: '/login' },
  secret: process.env.NEXTAUTH_SECRET,
});

export { handler as GET, handler as POST };
```

#### 2 — Create the adapter

```ts
// src/auth/adapter.ts
import { createNextAuthAdapter } from '@mounaji_npm/auth/nextauth';

export const authAdapter = createNextAuthAdapter({
  providers:       ['google', 'github'],
  defaultProvider: 'google',
  rolesField:      'roles',
  tokenField:      'accessToken',
  callbackUrl:     '/dashboard',
  syncOnFocus:     true,
  pollInterval:    0,
});
```

#### 3 — Wire providers

```tsx
// app/providers.tsx
'use client';
import { SessionProvider } from 'next-auth/react';
import { AuthProvider }    from '@mounaji_npm/auth';
import { authAdapter }     from './auth/adapter';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <SessionProvider>
      <AuthProvider adapter={authAdapter}>
        {children}
      </AuthProvider>
    </SessionProvider>
  );
}
```

```tsx
// app/layout.tsx
import { Providers } from './providers';
export default function RootLayout({ children }) {
  return <html><body><Providers>{children}</Providers></body></html>;
}
```

#### NextAuth adapter options

| Option | Type | Default | Description |
|---|---|---|---|
| `providers` | `string[]` | `[]` | Provider IDs available to `login({ method })` |
| `defaultProvider` | `string` | `providers[0]` | Used when `method` is omitted |
| `rolesField` | `string` | `'roles'` | Field on `session.user` containing roles |
| `tokenField` | `string` | `'accessToken'` | Field on `session` containing the token |
| `callbackUrl` | `string` | `'/dashboard'` | Default redirect URL after sign-in |
| `syncOnFocus` | `boolean` | `true` | Re-validate session on tab focus |
| `pollInterval` | `number` | `0` | Poll `getSession()` every N ms (0 = disabled) |

#### Required env variables

```env
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=   # openssl rand -base64 32

NEXTAUTH_GOOGLE_CLIENT_ID=
NEXTAUTH_GOOGLE_CLIENT_SECRET=
NEXTAUTH_GITHUB_CLIENT_ID=
NEXTAUTH_GITHUB_CLIENT_SECRET=
```

#### Credentials provider (email + password)

```ts
// In your NextAuth route
import CredentialsProvider from 'next-auth/providers/credentials';

CredentialsProvider({
  name: 'Credentials',
  credentials: {
    email:    { label: 'Email',    type: 'email'    },
    password: { label: 'Password', type: 'password' },
  },
  async authorize(credentials) {
    const res  = await fetch(process.env.API_URL + '/auth/login', {
      method:  'POST',
      headers: { 'Content-Type': 'application/json' },
      body:    JSON.stringify(credentials),
    });
    const user = await res.json();
    return res.ok && user ? user : null;
  },
}),
```

```tsx
const { login } = useAuth();
await login({ method: 'credentials', email, password, redirect: false });
```

---

### Custom adapter

Implement the `MounajiAdapter` interface to integrate any auth backend:

```js
export function createMyAdapter(config) {
  return {
    async login({ method, email, password }) {
      // Sign in and return a MounajiUser
    },

    // Optional — expose registration UI in LoginPage
    async register({ name, email, password }) {
      // Create account and return a MounajiUser
    },

    // Optional — expose forgot-password UI in LoginPage
    async resetPassword(email) {
      // Send a reset email
    },

    async logout() { /* ... */ },

    async getToken(forceRefresh = false) {
      // Return current token string or null
    },

    onAuthChange(callback) {
      // Subscribe to auth state. callback(user: MounajiUser | null, token: string | null)
      // Return an unsubscribe function.
    },

    provider: 'my-provider',
  };
}
```

---

## AuthProvider

Wrap your app (or a sub-tree) once at the top level.

```jsx
<AuthProvider
  adapter={adapter}
  onLogin={(user) => trackLogin(user)}
  onLogout={() => clearLocalCache()}
  onAuthReady={(user) => initApp(user)}
>
  {children}
</AuthProvider>
```

| Prop | Type | Description |
|---|---|---|
| `adapter` | `MounajiAdapter` | Required. Auth provider adapter. |
| `onLogin` | `(user) => void` | Called after a successful `login()` or `register()`. |
| `onLogout` | `() => void` | Called after `logout()`. |
| `onAuthReady` | `(user\|null) => void` | Called once when the initial auth state resolves. |

---

## useAuth

Full access to the auth context. Must be inside `<AuthProvider>`.

```js
const {
  user,             // MounajiUser | null
  token,            // string | null
  loading,          // boolean — true during initial session check
  error,            // string | null — last error message
  isAuthenticated,  // boolean — shorthand for !!user

  login,            // async ({ method, email?, password? }) => void
  logout,           // async () => void
  register,         // async ({ name?, email, password }) => void  — undefined if not supported
  resetPassword,    // async (email) => void                       — undefined if not supported
  getToken,         // async (forceRefresh?) => string | null
  hasRole,          // (role: string) => boolean
  hasAnyRole,       // (roles: string[]) => boolean
} = useAuth();
```

**Checking adapter capabilities**

`register` and `resetPassword` are `undefined` when the current adapter does not implement them. Use this to conditionally render UI:

```jsx
const { register, resetPassword } = useAuth();

{register      && <Link href="/signup">Create account</Link>}
{resetPassword && <button onClick={() => resetPassword(email)}>Forgot password?</button>}
```

**Examples**

```jsx
// Sign in with OAuth
await login({ method: 'google' });

// Sign in with email
await login({ method: 'email', email: 'user@example.com', password: 'pw' });

// Register with email
await register({ name: 'Jane Smith', email: 'jane@example.com', password: 'secret123' });

// Send password reset
await resetPassword('jane@example.com');

// Get fresh token (e.g. to attach to an API request)
const token = await getToken(true);
```

---

## useRole

Convenience hook built on top of `useAuth`.

```js
const {
  isAdmin,    // hasRole('admin')
  isEditor,   // hasRole('editor')
  isViewer,   // hasRole('viewer')
  can,        // (role: string) => boolean     — alias for hasRole
  canAny,     // (roles: string[]) => boolean  — alias for hasAnyRole
  roles,      // Record<string, boolean>        — raw roles object
} = useRole();
```

```jsx
function Toolbar() {
  const { isAdmin, can } = useRole();
  return (
    <div>
      {isAdmin       && <Button>Manage Users</Button>}
      {can('publish') && <Button>Publish</Button>}
    </div>
  );
}
```

---

## Guards

### RoleGate / GuestGate

Declarative guards — render children only when the condition is met.

```jsx
import { RoleGate, GuestGate } from '@mounaji_npm/auth';

// Single role
<RoleGate roles="admin">
  <AdminPanel />
</RoleGate>

// Multiple roles — any match
<RoleGate roles={['admin', 'editor']}>
  <EditTools />
</RoleGate>

// Any authenticated user
<RoleGate authenticated>
  <Dashboard />
</RoleGate>

// Custom fallback
<RoleGate roles="admin" fallback={<p>Access denied</p>}>
  <AdminPanel />
</RoleGate>

// Inverse — renders only for guests (logged out)
<GuestGate>
  <MarketingBanner />
</GuestGate>
```

**RoleGate props**

| Prop | Type | Default | Description |
|---|---|---|---|
| `roles` | `string \| string[]` | — | Required role(s). Any match grants access. |
| `authenticated` | `boolean` | `false` | Require any logged-in user (no specific role). |
| `fallback` | `ReactNode` | `null` | Rendered when access is denied. |
| `loadingFallback` | `ReactNode` | `null` | Rendered while auth is loading. |

---

### AuthGuard / withAuthGuard

Imperative redirect guard. Useful for protected route components.

```jsx
import { AuthGuard, withAuthGuard } from '@mounaji_npm/auth';

// Redirect to /login if not authenticated
<AuthGuard redirectTo="/login" onRedirect={router.push}>
  <ProtectedPage />
</AuthGuard>

// Require specific role — redirect to /unauthorized if missing
<AuthGuard redirectTo="/unauthorized" roles={['admin']} onRedirect={router.push}>
  <AdminPage />
</AuthGuard>

// HOC variant — wraps an entire page component
export default withAuthGuard(AdminPage, {
  redirectTo: '/login',
  roles:      ['admin'],
  onRedirect: (path) => router.push(path),
});
```

**AuthGuard props**

| Prop | Type | Default | Description |
|---|---|---|---|
| `redirectTo` | `string` | `'/login'` | Path to redirect to when access is denied. |
| `roles` | `string[]` | — | Required roles. Redirects if user lacks all of them. |
| `onRedirect` | `(path) => void` | — | Navigation handler. |
| `loadingFallback` | `ReactNode` | `null` | Shown while auth resolves. |

---

## Components

### LoginPage

A complete, self-contained auth UI. Handles three modes internally — sign in, sign up, forgot password — all in one component. No external routing needed between modes.

```jsx
import { LoginPage } from '@mounaji_npm/auth';

<LoginPage
  appName="My App"
  tagline="Build something great"
  logo={<img src="/logo.svg" alt="Logo" style={{ width: 60 }} />}
  isDark={true}
  methods={['google', 'github', 'facebook', 'email']}
  allowRegister={true}
  allowForgotPassword={true}
  initialMode="login"
  redirectTo="/dashboard"
  onRedirect={(path) => router.push(path)}
  onSuccess={(user) => analytics.track('login', { uid: user.id })}
  companyName="Acme Inc"
  termsUrl="/legal/terms"
  privacyUrl="/legal/privacy"
/>
```

#### LoginPage props

| Prop | Type | Default | Description |
|---|---|---|---|
| `methods` | `string[]` | `['google']` | Auth methods to display. Any combination of `'google'` `'github'` `'facebook'` `'twitter'` `'microsoft'` `'apple'` `'email'`. |
| `allowRegister` | `boolean` | `true` | Show "Sign up" link and register form. Hidden automatically if adapter has no `register()`. |
| `allowForgotPassword` | `boolean` | `true` | Show "Forgot password?" link. Hidden automatically if adapter has no `resetPassword()`. |
| `initialMode` | `string` | `'login'` | Starting mode: `'login'` \| `'register'` \| `'forgot'`. |
| `isDark` | `boolean` | `true` | Dark or light theme. |
| `appName` | `string` | `'Mounaji'` | Brand name shown in the card header. |
| `tagline` | `string` | `'AI Platform'` | Tagline shown below the brand name. |
| `subheadline` | `string` | *(per mode)* | Overrides the per-mode subheadline. |
| `logo` | `ReactNode` | gradient "M" square | Logo element. |
| `redirectTo` | `string` | `'/dashboard'` | Path passed to `onRedirect` after login/register. |
| `onRedirect` | `(path: string) => void` | — | Navigation handler called after successful auth. |
| `onSuccess` | `(user) => void` | — | Called immediately after successful login or registration. |
| `termsUrl` | `string` | `'/terms'` | Terms of service URL. |
| `privacyUrl` | `string` | `'/privacy'` | Privacy policy URL. |
| `companyName` | `string` | — | Copyright name in the card footer. |

#### OAuth provider layout

When 1–2 OAuth providers are in `methods`, buttons render full-width with the label "Continue with Google", "Continue with GitHub", etc.

When 3+ providers are listed, they collapse into a compact 2-column grid (icon + short name) to keep the card height reasonable.

#### Mode behavior

| Mode | Trigger | What's shown |
|---|---|---|
| `'login'` | Default / "Sign in" link | OAuth buttons · email + password fields · "Forgot password?" link · "Sign up" link |
| `'register'` | "Sign up" link | OAuth buttons · name + email + password + confirm fields · "Sign in" link |
| `'forgot'` | "Forgot password?" link | Email field → success screen after submission · "Back to sign in" link |

The forgot-password success screen shows a checkmark and the address the email was sent to.

#### Password fields

All password inputs include a show/hide toggle button (eye icon). Confirm password is validated client-side before calling `register()`.

---

### LoginButton

Drop-in login/logout button. Shows a "Sign in" button when logged out; an avatar dropdown when logged in.

```jsx
import { LoginButton } from '@mounaji_npm/auth';

// Full button
<LoginButton isDark={true} onNavigate={router.push} />

// Compact icon-only (for tight navbars)
<LoginButton compact isDark={true} onNavigate={router.push} />
```

**Props**

| Prop | Type | Default | Description |
|---|---|---|---|
| `isDark` | `boolean` | `true` | Dark/light theme |
| `compact` | `boolean` | `false` | Icon-only mode |
| `loginLabel` | `string` | `'Sign in'` | Button label when logged out |
| `onNavigate` | `(path) => void` | — | Navigation handler for menu items |
| `dashboardPath` | `string` | `'/dashboard'` | Dropdown "Dashboard" link target |
| `settingsPath` | `string` | `'/settings'` | Dropdown "Settings" link target |

The avatar dropdown shows: user photo / initials avatar, name, email, Dashboard link, Settings link, and Log out button.

---

### PublicNavbar

Sticky top navigation bar for public/marketing pages. Includes theme toggle, nav links, and a `LoginButton`.

```jsx
import { PublicNavbar } from '@mounaji_npm/auth';

<PublicNavbar
  appName="My App"
  logo={<img src="/logo.svg" />}
  isDark={isDark}
  onThemeToggle={() => setIsDark(d => !d)}
  activePath={currentPath}
  onNavigate={router.push}
  navItems={[
    { label: 'Home',     path: '/' },
    { label: 'Features', path: '/features' },
    { label: 'Pricing',  path: '/pricing' },
    { label: 'Docs',     path: '/docs' },
  ]}
  loginProps={{ redirectTo: '/dashboard' }}
  rightSlot={<SomeBadge />}
/>
```

**Props**

| Prop | Type | Default | Description |
|---|---|---|---|
| `appName` | `string` | `'Mounaji'` | Brand name |
| `logo` | `ReactNode` | default M logo | Logo element |
| `navItems` | `{ label, path, icon? }[]` | see source | Navigation links |
| `isDark` | `boolean` | `true` | Dark/light theme |
| `onThemeToggle` | `() => void` | — | Called on theme toggle button click |
| `activePath` | `string` | `'/'` | Highlights the matching nav link |
| `LinkComponent` | `React.ElementType` | `'a'` | Swap with `Link` from Next.js |
| `onNavigate` | `(path) => void` | — | Navigation handler |
| `loginProps` | `object` | `{}` | Props forwarded to the inner `<LoginButton>` |
| `rightSlot` | `ReactNode` | — | Extra content inserted before the login button |

---

## CSS Theming

All components use inline styles driven by CSS variables with the `--mn-*` prefix. No stylesheet import needed. Override any variable globally to match your brand:

```css
:root {
  /* Colors */
  --mn-color-primary:        #3B82F6;
  --mn-color-accent:         #7C3AED;
  --mn-color-bg-dark:        #060919;
  --mn-color-bg-light:       #E5DED2;
  --mn-color-card-dark:      #0B0F23;
  --mn-color-card-light:     #EDE8DE;
  --mn-color-nav-dark:       #07091C;
  --mn-color-nav-light:      #E1DAD0;

  /* Borders */
  --mn-border-dark:          rgba(255,255,255,0.07);
  --mn-border-light:         #C9C2B6;

  /* Typography */
  --mn-text-primary-dark:    #F0F4FF;
  --mn-text-primary-light:   #1C1915;
  --mn-text-secondary-dark:  #94A3B8;
  --mn-text-secondary-light: #47413C;
  --mn-text-muted-dark:      #64748B;
  --mn-text-muted-light:     #7A7470;
  --mn-font-family:          'Inter', system-ui, sans-serif;
  --mn-font-size-sm:         0.875rem;

  /* Shapes */
  --mn-radius-lg:            0.75rem;
  --mn-shadow-lg:            0 4px 30px rgba(0,0,0,0.4);
}
```

---

## Changelog

| Version | Changes |
|---|---|
| 0.3.0 | **Firebase adapter:** multi-provider support (`providers` map) for GitHub, Facebook, Twitter/X, Microsoft, Apple; added `register()` and `resetPassword()` methods. **AuthProvider:** exposes `register` and `resetPassword` in context (undefined when not supported). **LoginPage:** complete rewrite — three modes (sign in / sign up / forgot password), 6 OAuth providers with brand icons, 2-column grid layout for 3+ providers, labeled inputs, password visibility toggle, forgot-password success screen. |
| 0.2.0 | Added `@mounaji_npm/auth/firebase` subpath export, `AuthGuard` HOC variant, `GuestGate`, `PublicNavbar`, interactive `setup` CLI. |
| 0.1.0 | Initial release — adapters, context, hooks, guards, `LoginButton`, `LoginPage`, `PublicNavbar`. |
