{"_id":"@sp-uvb/elysia","_rev":"4-bd218cd7e6c41415611379f113585b6d","name":"@sp-uvb/elysia","dist-tags":{"latest":"0.2.1"},"versions":{"0.1.0":{"name":"@sp-uvb/elysia","version":"0.1.0","keywords":["uvb","elysia","bun","plugin","authentication","mfa","security"],"author":{"name":"UVB Team"},"license":"MIT","_id":"@sp-uvb/elysia@0.1.0","maintainers":[{"name":"brycejohnson-sparkz","email":"bryce.johnson@sparkz.systems"}],"dist":{"shasum":"37d23cd590494c24cda75f37eb7989cf1de9d8cc","tarball":"https://registry.npmjs.org/@sp-uvb/elysia/-/elysia-0.1.0.tgz","fileCount":9,"integrity":"sha512-yK7uBMcisUIY/omVbXNkZDDlSteX3MTg41RuWRDFu4gXIHZt8adtDk4VnkrBmA9zRx1VQS4lXz9NiYS8butU7g==","signatures":[{"sig":"MEQCIACoKF4r/7Zq0lkQvuOepiRa5B5Uo6SYx4ggQT/vUBAfAiBWCM26RTaS5z8nu/hjBCkz1qaHiFiIz9R8ZotC6Wqqug==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":361021},"main":"dist/index.js","_from":"file:sp-uvb-elysia-0.1.0.tgz","types":"dist/index.d.ts","module":"dist/index.mjs","engines":{"bun":">=1.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","build":"tsup src/index.ts --format cjs,esm --dts --clean","typecheck":"tsc --noEmit"},"_npmUser":{"name":"brycejohnson-sparkz","email":"bryce.johnson@sparkz.systems"},"_resolved":"/private/var/folders/vw/6kq6krgd6v1ch124jcm_kr6r0000gn/T/11a916ff8ebcac241d6832916662eac1/sp-uvb-elysia-0.1.0.tgz","_integrity":"sha512-yK7uBMcisUIY/omVbXNkZDDlSteX3MTg41RuWRDFu4gXIHZt8adtDk4VnkrBmA9zRx1VQS4lXz9NiYS8butU7g==","_npmVersion":"11.11.0","description":"Production-grade Elysia plugin for Universal Verification Broker (UVB) authentication","directories":{},"_nodeVersion":"25.8.1","dependencies":{"@sp-uvb/core":"0.1.0","@sp-uvb/client":"0.1.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.1","elysia":"^1.0.0","@types/bun":"^1.0.0","typescript":"^5.3.2","@elysiajs/swagger":"^1.0.0"},"peerDependencies":{"elysia":"^1.0.0","@elysiajs/swagger":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/elysia_0.1.0_1774496361353_0.4327371300166172","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@sp-uvb/elysia","version":"0.2.0","keywords":["uvb","elysia","bun","plugin","authentication","mfa","security"],"author":{"name":"UVB Team"},"license":"MIT","_id":"@sp-uvb/elysia@0.2.0","maintainers":[{"name":"brycejohnson-sparkz","email":"bryce.johnson@sparkz.systems"}],"homepage":"https://gitlab.com/sparkz-community/security/uvb","bugs":{"url":"https://gitlab.com/sparkz-community/security/uvb/-/issues"},"dist":{"shasum":"8815333649ab4c04772d2c46898c0d2d61f8eaf9","tarball":"https://registry.npmjs.org/@sp-uvb/elysia/-/elysia-0.2.0.tgz","fileCount":8,"integrity":"sha512-z4tZjttROY2DgVLfclvHLVXozBG/j5PyuFeLtkw395OOeFxL/EuXfrkAinFSPrpsg0jdCPNTyF0a6U0HxLPGNQ==","signatures":[{"sig":"MEUCIQCKwzWiOmFyMr0ozv8kWebxPexlpPy6Aaa315iIEBTMeQIgXYdu1ltVFDjWtmZYFWlMCUMbbIfXc1TY/N6msiDOH+4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":360298},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","engines":{"bun":">=1.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"6a258849c6fe538d704afdb2a9319120ed736687","scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","build":"tsup src/index.ts --format cjs,esm --dts --clean","typecheck":"tsc --noEmit","prepublishOnly":"bun run build"},"_npmUser":{"name":"brycejohnson-sparkz","email":"bryce.johnson@sparkz.systems"},"repository":{"url":"git+https://gitlab.com/sparkz-community/security/uvb.git","type":"git","directory":"packages/elysia"},"_npmVersion":"11.11.0","description":"Production-grade Elysia plugin for Universal Verification Broker (UVB) authentication","directories":{},"_nodeVersion":"25.8.1","dependencies":{"@sp-uvb/core":"workspace:*","@sp-uvb/client":"workspace:*"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.1","elysia":"^1.0.0","@types/bun":"^1.0.0","typescript":"^5.3.2","@elysiajs/swagger":"^1.0.0"},"peerDependencies":{"elysia":"^1.0.0","@elysiajs/swagger":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/elysia_0.2.0_1780441933611_0.8578505209246914","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@sp-uvb/elysia","version":"0.2.1","keywords":["uvb","elysia","bun","plugin","authentication","mfa","security"],"author":{"name":"UVB Team"},"license":"MIT","_id":"@sp-uvb/elysia@0.2.1","maintainers":[{"name":"brycejohnson-sparkz","email":"bryce.johnson@sparkz.systems"}],"homepage":"https://gitlab.com/sparkz-community/security/uvb","bugs":{"url":"https://gitlab.com/sparkz-community/security/uvb/-/issues"},"dist":{"shasum":"5c2525c9befcf37ad2a231d4b57056719bf26898","tarball":"https://registry.npmjs.org/@sp-uvb/elysia/-/elysia-0.2.1.tgz","fileCount":8,"integrity":"sha512-/Ba+MDk+GkJ+RrHj1ZSWAJOuZ+VoSghjpsMMNyy1rkZd/QcM5s3T5ePD8lXIOvFfrh4Eb60OJ5WhaQW4ZVOgjg==","signatures":[{"sig":"MEUCIQChC53ZlYMNWVULT8AGiDiNikzUhdcWcBvPtqC61mDYEwIgY6XlTJRufdjDBQsxhcxags6ujpysb2C5qcXWK0i0kE0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":360298},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","engines":{"bun":">=1.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"3cb03becbbd852a00252cc007636aeab17d3b4a1","scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","build":"tsup src/index.ts --format cjs,esm --dts --clean","typecheck":"tsc --noEmit","prepublishOnly":"bun run build"},"_npmUser":{"name":"brycejohnson-sparkz","email":"bryce.johnson@sparkz.systems"},"repository":{"url":"git+https://gitlab.com/sparkz-community/security/uvb.git","type":"git","directory":"packages/elysia"},"_npmVersion":"11.11.0","description":"Production-grade Elysia plugin for Universal Verification Broker (UVB) authentication","directories":{},"_nodeVersion":"25.8.1","dependencies":{"@sp-uvb/core":"workspace:*","@sp-uvb/client":"workspace:*"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.1","elysia":"^1.0.0","@types/bun":"^1.0.0","typescript":"^5.3.2","@elysiajs/swagger":"^1.0.0"},"peerDependencies":{"elysia":"^1.0.0","@elysiajs/swagger":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/elysia_0.2.1_1780443504626_0.8309452320474873","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-03-26T03:39:21.288Z","modified":"2026-09-14T19:45:39.438Z","0.1.0":"2026-03-26T03:39:21.521Z","0.2.0":"2026-06-02T23:12:13.752Z","0.2.1":"2026-06-02T23:38:24.773Z"},"bugs":{"url":"https://gitlab.com/sparkz-community/security/uvb/-/issues"},"author":{"name":"UVB Team"},"license":"MIT","homepage":"https://gitlab.com/sparkz-community/security/uvb","keywords":["uvb","elysia","bun","plugin","authentication","mfa","security"],"repository":{"url":"git+https://gitlab.com/sparkz-community/security/uvb.git","type":"git","directory":"packages/elysia"},"description":"Production-grade Elysia plugin for Universal Verification Broker (UVB) authentication","maintainers":[{"email":"bryce.johnson@sparkz.systems","name":"brycejohnson-sparkz"},{"email":"dallin.b.johnson@gmail.com","name":"dallin.b.johnson"}],"readme":"# @sp-uvb/elysia\n\nProduction-grade Elysia plugin for Universal Verification Broker (UVB) authentication. Built using Elysia's Service Locator pattern for type-safe session management across your Bun application.\n\n## Features\n\n- 🔐 **Automatic Session Validation** - Validates sessions on every request\n- 🎯 **Service Locator Pattern** - Type-safe `uvbSession` access in all routes\n- 🛡️ **MFA Factor Guards** - Require specific authentication factors per route\n- 👤 **Resource Ownership** - Verify users own resources they're accessing\n- 🚀 **Zero Configuration** - Works out of the box with sensible defaults\n- 📊 **Built-in Session Routes** - Optional `/uvb/session` and `/uvb/logout` endpoints\n- 🔧 **Fully Customizable** - Override error handlers, customize behavior\n- 💪 **TypeScript First** - Complete type safety with Elysia's plugin system\n\n## Installation\n\n```bash\nbun add @sp-uvb/elysia elysia\n```\n\n## Quick Start\n\n### Basic Setup\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb } from '@sp-uvb/elysia';\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL || 'http://localhost:8080',\n      tenantId: process.env.UVB_TENANT_ID || 'tenant_123',\n    })\n  )\n  .get('/', () => 'Hello UVB!')\n  .get('/profile', ({ uvbSession }) => {\n    if (!uvbSession) {\n      return { error: 'Not authenticated' };\n    }\n    return {\n      userId: uvbSession.userId,\n      factors: uvbSession.factorsVerified,\n    };\n  })\n  .listen(3000);\n\nconsole.log(`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`);\n```\n\n### Required Authentication\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb, uvbGuard } from '@sp-uvb/elysia';\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n      required: true, // All routes require authentication\n    })\n  )\n  .get('/dashboard', ({ uvbSession }) => {\n    // uvbSession is guaranteed to exist when required: true\n    return {\n      welcome: `Hello, user ${uvbSession.userId}!`,\n      sessionId: uvbSession.sessionId,\n    };\n  })\n  .listen(3000);\n```\n\n## Configuration Options\n\n### UvbPluginOptions\n\n```typescript\ninterface UvbPluginOptions {\n  // Required: UVB server URL\n  uvbUrl: string;\n\n  // Required: Your tenant ID\n  tenantId: string;\n\n  // Optional: Cookie name for session token (default: 'uvb_session')\n  cookieName?: string;\n\n  // Optional: Header name for session token (default: 'x-uvb-session')\n  headerName?: string;\n\n  // Optional: Paths to exclude from authentication (default: [])\n  excludePaths?: string[];\n\n  // Optional: Require authentication on all routes (default: false)\n  required?: boolean;\n\n  // Optional: Custom error handler\n  onError?: (context: Context, error: Error) => Response | Promise<Response>;\n\n  // Optional: Custom unauthorized handler\n  onUnauthorized?: (context: Context) => Response | Promise<Response>;\n\n  // Optional: Custom insufficient factors handler\n  onInsufficientFactors?: (\n    context: Context,\n    required: string[],\n    verified: string[]\n  ) => Response | Promise<Response>;\n}\n```\n\n## Session Object\n\nThe `uvbSession` object is automatically attached to your route context:\n\n```typescript\ninterface UvbSession {\n  userId: string; // Unique user identifier\n  tenantId: string; // Tenant identifier\n  sessionId: string; // Session identifier\n  factorsVerified: string[]; // List of verified authentication factors\n  expiresAt: Date; // Session expiration timestamp\n  metadata?: Record<string, any>; // Optional session metadata\n}\n```\n\n## Authentication Guards\n\n### Basic Guard\n\nRequire authentication for specific routes:\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb, uvbGuard } from '@sp-uvb/elysia';\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n    })\n  )\n  .get('/public', () => 'Anyone can access')\n  .get(\n    '/protected',\n    ({ uvbSession }) => {\n      return { userId: uvbSession!.userId };\n    },\n    {\n      beforeHandle: uvbGuard(),\n    }\n  )\n  .listen(3000);\n```\n\n### MFA Factor Requirements\n\n#### Require All Factors\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb, uvbRequireAllFactors } from '@sp-uvb/elysia';\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n    })\n  )\n  .post(\n    '/transfer',\n    async ({ body, uvbSession }) => {\n      // User has verified password + TOTP + WebAuthn\n      return {\n        transfer: 'authorized',\n        amount: body.amount,\n        userId: uvbSession!.userId,\n      };\n    },\n    {\n      beforeHandle: uvbRequireAllFactors(['password', 'totp', 'webauthn']),\n    }\n  )\n  .listen(3000);\n```\n\n#### Require Any Factor\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb, uvbRequireAnyFactor } from '@sp-uvb/elysia';\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n    })\n  )\n  .get(\n    '/settings',\n    ({ uvbSession }) => {\n      return {\n        userId: uvbSession!.userId,\n        mfaEnabled: true,\n      };\n    },\n    {\n      beforeHandle: uvbRequireAnyFactor(['totp', 'webauthn', 'sms']),\n    }\n  )\n  .listen(3000);\n```\n\n### Resource Ownership Verification\n\nEnsure users can only access their own resources:\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb, uvbRequireOwnership } from '@sp-uvb/elysia';\n\n// Mock database\nconst posts = new Map([\n  ['post_1', { id: 'post_1', title: 'Hello', authorId: 'user_123' }],\n  ['post_2', { id: 'post_2', title: 'World', authorId: 'user_456' }],\n]);\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n      required: true,\n    })\n  )\n  .delete(\n    '/posts/:id',\n    ({ params }) => {\n      posts.delete(params.id);\n      return { deleted: params.id };\n    },\n    {\n      beforeHandle: uvbRequireOwnership({\n        getUserId: async ({ params }) => {\n          const post = posts.get(params.id);\n          if (!post) throw new Error('Post not found');\n          return post.authorId;\n        },\n      }),\n    }\n  )\n  .listen(3000);\n```\n\n## Session Management Routes\n\nAdd built-in session management endpoints:\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb, uvbSessionRoutes } from '@sp-uvb/elysia';\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n    })\n  )\n  .use(\n    uvbSessionRoutes({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n    })\n  )\n  // Adds:\n  // GET  /uvb/session - Get current session info\n  // POST /uvb/logout  - Revoke session and clear cookie\n  .listen(3000);\n```\n\nNow you can:\n\n```bash\n# Get session info\ncurl http://localhost:3000/uvb/session \\\n  -H \"Authorization: Bearer <session_token>\"\n\n# Logout\ncurl -X POST http://localhost:3000/uvb/logout \\\n  -H \"Authorization: Bearer <session_token>\"\n```\n\n## Advanced Examples\n\n### Conditional MFA Requirements\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb, uvbGuard, hasAllFactors } from '@sp-uvb/elysia';\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n      required: true,\n    })\n  )\n  .post('/api/transactions', async ({ body, uvbSession }) => {\n    const amount = parseFloat(body.amount);\n\n    // Require MFA for large transactions\n    if (amount > 10000 && !hasAllFactors(uvbSession, ['totp', 'webauthn'])) {\n      return new Response(\n        JSON.stringify({\n          error: 'MFA required for large transactions',\n          required: ['totp', 'webauthn'],\n          verified: uvbSession!.factorsVerified,\n        }),\n        {\n          status: 403,\n          headers: { 'Content-Type': 'application/json' },\n        }\n      );\n    }\n\n    return {\n      transaction: 'processed',\n      amount,\n      userId: uvbSession!.userId,\n    };\n  })\n  .listen(3000);\n```\n\n### Custom Error Handlers\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb } from '@sp-uvb/elysia';\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n      required: true,\n      onUnauthorized: (context) => {\n        return new Response(\n          JSON.stringify({\n            error: 'Authentication required',\n            message: 'Please log in to continue',\n            loginUrl: '/auth/login',\n          }),\n          {\n            status: 401,\n            headers: {\n              'Content-Type': 'application/json',\n              'WWW-Authenticate': 'Bearer realm=\"UVB\"',\n            },\n          }\n        );\n      },\n      onError: (context, error) => {\n        console.error('UVB error:', error);\n        return new Response(\n          JSON.stringify({\n            error: 'Internal authentication error',\n            requestId: crypto.randomUUID(),\n          }),\n          {\n            status: 500,\n            headers: { 'Content-Type': 'application/json' },\n          }\n        );\n      },\n    })\n  )\n  .listen(3000);\n```\n\n### Path Exclusions\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb } from '@sp-uvb/elysia';\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n      required: true,\n      excludePaths: ['/health', '/metrics', '/public', '/auth'],\n    })\n  )\n  .get('/health', () => ({ status: 'ok' }))\n  .get('/public/terms', () => 'Terms of Service...')\n  .get('/dashboard', ({ uvbSession }) => {\n    return { userId: uvbSession!.userId };\n  })\n  .listen(3000);\n```\n\n### Multiple Authentication Schemes\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb } from '@sp-uvb/elysia';\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n    })\n  )\n  .get('/api/data', ({ uvbSession, headers }) => {\n    // Try UVB session first\n    if (uvbSession) {\n      return {\n        data: 'authenticated via UVB',\n        userId: uvbSession.userId,\n      };\n    }\n\n    // Fallback to API key\n    const apiKey = headers['x-api-key'];\n    if (apiKey && isValidApiKey(apiKey)) {\n      return {\n        data: 'authenticated via API key',\n        apiKey: apiKey.substring(0, 8) + '...',\n      };\n    }\n\n    return new Response(JSON.stringify({ error: 'Authentication required' }), {\n      status: 401,\n      headers: { 'Content-Type': 'application/json' },\n    });\n  })\n  .listen(3000);\n\nfunction isValidApiKey(key: string): boolean {\n  // Your API key validation logic\n  return key.startsWith('sk_');\n}\n```\n\n### WebSocket with Authentication\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb } from '@sp-uvb/elysia';\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n    })\n  )\n  .ws('/ws', {\n    open(ws) {\n      const session = ws.data.uvbSession;\n      if (!session) {\n        ws.close(1008, 'Authentication required');\n        return;\n      }\n      console.log(`User ${session.userId} connected`);\n    },\n    message(ws, message) {\n      const session = ws.data.uvbSession;\n      ws.send({\n        echo: message,\n        userId: session?.userId,\n        timestamp: new Date().toISOString(),\n      });\n    },\n    close(ws) {\n      console.log('Client disconnected');\n    },\n  })\n  .listen(3000);\n```\n\n## Helper Functions\n\n### Check Individual Factor\n\n```typescript\nimport { hasFactor } from '@sp-uvb/elysia';\n\napp.get('/settings/mfa', ({ uvbSession }) => {\n  return {\n    totpEnabled: hasFactor(uvbSession, 'totp'),\n    webauthnEnabled: hasFactor(uvbSession, 'webauthn'),\n    smsEnabled: hasFactor(uvbSession, 'sms'),\n  };\n});\n```\n\n### Check All Factors\n\n```typescript\nimport { hasAllFactors } from '@sp-uvb/elysia';\n\napp.get('/admin/panel', ({ uvbSession }) => {\n  if (!hasAllFactors(uvbSession, ['password', 'totp', 'webauthn'])) {\n    return new Response('Admin requires full MFA', { status: 403 });\n  }\n  return { admin: 'panel' };\n});\n```\n\n### Check Any Factor\n\n```typescript\nimport { hasAnyFactor } from '@sp-uvb/elysia';\n\napp.get('/settings', ({ uvbSession }) => {\n  const hasMFA = hasAnyFactor(uvbSession, ['totp', 'webauthn', 'sms']);\n  return {\n    mfaEnabled: hasMFA,\n    message: hasMFA ? 'MFA is active' : 'Enable MFA for better security',\n  };\n});\n```\n\n## Real-World Patterns\n\n### E-commerce API\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb, uvbGuard, uvbRequireAllFactors, uvbRequireOwnership } from '@sp-uvb/elysia';\n\n// Mock database\nconst orders = new Map<string, { id: string; userId: string; total: number }>();\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n    })\n  )\n\n  // Public product listing\n  .get('/products', () => {\n    return [\n      { id: 'prod_1', name: 'Widget', price: 29.99 },\n      { id: 'prod_2', name: 'Gadget', price: 49.99 },\n    ];\n  })\n\n  // Cart requires authentication\n  .post(\n    '/cart',\n    ({ body, uvbSession }) => {\n      return {\n        cart: body,\n        userId: uvbSession!.userId,\n      };\n    },\n    {\n      beforeHandle: uvbGuard(),\n    }\n  )\n\n  // Checkout requires MFA\n  .post(\n    '/checkout',\n    async ({ body, uvbSession }) => {\n      const orderId = crypto.randomUUID();\n      orders.set(orderId, {\n        id: orderId,\n        userId: uvbSession!.userId,\n        total: body.total,\n      });\n      return { orderId, status: 'confirmed' };\n    },\n    {\n      beforeHandle: uvbRequireAllFactors(['password', 'totp']),\n    }\n  )\n\n  // View order (must be owner)\n  .get(\n    '/orders/:id',\n    ({ params }) => {\n      const order = orders.get(params.id);\n      if (!order) {\n        return new Response('Order not found', { status: 404 });\n      }\n      return order;\n    },\n    {\n      beforeHandle: [\n        uvbGuard(),\n        uvbRequireOwnership({\n          getUserId: async ({ params }) => {\n            const order = orders.get(params.id);\n            if (!order) throw new Error('Order not found');\n            return order.userId;\n          },\n        }),\n      ],\n    }\n  )\n\n  .listen(3000);\n```\n\n### Multi-Tenant SaaS\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb, uvbGuard } from '@sp-uvb/elysia';\n\ninterface User {\n  id: string;\n  tenantId: string;\n  role: 'admin' | 'member';\n}\n\nconst users = new Map<string, User>([\n  ['user_1', { id: 'user_1', tenantId: 'tenant_123', role: 'admin' }],\n  ['user_2', { id: 'user_2', tenantId: 'tenant_123', role: 'member' }],\n  ['user_3', { id: 'user_3', tenantId: 'tenant_456', role: 'admin' }],\n]);\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n      required: true,\n    })\n  )\n  .derive(({ uvbSession }) => {\n    const user = users.get(uvbSession!.userId);\n    return { user };\n  })\n\n  // Tenant-scoped data access\n  .get('/api/workspace/:workspaceId', ({ params, user, uvbSession }) => {\n    if (!user) {\n      return new Response('User not found', { status: 404 });\n    }\n\n    // Verify tenant access\n    if (user.tenantId !== uvbSession!.tenantId) {\n      return new Response('Forbidden', { status: 403 });\n    }\n\n    return {\n      workspaceId: params.workspaceId,\n      tenantId: user.tenantId,\n      role: user.role,\n    };\n  })\n\n  // Admin-only endpoint\n  .post('/api/workspace/:workspaceId/settings', ({ params, user }) => {\n    if (!user || user.role !== 'admin') {\n      return new Response('Admin access required', { status: 403 });\n    }\n\n    return {\n      updated: true,\n      workspaceId: params.workspaceId,\n    };\n  })\n\n  .listen(3000);\n```\n\n### Social Media API\n\n```typescript\nimport { Elysia } from 'elysia';\nimport { uvb, uvbGuard, uvbRequireOwnership, uvbRequireAllFactors } from '@sp-uvb/elysia';\n\ninterface Post {\n  id: string;\n  authorId: string;\n  content: string;\n  visibility: 'public' | 'private';\n}\n\nconst posts = new Map<string, Post>();\n\nconst app = new Elysia()\n  .use(\n    uvb({\n      uvbUrl: process.env.UVB_URL!,\n      tenantId: process.env.UVB_TENANT_ID!,\n    })\n  )\n\n  // Public feed (no auth required)\n  .get('/feed', () => {\n    return Array.from(posts.values())\n      .filter((p) => p.visibility === 'public')\n      .slice(0, 20);\n  })\n\n  // Create post (auth required)\n  .post(\n    '/posts',\n    ({ body, uvbSession }) => {\n      const postId = crypto.randomUUID();\n      const post: Post = {\n        id: postId,\n        authorId: uvbSession!.userId,\n        content: body.content,\n        visibility: body.visibility || 'public',\n      };\n      posts.set(postId, post);\n      return post;\n    },\n    {\n      beforeHandle: uvbGuard(),\n    }\n  )\n\n  // Edit post (must be author)\n  .patch(\n    '/posts/:id',\n    ({ params, body }) => {\n      const post = posts.get(params.id);\n      if (!post) {\n        return new Response('Post not found', { status: 404 });\n      }\n      post.content = body.content;\n      return post;\n    },\n    {\n      beforeHandle: uvbRequireOwnership({\n        getUserId: async ({ params }) => {\n          const post = posts.get(params.id);\n          if (!post) throw new Error('Post not found');\n          return post.authorId;\n        },\n      }),\n    }\n  )\n\n  // Delete post (must be author + MFA)\n  .delete(\n    '/posts/:id',\n    ({ params }) => {\n      posts.delete(params.id);\n      return { deleted: params.id };\n    },\n    {\n      beforeHandle: [\n        uvbRequireAllFactors(['password', 'totp']),\n        uvbRequireOwnership({\n          getUserId: async ({ params }) => {\n            const post = posts.get(params.id);\n            if (!post) throw new Error('Post not found');\n            return post.authorId;\n          },\n        }),\n      ],\n    }\n  )\n\n  .listen(3000);\n```\n\n## Testing\n\n### Unit Testing\n\n```typescript\nimport { describe, expect, it } from 'bun:test';\nimport { Elysia } from 'elysia';\nimport { uvb, uvbGuard } from '@sp-uvb/elysia';\n\ndescribe('UVB Authentication', () => {\n  it('should allow public access without auth', async () => {\n    const app = new Elysia()\n      .use(\n        uvb({\n          uvbUrl: 'http://localhost:8080',\n          tenantId: 'test_tenant',\n        })\n      )\n      .get('/public', () => 'public');\n\n    const response = await app.handle(new Request('http://localhost/public'));\n    expect(response.status).toBe(200);\n    expect(await response.text()).toBe('public');\n  });\n\n  it('should block protected routes without auth', async () => {\n    const app = new Elysia()\n      .use(\n        uvb({\n          uvbUrl: 'http://localhost:8080',\n          tenantId: 'test_tenant',\n        })\n      )\n      .get(\n        '/protected',\n        ({ uvbSession }) => {\n          return { userId: uvbSession!.userId };\n        },\n        {\n          beforeHandle: uvbGuard(),\n        }\n      );\n\n    const response = await app.handle(new Request('http://localhost/protected'));\n    expect(response.status).toBe(401);\n  });\n\n  it('should allow access with valid session', async () => {\n    const app = new Elysia()\n      .use(\n        uvb({\n          uvbUrl: 'http://localhost:8080',\n          tenantId: 'test_tenant',\n        })\n      )\n      .get(\n        '/profile',\n        ({ uvbSession }) => {\n          return { userId: uvbSession!.userId };\n        },\n        {\n          beforeHandle: uvbGuard(),\n        }\n      );\n\n    const response = await app.handle(\n      new Request('http://localhost/profile', {\n        headers: {\n          Authorization: 'Bearer valid_session_token',\n        },\n      })\n    );\n\n    expect(response.status).toBe(200);\n    const data = await response.json();\n    expect(data.userId).toBeDefined();\n  });\n});\n```\n\n### Integration Testing\n\n```typescript\nimport { describe, expect, it, beforeAll, afterAll } from 'bun:test';\nimport { Elysia } from 'elysia';\nimport { uvb, uvbRequireAllFactors } from '@sp-uvb/elysia';\n\nlet app: Elysia;\nlet sessionToken: string;\n\nbeforeAll(async () => {\n  // Start app\n  app = new Elysia()\n    .use(\n      uvb({\n        uvbUrl: process.env.UVB_URL!,\n        tenantId: process.env.UVB_TENANT_ID!,\n      })\n    )\n    .get('/data', ({ uvbSession }) => ({ data: 'test' }), {\n      beforeHandle: uvbRequireAllFactors(['password', 'totp']),\n    })\n    .listen(3001);\n\n  // Create test session\n  const authResponse = await fetch(`${process.env.UVB_URL}/api/v1/auth/login`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({\n      tenant_id: process.env.UVB_TENANT_ID,\n      username: 'test@example.com',\n      password: 'password',\n    }),\n  });\n  const authData = await authResponse.json();\n  sessionToken = authData.session_token;\n});\n\nafterAll(() => {\n  app.stop();\n});\n\ndescribe('MFA Requirements', () => {\n  it('should block access without MFA', async () => {\n    const response = await fetch('http://localhost:3001/data', {\n      headers: { Authorization: `Bearer ${sessionToken}` },\n    });\n    expect(response.status).toBe(403);\n  });\n\n  it('should allow access with MFA', async () => {\n    // Complete MFA challenge\n    await fetch(`${process.env.UVB_URL}/api/v1/mfa/verify`, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({\n        session_token: sessionToken,\n        factor: 'totp',\n        code: '123456',\n      }),\n    });\n\n    const response = await fetch('http://localhost:3001/data', {\n      headers: { Authorization: `Bearer ${sessionToken}` },\n    });\n    expect(response.status).toBe(200);\n  });\n});\n```\n\n## API Reference\n\n### Main Plugin\n\n#### `uvb(options: UvbPluginOptions)`\n\nMain authentication plugin. Adds session validation and attaches `uvbSession` to context.\n\n### Guards\n\n#### `uvbGuard(options?: UvbGuardOptions)`\n\nBasic authentication guard. Returns 401 if not authenticated.\n\n#### `uvbRequireFactors(factors: string[], options?: UvbGuardOptions)`\n\nRequire specific authentication factors. Returns 403 if factors not verified.\n\n#### `uvbRequireAllFactors(factors: string[], options?: UvbGuardOptions)`\n\nRequire all specified factors. Returns 403 if any factor missing.\n\n#### `uvbRequireAnyFactor(factors: string[], options?: UvbGuardOptions)`\n\nRequire at least one of the specified factors. Returns 403 if no factors match.\n\n#### `uvbRequireOwnership(options: UvbOwnershipOptions)`\n\nVerify user owns a resource. Returns 403 if not owner.\n\n### Utilities\n\n#### `uvbSessionRoutes(options: { uvbUrl: string; tenantId: string })`\n\nAdd session management routes:\n\n- `GET /uvb/session` - Get current session\n- `POST /uvb/logout` - Revoke session\n\n#### `hasFactor(session: UvbSession | null, factor: string): boolean`\n\nCheck if session has specific factor.\n\n#### `hasAllFactors(session: UvbSession | null, factors: string[]): boolean`\n\nCheck if session has all specified factors.\n\n#### `hasAnyFactor(session: UvbSession | null, factors: string[]): boolean`\n\nCheck if session has any of the specified factors.\n\n## Best Practices\n\n### 1. Environment Variables\n\nAlways use environment variables for sensitive configuration:\n\n```typescript\nconst app = new Elysia().use(\n  uvb({\n    uvbUrl: process.env.UVB_URL!,\n    tenantId: process.env.UVB_TENANT_ID!,\n  })\n);\n```\n\n### 2. Path Exclusions\n\nExclude health checks and public endpoints:\n\n```typescript\n.use(uvb({\n  // ...\n  excludePaths: ['/health', '/metrics', '/public'],\n}))\n```\n\n### 3. Custom Error Handlers\n\nProvide user-friendly error messages:\n\n```typescript\n.use(uvb({\n  // ...\n  onUnauthorized: () => new Response(\n    JSON.stringify({ error: 'Please log in' }),\n    { status: 401, headers: { 'Content-Type': 'application/json' } }\n  ),\n}))\n```\n\n### 4. Factor Requirements\n\nUse appropriate MFA levels for sensitive operations:\n\n```typescript\n// Low risk: basic auth\n.get('/profile', handler, { beforeHandle: uvbGuard() })\n\n// Medium risk: any MFA\n.post('/settings', handler, {\n  beforeHandle: uvbRequireAnyFactor(['totp', 'webauthn'])\n})\n\n// High risk: all factors\n.post('/delete-account', handler, {\n  beforeHandle: uvbRequireAllFactors(['password', 'totp', 'webauthn'])\n})\n```\n\n### 5. Session Expiration\n\nCheck session expiration in your application:\n\n```typescript\n.get('/data', ({ uvbSession }) => {\n  if (uvbSession && uvbSession.expiresAt < new Date()) {\n    return new Response('Session expired', { status: 401 })\n  }\n  return { data: 'value' }\n})\n```\n\n## Troubleshooting\n\n### Session Not Found\n\n**Problem**: `uvbSession` is always `null`\n\n**Solutions**:\n\n- Check UVB server is running and accessible\n- Verify `uvbUrl` and `tenantId` are correct\n- Ensure session token is being sent (cookie or header)\n- Check session hasn't expired\n\n### 401 Errors\n\n**Problem**: All routes return 401\n\n**Solutions**:\n\n- Set `required: false` for optional auth\n- Add public paths to `excludePaths`\n- Verify session token is valid\n\n### 403 Errors\n\n**Problem**: Factor requirements failing\n\n**Solutions**:\n\n- Check user has completed required MFA factors\n- Use `hasAllFactors()` to debug which factors are verified\n- Consider using `uvbRequireAnyFactor` instead of `uvbRequireAllFactors`\n\n### TypeScript Errors\n\n**Problem**: `uvbSession` type errors\n\n**Solutions**:\n\n- Ensure `@sp-uvb/elysia` is properly installed\n- Use guards to guarantee session exists\n- Check for null: `if (!uvbSession) return ...`\n\n## Migration Guide\n\n### From Custom Elysia Middleware\n\nBefore:\n\n```typescript\n.derive(async ({ cookie }) => {\n  const token = cookie.session\n  const session = await validateWithUvb(token)\n  return { session }\n})\n```\n\nAfter:\n\n```typescript\n.use(uvb({\n  uvbUrl: process.env.UVB_URL!,\n  tenantId: process.env.UVB_TENANT_ID!,\n}))\n// Access via context.uvbSession\n```\n\n### From Manual Factor Checks\n\nBefore:\n\n```typescript\n.post('/transfer', async ({ session }) => {\n  if (!session.factors.includes('totp')) {\n    throw new Error('TOTP required')\n  }\n  // ...\n})\n```\n\nAfter:\n\n```typescript\n.post('/transfer', handler, {\n  beforeHandle: uvbRequireAllFactors(['totp'])\n})\n```\n\n## License\n\nMIT\n\n## Support\n\nFor issues and questions:\n\n- GitHub: [https://github.com/uvb/uvb](https://github.com/uvb/uvb)\n- Documentation: [https://docs.uvb.dev](https://docs.uvb.dev)\n","readmeFilename":"README.md"}