@lastshotlabs/bunshot 0.0.9 → 0.0.10

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.
package/README.md CHANGED
@@ -60,6 +60,48 @@ bun add @lastshotlabs/bunshot
60
60
 
61
61
  ---
62
62
 
63
+ ## Peer Dependencies
64
+
65
+ Bunshot declares the following as peer dependencies so you control their versions and avoid duplicate installs in your app.
66
+
67
+ ### Required
68
+
69
+ These must be installed in every consuming app:
70
+
71
+ ```bash
72
+ bun add hono zod
73
+ ```
74
+
75
+ | Package | Required version |
76
+ |---|---|
77
+ | `hono` | `>=4.12 <5` |
78
+ | `zod` | `>=4.0 <5` |
79
+
80
+ ### Optional
81
+
82
+ Install only what your app actually uses:
83
+
84
+ ```bash
85
+ # MongoDB auth / sessions / cache
86
+ bun add mongoose
87
+
88
+ # Redis sessions, cache, rate limiting, or BullMQ
89
+ bun add ioredis
90
+
91
+ # Background job queues
92
+ bun add bullmq
93
+ ```
94
+
95
+ | Package | Required version | When you need it |
96
+ |---|---|---|
97
+ | `mongoose` | `>=9.0 <10` | `db.auth: "mongo"`, `db.sessions: "mongo"`, or `db.cache: "mongo"` |
98
+ | `ioredis` | `>=5.0 <6` | `db.redis: true` (the default), or any store set to `"redis"` |
99
+ | `bullmq` | `>=5.0 <6` | Workers / queues |
100
+
101
+ If you're running fully on SQLite or memory (no Redis, no MongoDB), none of the optional peers are needed.
102
+
103
+ ---
104
+
63
105
  ## Quick Start
64
106
 
65
107
  ```ts
@@ -85,6 +127,8 @@ That's it. Your app gets:
85
127
  | `DELETE /auth/sessions/:sessionId` | Revoke a specific session by ID (requires login) |
86
128
  | `POST /auth/verify-email` | Verify email with token (when `emailVerification` is configured) |
87
129
  | `POST /auth/resend-verification` | Resend verification email (requires login, when `emailVerification` is configured) |
130
+ | `POST /auth/forgot-password` | Request a password reset email (when `passwordReset` is configured) |
131
+ | `POST /auth/reset-password` | Reset password using a token from the reset email (when `passwordReset` is configured) |
88
132
  | `GET /health` | Health check |
89
133
  | `GET /docs` | Scalar API docs UI |
90
134
  | `GET /openapi.json` | OpenAPI spec |
@@ -197,18 +241,31 @@ await createServer({
197
241
 
198
242
  Import `appConnection` and register models on it. This ensures your models use the correct connection whether you're on a single DB or a separate tenant DB.
199
243
 
244
+ `appConnection` is a lazy proxy — calling `.model()` at the top level works fine even before `connectMongo()` has been called. Mongoose buffers any queries until the connection is established.
245
+
200
246
  ```ts
201
247
  // src/models/Product.ts
202
- import { appConnection, mongoose } from "@lastshotlabs/bunshot";
248
+ import { appConnection } from "@lastshotlabs/bunshot";
249
+ import { Schema } from "mongoose";
250
+ import type { HydratedDocument } from "mongoose";
203
251
 
