@lastshotlabs/bunshot 0.0.16 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +322 -16
  2. package/dist/adapters/memoryAuth.d.ts +3 -0
  3. package/dist/adapters/memoryAuth.js +48 -2
  4. package/dist/adapters/mongoAuth.js +39 -1
  5. package/dist/adapters/sqliteAuth.d.ts +3 -0
  6. package/dist/adapters/sqliteAuth.js +53 -0
  7. package/dist/app.d.ts +45 -2
  8. package/dist/app.js +79 -4
  9. package/dist/index.d.ts +14 -7
  10. package/dist/index.js +8 -4
  11. package/dist/lib/appConfig.d.ts +35 -0
  12. package/dist/lib/appConfig.js +10 -0
  13. package/dist/lib/authAdapter.d.ts +24 -0
  14. package/dist/lib/authRateLimit.d.ts +2 -0
  15. package/dist/lib/authRateLimit.js +4 -0
  16. package/dist/lib/clientIp.d.ts +14 -0
  17. package/dist/lib/clientIp.js +52 -0
  18. package/dist/lib/constants.d.ts +2 -0
  19. package/dist/lib/constants.js +2 -0
  20. package/dist/lib/crypto.d.ts +11 -0
  21. package/dist/lib/crypto.js +22 -0
  22. package/dist/lib/emailVerification.d.ts +4 -0
  23. package/dist/lib/emailVerification.js +20 -12
  24. package/dist/lib/jwt.js +17 -4
  25. package/dist/lib/mfaChallenge.d.ts +23 -1
  26. package/dist/lib/mfaChallenge.js +151 -42
  27. package/dist/lib/oauth.d.ts +14 -1
  28. package/dist/lib/oauth.js +19 -1
  29. package/dist/lib/oauthCode.d.ts +15 -0
  30. package/dist/lib/oauthCode.js +90 -0
  31. package/dist/lib/resetPassword.js +12 -16
  32. package/dist/lib/session.js +6 -4
  33. package/dist/lib/ws.js +5 -1
  34. package/dist/lib/zodToMongoose.d.ts +2 -2
  35. package/dist/lib/zodToMongoose.js +7 -3
  36. package/dist/middleware/bearerAuth.js +4 -3
  37. package/dist/middleware/botProtection.js +2 -2
  38. package/dist/middleware/cacheResponse.d.ts +1 -0
  39. package/dist/middleware/cacheResponse.js +14 -2
  40. package/dist/middleware/cors.d.ts +2 -0
  41. package/dist/middleware/cors.js +22 -8
  42. package/dist/middleware/csrf.d.ts +18 -0
  43. package/dist/middleware/csrf.js +115 -0
  44. package/dist/middleware/rateLimit.js +2 -3
  45. package/dist/models/AuthUser.d.ts +9 -0
  46. package/dist/models/AuthUser.js +9 -0
  47. package/dist/routes/auth.js +21 -9
  48. package/dist/routes/mfa.d.ts +5 -1
  49. package/dist/routes/mfa.js +221 -14
  50. package/dist/routes/oauth.js +274 -10
  51. package/dist/schemas/auth.d.ts +2 -0
  52. package/dist/schemas/auth.js +22 -1
  53. package/dist/server.d.ts +6 -0
  54. package/dist/server.js +10 -3
  55. package/dist/services/auth.d.ts +1 -0
  56. package/dist/services/auth.js +21 -5
  57. package/dist/services/mfa.d.ts +47 -0
  58. package/dist/services/mfa.js +276 -9
  59. package/dist/ws/index.js +3 -2
  60. package/docs/sections/auth-flow/full.md +180 -2
  61. package/docs/sections/configuration/full.md +20 -0
  62. package/docs/sections/configuration/overview.md +1 -1
  63. package/docs/sections/configuration-example/full.md +19 -1
  64. package/docs/sections/exports/full.md +11 -2
  65. package/docs/sections/multi-tenancy/full.md +5 -1
  66. package/docs/sections/oauth/full.md +80 -10
  67. package/docs/sections/oauth/overview.md +2 -2
  68. package/docs/sections/peer-dependencies/full.md +6 -2
  69. package/docs/sections/response-caching/full.md +3 -1
  70. package/docs/sections/websocket/full.md +4 -3
  71. package/docs/sections/websocket/overview.md +1 -1
  72. package/package.json +16 -4
