npm package: @sp-uvb/koa
Description: Koa middleware for Universal Verification Broker (UVB)
Author: UVB Team
Latest version: 0.1.0

# @sp-uvb/koa

Koa middleware for Universal Verification Broker (UVB) authentication.

## Installation

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

## Quick Start

```typescript
import Koa from 'koa';
import Router from '@koa/router';
import { uvbMiddleware, requireFactors } from '@sp-uvb/koa';

const app = new Koa();
const router = new Router();

// Apply UVB authentication to all routes
app.use(
  uvbMiddleware({
    tenantId: 'my-tenant',
    uvbUrl: 'http://localhost:8080',
    excludePaths: ['/login', '/health', '/public'],
  })
);

// Access the authenticated user
router.get('/profile', async (ctx) => {
  const session = ctx.uvbSession;
  ctx.body = {
    userId: session?.userId,
    factors: session?.factorsVerified,
  };
});

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

app.use(router.routes());
app.listen(3000);
```

## API

### `uvbMiddleware(options)`

Main authentication middleware.

**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 Context:**

After successful authentication, `ctx.uvbSession` contains:

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

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

Middleware to require specific MFA factors for a route.

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

### `getSession(ctx: Context)`

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

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

router.get('/info', async (ctx) => {
  const session = getSession(ctx);
  if (!session) {
    ctx.status = 401;
    ctx.body = { error: 'Not authenticated' };
    return;
  }
  ctx.body = { userId: session.userId };
});
```

## Examples

### Protecting Specific Routes

```typescript
import Koa from 'koa';
import Router from '@koa/router';
import { uvbMiddleware } from '@sp-uvb/koa';

const app = new Koa();
const publicRouter = new Router();
const protectedRouter = new Router();

// Public routes
publicRouter.get('/health', async (ctx) => {
  ctx.body = { status: 'ok' };
});

// Protected routes
protectedRouter.use(
  uvbMiddleware({
    tenantId: 'my-tenant',
    uvbUrl: process.env.UVB_URL,
  })
);

protectedRouter.get('/api/data', async (ctx) => {
  // User is authenticated
  ctx.body = { data: 'protected' };
});

app.use(publicRouter.routes());
app.use(protectedRouter.routes());
app.listen(3000);
```

### Custom Error Handling

```typescript
app.use(
  uvbMiddleware({
    tenantId: 'my-tenant',
    onError: (err, ctx) => {
      console.error('UVB auth error:', err);
      ctx.status = 401;
      ctx.body = {
        error: 'Authentication failed',
        details: err.message,
      };
    },
  })
);
```

### Conditional MFA Requirements

```typescript
router.post(
  '/transfer',
  async (ctx, next) => {
    const amount = ctx.request.body.amount;

    // Require additional auth for large transfers
    if (amount > 10000) {
      await requireFactors(['totp', 'webauthn'])(ctx, next);
    } else {
      await next();
    }
  },
  async (ctx) => {
    ctx.body = { message: 'Transfer initiated' };
  }
);
```

### Combining Multiple Middlewares

```typescript
import compose from 'koa-compose';
import { uvbMiddleware, requireFactors } from '@sp-uvb/koa';

// Create a composed middleware for admin routes
const adminAuth = compose([
  uvbMiddleware({ tenantId: 'my-tenant' }),
  requireFactors(['totp', 'webauthn']),
]);

router.delete('/admin/user/:id', adminAuth, async (ctx) => {
  ctx.body = { message: 'User deleted' };
});
```

### Per-Route Authentication

```typescript
import Koa from 'koa';
import Router from '@koa/router';
import { uvbMiddleware } from '@sp-uvb/koa';

const app = new Koa();
const router = new Router();

// Create auth middleware
const auth = uvbMiddleware({ tenantId: 'my-tenant' });

// Apply selectively to routes
router.get('/public', async (ctx) => {
  ctx.body = { message: 'Public data' };
});

router.get('/private', auth, async (ctx) => {
  ctx.body = {
    message: 'Private data',
    userId: ctx.uvbSession?.userId,
  };
});

app.use(router.routes());
app.listen(3000);
```

## TypeScript

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

```typescript
import { Context } from 'koa';

router.get('/test', async (ctx: Context) => {
  // TypeScript knows about uvbSession
  const userId = ctx.uvbSession?.userId;
  ctx.body = { userId };
});
```

## License

MIT