204
- const ProductSchema = new mongoose.Schema({
252
+ interface IProduct {
253
+ name: string;
254
+ price: number;
255
+ }
256
+
257
+ export type ProductDocument = HydratedDocument<IProduct>;
258
+
259
+ const ProductSchema = new Schema<IProduct>({
205
260
  name: { type: String, required: true },
206
261
  price: { type: Number, required: true },
207
262
  }, { timestamps: true });
208
263
 
209
- export const Product = appConnection.model("Product", ProductSchema);
264
+ export const Product = appConnection.model<IProduct>("Product", ProductSchema);
210
265
  ```
211
266
 
267
+ > **Note:** Import types (`HydratedDocument`, `Schema`, etc.) directly from `"mongoose"` — the `appConnection` and `mongoose` exports from bunshot are runtime proxies and cannot be used as TypeScript namespaces.
268
+
212
269
  ---
213
270
 
214
271
  ## Jobs (BullMQ)
@@ -724,11 +781,19 @@ await createServer({
724
781
  await resend.emails.send({ to: email, subject: "Verify your email", text: `Token: ${token}` });
725
782
  },
726
783
  },
784
+ passwordReset: { // optional — only active when primaryField is "email"
785
+ tokenExpiry: 60 * 60, // default: 3600 (1 hour) — token TTL in seconds
786
+ onSend: async (email, token) => { // called by POST /auth/forgot-password — use any email provider
787
+ await resend.emails.send({ to: email, subject: "Reset your password", text: `Token: ${token}` });
788
+ },
789
+ },
727
790
  rateLimit: { // optional — built-in auth endpoint rate limiting
728
791
  login: { windowMs: 15 * 60 * 1000, max: 10 }, // default: 10 failures / 15 min
729
792
  register: { windowMs: 60 * 60 * 1000, max: 5 }, // default: 5 attempts / hour (per IP)
730
793
  verifyEmail: { windowMs: 15 * 60 * 1000, max: 10 }, // default: 10 attempts / 15 min (per IP)
731
794
  resendVerification: { windowMs: 60 * 60 * 1000, max: 3 }, // default: 3 attempts / hour (per user)
795
+ forgotPassword: { windowMs: 15 * 60 * 1000, max: 5 }, // default: 5 attempts / 15 min (per IP)
796
+ resetPassword: { windowMs: 15 * 60 * 1000, max: 10 }, // default: 10 attempts / 15 min (per IP)
732
797
  store: "redis", // default: "redis" when Redis is enabled, else "memory"
733
798
  },
734
799
  sessionPolicy: { // optional — session concurrency and metadata
@@ -994,6 +1059,8 @@ All built-in auth endpoints are rate-limited out of the box with sensible defaul
994
1059
  | `POST /auth/register` | IP address | Every attempt | 5 / hour |
995
1060
  | `POST /auth/verify-email` | IP address | Every attempt | 10 / 15 min |
996
1061
  | `POST /auth/resend-verification` | User ID (authenticated) | Every attempt | 3 / hour |
1062
+ | `POST /auth/forgot-password` | IP address | Every attempt | 5 / 15 min |
1063
+ | `POST /auth/reset-password` | IP address | Every attempt | 10 / 15 min |
997
1064
 
998
1065
  Login is keyed by the **identifier being targeted** — an attacker rotating IPs to brute-force `alice@example.com` is blocked regardless of source IP. A successful login resets the counter so legitimate users aren't locked out.
999
1066
 
@@ -25,3 +25,8 @@ export declare const memoryGetVerificationToken: (token: string) => {
25
25
  email: string;
26
26
  } | null;
27
27
  export declare const memoryDeleteVerificationToken: (token: string) => void;
28
+ export declare const memoryCreateResetToken: (token: string, userId: string, email: string, ttlSeconds: number) => void;
29
+ export declare const memoryConsumeResetToken: (hash: string) => {
30
+ userId: string;
31
+ email: string;
32
+ } | null;
@@ -7,6 +7,7 @@ const _userSessionIds = new Map(); // userId → Set<sessionId>
7
7
  const _oauthStates = new Map();
8
8
  const _cache = new Map();
9
9
  const _verificationTokens = new Map();
10
+ const _resetTokens = new Map();
10
11
  /** Reset all in-memory state. Useful for test isolation. */
11
12
  export const clearMemoryStore = () => {
12
13
  _users.clear();
@@ -16,6 +17,7 @@ export const clearMemoryStore = () => {
16
17
  _oauthStates.clear();
17
18
  _cache.clear();
18
19
  _verificationTokens.clear();
20
+ _resetTokens.clear();
19
21
  };
20
22
  // ---------------------------------------------------------------------------
21
23
  // Auth adapter
@@ -290,3 +292,24 @@ export const memoryGetVerificationToken = (token) => {
290
292
  export const memoryDeleteVerificationToken = (token) => {
291
293
  _verificationTokens.delete(token);
292
294
  };
295
+ // ---------------------------------------------------------------------------
296
+ // Password reset token helpers (used by src/lib/resetPassword.ts)
297
+ // ---------------------------------------------------------------------------
298
+ export const memoryCreateResetToken = (token, userId, email, ttlSeconds) => {
299
+ const now = Date.now();
300
+ // Opportunistically purge expired entries to prevent unbounded memory growth
301
+ for (const [k, v] of _resetTokens) {
302
+ if (v.expiresAt <= now)
303
+ _resetTokens.delete(k);
304
+ }
305
+ _resetTokens.set(token, { userId, email, expiresAt: now + ttlSeconds * 1000 });
306
+ };
307
+ export const memoryConsumeResetToken = (hash) => {
308
+ const entry = _resetTokens.get(hash);
309
+ if (!entry || entry.expiresAt <= Date.now()) {
310
+ _resetTokens.delete(hash);
311
+ return null;
312
+ }
313
+ _resetTokens.delete(hash);
314
+ return { userId: entry.userId, email: entry.email };
315
+ };
@@ -25,4 +25,9 @@ export declare const sqliteGetVerificationToken: (token: string) => {
25
25
  email: string;
26
26
  } | null;
27
27
  export declare const sqliteDeleteVerificationToken: (token: string) => void;
28
+ export declare const sqliteCreateResetToken: (token: string, userId: string, email: string, ttlSeconds: number) => void;
29
+ export declare const sqliteConsumeResetToken: (hash: string) => {
30
+ userId: string;
31
+ email: string;
32
+ } | null;
28
33
  export declare const startSqliteCleanup: (intervalMs?: number) => ReturnType<typeof setInterval>;
@@ -65,6 +65,12 @@ function initSchema(db) {
65
65
  email TEXT NOT NULL,
66
66
  expiresAt INTEGER NOT NULL
67
67
  )`);
68
+ db.run(`CREATE TABLE IF NOT EXISTS password_resets (
69
+ token TEXT PRIMARY KEY,
70
+ userId TEXT NOT NULL,
71
+ email TEXT NOT NULL,
72
+ expiresAt INTEGER NOT NULL
73
+ )`);
68
74
  }