@@ -27,7 +27,8 @@ import {
27
27
  createVerificationToken, getVerificationToken, deleteVerificationToken, // email verification tokens
28
28
  createResetToken, consumeResetToken, setPasswordResetStore, // password reset tokens
29
29
  createMfaChallenge, consumeMfaChallenge, replaceMfaChallengeOtp, setMfaChallengeStore, // MFA challenge tokens
30
- bustAuthLimit, trackAttempt, isLimited, // auth rate limiting — use in custom routes or admin unlocks
30
+ storeOAuthCode, consumeOAuthCode, setOAuthCodeStore, // OAuth one-time authorization codes
31
+ bustAuthLimit, trackAttempt, isLimited, clearMemoryRateLimitStore, // auth rate limiting — use in custom routes or admin unlocks
31
32
  buildFingerprint, // HTTP fingerprint hash (IP-independent) — use in custom bot detection logic
32
33
  sqliteAuthAdapter, setSqliteDb, startSqliteCleanup, // SQLite backend (persisted)
33
34
  memoryAuthAdapter, clearMemoryStore, // in-memory backend (ephemeral)
@@ -51,6 +52,14 @@ import {
51
52
  requireVerifiedEmail, // blocks unverified email addresses
52
53
  cacheResponse, bustCache, bustCachePattern, setCacheStore, // response caching (tenant-namespaced)
53
54
 
55
+ // Crypto utilities
56
+ timingSafeEqual, // constant-time string comparison for secrets/hashes
57
+ sha256, // SHA-256 hash helper
58
+
59
+ // IP / proxy utilities
60
+ getClientIp, // centralized IP extraction — respects security.trustProxy setting
61
+ setTrustProxy, // configure trust level (called automatically by createApp)
62
+
54
63
  // Utilities
55
64
  HttpError, log, validate, createRouter, createRoute,
56
65
  registerSchema, registerSchemas, // named OpenAPI schema registration
@@ -69,7 +78,7 @@ import {
69
78
  type DbConfig, type AppMeta, type AuthConfig, type OAuthConfig, type SecurityConfig,
70
79
  type PrimaryField, type EmailVerificationConfig, type PasswordResetConfig,
71
80
  type RefreshTokenConfig, type MfaConfig, type MfaEmailOtpConfig, type JobsConfig,
72
- type AccountDeletionConfig,
81
+ type AccountDeletionConfig, type PasswordPolicyConfig, type OAuthCodePayload,
73
82
  type SocketData, type WsConfig,
74
83
  } from "@lastshotlabs/bunshot";
75
84
 
@@ -31,6 +31,10 @@ await createServer({
31
31
 
32
32
  These paths skip tenant resolution by default: `/health`, `/docs`, `/openapi.json`, `/auth/` (auth is global — all tenants share a user pool). Add more via `exemptPaths`.
33
33
 
34
+ ### `onResolve` is required in production
35
+
36
+ When `tenancy` is configured without an `onResolve` callback, tenant IDs from headers/subdomains/paths are trusted without validation — a cross-tenant access risk. **In production (`NODE_ENV=production`), the server will refuse to start** if `onResolve` is missing. In development, a warning is logged instead.
37
+
34
38
  ### Accessing tenant in routes
35
39
 
36
40
  ```ts
@@ -59,4 +63,4 @@ await deleteTenant("acme"); // soft-delete + invalidates resolu
59
63
  When tenant context is present, rate limits and cache keys are automatically namespaced per-tenant — no code changes needed. Each tenant gets independent rate limit buckets and cache entries.
60
64
 
61
65
  - Rate limit keys: `t:${tenantId}:ip:${ip}` (instead of `ip:${ip}`)
62
- - Cache keys: `cache:${appName}:${tenantId}:${key}` (instead of `cache:${appName}:${key}`)
66
+ - Cache keys: `cache:${appName}:${tenantId}:${key}` (instead of `cache:${appName}:${key}`)
@@ -1,6 +1,6 @@
1
1
  ## Social Login (OAuth)
2
2
 
3
- Pass `auth.oauth.providers` to `createServer` to enable Google and/or Apple sign-in. Routes are mounted automatically for each configured provider.
3
+ Pass `auth.oauth.providers` to `createServer` to enable Google, Apple, Microsoft, and/or GitHub sign-in. Routes are mounted automatically for each configured provider.
4
4
 
5
5
  ```ts
6
6
  await createServer({
@@ -22,6 +22,17 @@ await createServer({
22
22
  privateKey: process.env.APPLE_PRIVATE_KEY!, // PEM string
23
23
  redirectUri: "https://myapp.com/auth/apple/callback",
24
24
  },
25
+ microsoft: {
26
+ tenantId: process.env.MICROSOFT_TENANT_ID!, // "common", "organizations", "consumers", or tenant GUID
27
+ clientId: process.env.MICROSOFT_CLIENT_ID!,
28
+ clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
29
+ redirectUri: "https://myapp.com/auth/microsoft/callback",
30
+ },
31
+ github: {
32
+ clientId: process.env.GITHUB_CLIENT_ID!,
33
+ clientSecret: process.env.GITHUB_CLIENT_SECRET!,
34
+ redirectUri: "https://myapp.com/auth/github/callback",
35
+ },
25
36
  },
26
37
  },
27
38
  },
@@ -34,22 +45,77 @@ await createServer({
34
45
  |---|---|---|---|---|
35
46
  | Google | `GET /auth/google` | `GET /auth/google/callback` | `GET /auth/google/link` | `DELETE /auth/google/link` |
36
47
  | Apple | `GET /auth/apple` | `POST /auth/apple/callback` | `GET /auth/apple/link` | — |
48
+ | Microsoft | `GET /auth/microsoft` | `GET /auth/microsoft/callback` | `GET /auth/microsoft/link` | `DELETE /auth/microsoft/link` |
49
+ | GitHub | `GET /auth/github` | `GET /auth/github/callback` | `GET /auth/github/link` | `DELETE /auth/github/link` |
37
50
 
38
51
  > Apple sends its callback as a **POST** with form data. Your server must be publicly reachable and the redirect URI must be registered in the Apple developer console.
39
52
 
53
+ > **Microsoft `tenantId` options:** `"common"` accepts any Microsoft account (personal + work/school), `"organizations"` accepts work/school accounts only, `"consumers"` accepts personal accounts only, or pass a specific tenant GUID to restrict to a single Azure AD tenant (recommended for company SSO).
54
+
55
+ > **GitHub:** Create an OAuth App (not a GitHub App) at [github.com/settings/developers](https://github.com/settings/developers). The `user:email` scope is requested to retrieve the user's verified email address, since the primary `/user` endpoint may not return it for users with private email settings.
56
+
57
+ Additionally, a shared code exchange endpoint is always mounted:
58
+
59
+ | Endpoint | Purpose |
60
+ |---|---|
61
+ | `POST /auth/oauth/exchange` | Exchange one-time authorization code for session token |
62
+
40
63
  ### Flow
41
64
 
42
- 1. Client navigates to `GET /auth/google` (or `/auth/apple`)
65
+ 1. Client navigates to `GET /auth/google` (or `/auth/apple`, `/auth/microsoft`, `/auth/github`)
43
66
  2. Package redirects to the provider's OAuth page
44
67
  3. Provider redirects (or POSTs) back to the callback URL
45
68
  4. Package exchanges the code, fetches the user profile, and calls `authAdapter.findOrCreateByProvider`
46
- 5. A session is created, the `auth-token` cookie is set, and the user is redirected to `auth.oauth.postRedirect`
69
+ 5. A session is created and a **one-time authorization code** is generated
70
+ 6. User is redirected to `auth.oauth.postRedirect?code=<one-time-code>`
71
+ 7. Client exchanges the code for a session token via `POST /auth/oauth/exchange`
72
+
73
+ > **Security:** The JWT is never exposed in the redirect URL. The one-time code expires after 60 seconds and can only be used once, preventing token leakage via browser history, server logs, or referrer headers.
74
+
75
+ #### Code exchange
76
+
77
+ After the OAuth redirect, the client must exchange the one-time code for a session token:
78
+
79
+ ```ts
80
+ // Client-side
81
+ const res = await fetch("/auth/oauth/exchange", {
82
+ method: "POST",
83
+ headers: { "Content-Type": "application/json" },
84
+ body: JSON.stringify({ code: new URLSearchParams(location.search).get("code") }),
85
+ });
86
+ const { token, userId, email, refreshToken } = await res.json();
87
+ ```
88
+
89
+ The exchange endpoint sets session cookies automatically for browser clients. Mobile/SPA clients can use the JSON response directly. Rate limited to 20 requests per minute per IP.
90
+
91
+ | Field | Description |
92
+ |---|---|
93
+ | `token` | Session JWT |
94
+ | `userId` | Authenticated user ID |
95
+ | `email` | User email (if available) |
96
+ | `refreshToken` | Refresh token (only when `auth.refreshTokens` is configured) |
97
+
98
+ ### Redirect URL validation
99
+
100
+ Pass `auth.oauth.allowedRedirectUrls` to restrict where OAuth callbacks can redirect:
101
+
102
+ ```ts
103
+ auth: {
104
+ oauth: {
105
+ postRedirect: "/dashboard",
106
+ allowedRedirectUrls: ["https://myapp.com", "https://staging.myapp.com"],
107
+ providers: { ... },
108
+ },
109
+ }
110
+ ```
111
+
112
+ When configured, the `postRedirect` value is validated against the allowlist at startup. If omitted, any redirect URL is accepted (not recommended for production).
47
113
 
48
114
  ### User storage
49
115
 
50
116
  The default `mongoAuthAdapter` stores social users in `AuthUser` with a `providerIds` field (e.g. `["google:1234567890"]`). If no existing provider key is found, a new account is created — emails are never auto-linked. To connect a social identity to an existing credential account the user must explicitly use the link flow below.
51
117
 
52
- **Email conflict handling:** If a user attempts to sign in via Google (or Apple) and the email returned by the provider already belongs to a credential-based account, `findOrCreateByProvider` throws `HttpError(409, ...)`. The OAuth callback catches this and redirects to `auth.oauth.postRedirect?error=<message>` so the client can display a helpful prompt (e.g. "An account with this email already exists — sign in with your password, then link Google from your account settings.").
118
+ **Email conflict handling:** If a user attempts to sign in via Google (or Apple/Microsoft/GitHub) and the email returned by the provider already belongs to a credential-based account, `findOrCreateByProvider` throws `HttpError(409, ...)`. The OAuth callback catches this and redirects to `auth.oauth.postRedirect?error=<message>` so the client can display a helpful prompt (e.g. "An account with this email already exists — sign in with your password, then link Google from your account settings.").
53
119
 
54
120
  To support social login with a custom adapter, implement `findOrCreateByProvider`:
55
121
 
@@ -66,11 +132,13 @@ const myAdapter: AuthAdapter = {
66
132
 
67
133
  ### Linking a provider to an existing account
68
134
 
69
- A logged-in user can link their account to a Google or Apple identity by navigating to the link route. This is the only way to associate a social login with an existing credential account — email matching is intentionally not done automatically.
135
+ A logged-in user can link their account to a Google, Apple, Microsoft, or GitHub identity by navigating to the link route. This is the only way to associate a social login with an existing credential account — email matching is intentionally not done automatically.
70
136
 
71
137
  ```
72
- GET /auth/google/link (requires active session via cookie)
73
- GET /auth/apple/link (requires active session via cookie)
138
+ GET /auth/google/link (requires active session via cookie)
139
+ GET /auth/apple/link (requires active session via cookie)
140
+ GET /auth/microsoft/link (requires active session via cookie)
141
+ GET /auth/github/link (requires active session via cookie)
74
142
  ```
75
143
 
76
144
  The link flow:
@@ -96,10 +164,12 @@ const myAdapter: AuthAdapter = {
96
164
 
97
165
  ### Unlinking a provider
98
166
 
99
- A logged-in user can remove a linked Google identity via:
167
+ A logged-in user can remove a linked Google, Microsoft, or GitHub identity via:
100
168
 
101
169
  ```
102
- DELETE /auth/google/link (requires active session via cookie)
170
+ DELETE /auth/google/link (requires active session via cookie)
171
+ DELETE /auth/microsoft/link (requires active session via cookie)
172
+ DELETE /auth/github/link (requires active session via cookie)
103
173
  ```
104
174
 
105
175
  Returns `204 No Content` on success. All `google:*` entries are removed from the user's `providerIds`.
@@ -116,4 +186,4 @@ const myAdapter: AuthAdapter = {
116
186
  await db.update(users).set({ providerIds: filtered }).where(eq(users.id, userId));
117
187
  },
118
188
  };
119
- ```
189
+ ```
@@ -1,6 +1,6 @@
1
1
  ## Social Login (OAuth)
2
2
 
3
- Pass `auth.oauth.providers` to enable Google and/or Apple sign-in. Routes are mounted automatically for each configured provider.
3
+ Pass `auth.oauth.providers` to enable Google, Apple, Microsoft, and/or GitHub sign-in. Routes are mounted automatically for each configured provider.
4
4
 
5
5
  ```ts
6
6
  auth: {
@@ -13,4 +13,4 @@ auth: {
13
13
  }
14
14
  ```
15
15
 
16
- Auto-mounted routes per provider: initiate (`GET /auth/{provider}`), callback, link to existing account (`GET /auth/{provider}/link`), and unlink (`DELETE /auth/{provider}/link`). Supports custom adapters via `findOrCreateByProvider`, `linkProvider`, and `unlinkProvider`.
16
+ Auto-mounted routes per provider: initiate (`GET /auth/{provider}`), callback, link to existing account (`GET /auth/{provider}/link`), and unlink (`DELETE /auth/{provider}/link`). After OAuth redirect, the client exchanges a one-time authorization code via `POST /auth/oauth/exchange` to receive the session token (the JWT is never exposed in the redirect URL). Supports custom adapters via `findOrCreateByProvider`, `linkProvider`, and `unlinkProvider`. Optionally restrict redirect URLs with `allowedRedirectUrls`.
@@ -31,6 +31,9 @@ bun add bullmq
31
31
 
32
32
  # MFA / TOTP
33
33
  bun add otpauth
34
+
35
+ # MFA / WebAuthn (security keys, Touch ID, Windows Hello)
36
+ bun add @simplewebauthn/server
34
37
  ```
35
38
 
36
39
  | Package | Required version | When you need it |
@@ -38,6 +41,7 @@ bun add otpauth
38
41
  | `mongoose` | `>=9.0 <10` | `db.auth: "mongo"`, `db.sessions: "mongo"`, or `db.cache: "mongo"` |
39
42
  | `ioredis` | `>=5.0 <6` | `db.redis: true` (the default), or any store set to `"redis"` |
40
43
  | `bullmq` | `>=5.0 <6` | Workers / queues |
41
- | `otpauth` | `>=9.0 <10` | `auth.mfa` configuration |
44
+ | `otpauth` | `>=9.0 <10` | `auth.mfa` configuration (TOTP) |
45
+ | `@simplewebauthn/server` | `>=10.0.0` | `auth.mfa.webauthn` configuration |
42
46
 
43
- If you're running fully on SQLite or memory (no Redis, no MongoDB), none of the optional peers are needed.
47
+ If you're running fully on SQLite or memory (no Redis, no MongoDB), none of the optional peers are needed.
@@ -100,6 +100,8 @@ router.put("/products/:id", userAuth, async (c) => {
100
100
 
101
101
  Only 2xx responses are cached. Non-2xx responses pass through uncached. Omit `ttl` to cache indefinitely — the entry will persist until explicitly busted with `bustCache`.
102
102
 
103
+ **Header sanitization:** Security-sensitive response headers (`set-cookie`, `www-authenticate`, `authorization`, `x-csrf-token`, `proxy-authenticate`) are automatically stripped before caching to prevent session fixation or auth bypass via cached responses.
104
+
103
105
  ### Busting by pattern
104
106
 
105
107
  When cache keys include variable parts (e.g. query params), use `bustCachePattern` to invalidate an entire logical group at once. It runs against all four stores — Redis (via SCAN), Mongo (via regex), SQLite (via LIKE), and Memory (via regex) — in parallel:
@@ -112,4 +114,4 @@ import { bustCachePattern } from "@lastshotlabs/bunshot";
112
114
  await bustCachePattern(`balance:${userId}:*`);
113
115
  ```
114
116
 
115
- The `*` wildcard is translated to a Redis glob, a Mongo/Memory regex, and a SQLite LIKE pattern automatically. Like `bustCache`, it silently skips any store that isn't connected, so it's safe to call in apps that only use one store.
117
+ The `*` wildcard is translated to a Redis glob, a Mongo/Memory regex, and a SQLite LIKE pattern automatically. Like `bustCache`, it silently skips any store that isn't connected, so it's safe to call in apps that only use one store.
@@ -8,8 +8,9 @@ The `/ws` endpoint is mounted automatically by `createServer`. No extra setup ne
8
8
  |---|---|
9
9
  | Upgrade / auth | Reads `auth-token` cookie → verifies JWT → checks session → sets `ws.data.userId` |
10
10
  | `open` | Logs connection, sends `{ event: "connected", id }` |
11
- | `message` | Handles room actions (see below), echoes everything else |
11
+ | `message` | Checks message size (closes with 1009 if exceeds `maxMessageSize`), handles room actions (see below), drops non-room messages unless custom handler provided |
12
12
  | `close` | Clears `ws.data.rooms`, logs disconnection |
13
+ | `maxMessageSize` | 65 536 bytes (64 KB) — configurable via `ws.maxMessageSize` |
13
14
 
14
15
  ### Socket data (`SocketData`)
15
16
 
@@ -56,7 +57,7 @@ With no type parameter, `SocketData` defaults to `{ id, userId, rooms }` — the
56
57
 
57
58
  ### Overriding the message handler
58
59
 
59
- Pass `ws.handler` to `createServer` to replace the default echo. Room action handling always runs first — your handler only receives non-room messages:
60
+ Pass `ws.handler` to `createServer` to add custom message handling. Room action handling always runs first — your handler only receives non-room messages:
60
61
 
61
62
  ```ts
62
63
  await createServer({
@@ -97,4 +98,4 @@ await createServer({
97
98
  },
98
99
  },
99
100
  });
100
- ```
101
+ ```
@@ -1,5 +1,5 @@
1
1
  ## WebSocket
2
2
 
3
- The `/ws` endpoint is mounted automatically by `createServer`. Default behavior: cookie-JWT auth on upgrade, room action handling, and echo for other messages.
3
+ The `/ws` endpoint is mounted automatically by `createServer`. Default behavior: cookie-JWT auth on upgrade, room action handling, message size enforcement (64 KB default). Non-room messages are dropped unless a custom handler is provided.
4
4
 
5
5
  `SocketData` carries `id`, `userId`, and `rooms` per connection. Pass a type parameter to `createServer<T>` to extend with custom fields. Override `ws.handler` (open/message/close) and `ws.upgradeHandler` for custom behavior.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastshotlabs/bunshot",
3
- "version": "0.0.16",
3
+ "version": "0.0.19",
4
4
  "description": "Batteries-included Bun + Hono API framework — auth, sessions, rate limiting, WebSocket, queues, and OpenAPI docs out of the box",
5
5
  "repository": {
6
6
  "type": "git",
@@ -51,7 +51,14 @@
51
51
  "dev": "bun --watch src/index.ts",
52
52
  "start": "bun src/index.ts",
53
53
  "readme": "bun docs/build-readme.ts",
54
- "readme:npm": "bun docs/build-readme.ts npm"
54
+ "readme:npm": "bun docs/build-readme.ts npm",
55
+ "test": "bun test tests/unit tests/integration",
56
+ "test:coverage": "bun test --coverage tests/unit tests/integration",
57
+ "test:docker:up": "docker compose -f docker-compose.test.yml up -d --wait",
58
+ "test:docker:down": "docker compose -f docker-compose.test.yml down",
59
+ "test:docker": "bun test --config bunfig.docker.toml tests/docker/",
60
+ "test:all": "bun test tests/unit tests/integration && bun run test:docker",
61
+ "test:coverage:full": "bun run test:docker:up && bun test --coverage --config bunfig.ci.toml tests/unit tests/integration tests/docker; bun run test:docker:down"
55
62
  },
56
63
  "dependencies": {
57
64
  "@hono/zod-openapi": "1.2.2",
@@ -65,7 +72,8 @@
65
72
  "mongoose": ">=9.0 <10",
66
73
  "ioredis": ">=5.0 <6",
67
74
  "bullmq": ">=5.0 <6",
68
- "otpauth": ">=9.0 <10"
75
+ "otpauth": ">=9.0 <10",
76
+ "@simplewebauthn/server": ">=10.0.0"
69
77
  },
70
78
  "peerDependenciesMeta": {
71
79
  "mongoose": {
@@ -79,6 +87,9 @@
79
87
  },
80
88
  "otpauth": {
81
89
  "optional": true
90
+ },
91
+ "@simplewebauthn/server": {
92
+ "optional": true
82
93
  }
83
94
  },
84
95
  "devDependencies": {
@@ -90,7 +101,8 @@
90
101
  "otpauth": "^9.5.0",
91
102
  "tsc-alias": "^1.8.16",
92
103
  "typescript": "^5.9.3",
93
- "zod": ">=4.0"
104
+ "zod": ">=4.0",
105
+ "@simplewebauthn/server": "^13.1.1"
94
106
  },
95
107
  "publishConfig": {
96
108
  "access": "public"