{"_id":"@sp-uvb/vue","_rev":"4-45117f49520e20e6c27b5a69d4822967","name":"@sp-uvb/vue","dist-tags":{"latest":"0.2.1"},"versions":{"0.1.0":{"name":"@sp-uvb/vue","version":"0.1.0","keywords":["uvb","vue","vue3","nuxt","quasar","composables","authentication","mfa","webauthn","security"],"author":{"name":"UVB Team"},"license":"MIT","_id":"@sp-uvb/vue@0.1.0","maintainers":[{"name":"brycejohnson-sparkz","email":"bryce.johnson@sparkz.systems"}],"dist":{"shasum":"4af090ebda9e5174cef12462a5a3e6b698ecf162","tarball":"https://registry.npmjs.org/@sp-uvb/vue/-/vue-0.1.0.tgz","fileCount":9,"integrity":"sha512-pdJBsNW5G5NKkAuVZamDMfra4hX29HEExkg0xP7m6X/1xWyi+Qd0/GvO4nNhYy2WSY3ARZC87zn81oVlA/UO0w==","signatures":[{"sig":"MEUCIQDcu39xKq9DkhS7C/IEnnMkseb8L7Nk/OiaDXjU3Wc2mgIgd7Ickin8nz3KFSCxw/Ul8Pmc8ph3mB2Bi+Ko9B9U9/w=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":293273},"main":"dist/index.js","_from":"file:sp-uvb-vue-0.1.0.tgz","types":"dist/index.d.ts","module":"dist/index.mjs","engines":{"node":">=16.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"brycejohnson-sparkz","email":"bryce.johnson@sparkz.systems"},"_resolved":"/private/var/folders/vw/6kq6krgd6v1ch124jcm_kr6r0000gn/T/2ece8026725eaa12241e79411e1ebd61/sp-uvb-vue-0.1.0.tgz","_integrity":"sha512-pdJBsNW5G5NKkAuVZamDMfra4hX29HEExkg0xP7m6X/1xWyi+Qd0/GvO4nNhYy2WSY3ARZC87zn81oVlA/UO0w==","_npmVersion":"11.11.0","description":"Official Vue.js composables for Universal Verification Broker (UVB)","directories":{},"_nodeVersion":"25.8.1","dependencies":{"@sp-uvb/core":"0.1.0","@sp-uvb/client":"0.1.0"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.3.0","tsup":"^8.0.1","eslint":"^8.54.0","typescript":"^5.3.2","@types/node":"^20.10.0","@typescript-eslint/parser":"^6.13.0","@typescript-eslint/eslint-plugin":"^6.13.0"},"peerDependencies":{"vue":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/vue_0.1.0_1774496353960_0.24411845170068558","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@sp-uvb/vue","version":"0.2.0","keywords":["uvb","vue","vue3","nuxt","quasar","composables","authentication","mfa","webauthn","security"],"author":{"name":"UVB Team"},"license":"MIT","_id":"@sp-uvb/vue@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":"f3d7bcf9e3f3f682b5c40f2f56c91ed4c16105d7","tarball":"https://registry.npmjs.org/@sp-uvb/vue/-/vue-0.2.0.tgz","fileCount":8,"integrity":"sha512-Okp+zg4NqPZMvgJ0Wqjlgtq5EuPWihkR1Frw7IAgk/F8RRr23Et95SS+Q5m6nsr7GlKcM9ffKodNGVFQH4hJjw==","signatures":[{"sig":"MEYCIQCRvJacgmK5uqnmvjUxYEpX0oRyvipH1qAUiK8wD8wD9gIhANnQaEsCSl3wAcWCC8F3KlyBySpZtdThjEDxCGFu79XP","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":292547},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"6a258849c6fe538d704afdb2a9319120ed736687","scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit","prepublishOnly":"npm 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/vue"},"_npmVersion":"11.11.0","description":"Official Vue.js composables for Universal Verification Broker (UVB)","directories":{},"_nodeVersion":"25.8.1","dependencies":{"@sp-uvb/core":"workspace:*","@sp-uvb/client":"workspace:*"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.3.0","tsup":"^8.0.1","eslint":"^8.54.0","typescript":"^5.3.2","@types/node":"^20.10.0","@typescript-eslint/parser":"^6.13.0","@typescript-eslint/eslint-plugin":"^6.13.0"},"peerDependencies":{"vue":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/vue_0.2.0_1780441928402_0.8102138243581203","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@sp-uvb/vue","version":"0.2.1","keywords":["uvb","vue","vue3","nuxt","quasar","composables","authentication","mfa","webauthn","security"],"author":{"name":"UVB Team"},"license":"MIT","_id":"@sp-uvb/vue@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":"6035016a18a621b13a52dfb6621d738bd9792ca5","tarball":"https://registry.npmjs.org/@sp-uvb/vue/-/vue-0.2.1.tgz","fileCount":8,"integrity":"sha512-TwtslGoFXPiWY4mGfqi5pzCcO4Klp6Lv4pAjkIEBE7lIsxPBlr7mSQ7YbqS+C/6g7CTAIH4F5UeZdWkjvk4GXw==","signatures":[{"sig":"MEUCICl+1mqrdBEZYfL8Lk10ghoTrdzRlcOs1hTnOnvn6+NQAiEApY6OXZelqeGp98EC5NOUYQ2rM5W/+CIvs6gOQAT7U0o=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":292547},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"3cb03becbbd852a00252cc007636aeab17d3b4a1","scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit","prepublishOnly":"npm 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/vue"},"_npmVersion":"11.11.0","description":"Official Vue.js composables for Universal Verification Broker (UVB)","directories":{},"_nodeVersion":"25.8.1","dependencies":{"@sp-uvb/core":"workspace:*","@sp-uvb/client":"workspace:*"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.3.0","tsup":"^8.0.1","eslint":"^8.54.0","typescript":"^5.3.2","@types/node":"^20.10.0","@typescript-eslint/parser":"^6.13.0","@typescript-eslint/eslint-plugin":"^6.13.0"},"peerDependencies":{"vue":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/vue_0.2.1_1780443500344_0.8587059598535212","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-03-26T03:39:13.860Z","modified":"2026-09-14T19:45:43.111Z","0.1.0":"2026-03-26T03:39:14.098Z","0.2.0":"2026-06-02T23:12:08.544Z","0.2.1":"2026-06-02T23:38:20.565Z"},"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","vue","vue3","nuxt","quasar","composables","authentication","mfa","webauthn","security"],"repository":{"url":"git+https://gitlab.com/sparkz-community/security/uvb.git","type":"git","directory":"packages/vue"},"description":"Official Vue.js composables for Universal Verification Broker (UVB)","maintainers":[{"email":"bryce.johnson@sparkz.systems","name":"brycejohnson-sparkz"},{"email":"dallin.b.johnson@gmail.com","name":"dallin.b.johnson"}],"readme":"# @sp-uvb/vue\n\nOfficial Vue.js composables for Universal Verification Broker (UVB). Provides type-safe, reactive authentication for Vue 3, Nuxt 3, and Quasar applications.\n\n## Features\n\n- 🎯 **Type-Safe Composables** - Full TypeScript support with Vue 3\n- 🔄 **Reactive State** - Automatic UI updates on auth state changes\n- 💾 **Auto-Persistence** - Session tokens stored in localStorage/sessionStorage\n- ⏰ **Session Management** - Automatic expiration handling\n- 🔐 **WebAuthn Support** - Built-in helpers for biometric authentication\n- 🚀 **SSR Compatible** - Works with Nuxt 3 server-side rendering\n- 📦 **Zero Configuration** - Sensible defaults, fully customizable\n\n## Installation\n\n```bash\nnpm install @sp-uvb/vue\n# or\nyarn add @sp-uvb/vue\n# or\npnpm add @sp-uvb/vue\n```\n\n## Quick Start\n\n### Vue 3\n\n```vue\n<script setup lang=\"ts\">\nimport { useUVB } from '@sp-uvb/vue';\n\nconst { session, isAuthenticated, isLoading, user, verify, logout } = useUVB({\n  tenantId: 'your-tenant-id',\n  uvbUrl: 'http://localhost:8080',\n});\n\nconst handleLogin = async () => {\n  await verify('totp', '123456');\n};\n</script>\n\n<template>\n  <div>\n    <div v-if=\"isLoading\">Loading...</div>\n    <div v-else-if=\"isAuthenticated\">\n      <p>Welcome, {{ user?.userId }}!</p>\n      <button @click=\"logout\">Logout</button>\n    </div>\n    <div v-else>\n      <button @click=\"handleLogin\">Login</button>\n    </div>\n  </div>\n</template>\n```\n\n### Nuxt 3\n\nCreate a plugin to provide UVB globally:\n\n```ts\n// plugins/uvb.ts\nimport { useUVB } from '@sp-uvb/vue';\n\nexport default defineNuxtPlugin(() => {\n  const uvb = useUVB({\n    tenantId: useRuntimeConfig().public.uvbTenantId,\n    uvbUrl: useRuntimeConfig().public.uvbUrl,\n  });\n\n  return {\n    provide: {\n      uvb,\n    },\n  };\n});\n```\n\nUse in components:\n\n```vue\n<script setup lang=\"ts\">\nconst { $uvb } = useNuxtApp();\n\nconst handleLogin = async () => {\n  await $uvb.verify('totp', '123456');\n};\n</script>\n\n<template>\n  <div v-if=\"$uvb.isAuthenticated.value\">\n    <p>Welcome, {{ $uvb.user.value?.userId }}!</p>\n  </div>\n</template>\n```\n\n### Quasar\n\nCreate a boot file:\n\n```ts\n// boot/uvb.ts\nimport { boot } from 'quasar/wrappers';\nimport { useUVB } from '@sp-uvb/vue';\n\nexport default boot(({ app }) => {\n  const uvb = useUVB({\n    tenantId: process.env.UVB_TENANT_ID || '',\n    uvbUrl: process.env.UVB_URL || 'http://localhost:8080',\n  });\n\n  app.config.globalProperties.$uvb = uvb;\n});\n```\n\n## API Reference\n\n### `useUVB(options)`\n\nMain composable for authentication and session management.\n\n#### Options\n\n```typescript\ninterface UseUVBOptions {\n  tenantId: string; // Required: Your UVB tenant ID\n  uvbUrl?: string; // Optional: UVB server URL (default: 'http://localhost:8080')\n  apiKey?: string; // Optional: API key for server-to-server auth\n  storage?:\n    | 'localStorage' // Optional: Storage type (default: 'localStorage')\n    | 'sessionStorage'\n    | 'memory';\n  storageKey?: string; // Optional: Storage key name (default: 'uvb_session_token')\n  autoValidate?: boolean; // Optional: Auto-validate on mount (default: true)\n}\n```\n\n#### Returns\n\n```typescript\ninterface UseUVBReturn {\n  // State\n  session: Ref<UVBSession | null>;\n  isAuthenticated: Readonly<Ref<boolean>>;\n  isLoading: Ref<boolean>;\n  error: Ref<Error | null>;\n\n  // Computed\n  user: Readonly<\n    Ref<{\n      userId: string;\n      tenantId: string;\n      sessionId: string;\n      factorsVerified: string[];\n      expiresAt: Date;\n    } | null>\n  >;\n\n  // Methods\n  validateSession: (token?: string) => Promise<UVBSession | null>;\n  verify: (\n    factorType: FactorType,\n    factorValue?: string,\n    metadata?: Record<string, string>\n  ) => Promise<any>;\n  logout: () => Promise<void>;\n  refreshSession: () => Promise<UVBSession | null>;\n  hasFactors: (...factors: FactorType[]) => boolean;\n}\n```\n\n#### Usage Examples\n\n**Basic Authentication**\n\n```vue\n<script setup>\nimport { useUVB } from '@sp-uvb/vue';\n\nconst { verify, isLoading, error } = useUVB({\n  tenantId: 'my-tenant',\n});\n\nconst totpCode = ref('');\n\nconst handleVerify = async () => {\n  try {\n    const result = await verify('totp', totpCode.value);\n    if (result.success) {\n      console.log('Authenticated!');\n    }\n  } catch (err) {\n    console.error('Verification failed:', err);\n  }\n};\n</script>\n```\n\n**Check Verified Factors**\n\n```vue\n<script setup>\nimport { useUVB } from '@sp-uvb/vue';\n\nconst { hasFactors, user } = useUVB({ tenantId: 'my-tenant' });\n\n// Check if user has verified both TOTP and WebAuthn\nconst hasStrongAuth = computed(() => hasFactors('totp', 'webauthn'));\n</script>\n\n<template>\n  <div v-if=\"hasStrongAuth\">Access to sensitive data granted</div>\n</template>\n```\n\n**Session Refresh**\n\n```vue\n<script setup>\nimport { useUVB } from '@sp-uvb/vue';\nimport { onMounted } from 'vue';\n\nconst { refreshSession, session } = useUVB({ tenantId: 'my-tenant' });\n\n// Refresh session periodically\nonMounted(() => {\n  setInterval(\n    async () => {\n      await refreshSession();\n    },\n    5 * 60 * 1000\n  ); // Every 5 minutes\n});\n</script>\n```\n\n### `useUVBFactor(options)`\n\nComposable for factor enrollment and management.\n\n#### Usage\n\n```vue\n<script setup>\nimport { useUVBFactor } from '@sp-uvb/vue'\n\nconst {\n  enroll,\n  verify,\n  listFactors,\n  removeFactor,\n  enrollmentData,\n  isLoading\n} = useUVBFactor({\n  tenantId: 'my-tenant'\n})\n\n// Enroll TOTP\nconst enrollTOTP = async (userId: string) => {\n  const result = await enroll(userId, 'totp')\n  if (result.success) {\n    console.log('QR Code:', enrollmentData.value?.qrCode)\n    console.log('Secret:', enrollmentData.value?.secret)\n  }\n}\n\n// List user's enrolled factors\nconst getUserFactors = async (userId: string) => {\n  const factors = await listFactors(userId)\n  console.log('Enrolled factors:', factors)\n}\n\n// Remove a factor\nconst removeUserFactor = async (userId: string, factorId: string) => {\n  await removeFactor(userId, factorId)\n}\n</script>\n\n<template>\n  <div v-if=\"enrollmentData?.qrCode\">\n    <img :src=\"enrollmentData.qrCode\" alt=\"TOTP QR Code\" />\n    <p>Secret: {{ enrollmentData.secret }}</p>\n  </div>\n</template>\n```\n\n### `useWebAuthn()`\n\nComposable for WebAuthn/FIDO2 authentication.\n\n#### Usage\n\n```vue\n<script setup>\nimport { useWebAuthn } from '@sp-uvb/vue'\n\nconst {\n  isSupported,\n  isLoading,\n  error,\n  register,\n  authenticate\n} = useWebAuthn()\n\n// Register a new WebAuthn credential\nconst registerWebAuthn = async (challenge: string, userName: string, userId: string) => {\n  if (!isSupported.value) {\n    alert('WebAuthn not supported in this browser')\n    return\n  }\n\n  try {\n    const credential = await register(challenge, userName, userId)\n    console.log('Registered credential:', credential)\n    // Send credential to your backend for verification\n  } catch (err) {\n    console.error('Registration failed:', err)\n  }\n}\n\n// Authenticate with WebAuthn\nconst authenticateWebAuthn = async (challenge: string) => {\n  try {\n    const assertion = await authenticate(challenge)\n    console.log('Authentication assertion:', assertion)\n    // Send assertion to your backend for verification\n  } catch (err) {\n    console.error('Authentication failed:', err)\n  }\n}\n</script>\n\n<template>\n  <div>\n    <div v-if=\"!isSupported\">WebAuthn is not supported in your browser</div>\n    <button\n      v-else\n      @click=\"registerWebAuthn(challenge, 'user@example.com', 'user123')\"\n      :disabled=\"isLoading\"\n    >\n      Register Biometric\n    </button>\n  </div>\n</template>\n```\n\n## Advanced Examples\n\n### Protected Routes (Vue Router)\n\n```ts\n// router/index.ts\nimport { createRouter, createWebHistory } from 'vue-router';\nimport { useUVB } from '@sp-uvb/vue';\n\nconst router = createRouter({\n  history: createWebHistory(),\n  routes: [\n    {\n      path: '/dashboard',\n      component: () => import('@/views/Dashboard.vue'),\n      meta: { requiresAuth: true },\n    },\n  ],\n});\n\nrouter.beforeEach((to, from, next) => {\n  const { isAuthenticated } = useUVB({ tenantId: 'my-tenant' });\n\n  if (to.meta.requiresAuth && !isAuthenticated.value) {\n    next('/login');\n  } else {\n    next();\n  }\n});\n\nexport default router;\n```\n\n### Nuxt 3 Middleware\n\n```ts\n// middleware/auth.ts\nexport default defineNuxtRouteMiddleware((to, from) => {\n  const { $uvb } = useNuxtApp();\n\n  if (!$uvb.isAuthenticated.value) {\n    return navigateTo('/login');\n  }\n});\n```\n\nUse in pages:\n\n```vue\n<script setup>\ndefinePageMeta({\n  middleware: 'auth',\n});\n</script>\n```\n\n### Quasar Route Guards\n\n```ts\n// router/routes.ts\nconst routes = [\n  {\n    path: '/dashboard',\n    component: () => import('pages/Dashboard.vue'),\n    beforeEnter: (to, from, next) => {\n      const { isAuthenticated } = useUVB({ tenantId: 'my-tenant' });\n      if (!isAuthenticated.value) {\n        next('/login');\n      } else {\n        next();\n      }\n    },\n  },\n];\n```\n\n### Multi-Factor Authentication Flow\n\n```vue\n<script setup>\nimport { ref, computed } from 'vue';\nimport { useUVB, useUVBFactor } from '@sp-uvb/vue';\n\nconst { session, verify, hasFactors } = useUVB({ tenantId: 'my-tenant' });\nconst { enroll, enrollmentData } = useUVBFactor({ tenantId: 'my-tenant' });\n\nconst step = (ref < 'login') | 'totp-setup' | 'totp-verify' | ('complete' > 'login');\nconst email = ref('');\nconst password = ref('');\nconst totpCode = ref('');\nconst userId = ref('');\n\n// Step 1: Email login\nconst handleEmailLogin = async () => {\n  const result = await verify('email_otp', email.value);\n  if (result.success) {\n    userId.value = result.userId;\n    step.value = hasFactors('totp') ? 'totp-verify' : 'totp-setup';\n  }\n};\n\n// Step 2a: Set up TOTP (first time)\nconst handleTOTPSetup = async () => {\n  await enroll(userId.value, 'totp');\n  step.value = 'totp-verify';\n};\n\n// Step 2b: Verify TOTP\nconst handleTOTPVerify = async () => {\n  const result = await verify('totp', totpCode.value);\n  if (result.success) {\n    step.value = 'complete';\n  }\n};\n</script>\n\n<template>\n  <div>\n    <div v-if=\"step === 'login'\">\n      <input v-model=\"email\" placeholder=\"Email\" />\n      <button @click=\"handleEmailLogin\">Login</button>\n    </div>\n\n    <div v-else-if=\"step === 'totp-setup'\">\n      <h3>Set up Two-Factor Authentication</h3>\n      <img v-if=\"enrollmentData?.qrCode\" :src=\"enrollmentData.qrCode\" />\n      <button @click=\"handleTOTPSetup\">Continue</button>\n    </div>\n\n    <div v-else-if=\"step === 'totp-verify'\">\n      <input v-model=\"totpCode\" placeholder=\"6-digit code\" />\n      <button @click=\"handleTOTPVerify\">Verify</button>\n    </div>\n\n    <div v-else-if=\"step === 'complete'\">\n      <h3>Welcome! You're authenticated.</h3>\n    </div>\n  </div>\n</template>\n```\n\n## TypeScript Support\n\nAll composables are fully typed. Import types for better DX:\n\n```typescript\nimport type { UVBSession, FactorType, UseUVBReturn } from '@sp-uvb/vue';\n\nconst uvb: UseUVBReturn = useUVB({ tenantId: 'my-tenant' });\nconst session: Ref<UVBSession | null> = uvb.session;\n```\n\n## SSR Considerations\n\nWhen using with Nuxt or other SSR frameworks:\n\n1. Storage is automatically disabled during SSR (falls back to memory)\n2. Auto-validation only runs on client-side\n3. Use `onMounted` for client-only auth logic\n\n```vue\n<script setup>\nimport { onMounted } from 'vue';\nimport { useUVB } from '@sp-uvb/vue';\n\nconst { validateSession } = useUVB({\n  tenantId: 'my-tenant',\n  autoValidate: false, // Disable auto-validate for manual control\n});\n\nonMounted(async () => {\n  // Only validate on client-side\n  await validateSession();\n});\n</script>\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}