69
75
  // ---------------------------------------------------------------------------
70
76
  // Auth adapter
@@ -282,6 +288,17 @@ export const sqliteDeleteVerificationToken = (token) => {
282
288
  getDb().run("DELETE FROM email_verifications WHERE token = ?", [token]);
283
289
  };
284
290
  // ---------------------------------------------------------------------------
291
+ // Password reset token helpers (used by src/lib/resetPassword.ts)
292
+ // ---------------------------------------------------------------------------
293
+ export const sqliteCreateResetToken = (token, userId, email, ttlSeconds) => {
294
+ const expiresAt = Date.now() + ttlSeconds * 1000;
295
+ getDb().run("INSERT INTO password_resets (token, userId, email, expiresAt) VALUES (?, ?, ?, ?)", [token, userId, email, expiresAt]);
296
+ };
297
+ export const sqliteConsumeResetToken = (hash) => {
298
+ const row = getDb().query("DELETE FROM password_resets WHERE token = ? AND expiresAt > ? RETURNING userId, email").get(hash, Date.now());
299
+ return row ?? null;
300
+ };
301
+ // ---------------------------------------------------------------------------
285
302
  // Optional periodic cleanup of expired rows
286
303
  // ---------------------------------------------------------------------------
287
304
  export const startSqliteCleanup = (intervalMs = 3_600_000) => {
@@ -298,5 +315,6 @@ export const startSqliteCleanup = (intervalMs = 3_600_000) => {
298
315
  db.run("DELETE FROM oauth_states WHERE expiresAt <= ?", [now]);
299
316
  db.run("DELETE FROM cache_entries WHERE expiresAt IS NOT NULL AND expiresAt <= ?", [now]);
300
317
  db.run("DELETE FROM email_verifications WHERE expiresAt <= ?", [now]);
318
+ db.run("DELETE FROM password_resets WHERE expiresAt <= ?", [now]);
301
319
  }, intervalMs);
