# Auth Module

Standalone authentication module for PocketBase OAuth2 + Microsoft Graph integration.

## Features

- **Frontend**: PocketBase OAuth2 authentication with Microsoft provider
- **Backend**: Microsoft Graph token management with automatic caching
- **No dependencies on project-specific code** - Use in any project

## Installation

Copy the `auth-module` folder to your project:

```bash
cp -r auth-module /path/to/your/project/
```

Install dependencies:

```bash
bun add pocketbase @azure/msal-node
```

## Frontend Usage

### Basic Setup

**Important**: The frontend is browser-side code. It must receive `pbUrl` from the consuming application (not from the backend).

```typescript
import { PocketBaseAuth } from './auth-module/frontend';

// In your project, load PB_URL from environment/config and pass it to frontend
const auth = new PocketBaseAuth({
  pbUrl: process.env.PB_URL,  // Required - must be provided by consuming application
  collection: 'Users',
  provider: 'microsoft',
  loginContainerId: 'loginContainer',
  userDisplayNameId: 'userDisplayName',
  userEmailId: 'userEmailValue',
  loginBtnId: 'loginBtn',
  loginErrorId: 'loginError',
});
```

### HTML Required

```html
<!-- Login container (shown when not authenticated) -->
<div id="loginContainer" class="hidden">
  <button id="loginBtn">Login with Microsoft</button>
  <div id="loginError" class="hidden"></div>
</div>

<!-- User display (shown when authenticated) -->
<div id="userDisplayName"></div>
<div id="userEmailValue"></div>
```

### Event Callbacks

```typescript
const auth = new PocketBaseAuth(config, {
  onAuthSuccess: (state) => {
    console.log('Logged in:', state.user.email);
    // Initialize other systems here
  },
  onAuthFailure: (error) => {
    console.error('Login failed:', error);
  },
  onTokenUpdate: (state) => {
    console.log('Token refreshed');
  },
  onUiUpdate: (state) => {
    console.log('UI updated');
  },
});
```

### Methods

```typescript
// Get current auth state
const state = auth.getAuthState();
console.log(state.isAuthenticated, state.user);

// Check token status
const status = await auth.checkTokenStatus();

// Logout
auth.logout();

// Get raw PocketBase instance if needed
const pb = auth.getPocketBase();
```

## Backend Usage

### Setup (server.ts)

```typescript
import { GraphTokenManager, PocketBaseValidator, BackendAuth } from './auth-module/backend';

// Option 1: Use individual managers
const graphMgr = new GraphTokenManager({
  clientId: process.env.CLIENT_ID,
  tenantId: process.env.TENANT_ID,
  clientSecret: process.env.CLIENT_SECRET,
});

const pbValidator = new PocketBaseValidator(process.env.PB_DB);

// Option 2: Use combined manager
const backendAuth = new BackendAuth({
  clientId: process.env.CLIENT_ID,
  tenantId: process.env.TENANT_ID,
  clientSecret: process.env.CLIENT_SECRET,
}, process.env.PB_DB);
```

### Get Graph Token

```typescript
// Get token string
const token = await graphMgr.getToken();

// Get token with expiration
const { token, expiresOnISO } = await graphMgr.getTokenWithExpiry();

// Check if cached and valid
if (graphMgr.isTokenValid()) {
  console.log('Using cached token');
}

// Clear cache (force refresh)
graphMgr.clearCache();
```

### Validate PocketBase Token

```typescript
// Validate token
const isValid = await pbValidator.validateUserToken(token);

// Get user record
const user = await pbValidator.getUserRecord(token);

// Get PocketBase instance
const pb = pbValidator.getPocketBase();
```

### Endpoint Example

```typescript
app.get('/api/graph/status', async (c) => {
  try {
    const { token, expiresOnISO } = await graphMgr.getTokenWithExpiry();
    return c.json({
      active: true,
      expiresOnISO,
    });
  } catch (error) {
    return c.json(
      { active: false, error: error.message },
      500
    );
  }
});

app.post('/api/submit', async (c) => {
  const body = await c.req.json();
  const pbToken = body.pbToken;

  if (!pbToken) {
    return c.json({ error: 'Missing pbToken' }, 401);
  }

  const isValid = await pbValidator.validateUserToken(pbToken);
  if (!isValid) {
    return c.json({ error: 'Invalid token' }, 401);
  }

  // Token is valid, proceed with business logic
  // ...
});
```

## Environment Variables

Required in `.env` file:

```env
CLIENT_ID=your-microsoft-app-id
TENANT_ID=your-azure-tenant-id
CLIENT_SECRET=your-microsoft-client-secret
PB_DB=https://your-pocketbase-instance.com
PB_URL=https://your-pocketbase-instance.com
```

## Configuration

### Frontend Config Options

```typescript
interface AuthConfig {
  pbUrl: string;                     // PocketBase URL (required)
  collection?: string;                // Auth collection (default: Users)
  provider?: string;                  // OAuth provider (default: microsoft)
  loginContainerId?: string;          // ID of login container element
  userDisplayNameId?: string;         // ID of user name display element
  userEmailId?: string;               // ID of user email display element
  loginBtnId?: string;                // ID of login button element
  loginErrorId?: string;              // ID of error message element
}
```

### Backend Config Options

```typescript
interface BackendAuthConfig {
  clientId?: string;      // Microsoft app ID (or CLIENT_ID env var)
  tenantId?: string;      // Azure tenant ID (or TENANT_ID env var)
  clientSecret?: string;  // Client secret (or CLIENT_SECRET env var)
}
```

## TypeScript

The module exports TypeScript types for type safety:

```typescript
import { AuthState, AuthConfig, GraphTokenCache } from './auth-module/types';

const state: AuthState = auth.getAuthState();
```

## Notes

- **Frontend** manages user authentication only
- **Backend** manages Graph API tokens (never exposed to client)
- Tokens are cached in-memory on backend with automatic refresh
- Module does not include project-specific features (alerts, etc.)
- Each project can implement its own business logic on top

## License

Same as parent project
