From 024f3a06ff0038222f643a3907c02dd340a8660e Mon Sep 17 00:00:00 2001 From: Alex Holovach Date: Thu, 2 Oct 2025 18:18:02 -0500 Subject: [PATCH] better auth package --- .../otel-better-auth/.changeset/README.md | 8 + .../otel-better-auth/.changeset/config.json | 11 + .../.changeset/initial-release.md | 40 ++ packages/otel-better-auth/LICENSE | 21 + packages/otel-better-auth/README.md | 301 +++++++++++ packages/otel-better-auth/build.ts | 70 +++ packages/otel-better-auth/package.json | 58 +++ packages/otel-better-auth/src/index.test.ts | 358 +++++++++++++ packages/otel-better-auth/src/index.ts | 351 +++++++++++++ packages/otel-better-auth/tsconfig.json | 21 + pnpm-lock.yaml | 492 +++++++++++++++++- 11 files changed, 1706 insertions(+), 25 deletions(-) create mode 100644 packages/otel-better-auth/.changeset/README.md create mode 100644 packages/otel-better-auth/.changeset/config.json create mode 100644 packages/otel-better-auth/.changeset/initial-release.md create mode 100644 packages/otel-better-auth/LICENSE create mode 100644 packages/otel-better-auth/README.md create mode 100644 packages/otel-better-auth/build.ts create mode 100644 packages/otel-better-auth/package.json create mode 100644 packages/otel-better-auth/src/index.test.ts create mode 100644 packages/otel-better-auth/src/index.ts create mode 100644 packages/otel-better-auth/tsconfig.json diff --git a/packages/otel-better-auth/.changeset/README.md b/packages/otel-better-auth/.changeset/README.md new file mode 100644 index 0000000..e5b6d8d --- /dev/null +++ b/packages/otel-better-auth/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/packages/otel-better-auth/.changeset/config.json b/packages/otel-better-auth/.changeset/config.json new file mode 100644 index 0000000..fce1c26 --- /dev/null +++ b/packages/otel-better-auth/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/packages/otel-better-auth/.changeset/initial-release.md b/packages/otel-better-auth/.changeset/initial-release.md new file mode 100644 index 0000000..c7cf433 --- /dev/null +++ b/packages/otel-better-auth/.changeset/initial-release.md @@ -0,0 +1,40 @@ +--- +"@kubiks/otel-better-auth": major +--- + +Initial release of @kubiks/otel-better-auth + +🎉 First release of OpenTelemetry instrumentation for Better Auth! + +## Features + +- **Plugin-based integration**: Clean one-line setup using Better Auth's native plugin system +- **Comprehensive auth event tracing**: Automatic instrumentation for signup, signin, OAuth, password reset, and more +- **Privacy-first design**: Email capture is opt-in, passwords never captured +- **Rich telemetry**: Captures user IDs, session IDs, auth methods, and success/failure status +- **Semantic conventions**: Follows OpenTelemetry standards with meaningful span attributes +- **Zero configuration**: Works out of the box with sensible defaults + +## Supported Auth Operations + +- User signup (email/password, OAuth, magic link) +- User signin (all authentication methods) +- Password reset flows (forgot password, reset password) +- Email verification +- Session management +- OAuth callbacks (Google, GitHub, Facebook, and more) +- Sign out + +## Usage + +```typescript +import { betterAuth } from "better-auth"; +import { otelPlugin } from "@kubiks/otel-better-auth"; + +export const auth = betterAuth({ + database: db, + emailAndPassword: { enabled: true }, +}).use(otelPlugin()); +``` + +That's it! All your authentication operations are now traced with OpenTelemetry. diff --git a/packages/otel-better-auth/LICENSE b/packages/otel-better-auth/LICENSE new file mode 100644 index 0000000..55f20b0 --- /dev/null +++ b/packages/otel-better-auth/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Kubiks + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/otel-better-auth/README.md b/packages/otel-better-auth/README.md new file mode 100644 index 0000000..a2d2d79 --- /dev/null +++ b/packages/otel-better-auth/README.md @@ -0,0 +1,301 @@ +# @kubiks/otel-better-auth + +OpenTelemetry instrumentation for [Better Auth](https://better-auth.com/). Add distributed tracing to your authentication flows with a single line of code. + +## 🚀 Features + +- **🔌 Plugin-based**: Clean integration using Better Auth's native plugin system +- **📊 Comprehensive Coverage**: Traces all auth operations (signup, signin, OAuth, password reset, etc.) +- **🎯 Semantic Conventions**: Follows OpenTelemetry standards with meaningful attributes +- **🔐 Privacy-First**: Email capture is opt-in by default +- **⚡ Zero Config**: Works out of the box with sensible defaults +- **🎨 Rich Telemetry**: Captures user IDs, session IDs, auth methods, and success/failure status + +## Installation + +```bash +npm install @kubiks/otel-better-auth +# or +pnpm add @kubiks/otel-better-auth +# or +yarn add @kubiks/otel-better-auth +``` + +**Peer Dependencies:** `@opentelemetry/api` >= 1.9.0, `better-auth` >= 0.1.0 + +## Usage + +### Basic Setup (One Line!) + +Simply add the plugin to your Better Auth configuration: + +```typescript +import { betterAuth } from "better-auth"; +import { otelPlugin } from "@kubiks/otel-better-auth"; + +export const auth = betterAuth({ + database: db, + emailAndPassword: { + enabled: true, + }, +}).use(otelPlugin()); + +// That's it! All auth operations are now traced automatically ✨ +``` + +### With Custom Configuration + +```typescript +import { betterAuth } from "better-auth"; +import { otelPlugin } from "@kubiks/otel-better-auth"; + +export const auth = betterAuth({ + database: db, + emailAndPassword: { + enabled: true, + }, + socialProviders: { + google: { + clientId: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET, + }, + github: { + clientId: process.env.GITHUB_CLIENT_ID, + clientSecret: process.env.GITHUB_CLIENT_SECRET, + }, + }, +}).use( + otelPlugin({ + tracerName: "my-app-auth", // Custom tracer name + captureEmail: true, // Include email addresses in traces (default: false) + captureErrors: true, // Capture detailed error messages (default: true) + }) +); +``` + +### Full Example with OpenTelemetry Setup + +```typescript +// instrumentation.ts +import { NodeSDK } from "@opentelemetry/sdk-node"; +import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; + +const sdk = new NodeSDK({ + traceExporter: new OTLPTraceExporter({ + url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, + }), + instrumentations: [getNodeAutoInstrumentations()], +}); + +sdk.start(); + +// auth.ts +import { betterAuth } from "better-auth"; +import { otelPlugin } from "@kubiks/otel-better-auth"; +import { drizzle } from "drizzle-orm/node-postgres"; +import { Pool } from "pg"; + +const pool = new Pool({ connectionString: process.env.DATABASE_URL }); +const db = drizzle(pool); + +export const auth = betterAuth({ + database: db, + emailAndPassword: { + enabled: true, + }, + socialProviders: { + google: { + clientId: process.env.GOOGLE_CLIENT_ID!, + clientSecret: process.env.GOOGLE_CLIENT_SECRET!, + }, + }, +}).use(otelPlugin()); + +// app.ts +import "./instrumentation"; // Must be imported first! +import { auth } from "./auth"; + +// Your auth is now fully instrumented! +``` + +## What You Get + +The plugin automatically traces the following authentication events: + +### 📝 Signup Operations + +- Email/password signup +- OAuth provider signup +- Magic link signup +- Automatic user ID capture + +**Span name**: `auth.signup` or `auth.signup.email` + +### 🔑 Signin Operations + +- Email/password signin +- OAuth provider signin (Google, GitHub, Facebook, etc.) +- Magic link signin +- Session creation tracking + +**Span name**: `auth.signin`, `auth.signin.email`, or `auth.oauth.{provider}` + +### 🔒 Password Management + +- Forgot password requests +- Password reset flows +- Email verification + +**Span names**: `auth.forgot_password`, `auth.reset_password`, `auth.verify_email` + +### 🚪 Signout + +- Session termination tracking + +**Span name**: `auth.signout` + +### 🌐 OAuth Flows + +- Automatic provider detection (Google, GitHub, Facebook, etc.) +- Callback tracking +- Success/failure monitoring + +**Span name**: `auth.oauth.{provider}` + +## Span Attributes + +Each traced operation includes rich telemetry data following OpenTelemetry semantic conventions: + +| Attribute | Description | Example | +| ------------------ | ------------------------------ | ------------------------ | +| `auth.operation` | Type of auth operation | `signin`, `signup` | +| `auth.method` | Authentication method | `password`, `oauth` | +| `auth.provider` | OAuth provider (if applicable) | `google`, `github` | +| `auth.success` | Whether operation succeeded | `true`, `false` | +| `auth.error` | Error message (if failed) | `Invalid credentials` | +| `user.id` | User identifier | `user_123456` | +| `user.email` | User email (opt-in) | `user@example.com` | +| `session.id` | Session identifier | `session_789012` | + +## Configuration Options + +### `tracerName` + +- **Type**: `string` +- **Default**: `"@kubiks/otel-better-auth"` +- **Description**: Custom name for the tracer + +### `captureEmail` + +- **Type**: `boolean` +- **Default**: `false` +- **Description**: Whether to include user email addresses in span attributes. **Note**: Email addresses are PII (Personally Identifiable Information). Only enable this if your tracing backend is compliant with your privacy requirements. + +### `captureErrors` + +- **Type**: `boolean` +- **Default**: `true` +- **Description**: Whether to capture detailed error messages in spans + +### `tracer` + +- **Type**: `Tracer` +- **Default**: `undefined` +- **Description**: Custom OpenTelemetry tracer instance. If not provided, the plugin will obtain a tracer using `trace.getTracer(tracerName)`. + +## Privacy & Security + +By default, the plugin is designed with privacy in mind: + +- ✅ User emails are **NOT** captured by default +- ✅ Passwords are **NEVER** captured +- ✅ Only operation metadata and success/failure status are traced +- ⚠️ Enable `captureEmail: true` only if your infrastructure is compliant with privacy regulations (GDPR, CCPA, etc.) + +## Architecture + +The plugin leverages Better Auth's powerful plugin API to hook into: + +1. **Lifecycle Hooks**: `user.create`, `session.create` for core auth events +2. **Endpoint Hooks**: All auth endpoints (`signInEmail`, `signUpEmail`, `forgetPassword`, etc.) +3. **Request/Response Hooks**: For OAuth callback detection and tracing + +This provides comprehensive coverage of all authentication flows without any code changes to your application. + +## Visualizing Traces + +When integrated with a tracing backend (Jaeger, Zipkin, Honeycomb, Datadog, etc.), you'll see: + +- 📊 End-to-end auth flow visualization +- ⏱️ Performance metrics for each auth operation +- 🔍 Detailed attributes for debugging +- 🚨 Error tracking with stack traces +- 📈 Success/failure rates across auth methods + +## Examples + +### Next.js App Router + +```typescript +// app/api/auth/[...all]/route.ts +import { auth } from "@/lib/auth"; +import { toNextJsHandler } from "better-auth/next-js"; + +export const { GET, POST } = toNextJsHandler(auth.handler); +``` + +### Express + +```typescript +import express from "express"; +import { auth } from "./auth"; + +const app = express(); + +app.all("/api/auth/*", auth.handler); + +app.listen(3000); +``` + +### SvelteKit + +```typescript +// src/hooks.server.ts +import { auth } from "$lib/auth"; +import { svelteKitHandler } from "better-auth/svelte-kit"; + +export const handle = svelteKitHandler(auth); +``` + +## Compatibility + +- ✅ Works with all Better Auth adapters (Drizzle, Prisma, Kysely, etc.) +- ✅ Compatible with all Better Auth plugins +- ✅ Framework agnostic (Next.js, Express, SvelteKit, etc.) +- ✅ Supports all authentication methods (email/password, OAuth, magic link) + +## Best Practices + +1. **Initialize OpenTelemetry early**: Import your instrumentation file before any other code +2. **Use environment-based configuration**: Enable `captureEmail` only in development/staging +3. **Combine with other instrumentation**: Use alongside `@kubiks/otel-drizzle` for database query tracing +4. **Monitor performance**: Set up alerts for slow auth operations or high failure rates +5. **Respect privacy**: Be mindful of what PII you capture in production traces + +## Related Packages + +- [`@kubiks/otel-drizzle`](https://www.npmjs.com/package/@kubiks/otel-drizzle) - OpenTelemetry instrumentation for Drizzle ORM +- [`better-auth`](https://better-auth.com/) - The best authentication library for TypeScript + +## Contributing + +We welcome contributions! Please check out our [GitHub repository](https://github.com/kubiks-inc/otel) for issues and pull requests. + +## License + +MIT + +--- + +Made with ❤️ by [Kubiks](https://github.com/kubiks-inc) diff --git a/packages/otel-better-auth/build.ts b/packages/otel-better-auth/build.ts new file mode 100644 index 0000000..043d1f5 --- /dev/null +++ b/packages/otel-better-auth/build.ts @@ -0,0 +1,70 @@ +import { stat } from "node:fs/promises"; +import type { Plugin } from "esbuild"; +import { build } from "esbuild"; + +const MINIFY = true; +const SOURCEMAP = true; + +const MAX_SIZE = 50_000; // 50KB max for instrumentation package + +type ExternalPluginFactory = (external: string[]) => Plugin; +const externalCjsToEsmPlugin: ExternalPluginFactory = (external) => ({ + name: "external", + setup(builder): void { + const escape = (text: string): string => + `^${text.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")}$`; + const filter = new RegExp(external.map(escape).join("|")); + builder.onResolve({ filter: /.*/, namespace: "external" }, (args) => ({ + path: args.path, + external: true, + })); + builder.onResolve({ filter }, (args) => ({ + path: args.path, + namespace: "external", + })); + builder.onLoad({ filter: /.*/, namespace: "external" }, (args) => ({ + contents: `export * from ${JSON.stringify(args.path)}`, + })); + }, +}); + +/** Adds support for require, __filename, and __dirname to ESM / Node. */ +const esmNodeSupportBanner = { + js: `import { fileURLToPath } from 'url'; +import { createRequire as topLevelCreateRequire } from 'module'; +import _nPath from 'path' +const require = topLevelCreateRequire(import.meta.url); +const __filename = fileURLToPath(import.meta.url); +const __dirname = _nPath.dirname(__filename);`, +}; + +const peerDependencies = ["@opentelemetry/api", "better-auth"]; + +async function buildAll(): Promise { + await build({ + platform: "node", + format: "esm", + splitting: false, + entryPoints: ["src/index.ts"], + outdir: "dist", + bundle: true, + minify: MINIFY, + sourcemap: SOURCEMAP, + banner: esmNodeSupportBanner, + external: peerDependencies, + plugins: [externalCjsToEsmPlugin(peerDependencies)], + }); + + // Check max size. + const outputFile = "dist/index.js"; + const s = await stat(outputFile); + if (s.size > MAX_SIZE) { + // eslint-disable-next-line no-console + console.error( + `${outputFile}: the size of ${s.size} is over the maximum allowed size of ${MAX_SIZE}`, + ); + process.exit(1); + } +} + +void buildAll(); diff --git a/packages/otel-better-auth/package.json b/packages/otel-better-auth/package.json new file mode 100644 index 0000000..5356183 --- /dev/null +++ b/packages/otel-better-auth/package.json @@ -0,0 +1,58 @@ +{ + "name": "@kubiks/otel-better-auth", + "version": "1.0.0", + "private": false, + "publishConfig": { + "access": "public" + }, + "description": "OpenTelemetry instrumentation for Better Auth - Add distributed tracing to your authentication flows with a single line of code", + "author": "Kubiks", + "license": "MIT", + "repository": "kubiks-inc/otel", + "sideEffects": false, + "type": "module", + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/types/index.d.ts", + "files": [ + "dist", + "LICENSE", + "README.md" + ], + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "scripts": { + "build": "pnpm clean && pnpm build-only && pnpm build-types", + "build-only": "pnpm tsx build.ts", + "build-types": "tsc --skipLibCheck --noEmit false --declaration --emitDeclarationOnly --stripInternal --declarationDir dist/types src/index.ts", + "clean": "rimraf dist", + "prepublishOnly": "pnpm build", + "type-check": "tsc --noEmit", + "unit-test": "vitest --run", + "unit-test-watch": "vitest" + }, + "dependencies": {}, + "devDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/sdk-trace-base": "^2.1.0", + "@opentelemetry/sdk-trace-node": "^1.28.0", + "@types/node": "18.15.11", + "better-auth": "^1.0.0", + "esbuild": "^0.19.4", + "rimraf": "3.0.2", + "tsx": "^4.6.2", + "typescript": "^5", + "vitest": "0.33.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <2.0.0", + "better-auth": ">=0.1.0" + } +} diff --git a/packages/otel-better-auth/src/index.test.ts b/packages/otel-better-auth/src/index.test.ts new file mode 100644 index 0000000..6adec7e --- /dev/null +++ b/packages/otel-better-auth/src/index.test.ts @@ -0,0 +1,358 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import { trace, SpanStatusCode } from "@opentelemetry/api"; +import { InMemorySpanExporter } from "@opentelemetry/sdk-trace-base"; +import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node"; +import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base"; +import { + otelPlugin, + SEMATTRS_AUTH_OPERATION, + SEMATTRS_AUTH_METHOD, + SEMATTRS_AUTH_PROVIDER, + SEMATTRS_USER_ID, + SEMATTRS_USER_EMAIL, + SEMATTRS_AUTH_SUCCESS, +} from "./index.js"; + +describe("otel-better-auth", () => { + let exporter: InMemorySpanExporter; + let provider: NodeTracerProvider; + + beforeEach(() => { + exporter = new InMemorySpanExporter(); + provider = new NodeTracerProvider(); + provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); + provider.register(); + }); + + afterEach(() => { + exporter.reset(); + provider.shutdown(); + }); + + describe("otelPlugin", () => { + it("should create a plugin with correct id", () => { + const plugin = otelPlugin(); + expect(plugin.id).toBe("otel"); + }); + + it("should have before and after hooks", () => { + const plugin = otelPlugin(); + expect(plugin.hooks?.before).toBeDefined(); + expect(plugin.hooks?.after).toBeDefined(); + expect(Array.isArray(plugin.hooks?.before)).toBe(true); + expect(Array.isArray(plugin.hooks?.after)).toBe(true); + }); + + it("should have multiple before hooks for different endpoints", () => { + const plugin = otelPlugin(); + expect(plugin.hooks?.before?.length).toBeGreaterThan(3); + }); + + it("should have after hook for span finalization", () => { + const plugin = otelPlugin(); + expect(plugin.hooks?.after?.length).toBe(1); + }); + }); + + describe("Hook matchers", () => { + it("should match signup endpoints", () => { + const plugin = otelPlugin(); + const signupHook = plugin.hooks?.before?.find(h => { + return h.matcher({ path: "/sign-up/email", request: {} } as any); + }); + expect(signupHook).toBeDefined(); + }); + + it("should match signin endpoints", () => { + const plugin = otelPlugin(); + const signinHook = plugin.hooks?.before?.find(h => { + return h.matcher({ path: "/sign-in/email", request: {} } as any); + }); + expect(signinHook).toBeDefined(); + }); + + it("should match forgot password endpoints", () => { + const plugin = otelPlugin(); + const forgotHook = plugin.hooks?.before?.find(h => { + return h.matcher({ path: "/forget-password", request: {} } as any); + }); + expect(forgotHook).toBeDefined(); + }); + + it("should match OAuth callback endpoints", () => { + const plugin = otelPlugin(); + const oauthHook = plugin.hooks?.before?.find(h => { + return h.matcher({ path: "/callback/google", request: {} } as any); + }); + expect(oauthHook).toBeDefined(); + }); + }); + + describe("Configuration", () => { + it("should use custom tracer name", () => { + const customName = "my-custom-auth-tracer"; + const plugin = otelPlugin({ tracerName: customName }); + expect(plugin.id).toBe("otel"); + }); + + it("should accept custom tracer instance", () => { + const customTracer = trace.getTracer("custom-tracer"); + const plugin = otelPlugin({ tracer: customTracer }); + expect(plugin.id).toBe("otel"); + }); + + it("should respect captureEmail setting", () => { + const plugin = otelPlugin({ captureEmail: true }); + expect(plugin.id).toBe("otel"); + }); + + it("should respect captureErrors setting", () => { + const plugin = otelPlugin({ captureErrors: false }); + expect(plugin.id).toBe("otel"); + }); + }); + + describe("Span creation", () => { + it("should create span for signup", async () => { + const plugin = otelPlugin(); + const signupHook = plugin.hooks?.before?.find(h => { + return h.matcher({ path: "/sign-up/email", request: {} } as any); + }); + + const ctx = { + path: "/sign-up/email", + body: { email: "test@example.com" }, + request: {}, + }; + + await signupHook?.handler(ctx as any); + + // Span is created but not finalized yet + expect((ctx as any).__otelSpan).toBeDefined(); + }); + + it("should capture email when enabled", async () => { + const plugin = otelPlugin({ captureEmail: true }); + const signupHook = plugin.hooks?.before?.find(h => { + return h.matcher({ path: "/sign-up/email", request: {} } as any); + }); + + const ctx = { + path: "/sign-up/email", + body: { email: "test@example.com" }, + request: {}, + }; + + await signupHook?.handler(ctx as any); + + const span = (ctx as any).__otelSpan; + expect(span).toBeDefined(); + }); + + it("should create span for OAuth callback", async () => { + const plugin = otelPlugin(); + const oauthHook = plugin.hooks?.before?.find(h => { + return h.matcher({ path: "/callback/google", request: {} } as any); + }); + + const ctx = { + path: "/callback/google?code=abc123", + request: {}, + }; + + await oauthHook?.handler(ctx as any); + + expect((ctx as any).__otelSpan).toBeDefined(); + }); + }); + + describe("Span creation and finalization", () => { + it("should attach span to context in before hook", async () => { + const plugin = otelPlugin(); + const signupHook = plugin.hooks?.before?.find(h => { + return h.matcher({ path: "/sign-up/email", request: {} } as any); + }); + + const ctx = { + path: "/sign-up/email", + body: { email: "test@example.com" }, + request: {}, + returned: { status: 200 }, + }; + + await signupHook?.handler(ctx as any); + + // Verify span was attached + expect((ctx as any).__otelSpan).toBeDefined(); + expect((ctx as any).__otelContext).toBeDefined(); + }); + + it("should cleanup span in after hook", async () => { + const plugin = otelPlugin(); + const signupHook = plugin.hooks?.before?.find(h => { + return h.matcher({ path: "/sign-up/email", request: {} } as any); + }); + + const ctx = { + path: "/sign-up/email", + body: { email: "test@example.com" }, + request: {}, + returned: { status: 200 }, + }; + + await signupHook?.handler(ctx as any); + + const afterHook = plugin.hooks?.after?.[0]; + await afterHook?.handler(ctx as any); + + // Verify span was cleaned up + expect((ctx as any).__otelSpan).toBeUndefined(); + expect((ctx as any).__otelContext).toBeUndefined(); + }); + + it("should handle error contexts", async () => { + const plugin = otelPlugin(); + const signinHook = plugin.hooks?.before?.find(h => { + return h.matcher({ path: "/sign-in/email", request: {} } as any); + }); + + const ctx = { + path: "/sign-in/email", + body: { email: "test@example.com" }, + request: {}, + error: new Error("Invalid credentials"), + returned: { status: 401 }, + }; + + await signinHook?.handler(ctx as any); + + const afterHook = plugin.hooks?.after?.[0]; + // Should not throw + expect(async () => await afterHook?.handler(ctx as any)).not.toThrow(); + }); + }); + + describe("Semantic conventions", () => { + it("should export semantic attribute constants", () => { + expect(SEMATTRS_AUTH_OPERATION).toBe("auth.operation"); + expect(SEMATTRS_AUTH_METHOD).toBe("auth.method"); + expect(SEMATTRS_AUTH_PROVIDER).toBe("auth.provider"); + expect(SEMATTRS_USER_ID).toBe("user.id"); + expect(SEMATTRS_USER_EMAIL).toBe("user.email"); + expect(SEMATTRS_AUTH_SUCCESS).toBe("auth.success"); + }); + + it("should create spans with correct operation types", async () => { + const plugin = otelPlugin({ captureEmail: true }); + const signupHook = plugin.hooks?.before?.find(h => { + return h.matcher({ path: "/sign-up/email", request: {} } as any); + }); + + const ctx = { + path: "/sign-up/email", + body: { email: "test@example.com" }, + request: {}, + }; + + await signupHook?.handler(ctx as any); + + // Span should be created and attached + expect((ctx as any).__otelSpan).toBeDefined(); + }); + + it("should support different OAuth providers", async () => { + const plugin = otelPlugin(); + const providers = ["google", "github", "facebook"]; + + for (const provider of providers) { + const hook = plugin.hooks?.before?.find(h => { + return h.matcher({ path: `/callback/${provider}`, request: {} } as any); + }); + + const ctx = { + path: `/callback/${provider}?code=abc`, + request: {}, + }; + + await hook?.handler(ctx as any); + + expect((ctx as any).__otelSpan).toBeDefined(); + + const afterHook = plugin.hooks?.after?.[0]; + await afterHook?.handler(ctx as any); + } + }); + }); + + describe("Multiple operations", () => { + it("should handle multiple auth operations sequentially", async () => { + const plugin = otelPlugin(); + + // Simulate multiple auth operations + const operations = [ + { path: "/sign-up/email", matcher: "signup" }, + { path: "/sign-in/email", matcher: "signin" }, + { path: "/forget-password", matcher: "forgot" }, + ]; + + for (const op of operations) { + const hook = plugin.hooks?.before?.find(h => { + return h.matcher({ path: op.path, request: {} } as any); + }); + + expect(hook).toBeDefined(); + + const ctx = { + path: op.path, + body: { email: "test@example.com" }, + request: {}, + returned: { status: 200 }, + }; + + await hook?.handler(ctx as any); + expect((ctx as any).__otelSpan).toBeDefined(); + + await plugin.hooks?.after?.[0].handler(ctx as any); + expect((ctx as any).__otelSpan).toBeUndefined(); + } + }); + + it("should handle concurrent operations", async () => { + const plugin = otelPlugin(); + + const operations = [ + { path: "/sign-up/email" }, + { path: "/sign-in/email" }, + { path: "/sign-out" }, + ]; + + const promises = operations.map(async (op) => { + const hook = plugin.hooks?.before?.find(h => { + return h.matcher({ path: op.path, request: {} } as any); + }); + + const ctx = { + path: op.path, + body: { email: "test@example.com" }, + request: {}, + returned: { status: 200 }, + }; + + await hook?.handler(ctx as any); + await plugin.hooks?.after?.[0].handler(ctx as any); + + return ctx; + }); + + const results = await Promise.all(promises); + expect(results).toHaveLength(3); + }); + }); + + describe("Default export", () => { + it("should export otelPlugin as default", async () => { + const { default: defaultExport } = await import("./index.js"); + expect(defaultExport).toBe(otelPlugin); + }); + }); +}); diff --git a/packages/otel-better-auth/src/index.ts b/packages/otel-better-auth/src/index.ts new file mode 100644 index 0000000..5af2e0e --- /dev/null +++ b/packages/otel-better-auth/src/index.ts @@ -0,0 +1,351 @@ +import { + context, + SpanKind, + SpanStatusCode, + trace, + type Span, + type Tracer, +} from "@opentelemetry/api"; +import type { BetterAuthPlugin } from "better-auth/plugins"; + +const DEFAULT_TRACER_NAME = "@kubiks/otel-better-auth"; + +// Semantic conventions for auth attributes +export const SEMATTRS_AUTH_OPERATION = "auth.operation"; +export const SEMATTRS_AUTH_METHOD = "auth.method"; +export const SEMATTRS_AUTH_PROVIDER = "auth.provider"; +export const SEMATTRS_USER_ID = "user.id"; +export const SEMATTRS_USER_EMAIL = "user.email"; +export const SEMATTRS_SESSION_ID = "session.id"; +export const SEMATTRS_AUTH_SUCCESS = "auth.success"; +export const SEMATTRS_AUTH_ERROR = "auth.error"; + +/** + * Configuration options for Better Auth OpenTelemetry instrumentation. + */ +export interface OtelBetterAuthConfig { + /** + * Custom tracer name. Defaults to "@kubiks/otel-better-auth". + */ + tracerName?: string; + + /** + * Whether to capture user email in spans. + * Defaults to false for privacy. + */ + captureEmail?: boolean; + + /** + * Whether to capture detailed error messages in spans. + * Defaults to true. + */ + captureErrors?: boolean; + + /** + * Custom tracer instance. If not provided, will use trace.getTracer(). + */ + tracer?: Tracer; +} + +/** + * Finalizes a span with status, timing, and optional error. + */ +function finalizeSpan(span: Span, error?: unknown, success = true): void { + span.setAttribute(SEMATTRS_AUTH_SUCCESS, success); + + if (error) { + if (error instanceof Error) { + span.recordException(error); + span.setAttribute(SEMATTRS_AUTH_ERROR, error.message); + } else { + const errorMsg = String(error); + span.recordException(new Error(errorMsg)); + span.setAttribute(SEMATTRS_AUTH_ERROR, errorMsg); + } + span.setStatus({ code: SpanStatusCode.ERROR }); + } else { + span.setStatus({ code: SpanStatusCode.OK }); + } + span.end(); +} + +/** + * Creates a Better Auth plugin that adds OpenTelemetry tracing to all auth operations. + * + * This plugin automatically instruments key authentication events including: + * - User signup (password, OAuth, magic link, etc.) + * - User signin (all methods) + * - Password reset flows + * - Email verification + * - Session creation and management + * + * @param config - Optional configuration for instrumentation behavior + * @returns A Better Auth plugin that can be added via .use() + * + * @example + * ```typescript + * import { betterAuth } from "better-auth"; + * import { otelPlugin } from "@kubiks/otel-better-auth"; + * + * export const auth = betterAuth({ + * database: db, + * // ... other config + * }).use(otelPlugin()); + * + * // Or with custom configuration + * export const auth = betterAuth({ + * database: db, + * }).use(otelPlugin({ + * tracerName: "my-app-auth", + * captureEmail: true, + * captureErrors: true, + * })); + * ``` + */ +export function otelPlugin(config?: OtelBetterAuthConfig): BetterAuthPlugin { + const { + tracerName = DEFAULT_TRACER_NAME, + captureEmail = false, + captureErrors = true, + tracer: customTracer, + } = config ?? {}; + + const tracer = customTracer ?? trace.getTracer(tracerName); + + return { + id: "otel", + hooks: { + before: [ + { + matcher: (ctx) => { + // Match signup endpoints + return ( + ctx.path === "/sign-up/email" || + ctx.path === "/sign-up" || + ctx.request?.url?.includes("/sign-up") + ); + }, + handler: async (ctx) => { + const attributes: Record = { + [SEMATTRS_AUTH_OPERATION]: "signup", + [SEMATTRS_AUTH_METHOD]: "password", + }; + + if (captureEmail && (ctx.body as any)?.email) { + attributes[SEMATTRS_USER_EMAIL] = (ctx.body as any).email; + } + + const span = tracer.startSpan("auth.signup.email", { + kind: SpanKind.INTERNAL, + attributes, + }); + + const activeContext = trace.setSpan(context.active(), span); + (ctx as any).__otelSpan = span; + (ctx as any).__otelContext = activeContext; + + return ctx; + }, + }, + { + matcher: (ctx) => { + // Match signin endpoints + return ( + ctx.path === "/sign-in/email" || + ctx.path === "/sign-in" || + ctx.request?.url?.includes("/sign-in") + ); + }, + handler: async (ctx) => { + const attributes: Record = { + [SEMATTRS_AUTH_OPERATION]: "signin", + [SEMATTRS_AUTH_METHOD]: "password", + }; + + if (captureEmail && (ctx.body as any)?.email) { + attributes[SEMATTRS_USER_EMAIL] = (ctx.body as any).email; + } + + const span = tracer.startSpan("auth.signin.email", { + kind: SpanKind.INTERNAL, + attributes, + }); + + const activeContext = trace.setSpan(context.active(), span); + (ctx as any).__otelSpan = span; + (ctx as any).__otelContext = activeContext; + + return ctx; + }, + }, + { + matcher: (ctx) => { + // Match forgot password endpoints + return ( + ctx.path === "/forget-password" || + ctx.request?.url?.includes("/forget-password") + ); + }, + handler: async (ctx) => { + const attributes: Record = { + [SEMATTRS_AUTH_OPERATION]: "forgot_password", + }; + + if (captureEmail && (ctx.body as any)?.email) { + attributes[SEMATTRS_USER_EMAIL] = (ctx.body as any).email; + } + + const span = tracer.startSpan("auth.forgot_password", { + kind: SpanKind.INTERNAL, + attributes, + }); + + const activeContext = trace.setSpan(context.active(), span); + (ctx as any).__otelSpan = span; + (ctx as any).__otelContext = activeContext; + + return ctx; + }, + }, + { + matcher: (ctx) => { + // Match reset password endpoints + return ( + ctx.path === "/reset-password" || + ctx.request?.url?.includes("/reset-password") + ); + }, + handler: async (ctx) => { + const attributes: Record = { + [SEMATTRS_AUTH_OPERATION]: "reset_password", + }; + + const span = tracer.startSpan("auth.reset_password", { + kind: SpanKind.INTERNAL, + attributes, + }); + + const activeContext = trace.setSpan(context.active(), span); + (ctx as any).__otelSpan = span; + (ctx as any).__otelContext = activeContext; + + return ctx; + }, + }, + { + matcher: (ctx) => { + // Match signout endpoints + return ( + ctx.path === "/sign-out" || ctx.request?.url?.includes("/sign-out") + ); + }, + handler: async (ctx) => { + const attributes: Record = { + [SEMATTRS_AUTH_OPERATION]: "signout", + }; + + const span = tracer.startSpan("auth.signout", { + kind: SpanKind.INTERNAL, + attributes, + }); + + const activeContext = trace.setSpan(context.active(), span); + (ctx as any).__otelSpan = span; + (ctx as any).__otelContext = activeContext; + + return ctx; + }, + }, + { + matcher: (ctx) => { + // Match verify email endpoints + return ( + ctx.path === "/verify-email" || + ctx.request?.url?.includes("/verify-email") + ); + }, + handler: async (ctx) => { + const attributes: Record = { + [SEMATTRS_AUTH_OPERATION]: "verify_email", + }; + + const span = tracer.startSpan("auth.verify_email", { + kind: SpanKind.INTERNAL, + attributes, + }); + + const activeContext = trace.setSpan(context.active(), span); + (ctx as any).__otelSpan = span; + (ctx as any).__otelContext = activeContext; + + return ctx; + }, + }, + { + matcher: (ctx) => { + // Match OAuth callback endpoints + return ( + (ctx as any).path?.includes("/callback/") || + ctx.request?.url?.includes("/callback/") + ); + }, + handler: async (ctx) => { + const url = ctx.request?.url || (ctx as any).path; + const provider = url?.split("/callback/")[1]?.split("/")[0]?.split("?")[0]; + + if (provider) { + const attributes: Record = { + [SEMATTRS_AUTH_OPERATION]: "signin", + [SEMATTRS_AUTH_METHOD]: "oauth", + [SEMATTRS_AUTH_PROVIDER]: provider, + }; + + const span = tracer.startSpan(`auth.oauth.${provider}`, { + kind: SpanKind.INTERNAL, + attributes, + }); + + const activeContext = trace.setSpan(context.active(), span); + (ctx as any).__otelSpan = span; + (ctx as any).__otelContext = activeContext; + } + + return ctx; + }, + }, + ], + after: [ + { + matcher: () => true, // Match all requests + handler: async (ctx) => { + const span = (ctx as any).__otelSpan; + if (span) { + const ctxAny = ctx as any; + const success = + !ctxAny.error && + (!ctxAny.returned || + (ctxAny.returned.status >= 200 && ctxAny.returned.status < 300)); + + // Add user/session info if available + if (ctxAny.context?.session?.userId) { + span.setAttribute(SEMATTRS_USER_ID, ctxAny.context.session.userId); + } + if (ctxAny.context?.session?.sessionId) { + span.setAttribute(SEMATTRS_SESSION_ID, ctxAny.context.session.sessionId); + } + + finalizeSpan(span, ctxAny.error, success); + delete (ctx as any).__otelSpan; + delete (ctx as any).__otelContext; + } + + return ctx; + }, + }, + ], + }, + }; +} + +// Re-export for convenience +export { otelPlugin as default }; diff --git a/packages/otel-better-auth/tsconfig.json b/packages/otel-better-auth/tsconfig.json new file mode 100644 index 0000000..477220d --- /dev/null +++ b/packages/otel-better-auth/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2020", "DOM"], + "outDir": "dist", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "strict": false, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "incremental": true, + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e1bb4cf..ae90ac4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,6 +21,39 @@ importers: specifier: ^1.11.3 version: 1.11.3 + packages/otel-better-auth: + devDependencies: + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 + '@opentelemetry/sdk-trace-base': + specifier: ^2.1.0 + version: 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': + specifier: ^1.28.0 + version: 1.30.1(@opentelemetry/api@1.9.0) + '@types/node': + specifier: 18.15.11 + version: 18.15.11 + better-auth: + specifier: ^1.0.0 + version: 1.3.25 + esbuild: + specifier: ^0.19.4 + version: 0.19.11 + rimraf: + specifier: 3.0.2 + version: 3.0.2 + tsx: + specifier: ^4.6.2 + version: 4.7.0 + typescript: + specifier: ^5 + version: 5.3.3 + vitest: + specifier: 0.33.0 + version: 0.33.0(less@4.2.0)(sass@1.69.7)(stylus@0.59.0) + packages/otel-drizzle: devDependencies: '@opentelemetry/api': @@ -37,7 +70,7 @@ importers: version: 8.15.5 drizzle-orm: specifier: ^0.36.4 - version: 0.36.4(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(@types/react@18.2.46)(postgres@3.4.7)(react@18.2.0) + version: 0.36.4(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(@types/react@18.2.46)(kysely@0.28.7)(postgres@3.4.7)(react@18.2.0) esbuild: specifier: ^0.19.4 version: 0.19.11 @@ -78,6 +111,15 @@ packages: resolution: {integrity: sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==} engines: {node: '>=6.9.0'} + '@better-auth/core@1.3.25': + resolution: {integrity: sha512-+Z09RkrpufX8+skD05pAoMSNo95Cl6nwfqrcEHfXJPaiSGJNXoo4/0GNtxZAgsW89xfRdsmbVAwwEd1AlCrHZA==} + + '@better-auth/utils@0.3.0': + resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==} + + '@better-fetch/fetch@1.1.18': + resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==} + '@changesets/apply-release-plan@7.0.0': resolution: {integrity: sha512-vfi69JR416qC9hWmFGSxj7N6wA5J222XNBmezSVATPWDVPIF7gkd4d8CpbEbXmRWbVrkoli3oerGS6dcL/BGsQ==} @@ -409,6 +451,9 @@ packages: cpu: [x64] os: [win32] + '@hexagon/base64@1.1.28': + resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==} + '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -416,12 +461,23 @@ packages: '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@levischuck/tiny-cbor@0.2.11': + resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==} + '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@noble/ciphers@2.0.1': + resolution: {integrity: sha512-xHK3XHPUW8DTAobU+G0XT+/w+JLM7/8k1UFdB5xg/zTFPnFCobhftzw8wl4Lw2aq/Rvir5pxfZV5fEazmeCJ2g==} + engines: {node: '>= 20.19.0'} + + '@noble/hashes@2.0.1': + resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} + engines: {node: '>= 20.19.0'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -438,28 +494,117 @@ packages: resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} + '@opentelemetry/context-async-hooks@1.30.1': + resolution: {integrity: sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@1.30.1': + resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@2.1.0': resolution: {integrity: sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/propagator-b3@1.30.1': + resolution: {integrity: sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/propagator-jaeger@1.30.1': + resolution: {integrity: sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/resources@1.30.1': + resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/resources@2.1.0': resolution: {integrity: sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-trace-base@1.30.1': + resolution: {integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/sdk-trace-base@2.1.0': resolution: {integrity: sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-trace-node@1.30.1': + resolution: {integrity: sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.28.0': + resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} + engines: {node: '>=14'} + '@opentelemetry/semantic-conventions@1.37.0': resolution: {integrity: sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==} engines: {node: '>=14'} + '@peculiar/asn1-android@2.5.0': + resolution: {integrity: sha512-t8A83hgghWQkcneRsgGs2ebAlRe54ns88p7ouv8PW2tzF1nAW4yHcL4uZKrFpIU+uszIRzTkcCuie37gpkId0A==} + + '@peculiar/asn1-cms@2.5.0': + resolution: {integrity: sha512-p0SjJ3TuuleIvjPM4aYfvYw8Fk1Hn/zAVyPJZTtZ2eE9/MIer6/18ROxX6N/e6edVSfvuZBqhxAj3YgsmSjQ/A==} + + '@peculiar/asn1-csr@2.5.0': + resolution: {integrity: sha512-ioigvA6WSYN9h/YssMmmoIwgl3RvZlAYx4A/9jD2qaqXZwGcNlAxaw54eSx2QG1Yu7YyBC5Rku3nNoHrQ16YsQ==} + + '@peculiar/asn1-ecc@2.5.0': + resolution: {integrity: sha512-t4eYGNhXtLRxaP50h3sfO6aJebUCDGQACoeexcelL4roMFRRVgB20yBIu2LxsPh/tdW9I282gNgMOyg3ywg/mg==} + + '@peculiar/asn1-pfx@2.5.0': + resolution: {integrity: sha512-Vj0d0wxJZA+Ztqfb7W+/iu8Uasw6hhKtCdLKXLG/P3kEPIQpqGI4P4YXlROfl7gOCqFIbgsj1HzFIFwQ5s20ug==} + + '@peculiar/asn1-pkcs8@2.5.0': + resolution: {integrity: sha512-L7599HTI2SLlitlpEP8oAPaJgYssByI4eCwQq2C9eC90otFpm8MRn66PpbKviweAlhinWQ3ZjDD2KIVtx7PaVw==} + + '@peculiar/asn1-pkcs9@2.5.0': + resolution: {integrity: sha512-UgqSMBLNLR5TzEZ5ZzxR45Nk6VJrammxd60WMSkofyNzd3DQLSNycGWSK5Xg3UTYbXcDFyG8pA/7/y/ztVCa6A==} + + '@peculiar/asn1-rsa@2.5.0': + resolution: {integrity: sha512-qMZ/vweiTHy9syrkkqWFvbT3eLoedvamcUdnnvwyyUNv5FgFXA3KP8td+ATibnlZ0EANW5PYRm8E6MJzEB/72Q==} + + '@peculiar/asn1-schema@2.5.0': + resolution: {integrity: sha512-YM/nFfskFJSlHqv59ed6dZlLZqtZQwjRVJ4bBAiWV08Oc+1rSd5lDZcBEx0lGDHfSoH3UziI2pXt2UM33KerPQ==} + + '@peculiar/asn1-x509-attr@2.5.0': + resolution: {integrity: sha512-9f0hPOxiJDoG/bfNLAFven+Bd4gwz/VzrCIIWc1025LEI4BXO0U5fOCTNDPbbp2ll+UzqKsZ3g61mpBp74gk9A==} + + '@peculiar/asn1-x509@2.5.0': + resolution: {integrity: sha512-CpwtMCTJvfvYTFMuiME5IH+8qmDe3yEWzKHe7OOADbGfq7ohxeLaXwQo0q4du3qs0AII3UbLCvb9NF/6q0oTKQ==} + + '@peculiar/x509@1.14.0': + resolution: {integrity: sha512-Yc4PDxN3OrxUPiXgU63c+ZRXKGE8YKF2McTciYhUHFtHVB0KMnjeFSU0qpztGhsp4P0uKix4+J2xEpIEDu8oXg==} + + '@simplewebauthn/browser@13.2.0': + resolution: {integrity: sha512-N3fuA1AAnTo5gCStYoIoiasPccC+xPLx2YU88Dv0GeAmPQTWHETlZQq5xZ0DgUq1H9loXMWQH5qqUjcI7BHJ1A==} + + '@simplewebauthn/server@13.2.1': + resolution: {integrity: sha512-Inmfye5opZXe3HI0GaksqBnQiM7glcNySoG6DH1GgkO1Lh9dvuV4XSV9DK02DReUVX39HpcDob9nxHELjECoQw==} + engines: {node: '>=20.0.0'} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -478,9 +623,6 @@ packages: '@types/node@18.15.11': resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==} - '@types/node@20.11.0': - resolution: {integrity: sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==} - '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -569,6 +711,10 @@ packages: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} + asn1js@3.0.6: + resolution: {integrity: sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==} + engines: {node: '>=12.0.0'} + assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} @@ -579,6 +725,38 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + better-auth@1.3.25: + resolution: {integrity: sha512-prTPitTvhIbYPeO/M1QrkveT5afbkyV1OAfnSxYe3WfgTdKgRiEO3tMIPzwmKA54zSWw6XBYKtSD9uIuAAfvuQ==} + peerDependencies: + '@lynx-js/react': '*' + '@sveltejs/kit': '*' + next: '*' + react: '*' + react-dom: '*' + solid-js: '*' + svelte: '*' + vue: '*' + peerDependenciesMeta: + '@lynx-js/react': + optional: true + '@sveltejs/kit': + optional: true + next: + optional: true + react: + optional: true + react-dom: + optional: true + solid-js: + optional: true + svelte: + optional: true + vue: + optional: true + + better-call@1.0.19: + resolution: {integrity: sha512-sI3GcA1SCVa3H+CDHl8W8qzhlrckwXOTKhqq3OOPXjgn5aTOMIqGY34zLY/pHA6tRRMjTUC3lz5Mi7EbDA24Kw==} + better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} @@ -722,6 +900,9 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} @@ -1152,6 +1333,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jose@6.1.0: + resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1176,6 +1360,10 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + kysely@0.28.7: + resolution: {integrity: sha512-u/cAuTL4DRIiO2/g4vNGRgklEKNIj5Q3CG7RoUB5DV5SfEC2hMvPxKi0GWPmnzwL2ryIeud2VTcEEmqzTzEPNw==} + engines: {node: '>=20.0.0'} + less@4.2.0: resolution: {integrity: sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==} engines: {node: '>=6'} @@ -1276,6 +1464,10 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanostores@1.0.1: + resolution: {integrity: sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw==} + engines: {node: ^20.0.0 || >=22.0.0} + needle@3.3.1: resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==} engines: {node: '>= 4.4.x'} @@ -1456,6 +1648,13 @@ packages: pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + pvtsutils@1.3.6: + resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} + + pvutils@1.1.3: + resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==} + engines: {node: '>=6.0.0'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1490,6 +1689,9 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -1529,6 +1731,9 @@ packages: engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true + rou3@0.5.1: + resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -1565,6 +1770,9 @@ packages: set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + set-function-length@1.1.1: resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} engines: {node: '>= 0.4'} @@ -1713,14 +1921,21 @@ packages: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} - tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} tsx@4.7.0: resolution: {integrity: sha512-I+t79RYPlEYlHn9a+KzwrvEwhJg35h/1zHsLC2JXvhC2mdynMv6Zxzvhv5EMV6VF5qJlLlkSnMVvdZV3PSIGcg==} engines: {node: '>=18.0.0'} hasBin: true + tsyringe@4.10.0: + resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==} + engines: {node: '>= 6.0.0'} + tty-table@4.2.3: resolution: {integrity: sha512-Fs15mu0vGzCrj8fmJNP7Ynxt5J7praPXqFN0leZeZBXJwkMxv9cb2D454k1ltrtUSJbZ4yH4e0CynsHLxmUfFA==} engines: {node: '>=8.0.0'} @@ -1802,8 +2017,8 @@ packages: unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} @@ -1960,6 +2175,9 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} + zod@4.1.11: + resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==} + snapshots: '@adobe/css-tools@4.3.2': @@ -1982,6 +2200,15 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@better-auth/core@1.3.25': + dependencies: + better-call: 1.0.19 + zod: 4.1.11 + + '@better-auth/utils@0.3.0': {} + + '@better-fetch/fetch@1.1.18': {} + '@changesets/apply-release-plan@7.0.0': dependencies: '@babel/runtime': 7.23.7 @@ -2282,12 +2509,16 @@ snapshots: '@esbuild/win32-x64@0.19.11': optional: true + '@hexagon/base64@1.1.28': {} + '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 '@jridgewell/sourcemap-codec@1.4.15': {} + '@levischuck/tiny-cbor@0.2.11': {} + '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.23.7 @@ -2304,6 +2535,10 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 + '@noble/ciphers@2.0.1': {} + + '@noble/hashes@2.0.1': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2318,17 +2553,49 @@ snapshots: '@opentelemetry/api@1.9.0': {} + '@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/propagator-b3@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/propagator-jaeger@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/resources@2.1.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -2336,8 +2603,129 @@ snapshots: '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/sdk-trace-node@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) + semver: 7.5.4 + + '@opentelemetry/semantic-conventions@1.28.0': {} + '@opentelemetry/semantic-conventions@1.37.0': {} + '@peculiar/asn1-android@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-cms@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + '@peculiar/asn1-x509-attr': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-csr@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-ecc@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-pfx@2.5.0': + dependencies: + '@peculiar/asn1-cms': 2.5.0 + '@peculiar/asn1-pkcs8': 2.5.0 + '@peculiar/asn1-rsa': 2.5.0 + '@peculiar/asn1-schema': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-pkcs8@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-pkcs9@2.5.0': + dependencies: + '@peculiar/asn1-cms': 2.5.0 + '@peculiar/asn1-pfx': 2.5.0 + '@peculiar/asn1-pkcs8': 2.5.0 + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + '@peculiar/asn1-x509-attr': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-rsa@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-schema@2.5.0': + dependencies: + asn1js: 3.0.6 + pvtsutils: 1.3.6 + tslib: 2.8.1 + + '@peculiar/asn1-x509-attr@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-x509@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + asn1js: 3.0.6 + pvtsutils: 1.3.6 + tslib: 2.8.1 + + '@peculiar/x509@1.14.0': + dependencies: + '@peculiar/asn1-cms': 2.5.0 + '@peculiar/asn1-csr': 2.5.0 + '@peculiar/asn1-ecc': 2.5.0 + '@peculiar/asn1-pkcs9': 2.5.0 + '@peculiar/asn1-rsa': 2.5.0 + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + pvtsutils: 1.3.6 + reflect-metadata: 0.2.2 + tslib: 2.8.1 + tsyringe: 4.10.0 + + '@simplewebauthn/browser@13.2.0': {} + + '@simplewebauthn/server@13.2.1': + dependencies: + '@hexagon/base64': 1.1.28 + '@levischuck/tiny-cbor': 0.2.11 + '@peculiar/asn1-android': 2.5.0 + '@peculiar/asn1-ecc': 2.5.0 + '@peculiar/asn1-rsa': 2.5.0 + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + '@peculiar/x509': 1.14.0 + '@sinclair/typebox@0.27.8': {} '@types/chai-subset@1.3.5': @@ -2352,15 +2740,11 @@ snapshots: '@types/node@18.15.11': {} - '@types/node@20.11.0': - dependencies: - undici-types: 5.26.5 - '@types/normalize-package-data@2.4.4': {} '@types/pg@8.15.5': dependencies: - '@types/node': 20.11.0 + '@types/node': 18.15.11 pg-protocol: 1.10.3 pg-types: 2.2.0 @@ -2461,12 +2845,42 @@ snapshots: arrify@1.0.1: {} + asn1js@3.0.6: + dependencies: + pvtsutils: 1.3.6 + pvutils: 1.1.3 + tslib: 2.8.1 + assertion-error@1.1.0: {} available-typed-arrays@1.0.5: {} balanced-match@1.0.2: {} + better-auth@1.3.25: + dependencies: + '@better-auth/core': 1.3.25 + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.18 + '@noble/ciphers': 2.0.1 + '@noble/hashes': 2.0.1 + '@simplewebauthn/browser': 13.2.0 + '@simplewebauthn/server': 13.2.1 + better-call: 1.0.19 + defu: 6.1.4 + jose: 6.1.0 + kysely: 0.28.7 + nanostores: 1.0.1 + zod: 4.1.11 + + better-call@1.0.19: + dependencies: + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.18 + rou3: 0.5.1 + set-cookie-parser: 2.7.1 + uncrypto: 0.1.3 + better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 @@ -2633,6 +3047,8 @@ snapshots: has-property-descriptors: 1.0.1 object-keys: 1.1.1 + defu@6.1.4: {} + detect-indent@6.1.0: {} diff-sequences@29.6.3: {} @@ -2643,11 +3059,12 @@ snapshots: dotenv@8.6.0: {} - drizzle-orm@0.36.4(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(@types/react@18.2.46)(postgres@3.4.7)(react@18.2.0): + drizzle-orm@0.36.4(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(@types/react@18.2.46)(kysely@0.28.7)(postgres@3.4.7)(react@18.2.0): optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/pg': 8.15.5 '@types/react': 18.2.46 + kysely: 0.28.7 postgres: 3.4.7 react: 18.2.0 @@ -3055,6 +3472,8 @@ snapshots: isexe@2.0.0: {} + jose@6.1.0: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -3074,11 +3493,13 @@ snapshots: kleur@4.1.5: {} + kysely@0.28.7: {} + less@4.2.0: dependencies: copy-anything: 2.0.6 parse-node-version: 1.0.1 - tslib: 2.6.2 + tslib: 2.8.1 optionalDependencies: errno: 0.1.8 graceful-fs: 4.2.11 @@ -3191,6 +3612,8 @@ snapshots: nanoid@3.3.7: {} + nanostores@1.0.1: {} + needle@3.3.1: dependencies: iconv-lite: 0.6.3 @@ -3348,6 +3771,12 @@ snapshots: pseudomap@1.0.2: {} + pvtsutils@1.3.6: + dependencies: + tslib: 2.8.1 + + pvutils@1.1.3: {} + queue-microtask@1.2.3: {} quick-lru@4.0.1: {} @@ -3389,6 +3818,8 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 + reflect-metadata@0.2.2: {} + regenerator-runtime@0.14.1: {} regexp.prototype.flags@1.5.1: @@ -3421,6 +3852,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + rou3@0.5.1: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -3461,6 +3894,8 @@ snapshots: set-blocking@2.0.0: {} + set-cookie-parser@2.7.1: {} + set-function-length@1.1.1: dependencies: define-data-property: 1.1.1 @@ -3617,8 +4052,9 @@ snapshots: trim-newlines@3.0.1: {} - tslib@2.6.2: - optional: true + tslib@1.14.1: {} + + tslib@2.8.1: {} tsx@4.7.0: dependencies: @@ -3627,6 +4063,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tsyringe@4.10.0: + dependencies: + tslib: 1.14.1 + tty-table@4.2.3: dependencies: chalk: 4.1.2 @@ -3710,7 +4150,7 @@ snapshots: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 - undici-types@5.26.5: {} + uncrypto@0.1.3: {} universalify@0.1.2: {} @@ -3719,14 +4159,14 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - vite-node@0.33.0(@types/node@20.11.0)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0): + vite-node@0.33.0(@types/node@18.15.11)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0): dependencies: cac: 6.7.14 debug: 4.3.4 mlly: 1.4.2 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.5.1(@types/node@20.11.0)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0) + vite: 4.5.1(@types/node@18.15.11)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0) transitivePeerDependencies: - '@types/node' - less @@ -3737,13 +4177,13 @@ snapshots: - supports-color - terser - vite@4.5.1(@types/node@20.11.0)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0): + vite@4.5.1(@types/node@18.15.11)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0): dependencies: esbuild: 0.18.20 postcss: 8.4.33 rollup: 3.29.4 optionalDependencies: - '@types/node': 20.11.0 + '@types/node': 18.15.11 fsevents: 2.3.3 less: 4.2.0 sass: 1.69.7 @@ -3753,7 +4193,7 @@ snapshots: dependencies: '@types/chai': 4.3.11 '@types/chai-subset': 1.3.5 - '@types/node': 20.11.0 + '@types/node': 18.15.11 '@vitest/expect': 0.33.0 '@vitest/runner': 0.33.0 '@vitest/snapshot': 0.33.0 @@ -3772,8 +4212,8 @@ snapshots: strip-literal: 1.3.0 tinybench: 2.5.1 tinypool: 0.6.0 - vite: 4.5.1(@types/node@20.11.0)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0) - vite-node: 0.33.0(@types/node@20.11.0)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0) + vite: 4.5.1(@types/node@18.15.11)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0) + vite-node: 0.33.0(@types/node@18.15.11)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -3885,3 +4325,5 @@ snapshots: yocto-queue@0.1.0: {} yocto-queue@1.0.0: {} + + zod@4.1.11: {}