302
320
  };
package/dist/app.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { OpenAPIHono } from "@hono/zod-openapi";
2
2
  import type { MiddlewareHandler } from "hono";
3
3
  import type { AppEnv } from "./lib/context";
4
- import type { PrimaryField, EmailVerificationConfig } from "./lib/appConfig";
4
+ import type { PrimaryField, EmailVerificationConfig, PasswordResetConfig } from "./lib/appConfig";
5
5
  import type { AuthAdapter } from "./lib/authAdapter";
6
6
  import type { OAuthProviderConfig } from "./lib/oauth";
7
7
  type StoreType = "redis" | "mongo" | "sqlite" | "memory";
@@ -82,6 +82,16 @@ export interface AuthRateLimitConfig {
82
82
  windowMs?: number;
83
83
  max?: number;
84
84
  };
85
+ /** Max forgot-password requests per IP per window. Default: 5 per 15 min. */
86
+ forgotPassword?: {
87
+ windowMs?: number;
88
+ max?: number;
89
+ };
90
+ /** Max reset-password attempts per IP per window. Default: 10 per 15 min. */
91
+ resetPassword?: {
92
+ windowMs?: number;
93
+ max?: number;
94
+ };
85
95
  /**
86
96
  * Store backend for auth rate limit counters.
87
97
  * Defaults to "redis" when Redis is enabled, otherwise "memory".
@@ -116,6 +126,12 @@ export interface AuthConfig {
116
126
  * Provide an onSend callback to send the verification email via any provider (Resend, SendGrid, etc.).
117
127
  */
118
128
  emailVerification?: EmailVerificationConfig;
129
+ /**
130
+ * Password reset configuration. Only active when primaryField is "email".
131
+ * Provide an onSend callback to send the reset email via any provider (Resend, SendGrid, etc.).
132
+ * Mounts POST /auth/forgot-password and POST /auth/reset-password.
133
+ */
134
+ passwordReset?: PasswordResetConfig;
119
135
  /** Rate limit configuration for built-in auth endpoints. */
120
136
  rateLimit?: AuthRateLimitConfig;
121
137
  /** Session concurrency and metadata persistence policy. */
@@ -140,7 +156,7 @@ export interface AuthSessionPolicyConfig {
140
156
  */
141
157
  trackLastActive?: boolean;
142
158
  }
