> ## Documentation Index
> Fetch the complete documentation index at: https://docs.charle.agency/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> How authentication and authorization work in CharleOS

CharleOS uses **Better Auth** for authentication with separate systems for team members and clients. This page provides an overview of the auth architecture.

## Two Auth Systems

CharleOS has **two completely separate authentication systems**:

<CardGroup cols={2}>
  <Card title="Team Auth" icon="users">
    Internal team members (developers, PMs, CSMs, managers)

    * Google OAuth only
    * Invite-only (no public signup)
    * Role-based access control
    * Session-based authentication
  </Card>

  <Card title="Client Portal Auth" icon="building">
    Client employees accessing the client portal

    * Email/password authentication
    * Invitation workflow
    * Per-client access control
    * Separate database tables
  </Card>
</CardGroup>

<Warning>
  These systems are **completely isolated**. A team member cannot log into the client portal with their team credentials, and vice versa.
</Warning>

## Architecture Overview

### Team Authentication Flow

```
User visits charle.agency
  ↓
"Sign in with Google" button
  ↓
Google OAuth consent screen
  ↓
Better Auth verifies @charle.co.uk email
  ↓
Session created → Dashboard
```

### Client Portal Authentication Flow

```
Client visits clients.charle.agency
  ↓
Email + password sign-in
  ↓
Better Auth verifies credentials
  ↓
Session created → Client Portal
```

## Database Tables

### Team Auth Tables

| Table | Purpose |
| - | - |
| `user` | Team member profiles |
| `session` | Active team sessions |
| `account` | OAuth provider connections |
| `user_preference` | UI preferences |

### Client Portal Auth Tables

| Table | Purpose |
| - | - |
| `client_user` | Client portal users |
| `client_session` | Active client sessions |
| `client_account` | Client credentials |
| `client_verification` | Password reset tokens |
| `client_user_preference` | Client UI preferences |

## Access Control

### Team Member Access Levels

CharleOS uses a two-tier access system:

#### Access Levels (System Permissions)

| Level | Who | Permissions |
| - | - | - |
| **Admin** | Luke | Full system access, all features |
| **Manager** | Simon, Andre, Ben, Nic | Management features, reports, admin tools |
| **Staff** | Everyone else | Standard access based on work type |

#### Work Types (Role-Based Features)

| Work Type | Dashboard | Key Features |
| - | - | - |
| **development** | IC Dashboard | My Tasks, My Schedule, Time Tracking |
| **design** | IC Dashboard | My Tasks, My Schedule, Time Tracking |
| **qa** | IC Dashboard | My Tasks, My Schedule, Time Tracking |
| **pm** | PM Dashboard | Task scheduling, client blocks |
| **csm** | CSM Dashboard | Quotes, clients, help desk |
| **sdr** | Staff Dashboard | Limited access |

**Combined Logic:**

* `accessLevel` controls what management features you see
* `workType` controls which dashboard and features you get
* Example: A `developer` with `manager` access level sees the Manager Dashboard (not IC Dashboard)

### Client Portal Access

Client users have simpler access control:

| Role | Permissions |
| - | - |
| **admin** | Can manage other client users, approve quotes |
| **member** | Can view tasks, submit tickets, view quotes |

## Authentication Features

### Team Authentication

<AccordionGroup>
  <Accordion title="Google OAuth" icon="google">
    * Only authentication method (no password)
    * Must use @charle.co.uk email
    * Profile picture synced from Google
    * Auto-uploaded to Cloudflare R2
  </Accordion>

  <Accordion title="Invite-Only System" icon="envelope">
    * No public signup page
    * Admin creates user accounts
    * Invitation email sent
    * User signs in with Google
  </Accordion>

  <Accordion title="Session Management" icon="clock">
    * 7-day session expiry
    * Automatically refreshed on activity
    * Stored in PostgreSQL
    * Tracks IP and user agent
  </Accordion>

  <Accordion title="Pending Approval Screen" icon="hourglass">
    * New users start as "pending"
    * See waiting screen until admin assigns role
    * Prevents access to app features
    * Admin updates status to "active"
  </Accordion>
</AccordionGroup>

### Client Portal Authentication

<AccordionGroup>
  <Accordion title="Email/Password" icon="lock">
    * Password-based authentication
    * Passwords hashed with bcrypt
    * Password reset via email token
    * No Google OAuth
  </Accordion>

  <Accordion title="Invitation Workflow" icon="user-plus">
    * CSM creates client user
    * Invitation email sent with token
    * Client sets password on first login
    * Token expires after 7 days
  </Accordion>

  <Accordion title="Per-Client Isolation" icon="shield">
    * Users belong to one client
    * Can only see their client's data
    * No cross-client access
    * Enforced at database level
  </Accordion>
