@lastshotlabs/bunshot 0.0.8 → 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 +97 -7
- package/dist/adapters/memoryAuth.d.ts +13 -3
- package/dist/adapters/memoryAuth.js +116 -8
- package/dist/adapters/sqliteAuth.d.ts +13 -3
- package/dist/adapters/sqliteAuth.js +93 -15
- package/dist/app.d.ts +39 -2
- package/dist/app.js +23 -5
- package/dist/cli.js +0 -0
- package/dist/entrypoints/mongo.d.ts +3 -0
- package/dist/entrypoints/mongo.js +3 -0
- package/dist/entrypoints/queue.d.ts +2 -0
- package/dist/entrypoints/queue.js +1 -0
- package/dist/entrypoints/redis.d.ts +1 -0
- package/dist/entrypoints/redis.js +1 -0
- package/dist/index.d.ts +6 -8
- package/dist/index.js +5 -6
- package/dist/lib/appConfig.d.ts +17 -0
- package/dist/lib/appConfig.js +20 -0
- package/dist/lib/context.d.ts +1 -0
- package/dist/lib/emailVerification.js +11 -10
- package/dist/lib/jwt.d.ts +1 -1
- package/dist/lib/jwt.js +1 -1
- package/dist/lib/mongo.d.ts +9 -4
- package/dist/lib/mongo.js +61 -10
- package/dist/lib/oauth.js +11 -10
- package/dist/lib/queue.d.ts +3 -4
- package/dist/lib/queue.js +18 -3
- package/dist/lib/redis.d.ts +3 -8
- package/dist/lib/redis.js +19 -8
- package/dist/lib/resetPassword.d.ts +12 -0
- package/dist/lib/resetPassword.js +95 -0
- package/dist/lib/session.d.ts +20 -3
- package/dist/lib/session.js +288 -35
- package/dist/middleware/cacheResponse.js +10 -9
- package/dist/middleware/identify.js +21 -7
- package/dist/models/AuthUser.d.ts +14 -106
- package/dist/models/AuthUser.js +31 -14
- package/dist/routes/auth.d.ts +3 -2
- package/dist/routes/auth.js +139 -4
- package/dist/routes/oauth.js +13 -4
- package/dist/services/auth.d.ts +3 -2
- package/dist/services/auth.js +20 -11
- package/dist/ws/index.js +6 -3
- package/package.json +39 -9
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 } 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,7 +40,9 @@ 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;
|
|
45
|
+
const sessionPolicy = authConfig.sessionPolicy ?? {};
|
|
43
46
|
const { sqlite, mongo = "single", redis: enableRedis = true } = db;
|
|
44
47
|
// Smart fallback: pick the best available store rather than blindly defaulting to "redis"
|
|
45
48
|
const defaultStore = enableRedis
|
|
@@ -81,16 +84,31 @@ export const createApp = async (config) => {
|
|
|
81
84
|
else {
|
|
82
85
|
authAdapter = mongoAuthAdapter;
|
|
83
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
|
+
}
|
|
84
99
|
setAuthAdapter(authAdapter);
|
|
85
100
|
setAppRoles(roles);
|
|
86
101
|
setDefaultRole(defaultRole ?? null);
|
|
87
102
|
setPrimaryField(primaryField);
|
|
88
103
|
setEmailVerificationConfig(emailVerification ?? null);
|
|
89
104
|
setEmailVerificationStore(sessions);
|
|
105
|
+
setPasswordResetConfig(passwordReset ?? null);
|
|
106
|
+
setPasswordResetStore(sessions);
|
|
90
107
|
setAuthRateLimitStore(authRateLimit?.store ?? (enableRedis ? "redis" : "memory"));
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
108
|
+
setMaxSessions(sessionPolicy.maxSessions ?? 6);
|
|
109
|
+
setPersistSessionMetadata(sessionPolicy.persistSessionMetadata ?? true);
|
|
110
|
+
setIncludeInactiveSessions(sessionPolicy.includeInactiveSessions ?? false);
|
|
111
|
+
setTrackLastActive(sessionPolicy.trackLastActive ?? false);
|
|
94
112
|
if (oauthProviders)
|
|
95
113
|
initOAuthProviders(oauthProviders);
|
|
96
114
|
const configuredOAuth = getConfiguredOAuthProviders();
|
|
@@ -139,7 +157,7 @@ export const createApp = async (config) => {
|
|
|
139
157
|
}
|
|
140
158
|
if (enableAuthRoutes) {
|
|
141
159
|
const { createAuthRouter } = await import(`${coreRoutesDir}/auth`);
|
|
142
|
-
app.route("/", createAuthRouter({ primaryField, emailVerification, rateLimit: authRateLimit }));
|
|
160
|
+
app.route("/", createAuthRouter({ primaryField, emailVerification, passwordReset, rateLimit: authRateLimit }));
|
|
143
161
|
}
|
|
144
162
|
if (configuredOAuth.length > 0) {
|
|
145
163
|
app.route("/", createOAuthRouter(configuredOAuth, postOAuthRedirect));
|
package/dist/cli.js
CHANGED
|
File without changes
|
|
@@ -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,11 +11,9 @@ 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 {
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
15
|
-
export type { Job } from "./lib/queue";
|
|
16
|
-
export { createSession, getSession, deleteSession, setSessionStore } from "./lib/session";
|
|
14
|
+
export { createResetToken, consumeResetToken, setPasswordResetStore } from "./lib/resetPassword";
|
|
15
|
+
export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore } from "./lib/session";
|
|
16
|
+
export type { SessionMetadata, SessionInfo } from "./lib/session";
|
|
17
17
|
export { createVerificationToken, getVerificationToken, deleteVerificationToken } from "./lib/emailVerification";
|
|
18
18
|
export { bustAuthLimit, trackAttempt, isLimited } from "./lib/authRateLimit";
|
|
19
19
|
export type { LimitOpts } from "./lib/authRateLimit";
|
|
@@ -29,8 +29,6 @@ export { requireRole } from "./middleware/requireRole";
|
|
|
29
29
|
export { requireVerifiedEmail } from "./middleware/requireVerifiedEmail";
|
|
30
30
|
export { cacheResponse, bustCache, bustCachePattern, setCacheStore } from "./middleware/cacheResponse";
|
|
31
31
|
export { buildFingerprint } from "./lib/fingerprint";
|
|
32
|
-
export { AuthUser } from "./models/AuthUser";
|
|
33
|
-
export { mongoAuthAdapter } from "./adapters/mongoAuth";
|
|
34
32
|
export { sqliteAuthAdapter, setSqliteDb, startSqliteCleanup } from "./adapters/sqliteAuth";
|
|
35
33
|
export { memoryAuthAdapter, clearMemoryStore } from "./adapters/memoryAuth";
|
|
36
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,10 +11,8 @@ 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 {
|
|
12
|
-
export {
|
|
13
|
-
export { createQueue, createWorker } from "./lib/queue";
|
|
14
|
-
export { createSession, getSession, deleteSession, setSessionStore } from "./lib/session";
|
|
14
|
+
export { createResetToken, consumeResetToken, setPasswordResetStore } from "./lib/resetPassword";
|
|
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";
|
|
17
18
|
export { validate } from "./lib/validate";
|
|
@@ -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";
|
package/dist/lib/appConfig.d.ts
CHANGED
|
@@ -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,3 +24,14 @@ 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;
|
|
30
|
+
export declare const setMaxSessions: (n: number) => void;
|
|
31
|
+
export declare const getMaxSessions: () => number;
|
|
32
|
+
export declare const setPersistSessionMetadata: (v: boolean) => void;
|
|
33
|
+
export declare const getPersistSessionMetadata: () => boolean;
|
|
34
|
+
export declare const setIncludeInactiveSessions: (v: boolean) => void;
|
|
35
|
+
export declare const getIncludeInactiveSessions: () => boolean;
|
|
36
|
+
export declare const setTrackLastActive: (v: boolean) => void;
|
|
37
|
+
export declare const getTrackLastActive: () => boolean;
|
package/dist/lib/appConfig.js
CHANGED
|
@@ -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,3 +16,22 @@ 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;
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Session policy
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
let _maxSessions = 6;
|
|
27
|
+
let _persistSessionMetadata = true;
|
|
28
|
+
let _includeInactiveSessions = false;
|
|
29
|
+
let _trackLastActive = false;
|
|
30
|
+
export const setMaxSessions = (n) => { _maxSessions = Number.isFinite(n) && n >= 1 ? Math.floor(n) : 1; };
|
|
31
|
+
export const getMaxSessions = () => _maxSessions;
|
|
32
|
+
export const setPersistSessionMetadata = (v) => { _persistSessionMetadata = v; };
|
|
33
|
+
export const getPersistSessionMetadata = () => _persistSessionMetadata;
|
|
34
|
+
export const setIncludeInactiveSessions = (v) => { _includeInactiveSessions = v; };
|
|
35
|
+
export const getIncludeInactiveSessions = () => _includeInactiveSessions;
|
|
36
|
+
export const setTrackLastActive = (v) => { _trackLastActive = v; };
|
|
37
|
+
export const getTrackLastActive = () => _trackLastActive;
|
package/dist/lib/context.d.ts
CHANGED
|
@@ -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
|
-
|
|
15
|
-
appConnection.
|
|
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; };
|
package/dist/lib/jwt.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const signToken: (userId: string) => Promise<string>;
|
|
1
|
+
export declare const signToken: (userId: string, sessionId: string) => Promise<string>;
|
|
2
2
|
export declare const verifyToken: (token: string) => Promise<import("jose").JWTPayload>;
|
package/dist/lib/jwt.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SignJWT, jwtVerify } from "jose";
|
|
2
2
|
const isProd = process.env.NODE_ENV === "production";
|
|
3
3
|
const secret = new TextEncoder().encode(isProd ? process.env.JWT_SECRET_PROD : process.env.JWT_SECRET_DEV);
|
|
4
|
-
export const signToken = async (userId) => new SignJWT({ sub: userId })
|
|
4
|
+
export const signToken = async (userId, sessionId) => new SignJWT({ sub: userId, sid: sessionId })
|
|
5
5
|
.setProtectedHeader({ alg: "HS256" })
|
|
6
6
|
.setExpirationTime("7d")
|
|
7
7
|
.sign(secret);
|
package/dist/lib/mongo.d.ts
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import
|
|
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:
|
|
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:
|
|
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 {
|
|
39
|
+
export {};
|
package/dist/lib/mongo.js
CHANGED
|
@@ -1,32 +1,74 @@
|
|
|
1
|
-
import mongoose from "mongoose";
|
|
2
1
|
import { log } from "./logger";
|
|
3
2
|
const isProd = process.env.NODE_ENV === "production";
|
|
3
|
+
function requireMongoose() {
|
|
4
|
+
try {
|
|
5
|
+
// Bun supports require() in ESM; this defers the import to call time
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
7
|
+
const mod = require("mongoose");
|
|
8
|
+
return mod.default ?? mod;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
throw new Error("mongoose is not installed. Run: bun add mongoose");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
4
14
|
function buildUri(user, password, host, db) {
|
|
5
15
|
const [hostPart, queryPart] = host.split("?");
|
|
6
16
|
return `mongodb+srv://${user}:${password}@${hostPart.replace(/\/$/, "")}/${db}${queryPart ? `?${queryPart}` : ""}`;
|
|
7
17
|
}
|
|
18
|
+
// Internal mutable references — set inside connect functions
|
|
19
|
+
let _authConn = null;
|
|
20
|
+
let _appConn = null;
|
|
21
|
+
let _mongoose = null;
|
|
22
|
+
function makeConnectionProxy(label, getConn, setConn) {
|
|
23
|
+
return new Proxy({}, {
|
|
24
|
+
get(_, prop) {
|
|
25
|
+
let conn = getConn();
|
|
26
|
+
if (!conn) {
|
|
27
|
+
// Lazily create a disconnected connection so appConnection.model() works at module
|
|
28
|
+
// load time. Mongoose buffers queries until openUri() is called by connectMongo().
|
|
29
|
+
conn = requireMongoose().createConnection();
|
|
30
|
+
setConn(conn);
|
|
31
|
+
}
|
|
32
|
+
const val = conn[prop];
|
|
33
|
+
return typeof val === "function" ? val.bind(conn) : val;
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
8
37
|
/**
|
|
9
38
|
* Named connection used exclusively for auth data (AuthUser model).
|
|
10
39
|
* Connected via connectAuthMongo() or connectMongo() (backward compat).
|
|
11
40
|
*/
|
|
12
|
-
export const authConnection =
|
|
41
|
+
export const authConnection = makeConnectionProxy("auth", () => _authConn, (c) => { _authConn = c; });
|
|
13
42
|
/**
|
|
14
43
|
* Named connection for app/tenant data.
|
|
15
44
|
* Connected via connectAppMongo() or connectMongo() (backward compat).
|
|
16
45
|
* Use this when registering your own models: appConnection.model("Product", schema).
|
|
17
46
|
*/
|
|
18
|
-
export const appConnection =
|
|
47
|
+
export const appConnection = makeConnectionProxy("app", () => _appConn, (c) => { _appConn = c; });
|
|
48
|
+
/**
|
|
49
|
+
* The mongoose instance. Available after connectMongo() / connectAuthMongo() is called.
|
|
50
|
+
*/
|
|
51
|
+
export const mongoose = new Proxy({}, {
|
|
52
|
+
get(_, prop) {
|
|
53
|
+
const mg = _mongoose ?? requireMongoose();
|
|
54
|
+
return mg[prop];
|
|
55
|
+
},
|
|
56
|
+
});
|
|
19
57
|
/**
|
|
20
58
|
* Connect the auth connection to its dedicated MongoDB server.
|
|
21
59
|
* Uses MONGO_AUTH_USER_*, MONGO_AUTH_PW_*, MONGO_AUTH_HOST_*, MONGO_AUTH_DB_* env vars.
|
|
22
60
|
*/
|
|
23
61
|
export const connectAuthMongo = async () => {
|
|
62
|
+
const mg = requireMongoose();
|
|
63
|
+
_mongoose = mg;
|
|
64
|
+
if (!_authConn)
|
|
65
|
+
_authConn = mg.createConnection();
|
|
24
66
|
const user = isProd ? process.env.MONGO_AUTH_USER_PROD : process.env.MONGO_AUTH_USER_DEV;
|
|
25
67
|
const password = isProd ? process.env.MONGO_AUTH_PW_PROD : process.env.MONGO_AUTH_PW_DEV;
|
|
26
68
|
const host = isProd ? process.env.MONGO_AUTH_HOST_PROD : process.env.MONGO_AUTH_HOST_DEV;
|
|
27
69
|
const db = isProd ? process.env.MONGO_AUTH_DB_PROD : process.env.MONGO_AUTH_DB_DEV;
|
|
28
70
|
const uri = buildUri(user, password, host, db);
|
|
29
|
-
await
|
|
71
|
+
await _authConn.openUri(uri);
|
|
30
72
|
log(`[mongo] auth connected to ${host} as ${user}`);
|
|
31
73
|
};
|
|
32
74
|
/**
|
|
@@ -34,12 +76,16 @@ export const connectAuthMongo = async () => {
|
|
|
34
76
|
* Uses MONGO_USER_*, MONGO_PW_*, MONGO_HOST_*, MONGO_DB_* env vars.
|
|
35
77
|
*/
|
|
36
78
|
export const connectAppMongo = async () => {
|
|
79
|
+
const mg = requireMongoose();
|
|
80
|
+
_mongoose = mg;
|
|
81
|
+
if (!_appConn)
|
|
82
|
+
_appConn = mg.createConnection();
|
|
37
83
|
const user = isProd ? process.env.MONGO_USER_PROD : process.env.MONGO_USER_DEV;
|
|
38
84
|
const password = isProd ? process.env.MONGO_PW_PROD : process.env.MONGO_PW_DEV;
|
|
39
85
|
const host = isProd ? process.env.MONGO_HOST_PROD : process.env.MONGO_HOST_DEV;
|
|
40
86
|
const db = isProd ? process.env.MONGO_DB_PROD : process.env.MONGO_DB_DEV;
|
|
41
87
|
const uri = buildUri(user, password, host, db);
|
|
42
|
-
await
|
|
88
|
+
await _appConn.openUri(uri);
|
|
43
89
|
log(`[mongo] app connected to ${host} as ${user}`);
|
|
44
90
|
};
|
|
45
91
|
/**
|
|
@@ -48,14 +94,20 @@ export const connectAppMongo = async () => {
|
|
|
48
94
|
* Uses MONGO_USER_*, MONGO_PW_*, MONGO_HOST_*, MONGO_DB_* env vars.
|
|
49
95
|
*/
|
|
50
96
|
export const connectMongo = async () => {
|
|
97
|
+
const mg = requireMongoose();
|
|
98
|
+
_mongoose = mg;
|
|
99
|
+
if (!_authConn)
|
|
100
|
+
_authConn = mg.createConnection();
|
|
101
|
+
if (!_appConn)
|
|
102
|
+
_appConn = mg.createConnection();
|
|
51
103
|
const user = isProd ? process.env.MONGO_USER_PROD : process.env.MONGO_USER_DEV;
|
|
52
104
|
const password = isProd ? process.env.MONGO_PW_PROD : process.env.MONGO_PW_DEV;
|
|
53
105
|
const host = isProd ? process.env.MONGO_HOST_PROD : process.env.MONGO_HOST_DEV;
|
|
54
106
|
const db = isProd ? process.env.MONGO_DB_PROD : process.env.MONGO_DB_DEV;
|
|
55
107
|
const uri = buildUri(user, password, host, db);
|
|
56
108
|
await Promise.all([
|
|
57
|
-
|
|
58
|
-
|
|
109
|
+
_authConn.openUri(uri),
|
|
110
|
+
_appConn.openUri(uri),
|
|
59
111
|
]);
|
|
60
112
|
log(`[mongo] connected to ${host} as ${user}`);
|
|
61
113
|
};
|
|
@@ -65,9 +117,8 @@ export const connectMongo = async () => {
|
|
|
65
117
|
*/
|
|
66
118
|
export const disconnectMongo = async () => {
|
|
67
119
|
await Promise.all([
|
|
68
|
-
|
|
69
|
-
|
|
120
|
+
_authConn && _authConn.readyState !== 0 ? _authConn.close() : Promise.resolve(),
|
|
121
|
+
_appConn && _appConn.readyState !== 0 ? _appConn.close() : Promise.resolve(),
|
|
70
122
|
]);
|
|
71
123
|
log("[mongo] disconnected");
|
|
72
124
|
};
|
|
73
|
-
export { mongoose };
|
package/dist/lib/oauth.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Google, Apple, generateState, generateCodeVerifier } from "arctic";
|
|
2
2
|
import { getRedis } from "./redis";
|
|
3
|
-
import { appConnection } from "./mongo";
|
|
3
|
+
import { appConnection, mongoose } from "./mongo";
|
|
4
4
|
import { getAppName } from "./appConfig";
|
|
5
|
-
import { Schema } from "mongoose";
|
|
6
5
|
import { sqliteStoreOAuthState, sqliteConsumeOAuthState } from "../adapters/sqliteAuth";
|
|
7
6
|
import { memoryStoreOAuthState, memoryConsumeOAuthState } from "../adapters/memoryAuth";
|
|
8
7
|
let _providers = {};
|
|
@@ -29,15 +28,17 @@ export const getApple = () => {
|
|
|
29
28
|
export const getConfiguredOAuthProviders = () => Object.entries(_providers)
|
|
30
29
|
.filter(([, v]) => v != null)
|
|
31
30
|
.map(([k]) => k);
|
|
32
|
-
const oauthStateSchema = new Schema({
|
|
33
|
-
state: { type: String, required: true, unique: true },
|
|
34
|
-
codeVerifier: { type: String },
|
|
35
|
-
linkUserId: { type: String },
|
|
36
|
-
expiresAt: { type: Date, required: true, index: { expireAfterSeconds: 0 } },
|
|
37
|
-
}, { collection: "oauth_states" });
|
|
38
31
|
function getOAuthStateModel() {
|
|
39
|
-
|
|
40
|
-
appConnection.
|
|
32
|
+
if (appConnection.models["OAuthState"])
|
|
33
|
+
return appConnection.models["OAuthState"];
|
|
34
|
+
const { Schema } = mongoose;
|
|
35
|
+
const oauthStateSchema = new Schema({
|
|
36
|
+
state: { type: String, required: true, unique: true },
|
|
37
|
+
codeVerifier: { type: String },
|
|
38
|
+
linkUserId: { type: String },
|
|
39
|
+
expiresAt: { type: Date, required: true, index: { expireAfterSeconds: 0 } },
|
|
40
|
+
}, { collection: "oauth_states" });
|
|
41
|
+
return appConnection.model("OAuthState", oauthStateSchema);
|
|
41
42
|
}
|
|
42
43
|
let _oauthStore = "redis";
|
|
43
44
|
export const setOAuthStateStore = (store) => { _oauthStore = store; };
|
package/dist/lib/queue.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Queue, Worker } from "bullmq";
|
|
2
|
-
|
|
3
|
-
export declare const
|
|
4
|
-
export declare const createWorker: <T = unknown, R = unknown>(name: string, processor: Processor<T, R>, options?: Omit<WorkerOptions, "connection">) => Worker<T, R>;
|
|
1
|
+
import type { Queue as QueueType, Worker as WorkerType, Processor, QueueOptions, WorkerOptions, Job } from "bullmq";
|
|
2
|
+
export declare const createQueue: <T = unknown, R = unknown>(name: string, options?: Omit<QueueOptions, "connection">) => QueueType<T, R>;
|
|
3
|
+
export declare const createWorker: <T = unknown, R = unknown>(name: string, processor: Processor<T, R>, options?: Omit<WorkerOptions, "connection">) => WorkerType<T, R>;
|
|
5
4
|
export type { Job };
|
package/dist/lib/queue.js
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
import { Queue, Worker } from "bullmq";
|
|
2
1
|
import { getRedisConnectionOptions } from "./redis";
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
function requireBullMQ() {
|
|
3
|
+
try {
|
|
4
|
+
// Bun supports require() in ESM; this defers the import to call time
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
6
|
+
return require("bullmq");
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
throw new Error("bullmq is not installed. Run: bun add bullmq");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export const createQueue = (name, options) => {
|
|
13
|
+
const { Queue } = requireBullMQ();
|
|
14
|
+
return new Queue(name, { connection: getRedisConnectionOptions(), ...options });
|
|
15
|
+
};
|
|
16
|
+
export const createWorker = (name, processor, options) => {
|
|
17
|
+
const { Worker } = requireBullMQ();
|
|
18
|
+
return new Worker(name, processor, { connection: getRedisConnectionOptions(), ...options });
|
|
19
|
+
};
|
package/dist/lib/redis.d.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
export declare const getRedisConnectionOptions: () =>
|
|
3
|
-
password?: string | undefined;
|
|
4
|
-
username?: string | undefined;
|
|
5
|
-
host: string;
|
|
6
|
-
port: number;
|
|
7
|
-
};
|
|
1
|
+
import type { default as RedisClass, RedisOptions } from "ioredis";
|
|
2
|
+
export declare const getRedisConnectionOptions: () => RedisOptions;
|
|
8
3
|
export declare const connectRedis: () => Promise<void>;
|
|
9
4
|
/**
|
|
10
5
|
* Gracefully close the Redis connection.
|
|
11
6
|
* Useful for one-off scripts that need a clean exit.
|
|
12
7
|
*/
|
|
13
8
|
export declare const disconnectRedis: () => Promise<void>;
|
|
14
|
-
export declare const getRedis: () =>
|
|
9
|
+
export declare const getRedis: () => RedisClass;
|
package/dist/lib/redis.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import Redis from "ioredis";
|
|
2
1
|
import { log } from "./logger";
|
|
3
2
|
const isProd = process.env.NODE_ENV === "production";
|
|
3
|
+
function requireIoredis() {
|
|
4
|
+
try {
|
|
5
|
+
// Bun supports require() in ESM; this defers the import to call time
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
7
|
+
const mod = require("ioredis");
|
|
8
|
+
return mod.default ?? mod;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
throw new Error("ioredis is not installed. Run: bun add ioredis");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
4
14
|
export const getRedisConnectionOptions = () => {
|
|
5
15
|
const host_port = isProd ? process.env.REDIS_HOST_PROD : process.env.REDIS_HOST_DEV;
|
|
6
16
|
if (!host_port)
|
|
@@ -18,17 +28,18 @@ export const getRedisConnectionOptions = () => {
|
|
|
18
28
|
};
|
|
19
29
|
};
|
|
20
30
|
let client = null;
|
|
21
|
-
const createClient = () => {
|
|
22
|
-
const redis = new Redis(getRedisConnectionOptions());
|
|
23
|
-
redis.on("error", (err) => log(`[redis] error: ${err.message}`));
|
|
24
|
-
return redis;
|
|
25
|
-
};
|
|
26
31
|
export const connectRedis = () => {
|
|
27
32
|
if (client)
|
|
28
33
|
return Promise.resolve();
|
|
29
|
-
|
|
34
|
+
const Redis = requireIoredis();
|
|
35
|
+
client = new Redis(getRedisConnectionOptions());
|
|
36
|
+
client.on("error", (err) => log(`[redis] error: ${err.message}`));
|
|
30
37
|
return new Promise((resolve, reject) => {
|
|
31
|
-
client.once("ready", () => {
|
|
38
|
+
client.once("ready", () => {
|
|
39
|
+
const opts = getRedisConnectionOptions();
|
|
40
|
+
log(`[redis] connected to ${opts.host}:${opts.port} as ${opts.username || "default user"}`);
|
|
41
|
+
resolve();
|
|
42
|
+
});
|
|
32
43
|
client.once("error", reject);
|
|
33
44
|
});
|
|
34
45
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type ResetStore = "redis" | "mongo" | "sqlite" | "memory";
|
|
2
|
+
export declare const setPasswordResetStore: (store: ResetStore) => void;
|
|
3
|
+
/** Create a reset token. Returns the raw token (to embed in the email link).
|
|
4
|
+
* Only the SHA-256 hash is persisted in the store. */
|
|
5
|
+
export declare const createResetToken: (userId: string, email: string) => Promise<string>;
|
|
6
|
+
/** Atomically consume a reset token — returns its payload and deletes it in one operation.
|
|
7
|
+
* Returns null if the token is invalid, expired, or already used. */
|
|
8
|
+
export declare const consumeResetToken: (token: string) => Promise<{
|
|
9
|
+
userId: string;
|
|
10
|
+
email: string;
|
|
11
|
+
} | null>;
|
|
12
|
+
export {};
|