143
- export type { PrimaryField, EmailVerificationConfig };
159
+ export type { PrimaryField, EmailVerificationConfig, PasswordResetConfig };
144
160
  export interface BotProtectionConfig {
145
161
  /**
146
162
  * List of IPv4 CIDRs (e.g. "198.51.100.0/24"), IPv4 addresses, or IPv6 addresses to block outright.
package/dist/app.js CHANGED
@@ -8,8 +8,9 @@ import { rateLimit } from "./middleware/rateLimit";
8
8
  import { bearerAuth } from "./middleware/bearerAuth";
9
9
  import { identify } from "./middleware/identify";
10
10
  import { HEADER_USER_TOKEN } from "./lib/constants";
11
- import { setAppName, setAppRoles, setDefaultRole, setPrimaryField, setEmailVerificationConfig, setMaxSessions, setPersistSessionMetadata, setIncludeInactiveSessions, setTrackLastActive } from "./lib/appConfig";
11
+ import { setAppName, setAppRoles, setDefaultRole, setPrimaryField, setEmailVerificationConfig, setPasswordResetConfig, setMaxSessions, setPersistSessionMetadata, setIncludeInactiveSessions, setTrackLastActive } from "./lib/appConfig";
12
12
  import { setEmailVerificationStore } from "./lib/emailVerification";
13
+ import { setPasswordResetStore } from "./lib/resetPassword";
13
14
  import { setAuthRateLimitStore } from "./lib/authRateLimit";
14
15
  import { setAuthAdapter } from "./lib/authAdapter";
15
16
  import { mongoAuthAdapter } from "./adapters/mongoAuth";
@@ -39,6 +40,7 @@ export const createApp = async (config) => {
39
40
  const defaultRole = authConfig.defaultRole;
40
41
  const primaryField = authConfig.primaryField ?? "email";
41
42
  const emailVerification = authConfig.emailVerification;
43
+ const passwordReset = authConfig.passwordReset;
42
44
  const authRateLimit = authConfig.rateLimit;
43
45
  const sessionPolicy = authConfig.sessionPolicy ?? {};
44
46
  const { sqlite, mongo = "single", redis: enableRedis = true } = db;
@@ -82,20 +84,31 @@ export const createApp = async (config) => {
82
84
  else {
83
85
  authAdapter = mongoAuthAdapter;
84
86
  }
87
+ if (defaultRole && !authAdapter.setRoles) {
88
+ throw new Error(`createApp: "defaultRole" is set to "${defaultRole}" but the auth adapter does not implement setRoles. Add setRoles to your adapter or remove defaultRole.`);
89
+ }
90
+ if (emailVerification && primaryField !== "email") {
91
+ throw new Error(`createApp: "emailVerification" is only supported when primaryField is "email". Either set primaryField to "email" or remove emailVerification.`);
92
+ }
93
+ if (passwordReset && primaryField !== "email") {
94
+ throw new Error(`createApp: "passwordReset" is only supported when primaryField is "email". Either set primaryField to "email" or remove passwordReset.`);
95
+ }
96
+ if (passwordReset && !authAdapter.setPassword) {
97
+ throw new Error(`createApp: "passwordReset" is configured but the auth adapter does not implement setPassword. Add setPassword to your adapter or remove passwordReset.`);
98
+ }
85
99
  setAuthAdapter(authAdapter);
86
100
  setAppRoles(roles);
87
101
  setDefaultRole(defaultRole ?? null);
88
102
  setPrimaryField(primaryField);
89
103
  setEmailVerificationConfig(emailVerification ?? null);
90
104
  setEmailVerificationStore(sessions);
105
+ setPasswordResetConfig(passwordReset ?? null);
106
+ setPasswordResetStore(sessions);
91
107
  setAuthRateLimitStore(authRateLimit?.store ?? (enableRedis ? "redis" : "memory"));
92
108
  setMaxSessions(sessionPolicy.maxSessions ?? 6);
93
109
  setPersistSessionMetadata(sessionPolicy.persistSessionMetadata ?? true);
94
110
  setIncludeInactiveSessions(sessionPolicy.includeInactiveSessions ?? false);
95
111
  setTrackLastActive(sessionPolicy.trackLastActive ?? false);
96
- if (defaultRole && !authAdapter.setRoles) {
97
- throw new Error(`createApp: "defaultRole" is set to "${defaultRole}" but the auth adapter does not implement setRoles. Add setRoles to your adapter or remove defaultRole.`);
98
- }
99
112
  if (oauthProviders)
100
113
  initOAuthProviders(oauthProviders);
101
114
  const configuredOAuth = getConfiguredOAuthProviders();
@@ -144,7 +157,7 @@ export const createApp = async (config) => {
144
157
  }
145
158
  if (enableAuthRoutes) {
146
159
  const { createAuthRouter } = await import(`${coreRoutesDir}/auth`);
147
- app.route("/", createAuthRouter({ primaryField, emailVerification, rateLimit: authRateLimit }));
160
+ app.route("/", createAuthRouter({ primaryField, emailVerification, passwordReset, rateLimit: authRateLimit }));
148
161
  }
149
162
  if (configuredOAuth.length > 0) {
150
163
  app.route("/", createOAuthRouter(configuredOAuth, postOAuthRedirect));
@@ -0,0 +1,3 @@
1
+ export { connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo, authConnection, appConnection, mongoose } from "../lib/mongo";
2
+ export { mongoAuthAdapter } from "../adapters/mongoAuth";
3
+ export { AuthUser } from "../models/AuthUser";
@@ -0,0 +1,3 @@
1
+ export { connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo, authConnection, appConnection, mongoose } from "../lib/mongo";
2
+ export { mongoAuthAdapter } from "../adapters/mongoAuth";
3
+ export { AuthUser } from "../models/AuthUser";
@@ -0,0 +1,2 @@
1
+ export { createQueue, createWorker } from "../lib/queue";
2
+ export type { Job } from "../lib/queue";
@@ -0,0 +1 @@
1
+ export { createQueue, createWorker } from "../lib/queue";
@@ -0,0 +1 @@
1
+ export { connectRedis, disconnectRedis, getRedis } from "../lib/redis";
@@ -0,0 +1 @@
1
+ export { connectRedis, disconnectRedis, getRedis } from "../lib/redis";
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export { createApp } from "./app";
2
2
  export { createServer } from "./server";
3
- export type { CreateAppConfig, DbConfig, AppMeta, AuthConfig, AuthRateLimitConfig, OAuthConfig, SecurityConfig, BotProtectionConfig, PrimaryField, EmailVerificationConfig } from "./app";
3
+ export type { CreateAppConfig, DbConfig, AppMeta, AuthConfig, AuthRateLimitConfig, OAuthConfig, SecurityConfig, BotProtectionConfig, PrimaryField, EmailVerificationConfig, PasswordResetConfig } from "./app";
4
4
  export type { CreateServerConfig, WsConfig } from "./server";
5
+ export { appConnection, authConnection, mongoose, connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo } from "./lib/mongo";
6
+ export { connectRedis, disconnectRedis, getRedis } from "./lib/redis";
5
7
  export { getAppRoles } from "./lib/appConfig";
6
8
  export { HttpError } from "./lib/HttpError";
7
9
  export { COOKIE_TOKEN, HEADER_USER_TOKEN } from "./lib/constants";
@@ -9,10 +11,7 @@ export { createRouter } from "./lib/context";
9
11
  export type { AppEnv, AppVariables } from "./lib/context";
10
12
  export { signToken, verifyToken } from "./lib/jwt";
11
13
  export { log } from "./lib/logger";
12
- export { connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo, authConnection, appConnection, mongoose } from "./lib/mongo";
13
- export { connectRedis, disconnectRedis, getRedis } from "./lib/redis";
14
- export { createQueue, createWorker } from "./lib/queue";
15
- export type { Job } from "./lib/queue";
14
+ export { createResetToken, consumeResetToken, setPasswordResetStore } from "./lib/resetPassword";
16
15
  export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore } from "./lib/session";
17
16
  export type { SessionMetadata, SessionInfo } from "./lib/session";
18
17
  export { createVerificationToken, getVerificationToken, deleteVerificationToken } from "./lib/emailVerification";
@@ -30,8 +29,6 @@ export { requireRole } from "./middleware/requireRole";
30
29
  export { requireVerifiedEmail } from "./middleware/requireVerifiedEmail";
31
30
  export { cacheResponse, bustCache, bustCachePattern, setCacheStore } from "./middleware/cacheResponse";
32
31
  export { buildFingerprint } from "./lib/fingerprint";
33
- export { AuthUser } from "./models/AuthUser";
34
- export { mongoAuthAdapter } from "./adapters/mongoAuth";
35
32
  export { sqliteAuthAdapter, setSqliteDb, startSqliteCleanup } from "./adapters/sqliteAuth";
36
33
  export { memoryAuthAdapter, clearMemoryStore } from "./adapters/memoryAuth";
37
34
  export { setUserRoles, addUserRole, removeUserRole } from "./lib/roles";
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  // App factory
2
2
  export { createApp } from "./app";
3
3
  export { createServer } from "./server";
4
+ // Database
5
+ export { appConnection, authConnection, mongoose, connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo } from "./lib/mongo";
6
+ export { connectRedis, disconnectRedis, getRedis } from "./lib/redis";
4
7
  // Lib utilities
5
8
  export { getAppRoles } from "./lib/appConfig";
6
9
  export { HttpError } from "./lib/HttpError";
@@ -8,9 +11,7 @@ export { COOKIE_TOKEN, HEADER_USER_TOKEN } from "./lib/constants";
8
11
  export { createRouter } from "./lib/context";
9
12
  export { signToken, verifyToken } from "./lib/jwt";
10
13
  export { log } from "./lib/logger";
11
- export { connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo, authConnection, appConnection, mongoose } from "./lib/mongo";
12
- export { connectRedis, disconnectRedis, getRedis } from "./lib/redis";
13
- export { createQueue, createWorker } from "./lib/queue";
14
+ export { createResetToken, consumeResetToken, setPasswordResetStore } from "./lib/resetPassword";
14
15
  export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore } from "./lib/session";
15
16
  export { createVerificationToken, getVerificationToken, deleteVerificationToken } from "./lib/emailVerification";
16
17
  export { bustAuthLimit, trackAttempt, isLimited } from "./lib/authRateLimit";
@@ -27,8 +28,6 @@ export { cacheResponse, bustCache, bustCachePattern, setCacheStore } from "./mid
27
28
  // Lib utilities (bot protection)
28
29
  export { buildFingerprint } from "./lib/fingerprint";
29
30
  // Models
30
- export { AuthUser } from "./models/AuthUser";
31
- export { mongoAuthAdapter } from "./adapters/mongoAuth";
32
31
  export { sqliteAuthAdapter, setSqliteDb, startSqliteCleanup } from "./adapters/sqliteAuth";
33
32
  export { memoryAuthAdapter, clearMemoryStore } from "./adapters/memoryAuth";
34
33
  export { setUserRoles, addUserRole, removeUserRole } from "./lib/roles";
@@ -7,6 +7,12 @@ export interface EmailVerificationConfig {
7
7
  /** Called after registration with the identifier and verification token. Use to send the email. */
8
8
  onSend: (email: string, token: string) => Promise<void>;
9
9
  }
