npm package: @sp-uvb/fastify
Description: Fastify plugin for Universal Verification Broker (UVB)
Author: UVB Team
Latest version: 0.1.0

# @sp-uvb/fastify

Fastify plugin for Universal Verification Broker (UVB) authentication.

## Installation

```bash
npm install @sp-uvb/fastify
# or
yarn add @sp-uvb/fastify
# or
pnpm add @sp-uvb/fastify
```

## Quick Start

```typescript
import Fastify from 'fastify';
import uvbPlugin, { requireFactors } from '@sp-uvb/fastify';

const fastify = Fastify();

// Register UVB authentication plugin
await fastify.register(uvbPlugin, {
  tenantId: 'my-tenant',
  uvbUrl: 'http://localhost:8080',
  excludePaths: ['/login', '/health', '/public'],
});

// Access the authenticated user
fastify.get('/profile', async (request, reply) => {
  const session = request.uvbSession;
  return {
    userId: session?.userId,
    factors: session?.factorsVerified,
  };
});

// Require specific MFA factors for sensitive routes
fastify.post(
  '/admin/delete',
  { preHandler: requireFactors(['totp', 'webauthn']) },
  async (request, reply) => {
    return { message: 'Admin action completed' };
  }
);

await fastify.listen({ port: 3000 });
```

## API

### Plugin Options

When registering the plugin, you can provide these options:

- `tenantId` (required): Your UVB tenant ID
- `uvbUrl` (optional): UVB server URL, defaults to `http://localhost:8080`
- `apiKey` (optional): API key for server-to-server authentication
- `cookieName` (optional): Cookie name for session token, defaults to `uvb_session`
- `excludePaths` (optional): Array of paths to exclude from authentication, defaults to `[]`
- `onError` (optional): Custom error handler function

**Attached to Request:**

After successful authentication, `request.uvbSession` contains:

```typescript
interface UVBSession {
  userId: string;
  tenantId: string;
  factorsVerified: string[];
  sessionId: string;
  expiresAt: Date;
}
```

### `requireFactors(factors: string[])`

Pre-handler to require specific MFA factors for a route.

```typescript
fastify.get(
  '/sensitive',
  { preHandler: requireFactors(['totp', 'webauthn']) },
  async (request, reply) => {
    // Only accessible if user has verified both TOTP and WebAuthn
    return { message: 'Sensitive data' };
  }
);
```

### `getSession(request: FastifyRequest)`

Helper function to get the current UVB session from a request.

```typescript
import { getSession } from '@sp-uvb/fastify';

fastify.get('/info', async (request, reply) => {
  const session = getSession(request);
  if (!session) {
    return reply.code(401).send({ error: 'Not authenticated' });
  }
  return { userId: session.userId };
});
```

## Examples

### Protecting Specific Routes

```typescript
import Fastify from 'fastify';
import uvbPlugin from '@sp-uvb/fastify';

const fastify = Fastify();

// Public routes
fastify.get('/health', async (request, reply) => {
  return { status: 'ok' };
});

// Protected routes
await fastify.register(async (protectedRoutes) => {
  await protectedRoutes.register(uvbPlugin, {
    tenantId: 'my-tenant',
    uvbUrl: process.env.UVB_URL,
  });

  protectedRoutes.get('/api/data', async (request, reply) => {
    // User is authenticated
    return { data: 'protected' };
  });
});

await fastify.listen({ port: 3000 });
```

### Custom Error Handling

```typescript
await fastify.register(uvbPlugin, {
  tenantId: 'my-tenant',
  onError: (err, request, reply) => {
    fastify.log.error('UVB auth error:', err);
    return reply.code(401).send({
      error: 'Authentication failed',
      details: err.message,
    });
  },
});
```

### Conditional MFA Requirements

```typescript
import { requireFactors } from '@sp-uvb/fastify';

fastify.post('/transfer', async (request, reply) => {
  const amount = request.body.amount;

  // Require additional auth for large transfers
  if (amount > 10000) {
    await requireFactors(['totp', 'webauthn'])(request, reply);
  }

  return { message: 'Transfer initiated' };
});
```

### Multiple Route Groups

```typescript
const fastify = Fastify();

// Public API
fastify.get('/public/info', async () => ({ info: 'public' }));

// Basic auth required
await fastify.register(async (basicRoutes) => {
  await basicRoutes.register(uvbPlugin, { tenantId: 'my-tenant' });

  basicRoutes.get('/user/profile', async (request) => ({
    userId: request.uvbSession?.userId,
  }));
});

// Admin routes with strict MFA
await fastify.register(async (adminRoutes) => {
  await adminRoutes.register(uvbPlugin, { tenantId: 'my-tenant' });

  adminRoutes.addHook('onRequest', requireFactors(['totp', 'webauthn']));

  adminRoutes.delete('/admin/user/:id', async (request) => ({
    message: 'User deleted',
  }));
});
```

## TypeScript

This package includes TypeScript definitions. The `FastifyRequest` type is automatically extended:

```typescript
import { FastifyRequest, FastifyReply } from 'fastify';

fastify.get('/test', async (request: FastifyRequest, reply: FastifyReply) => {
  // TypeScript knows about uvbSession
  const userId = request.uvbSession?.userId;
  return { userId };
});
```

## License

MIT