</AccordionGroup>

## Environment Variables

### Team Auth

| Variable | Description |
| - | - |
| `BETTER_AUTH_SECRET` | Secret for encrypting sessions (32+ chars) |
| `BETTER_AUTH_URL` | Base URL (`http://localhost:3000` local, `https://charle.agency` prod) |
| `NEXT_PUBLIC_BETTER_AUTH_URL` | Public-facing auth URL (client-side) |
| `GOOGLE_CLIENT_ID` | Google OAuth client ID |
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret |

### Client Portal Auth

Uses the same Better Auth instance but with client-specific context:

| Variable | Description |
| - | - |
| `NEXT_PUBLIC_CLIENT_PORTAL_URL` | Client portal URL (`http://clients.localhost:3000` local, `https://clients.charle.agency` prod) |

## Code Structure

### Team Auth

```
lib/
├── auth.ts              # Better Auth config (team)
├── auth-client.ts       # Client-side auth hooks (team)
└── permissions.ts       # Permission checks

app/
├── (auth)/              # Auth pages (team login, pending)
├── (dashboard)/         # Protected team routes
└── api/auth/[...all]/   # Better Auth API handler
```

### Client Portal Auth

```
lib/
├── client-auth.ts       # Better Auth config (client portal)
└── client-auth-client.ts # Client-side auth hooks (portal)

app/client-portal/
├── sign-in/             # Client portal login
├── invite/              # Client invitation flow
├── (authenticated)/     # Protected client routes
└── callback/            # Auth callback
```

## Security Features

<AccordionGroup>
  <Accordion title="CSRF Protection" icon="shield-check">
    Better Auth includes built-in CSRF protection for all auth requests
  </Accordion>

  <Accordion title="Session Security" icon="fingerprint">
    * Sessions stored server-side in PostgreSQL
    * Tokens are random, not predictable
    * IP address and user agent tracked
    * Automatic session cleanup on expiry
  </Accordion>

  <Accordion title="Password Security (Client Portal)" icon="key">
    * Passwords hashed with bcrypt (cost factor 10)
    * Never stored in plain text
    * Password reset requires email verification
    * Tokens expire after 7 days
  </Accordion>

  <Accordion title="Domain Restrictions" icon="lock">
    * Team auth: Only @charle.co.uk emails
    * Client portal: Per-client isolation
    * No cross-origin session sharing
  </Accordion>
</AccordionGroup>

## Session Management

### Team Sessions

```typescript theme={null}
// Check if user is authenticated
const { data: session } = await auth.api.getSession({
  headers: headers(),
});

if (!session) {
  redirect("/auth");
}

// Access user data
const user = session.user;
console.log(user.name, user.email, user.accessLevel);
```

### Client Portal Sessions

```typescript theme={null}
// Check if client user is authenticated
const { data: session } = await clientAuth.api.getSession({
  headers: headers(),
});

if (!session) {
  redirect("/client-portal/sign-in");
}

// Access client user data
const clientUser = session.user;
console.log(clientUser.name, clientUser.clientId);
```

## Common Auth Patterns

### Protected Route (Server Component)

```typescript theme={null}
// app/(dashboard)/tasks/page.tsx
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { redirect } from "next/navigation";

export default async function TasksPage() {
  const { data: session } = await auth.api.getSession({
    headers: headers(),
  });

  if (!session) {
    redirect("/auth");
  }

  // Check permissions
  if (!canViewTasks(session.user)) {
    redirect("/dashboard");
  }

  return <TasksList />;
}
```

### Protected API Route

```typescript theme={null}
// app/api/tasks/route.ts
import { auth } from "@/lib/auth";
import { headers } from "next/headers";

export async function GET(req: Request) {
  const { data: session } = await auth.api.getSession({
    headers: headers(),
  });

  if (!session) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }

  // Fetch tasks...
}
```

### Client-Side Auth Check

```typescript theme={null}
"use client";

import { useSession } from "@/lib/auth-client";

export function ProfileButton() {
  const { data: session, isPending } = useSession();

  if (isPending) return <Skeleton />;
  if (!session) return <SignInButton />;

  return <UserDropdown user={session.user} />;
}
```

## Related Documentation

<CardGroup cols={2}>
  <Card title="Better Auth" icon="shield-check" href="/developer/auth/better-auth">
    Better Auth configuration details
  </Card>

  <Card title="Permissions" icon="user-lock" href="/developer/auth/permissions">
    Role-based access control
  </Card>

  <Card title="Sessions" icon="clock" href="/developer/auth/sessions">
    Session management and lifecycle
  </Card>

  <Card title="Environment Variables" icon="gear" href="/developer/getting-started/environment">
    Auth environment configuration
  </Card>
</CardGroup>