10
+ export interface PasswordResetConfig {
11
+ /** Token time-to-live in seconds. Defaults to 3 600 (1 hour). */
12
+ tokenExpiry?: number;
13
+ /** Called with the user's email and the reset token. Use to send the reset email. */
14
+ onSend: (email: string, token: string) => Promise<void>;
15
+ }
10
16
  export declare const setAppName: (name: string) => void;
11
17
  export declare const getAppName: () => string;
12
18
  export declare const setAppRoles: (roles: string[]) => void;
@@ -18,6 +24,9 @@ export declare const getPrimaryField: () => PrimaryField;
18
24
  export declare const setEmailVerificationConfig: (config: EmailVerificationConfig | null) => void;
19
25
  export declare const getEmailVerificationConfig: () => EmailVerificationConfig | null;
20
26
  export declare const getTokenExpiry: () => number;
27
+ export declare const setPasswordResetConfig: (config: PasswordResetConfig | null) => void;
28
+ export declare const getPasswordResetConfig: () => PasswordResetConfig | null;
29
+ export declare const getResetTokenExpiry: () => number;
21
30
  export declare const setMaxSessions: (n: number) => void;
22
31
  export declare const getMaxSessions: () => number;
23
32
  export declare const setPersistSessionMetadata: (v: boolean) => void;
@@ -3,6 +3,7 @@ let appRoles = [];
3
3
  let defaultRole = null;
4
4
  let _primaryField = "email";
5
5
  let _emailVerificationConfig = null;
6
+ let _passwordResetConfig = null;
6
7
  export const setAppName = (name) => { appName = name; };
7
8
  export const getAppName = () => appName;
8
9
  export const setAppRoles = (roles) => { appRoles = roles; };
@@ -15,6 +16,10 @@ export const setEmailVerificationConfig = (config) => { _emailVerificationConfig
15
16
  export const getEmailVerificationConfig = () => _emailVerificationConfig;
16
17
  const DEFAULT_TOKEN_EXPIRY = 60 * 60 * 24; // 24 hours
17
18
  export const getTokenExpiry = () => _emailVerificationConfig?.tokenExpiry ?? DEFAULT_TOKEN_EXPIRY;
19
+ export const setPasswordResetConfig = (config) => { _passwordResetConfig = config; };
20
+ export const getPasswordResetConfig = () => _passwordResetConfig;
21
+ const DEFAULT_RESET_TOKEN_EXPIRY = 60 * 60; // 1 hour
22
+ export const getResetTokenExpiry = () => _passwordResetConfig?.tokenExpiry ?? DEFAULT_RESET_TOKEN_EXPIRY;
18
23
  // ---------------------------------------------------------------------------
19
24
  // Session policy
20
25
  // ---------------------------------------------------------------------------
@@ -1,18 +1,19 @@
1
1
  import { getRedis } from "./redis";
2
- import { appConnection } from "./mongo";
2
+ import { appConnection, mongoose } from "./mongo";
3
3
  import { getAppName, getTokenExpiry } from "./appConfig";
4
- import { Schema } from "mongoose";
5
4
  import { sqliteCreateVerificationToken, sqliteGetVerificationToken, sqliteDeleteVerificationToken, } from "../adapters/sqliteAuth";
6
5
  import { memoryCreateVerificationToken, memoryGetVerificationToken, memoryDeleteVerificationToken, } from "../adapters/memoryAuth";
7
- const verificationSchema = new Schema({
8
- token: { type: String, required: true, unique: true },
9
- userId: { type: String, required: true },
10
- email: { type: String, required: true },
11
- expiresAt: { type: Date, required: true, index: { expireAfterSeconds: 0 } },
12
- }, { collection: "email_verifications" });
13
6
  function getVerificationModel() {
14
- return appConnection.models["EmailVerification"] ??
15
- appConnection.model("EmailVerification", verificationSchema);
7
+ if (appConnection.models["EmailVerification"])
8
+ return appConnection.models["EmailVerification"];
9
+ const { Schema } = mongoose;
10
+ const verificationSchema = new Schema({
11
+ token: { type: String, required: true, unique: true },
12
+ userId: { type: String, required: true },
13
+ email: { type: String, required: true },
14
+ expiresAt: { type: Date, required: true, index: { expireAfterSeconds: 0 } },
15
+ }, { collection: "email_verifications" });
16
+ return appConnection.model("EmailVerification", verificationSchema);
16
17
  }
17
18
  let _store = "redis";
18
19
  export const setEmailVerificationStore = (store) => { _store = store; };
@@ -1,15 +1,20 @@
1
- import mongoose from "mongoose";
1
+ import type { Connection, Mongoose } from "mongoose";
2
+ type MongooseModule = Mongoose;
2
3
  /**
3
4
  * Named connection used exclusively for auth data (AuthUser model).
4
5
  * Connected via connectAuthMongo() or connectMongo() (backward compat).
5
6
  */
6
- export declare const authConnection: mongoose.Connection;
7
+ export declare const authConnection: Connection;
7
8
  /**
8
9
  * Named connection for app/tenant data.
9
10
  * Connected via connectAppMongo() or connectMongo() (backward compat).
10
11
  * Use this when registering your own models: appConnection.model("Product", schema).
11
12
  */
12
- export declare const appConnection: mongoose.Connection;
13
+ export declare const appConnection: Connection;
14
+ /**
15
+ * The mongoose instance. Available after connectMongo() / connectAuthMongo() is called.
16
+ */
17
+ export declare const mongoose: MongooseModule;
13
18
  /**
14
19
  * Connect the auth connection to its dedicated MongoDB server.
15
20
  * Uses MONGO_AUTH_USER_*, MONGO_AUTH_PW_*, MONGO_AUTH_HOST_*, MONGO_AUTH_DB_* env vars.
@@ -31,4 +36,4 @@ export declare const connectMongo: () => Promise<void>;
31
36
  * Useful for one-off scripts that need a clean exit.
32
37
  */
33
38
  export declare const disconnectMongo: () => Promise<void>;
34
- export { mongoose };
39
+ export {};