@lastshotlabs/bunshot 0.0.10 → 0.0.16
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 +2510 -1580
- package/dist/adapters/memoryAuth.d.ts +4 -0
- package/dist/adapters/memoryAuth.js +131 -2
- package/dist/adapters/mongoAuth.js +56 -0
- package/dist/adapters/sqliteAuth.d.ts +6 -0
- package/dist/adapters/sqliteAuth.js +137 -2
- package/dist/app.d.ts +107 -2
- package/dist/app.js +83 -4
- package/dist/entrypoints/queue.d.ts +2 -2
- package/dist/entrypoints/queue.js +1 -1
- package/dist/index.d.ts +15 -5
- package/dist/index.js +10 -3
- package/dist/lib/appConfig.d.ts +46 -0
- package/dist/lib/appConfig.js +20 -0
- package/dist/lib/authAdapter.d.ts +30 -0
- package/dist/lib/constants.d.ts +2 -0
- package/dist/lib/constants.js +2 -0
- package/dist/lib/context.d.ts +2 -0
- package/dist/lib/createDtoMapper.d.ts +33 -0
- package/dist/lib/createDtoMapper.js +69 -0
- package/dist/lib/createRoute.d.ts +61 -0
- package/dist/lib/createRoute.js +147 -0
- package/dist/lib/jwt.d.ts +1 -1
- package/dist/lib/jwt.js +2 -2
- package/dist/lib/mfaChallenge.d.ts +20 -0
- package/dist/lib/mfaChallenge.js +184 -0
- package/dist/lib/queue.d.ts +33 -0
- package/dist/lib/queue.js +98 -0
- package/dist/lib/roles.d.ts +4 -0
- package/dist/lib/roles.js +27 -0
- package/dist/lib/session.d.ts +12 -0
- package/dist/lib/session.js +163 -5
- package/dist/lib/tenant.d.ts +15 -0
- package/dist/lib/tenant.js +65 -0
- package/dist/lib/zodToMongoose.d.ts +38 -0
- package/dist/lib/zodToMongoose.js +84 -0
- package/dist/middleware/cacheResponse.js +4 -1
- package/dist/middleware/rateLimit.d.ts +2 -1
- package/dist/middleware/rateLimit.js +5 -2
- package/dist/middleware/requireRole.d.ts +14 -3
- package/dist/middleware/requireRole.js +46 -6
- package/dist/middleware/tenant.d.ts +5 -0
- package/dist/middleware/tenant.js +116 -0
- package/dist/models/AuthUser.d.ts +8 -0
- package/dist/models/AuthUser.js +8 -0
- package/dist/models/TenantRole.d.ts +15 -0
- package/dist/models/TenantRole.js +23 -0
- package/dist/routes/auth.d.ts +5 -3
- package/dist/routes/auth.js +253 -80
- package/dist/routes/jobs.d.ts +2 -0
- package/dist/routes/jobs.js +270 -0
- package/dist/routes/mfa.d.ts +1 -0
- package/dist/routes/mfa.js +409 -0
- package/dist/routes/oauth.js +107 -16
- package/dist/server.js +9 -0
- package/dist/services/auth.d.ts +21 -2
- package/dist/services/auth.js +97 -17
- package/dist/services/mfa.d.ts +37 -0
- package/dist/services/mfa.js +276 -0
- package/docs/sections/adding-middleware/full.md +35 -0
- package/docs/sections/adding-models/full.md +125 -0
- package/docs/sections/adding-models/overview.md +13 -0
- package/docs/sections/adding-routes/full.md +182 -0
- package/docs/sections/adding-routes/overview.md +23 -0
- package/docs/sections/auth-flow/full.md +456 -0
- package/docs/sections/auth-flow/overview.md +10 -0
- package/docs/sections/cli/full.md +30 -0
- package/docs/sections/configuration/full.md +135 -0
- package/docs/sections/configuration/overview.md +17 -0
- package/docs/sections/configuration-example/full.md +99 -0
- package/docs/sections/configuration-example/overview.md +30 -0
- package/docs/sections/documentation/full.md +171 -0
- package/docs/sections/environment-variables/full.md +55 -0
- package/docs/sections/exports/full.md +83 -0
- package/docs/sections/extending-context/full.md +59 -0
- package/docs/sections/header.md +3 -0
- package/docs/sections/installation/full.md +6 -0
- package/docs/sections/jobs/full.md +140 -0
- package/docs/sections/jobs/overview.md +15 -0
- package/docs/sections/mongodb-connections/full.md +45 -0
- package/docs/sections/mongodb-connections/overview.md +7 -0
- package/docs/sections/multi-tenancy/full.md +62 -0
- package/docs/sections/multi-tenancy/overview.md +15 -0
- package/docs/sections/oauth/full.md +119 -0
- package/docs/sections/oauth/overview.md +16 -0
- package/docs/sections/package-development/full.md +7 -0
- package/docs/sections/peer-dependencies/full.md +43 -0
- package/docs/sections/quick-start/full.md +43 -0
- package/docs/sections/response-caching/full.md +115 -0
- package/docs/sections/response-caching/overview.md +13 -0
- package/docs/sections/roles/full.md +136 -0
- package/docs/sections/roles/overview.md +12 -0
- package/docs/sections/running-without-redis/full.md +16 -0
- package/docs/sections/running-without-redis-or-mongodb/full.md +60 -0
- package/docs/sections/stack/full.md +10 -0
- package/docs/sections/websocket/full.md +100 -0
- package/docs/sections/websocket/overview.md +5 -0
- package/docs/sections/websocket-rooms/full.md +97 -0
- package/docs/sections/websocket-rooms/overview.md +5 -0
- package/package.json +19 -10
package/dist/app.js
CHANGED
|
@@ -7,8 +7,8 @@ import { HttpError } from "./lib/HttpError";
|
|
|
7
7
|
import { rateLimit } from "./middleware/rateLimit";
|
|
8
8
|
import { bearerAuth } from "./middleware/bearerAuth";
|
|
9
9
|
import { identify } from "./middleware/identify";
|
|
10
|
-
import { HEADER_USER_TOKEN } from "./lib/constants";
|
|
11
|
-
import { setAppName, setAppRoles, setDefaultRole, setPrimaryField, setEmailVerificationConfig, setPasswordResetConfig, setMaxSessions, setPersistSessionMetadata, setIncludeInactiveSessions, setTrackLastActive } from "./lib/appConfig";
|
|
10
|
+
import { HEADER_USER_TOKEN, HEADER_REFRESH_TOKEN } from "./lib/constants";
|
|
11
|
+
import { setAppName, setAppRoles, setDefaultRole, setPrimaryField, setEmailVerificationConfig, setPasswordResetConfig, setMaxSessions, setPersistSessionMetadata, setIncludeInactiveSessions, setTrackLastActive, setRefreshTokenConfig, setMfaConfig } from "./lib/appConfig";
|
|
12
12
|
import { setEmailVerificationStore } from "./lib/emailVerification";
|
|
13
13
|
import { setPasswordResetStore } from "./lib/resetPassword";
|
|
14
14
|
import { setAuthRateLimitStore } from "./lib/authRateLimit";
|
|
@@ -21,6 +21,7 @@ import { connectMongo, connectAuthMongo, connectAppMongo } from "./lib/mongo";
|
|
|
21
21
|
import { connectRedis } from "./lib/redis";
|
|
22
22
|
import { setSessionStore } from "./lib/session";
|
|
23
23
|
import { setCacheStore } from "./middleware/cacheResponse";
|
|
24
|
+
import { maybeAutoRegister } from "./lib/createRoute";
|
|
24
25
|
export const createApp = async (config) => {
|
|
25
26
|
const { routesDir, app: appConfig = {}, auth: authConfig = {}, security: securityConfig = {}, middleware = [], db = {}, } = config;
|
|
26
27
|
const appName = appConfig.name ?? "Bun Core API";
|
|
@@ -109,6 +110,8 @@ export const createApp = async (config) => {
|
|
|
109
110
|
setPersistSessionMetadata(sessionPolicy.persistSessionMetadata ?? true);
|
|
110
111
|
setIncludeInactiveSessions(sessionPolicy.includeInactiveSessions ?? false);
|
|
111
112
|
setTrackLastActive(sessionPolicy.trackLastActive ?? false);
|
|
113
|
+
setRefreshTokenConfig(authConfig.refreshTokens ?? null);
|
|
114
|
+
setMfaConfig(authConfig.mfa ?? null);
|
|
112
115
|
if (oauthProviders)
|
|
113
116
|
initOAuthProviders(oauthProviders);
|
|
114
117
|
const configuredOAuth = getConfiguredOAuthProviders();
|
|
@@ -124,7 +127,7 @@ export const createApp = async (config) => {
|
|
|
124
127
|
const app = new OpenAPIHono();
|
|
125
128
|
app.use(logger());
|
|
126
129
|
app.use(secureHeaders());
|
|
127
|
-
app.use(cors({ origin: corsOrigins, allowHeaders: ["Content-Type", "Authorization", HEADER_USER_TOKEN], exposeHeaders: ["x-cache"], credentials: true }));
|
|
130
|
+
app.use(cors({ origin: corsOrigins, allowHeaders: ["Content-Type", "Authorization", HEADER_USER_TOKEN, HEADER_REFRESH_TOKEN], exposeHeaders: ["x-cache"], credentials: true }));
|
|
128
131
|
if ((botCfg.blockList?.length ?? 0) > 0) {
|
|
129
132
|
const { botProtection } = await import("./middleware/botProtection");
|
|
130
133
|
app.use(botProtection({ blockList: botCfg.blockList }));
|
|
@@ -140,9 +143,50 @@ export const createApp = async (config) => {
|
|
|
140
143
|
});
|
|
141
144
|
}
|
|
142
145
|
app.use(identify);
|
|
146
|
+
// Tenant resolution middleware (after identify, before user middleware + routes)
|
|
147
|
+
if (config.tenancy) {
|
|
148
|
+
const { createTenantMiddleware } = await import("./middleware/tenant");
|
|
149
|
+
app.use(createTenantMiddleware(config.tenancy));
|
|
150
|
+
}
|
|
143
151
|
for (const mw of middleware)
|
|
144
152
|
app.use(mw);
|
|
145
153
|
setAppName(appName);
|
|
154
|
+
// Schema pre-loading — import shared schema files before routes so registerSchema /
|
|
155
|
+
// registerSchemas calls run first, guaranteeing $ref instead of inline shapes.
|
|
156
|
+
const msConfig = config.modelSchemas;
|
|
157
|
+
if (msConfig) {
|
|
158
|
+
const { paths, registration = "auto" } = typeof msConfig === "string" || Array.isArray(msConfig)
|
|
159
|
+
? { paths: msConfig, registration: "auto" }
|
|
160
|
+
: msConfig;
|
|
161
|
+
const pathArray = paths ? (Array.isArray(paths) ? paths : [paths]) : [];
|
|
162
|
+
for (const entry of pathArray) {
|
|
163
|
+
// Normalize to forward slashes so splitting works on both Windows and Unix.
|
|
164
|
+
const normalized = entry.replaceAll("\\", "/");
|
|
165
|
+
// Split glob patterns: everything before the first wildcard segment is the cwd.
|
|
166
|
+
let cwd;
|
|
167
|
+
let pattern;
|
|
168
|
+
if (!normalized.includes("*")) {
|
|
169
|
+
cwd = normalized;
|
|
170
|
+
pattern = "**/*.ts";
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
const parts = normalized.split("/");
|
|
174
|
+
const starIdx = parts.findIndex((p) => p.includes("*"));
|
|
175
|
+
cwd = parts.slice(0, starIdx).join("/");
|
|
176
|
+
pattern = parts.slice(starIdx).join("/");
|
|
177
|
+
}
|
|
178
|
+
const schemaGlob = new Bun.Glob(pattern);
|
|
179
|
+
for await (const file of schemaGlob.scan({ cwd })) {
|
|
180
|
+
const mod = await import(`${cwd}/${file}`);
|
|
181
|
+
if (registration === "auto") {
|
|
182
|
+
for (const [exportName, value] of Object.entries(mod)) {
|
|
183
|
+
maybeAutoRegister(exportName, value);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// "explicit": file imported; any registerSchema/registerSchemas calls inside already ran
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
146
190
|
// Core routes (auth, etc.)
|
|
147
191
|
const coreRoutesDir = import.meta.dir + "/routes";
|
|
148
192
|
const coreGlob = new Bun.Glob("*.ts");
|
|
@@ -151,17 +195,35 @@ export const createApp = async (config) => {
|
|
|
151
195
|
continue; // mounted separately below via createAuthRouter
|
|
152
196
|
if (file === "oauth.ts")
|
|
153
197
|
continue; // mounted separately below
|
|
198
|
+
if (file === "mfa.ts")
|
|
199
|
+
continue; // mounted separately below when mfa is configured
|
|
200
|
+
if (file === "jobs.ts")
|
|
201
|
+
continue; // mounted separately below when jobs.statusEndpoint is true
|
|
154
202
|
const mod = await import(`${coreRoutesDir}/${file}`);
|
|
155
203
|
if (mod.router)
|
|
156
204
|
app.route("/", mod.router);
|
|
157
205
|
}
|
|
158
206
|
if (enableAuthRoutes) {
|
|
159
207
|
const { createAuthRouter } = await import(`${coreRoutesDir}/auth`);
|
|
160
|
-
app.route("/", createAuthRouter({ primaryField, emailVerification, passwordReset, rateLimit: authRateLimit }));
|
|
208
|
+
app.route("/", createAuthRouter({ primaryField, emailVerification, passwordReset, rateLimit: authRateLimit, accountDeletion: authConfig.accountDeletion, refreshTokens: authConfig.refreshTokens }));
|
|
161
209
|
}
|
|
162
210
|
if (configuredOAuth.length > 0) {
|
|
163
211
|
app.route("/", createOAuthRouter(configuredOAuth, postOAuthRedirect));
|
|
164
212
|
}
|
|
213
|
+
if (authConfig.mfa && enableAuthRoutes) {
|
|
214
|
+
const { setMfaChallengeStore, setMfaChallengeSqliteDb } = await import("./lib/mfaChallenge");
|
|
215
|
+
setMfaChallengeStore(sessions);
|
|
216
|
+
if (sessions === "sqlite") {
|
|
217
|
+
const { getDb } = await import("./adapters/sqliteAuth");
|
|
218
|
+
setMfaChallengeSqliteDb(getDb());
|
|
219
|
+
}
|
|
220
|
+
const { createMfaRouter } = await import(`${coreRoutesDir}/mfa`);
|
|
221
|
+
app.route("/", createMfaRouter());
|
|
222
|
+
}
|
|
223
|
+
if (config.jobs?.statusEndpoint) {
|
|
224
|
+
const { createJobsRouter } = await import(`${coreRoutesDir}/jobs`);
|
|
225
|
+
app.route("/", createJobsRouter(config.jobs));
|
|
226
|
+
}
|
|
165
227
|
// Service routes — collect all, sort by optional exported `priority`, then mount
|
|
166
228
|
const serviceGlob = new Bun.Glob("**/*.ts");
|
|
167
229
|
const serviceFiles = [];
|
|
@@ -186,6 +248,23 @@ export const createApp = async (config) => {
|
|
|
186
248
|
return c.json({ error: "Internal Server Error" }, 500);
|
|
187
249
|
});
|
|
188
250
|
app.notFound((c) => c.json({ error: "Not Found" }, 404));
|
|
251
|
+
app.openAPIRegistry.registerComponent("securitySchemes", "cookieAuth", {
|
|
252
|
+
type: "apiKey",
|
|
253
|
+
in: "cookie",
|
|
254
|
+
name: "token",
|
|
255
|
+
description: "Session cookie set automatically on login/register.",
|
|
256
|
+
});
|
|
257
|
+
app.openAPIRegistry.registerComponent("securitySchemes", "userToken", {
|
|
258
|
+
type: "apiKey",
|
|
259
|
+
in: "header",
|
|
260
|
+
name: "x-user-token",
|
|
261
|
+
description: "JWT session token passed as the x-user-token request header (alternative to the session cookie).",
|
|
262
|
+
});
|
|
263
|
+
app.openAPIRegistry.registerComponent("securitySchemes", "bearerAuth", {
|
|
264
|
+
type: "http",
|
|
265
|
+
scheme: "bearer",
|
|
266
|
+
description: "API key passed as Authorization: Bearer <token>. Required on all endpoints unless bearer auth is disabled in CreateAppConfig or the path is in the bypass list.",
|
|
267
|
+
});
|
|
189
268
|
app.doc("/openapi.json", { openapi: "3.0.0", info: { title: appName, version: openApiVersion } });
|
|
190
269
|
app.get("/docs", Scalar({ url: "/openapi.json" }));
|
|
191
270
|
app.get("/sw.js", (c) => c.body("", 200, { "Content-Type": "application/javascript" }));
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { createQueue, createWorker } from "../lib/queue";
|
|
2
|
-
export type { Job } from "../lib/queue";
|
|
1
|
+
export { createQueue, createWorker, createCronWorker, cleanupStaleSchedulers, getRegisteredCronNames, createDLQHandler } from "../lib/queue";
|
|
2
|
+
export type { Job, CronSchedule, DLQOptions } from "../lib/queue";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { createQueue, createWorker } from "../lib/queue";
|
|
1
|
+
export { createQueue, createWorker, createCronWorker, cleanupStaleSchedulers, getRegisteredCronNames, createDLQHandler } from "../lib/queue";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
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, PasswordResetConfig } from "./app";
|
|
3
|
+
export type { CreateAppConfig, ModelSchemasConfig, DbConfig, AppMeta, AuthConfig, AuthRateLimitConfig, AccountDeletionConfig, OAuthConfig, SecurityConfig, BotProtectionConfig, PrimaryField, EmailVerificationConfig, PasswordResetConfig, RefreshTokenConfig, MfaConfig, MfaEmailOtpConfig, JobsConfig, TenancyConfig, TenantConfig } from "./app";
|
|
4
4
|
export type { CreateServerConfig, WsConfig } from "./server";
|
|
5
5
|
export { appConnection, authConnection, mongoose, connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo } from "./lib/mongo";
|
|
6
6
|
export { connectRedis, disconnectRedis, getRedis } from "./lib/redis";
|
|
7
7
|
export { getAppRoles } from "./lib/appConfig";
|
|
8
8
|
export { HttpError } from "./lib/HttpError";
|
|
9
|
-
export { COOKIE_TOKEN, HEADER_USER_TOKEN } from "./lib/constants";
|
|
9
|
+
export { COOKIE_TOKEN, HEADER_USER_TOKEN, COOKIE_REFRESH_TOKEN, HEADER_REFRESH_TOKEN } from "./lib/constants";
|
|
10
10
|
export { createRouter } from "./lib/context";
|
|
11
|
+
export { createRoute, withSecurity, registerSchema, registerSchemas } from "./lib/createRoute";
|
|
12
|
+
export { zodToMongoose } from "./lib/zodToMongoose";
|
|
13
|
+
export type { ZodToMongooseConfig, ZodToMongooseRefConfig } from "./lib/zodToMongoose";
|
|
14
|
+
export { createDtoMapper } from "./lib/createDtoMapper";
|
|
15
|
+
export type { DtoMapperConfig } from "./lib/createDtoMapper";
|
|
11
16
|
export type { AppEnv, AppVariables } from "./lib/context";
|
|
12
17
|
export { signToken, verifyToken } from "./lib/jwt";
|
|
13
18
|
export { log } from "./lib/logger";
|
|
14
19
|
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";
|
|
20
|
+
export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore, deleteUserSessions, setRefreshToken, getSessionByRefreshToken, rotateRefreshToken } from "./lib/session";
|
|
21
|
+
export type { SessionMetadata, SessionInfo, RefreshResult } from "./lib/session";
|
|
17
22
|
export { createVerificationToken, getVerificationToken, deleteVerificationToken } from "./lib/emailVerification";
|
|
23
|
+
export { createMfaChallenge, consumeMfaChallenge, replaceMfaChallengeOtp, setMfaChallengeStore } from "./lib/mfaChallenge";
|
|
24
|
+
export type { MfaChallengeData } from "./lib/mfaChallenge";
|
|
18
25
|
export { bustAuthLimit, trackAttempt, isLimited } from "./lib/authRateLimit";
|
|
19
26
|
export type { LimitOpts } from "./lib/authRateLimit";
|
|
20
27
|
export { validate } from "./lib/validate";
|
|
@@ -31,9 +38,12 @@ export { cacheResponse, bustCache, bustCachePattern, setCacheStore } from "./mid
|
|
|
31
38
|
export { buildFingerprint } from "./lib/fingerprint";
|
|
32
39
|
export { sqliteAuthAdapter, setSqliteDb, startSqliteCleanup } from "./adapters/sqliteAuth";
|
|
33
40
|
export { memoryAuthAdapter, clearMemoryStore } from "./adapters/memoryAuth";
|
|
34
|
-
export { setUserRoles, addUserRole, removeUserRole } from "./lib/roles";
|
|
41
|
+
export { setUserRoles, addUserRole, removeUserRole, getTenantRoles, setTenantRoles, addTenantRole, removeTenantRole } from "./lib/roles";
|
|
35
42
|
export type { AuthAdapter, OAuthProfile } from "./lib/authAdapter";
|
|
36
43
|
export type { OAuthProviderConfig } from "./lib/oauth";
|
|
37
44
|
export { websocket, createWsUpgradeHandler } from "./ws/index";
|
|
38
45
|
export type { SocketData } from "./ws/index";
|
|
39
46
|
export { publish, subscribe, unsubscribe, getSubscriptions, handleRoomActions, getRooms, getRoomSubscribers } from "./lib/ws";
|
|
47
|
+
export { createTenant, deleteTenant, getTenant, listTenants } from "./lib/tenant";
|
|
48
|
+
export type { TenantInfo, CreateTenantOptions } from "./lib/tenant";
|
|
49
|
+
export { invalidateTenantCache } from "./middleware/tenant";
|
package/dist/index.js
CHANGED
|
@@ -7,13 +7,17 @@ export { connectRedis, disconnectRedis, getRedis } from "./lib/redis";
|
|
|
7
7
|
// Lib utilities
|
|
8
8
|
export { getAppRoles } from "./lib/appConfig";
|
|
9
9
|
export { HttpError } from "./lib/HttpError";
|
|
10
|
-
export { COOKIE_TOKEN, HEADER_USER_TOKEN } from "./lib/constants";
|
|
10
|
+
export { COOKIE_TOKEN, HEADER_USER_TOKEN, COOKIE_REFRESH_TOKEN, HEADER_REFRESH_TOKEN } from "./lib/constants";
|
|
11
11
|
export { createRouter } from "./lib/context";
|
|
12
|
+
export { createRoute, withSecurity, registerSchema, registerSchemas } from "./lib/createRoute";
|
|
13
|
+
export { zodToMongoose } from "./lib/zodToMongoose";
|
|
14
|
+
export { createDtoMapper } from "./lib/createDtoMapper";
|
|
12
15
|
export { signToken, verifyToken } from "./lib/jwt";
|
|
13
16
|
export { log } from "./lib/logger";
|
|
14
17
|
export { createResetToken, consumeResetToken, setPasswordResetStore } from "./lib/resetPassword";
|
|
15
|
-
export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore } from "./lib/session";
|
|
18
|
+
export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore, deleteUserSessions, setRefreshToken, getSessionByRefreshToken, rotateRefreshToken } from "./lib/session";
|
|
16
19
|
export { createVerificationToken, getVerificationToken, deleteVerificationToken } from "./lib/emailVerification";
|
|
20
|
+
export { createMfaChallenge, consumeMfaChallenge, replaceMfaChallengeOtp, setMfaChallengeStore } from "./lib/mfaChallenge";
|
|
17
21
|
export { bustAuthLimit, trackAttempt, isLimited } from "./lib/authRateLimit";
|
|
18
22
|
export { validate } from "./lib/validate";
|
|
19
23
|
// Middleware
|
|
@@ -30,7 +34,10 @@ export { buildFingerprint } from "./lib/fingerprint";
|
|
|
30
34
|
// Models
|
|
31
35
|
export { sqliteAuthAdapter, setSqliteDb, startSqliteCleanup } from "./adapters/sqliteAuth";
|
|
32
36
|
export { memoryAuthAdapter, clearMemoryStore } from "./adapters/memoryAuth";
|
|
33
|
-
export { setUserRoles, addUserRole, removeUserRole } from "./lib/roles";
|
|
37
|
+
export { setUserRoles, addUserRole, removeUserRole, getTenantRoles, setTenantRoles, addTenantRole, removeTenantRole } from "./lib/roles";
|
|
34
38
|
// WebSocket
|
|
35
39
|
export { websocket, createWsUpgradeHandler } from "./ws/index";
|
|
36
40
|
export { publish, subscribe, unsubscribe, getSubscriptions, handleRoomActions, getRooms, getRoomSubscribers } from "./lib/ws";
|
|
41
|
+
// Tenancy
|
|
42
|
+
export { createTenant, deleteTenant, getTenant, listTenants } from "./lib/tenant";
|
|
43
|
+
export { invalidateTenantCache } from "./middleware/tenant";
|
package/dist/lib/appConfig.d.ts
CHANGED
|
@@ -35,3 +35,49 @@ export declare const setIncludeInactiveSessions: (v: boolean) => void;
|
|
|
35
35
|
export declare const getIncludeInactiveSessions: () => boolean;
|
|
36
36
|
export declare const setTrackLastActive: (v: boolean) => void;
|
|
37
37
|
export declare const getTrackLastActive: () => boolean;
|
|
38
|
+
export interface RefreshTokenConfig {
|
|
39
|
+
/** Access token expiry in seconds. Default: 900 (15 min). */
|
|
40
|
+
accessTokenExpiry?: number;
|
|
41
|
+
/** Refresh token expiry in seconds. Default: 2_592_000 (30 days). */
|
|
42
|
+
refreshTokenExpiry?: number;
|
|
43
|
+
/** Grace window in seconds where the old refresh token still works after rotation.
|
|
44
|
+
* Prevents lockout when the client's network drops mid-refresh. Default: 30. */
|
|
45
|
+
rotationGraceSeconds?: number;
|
|
46
|
+
}
|
|
47
|
+
export declare const setRefreshTokenConfig: (config: RefreshTokenConfig | null) => void;
|
|
48
|
+
export declare const getRefreshTokenConfig: () => RefreshTokenConfig | null;
|
|
49
|
+
export declare const getAccessTokenExpiry: () => number;
|
|
50
|
+
export declare const getRefreshTokenExpiry: () => number;
|
|
51
|
+
export declare const getRotationGraceSeconds: () => number;
|
|
52
|
+
export interface MfaEmailOtpConfig {
|
|
53
|
+
/** Called with the user's email and the OTP code. Use to send the email. */
|
|
54
|
+
onSend: (email: string, code: string) => Promise<void>;
|
|
55
|
+
/** OTP code length. Default: 6. */
|
|
56
|
+
codeLength?: number;
|
|
57
|
+
}
|
|
58
|
+
export interface MfaConfig {
|
|
59
|
+
/** Issuer name shown in authenticator apps. Defaults to app name. */
|
|
60
|
+
issuer?: string;
|
|
61
|
+
/** TOTP algorithm. Default: "SHA1" (most compatible). */
|
|
62
|
+
algorithm?: "SHA1" | "SHA256" | "SHA512";
|
|
63
|
+
/** TOTP digits. Default: 6. */
|
|
64
|
+
digits?: number;
|
|
65
|
+
/** TOTP period in seconds. Default: 30. */
|
|
66
|
+
period?: number;
|
|
67
|
+
/** Number of recovery codes to generate. Default: 10. */
|
|
68
|
+
recoveryCodes?: number;
|
|
69
|
+
/** MFA challenge window in seconds. Default: 300 (5 min). */
|
|
70
|
+
challengeTtlSeconds?: number;
|
|
71
|
+
/** Email OTP configuration. When set, enables email-based MFA as an option. */
|
|
72
|
+
emailOtp?: MfaEmailOtpConfig;
|
|
73
|
+
}
|
|
74
|
+
export declare const setMfaConfig: (config: MfaConfig | null) => void;
|
|
75
|
+
export declare const getMfaConfig: () => MfaConfig | null;
|
|
76
|
+
export declare const getMfaIssuer: () => string;
|
|
77
|
+
export declare const getMfaAlgorithm: () => string;
|
|
78
|
+
export declare const getMfaDigits: () => number;
|
|
79
|
+
export declare const getMfaPeriod: () => number;
|
|
80
|
+
export declare const getMfaRecoveryCodeCount: () => number;
|
|
81
|
+
export declare const getMfaChallengeTtl: () => number;
|
|
82
|
+
export declare const getMfaEmailOtpConfig: () => MfaEmailOtpConfig | null;
|
|
83
|
+
export declare const getMfaEmailOtpCodeLength: () => number;
|
package/dist/lib/appConfig.js
CHANGED
|
@@ -35,3 +35,23 @@ export const setIncludeInactiveSessions = (v) => { _includeInactiveSessions = v;
|
|
|
35
35
|
export const getIncludeInactiveSessions = () => _includeInactiveSessions;
|
|
36
36
|
export const setTrackLastActive = (v) => { _trackLastActive = v; };
|
|
37
37
|
export const getTrackLastActive = () => _trackLastActive;
|
|
38
|
+
let _refreshTokenConfig = null;
|
|
39
|
+
export const setRefreshTokenConfig = (config) => { _refreshTokenConfig = config; };
|
|
40
|
+
export const getRefreshTokenConfig = () => _refreshTokenConfig;
|
|
41
|
+
const DEFAULT_ACCESS_TOKEN_EXPIRY = 900; // 15 min
|
|
42
|
+
const DEFAULT_REFRESH_TOKEN_EXPIRY = 2_592_000; // 30 days
|
|
43
|
+
const DEFAULT_ROTATION_GRACE_SECONDS = 30;
|
|
44
|
+
export const getAccessTokenExpiry = () => _refreshTokenConfig?.accessTokenExpiry ?? DEFAULT_ACCESS_TOKEN_EXPIRY;
|
|
45
|
+
export const getRefreshTokenExpiry = () => _refreshTokenConfig?.refreshTokenExpiry ?? DEFAULT_REFRESH_TOKEN_EXPIRY;
|
|
46
|
+
export const getRotationGraceSeconds = () => _refreshTokenConfig?.rotationGraceSeconds ?? DEFAULT_ROTATION_GRACE_SECONDS;
|
|
47
|
+
let _mfaConfig = null;
|
|
48
|
+
export const setMfaConfig = (config) => { _mfaConfig = config; };
|
|
49
|
+
export const getMfaConfig = () => _mfaConfig;
|
|
50
|
+
export const getMfaIssuer = () => _mfaConfig?.issuer ?? getAppName();
|
|
51
|
+
export const getMfaAlgorithm = () => _mfaConfig?.algorithm ?? "SHA1";
|
|
52
|
+
export const getMfaDigits = () => _mfaConfig?.digits ?? 6;
|
|
53
|
+
export const getMfaPeriod = () => _mfaConfig?.period ?? 30;
|
|
54
|
+
export const getMfaRecoveryCodeCount = () => _mfaConfig?.recoveryCodes ?? 10;
|
|
55
|
+
export const getMfaChallengeTtl = () => _mfaConfig?.challengeTtlSeconds ?? 300;
|
|
56
|
+
export const getMfaEmailOtpConfig = () => _mfaConfig?.emailOtp ?? null;
|
|
57
|
+
export const getMfaEmailOtpCodeLength = () => _mfaConfig?.emailOtp?.codeLength ?? 6;
|
|
@@ -48,6 +48,36 @@ export interface AuthAdapter {
|
|
|
48
48
|
setEmailVerified?(userId: string, verified: boolean): Promise<void>;
|
|
49
49
|
/** Optional. Return whether a user's email address has been verified. */
|
|
50
50
|
getEmailVerified?(userId: string): Promise<boolean>;
|
|
51
|
+
/** Optional. Permanently delete a user account. Used by DELETE /auth/me. */
|
|
52
|
+
deleteUser?(userId: string): Promise<void>;
|
|
53
|
+
/** Optional. Check whether a user has a password set (credential account vs OAuth-only). */
|
|
54
|
+
hasPassword?(userId: string): Promise<boolean>;
|
|
55
|
+
/** Optional. Store the TOTP secret for MFA setup (encrypted or plaintext, adapter decides). */
|
|
56
|
+
setMfaSecret?(userId: string, secret: string | null): Promise<void>;
|
|
57
|
+
/** Optional. Retrieve the TOTP secret for MFA verification. */
|
|
58
|
+
getMfaSecret?(userId: string): Promise<string | null>;
|
|
59
|
+
/** Optional. Check whether MFA is enabled for a user. */
|
|
60
|
+
isMfaEnabled?(userId: string): Promise<boolean>;
|
|
61
|
+
/** Optional. Enable or disable MFA for a user. */
|
|
62
|
+
setMfaEnabled?(userId: string, enabled: boolean): Promise<void>;
|
|
63
|
+
/** Optional. Store hashed recovery codes for MFA. */
|
|
64
|
+
setRecoveryCodes?(userId: string, codes: string[]): Promise<void>;
|
|
65
|
+
/** Optional. Retrieve hashed recovery codes for MFA. */
|
|
66
|
+
getRecoveryCodes?(userId: string): Promise<string[]>;
|
|
67
|
+
/** Optional. Remove a single recovery code after use. */
|
|
68
|
+
removeRecoveryCode?(userId: string, code: string): Promise<void>;
|
|
69
|
+
/** Optional. Get the MFA methods enabled for a user (e.g., ["totp"], ["emailOtp"], ["totp", "emailOtp"]). */
|
|
70
|
+
getMfaMethods?(userId: string): Promise<string[]>;
|
|
71
|
+
/** Optional. Set the MFA methods enabled for a user. */
|
|
72
|
+
setMfaMethods?(userId: string, methods: string[]): Promise<void>;
|
|
73
|
+
/** Optional. Get roles for a user within a specific tenant. */
|
|
74
|
+
getTenantRoles?(userId: string, tenantId: string): Promise<string[]>;
|
|
75
|
+
/** Optional. Set roles for a user within a specific tenant (replaces existing). */
|
|
76
|
+
setTenantRoles?(userId: string, tenantId: string, roles: string[]): Promise<void>;
|
|
77
|
+
/** Optional. Add a single role to a user within a specific tenant. */
|
|
78
|
+
addTenantRole?(userId: string, tenantId: string, role: string): Promise<void>;
|
|
79
|
+
/** Optional. Remove a single role from a user within a specific tenant. */
|
|
80
|
+
removeTenantRole?(userId: string, tenantId: string, role: string): Promise<void>;
|
|
51
81
|
}
|
|
52
82
|
export declare const setAuthAdapter: (adapter: AuthAdapter) => void;
|
|
53
83
|
export declare const getAuthAdapter: () => AuthAdapter;
|
package/dist/lib/constants.d.ts
CHANGED
package/dist/lib/constants.js
CHANGED
package/dist/lib/context.d.ts
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
type ZodSchema = any;
|
|
2
|
+
export type DtoMapperConfig = {
|
|
3
|
+
/** DB field name → API field name for ObjectId refs (e.g., { account: "accountId" }) */
|
|
4
|
+
refs?: Record<string, string>;
|
|
5
|
+
/** API field names that are Date in DB, string in DTO */
|
|
6
|
+
dates?: string[];
|
|
7
|
+
/** Subdocument array fields mapped with a sub-mapper: { items: itemMapper } */
|
|
8
|
+
subdocs?: Record<string, (item: any) => any>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Create a toDto mapper function from a Zod schema.
|
|
12
|
+
*
|
|
13
|
+
* The Zod schema defines which fields exist in the DTO. The config declares
|
|
14
|
+
* how to transform DB-specific types (ObjectId refs, Dates, subdocuments).
|
|
15
|
+
*
|
|
16
|
+
* Handles automatically:
|
|
17
|
+
* - `_id` → `id` (toString)
|
|
18
|
+
* - ObjectId refs → string (toString), with field renaming via `refs`
|
|
19
|
+
* - Date fields → ISO string via `dates`
|
|
20
|
+
* - Subdocument arrays via `subdocs`
|
|
21
|
+
* - Nullable/optional fields → `null` coercion (from `undefined`)
|
|
22
|
+
* - All other fields → passthrough
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const toDto = createDtoMapper<LedgerItemDto>(LedgerItemSchema, {
|
|
27
|
+
* refs: { account: "accountId" },
|
|
28
|
+
* dates: ["date"],
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function createDtoMapper<TDto>(zodSchema: ZodSchema, config?: DtoMapperConfig): (doc: any) => TDto;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** Check if a Zod type is nullable or optional */
|
|
2
|
+
function isNullable(zodType) {
|
|
3
|
+
const defType = zodType?._zod?.def?.type;
|
|
4
|
+
if (defType === "nullable")
|
|
5
|
+
return true;
|
|
6
|
+
if (defType === "optional")
|
|
7
|
+
return true;
|
|
8
|
+
if (defType === "default")
|
|
9
|
+
return isNullable(zodType._zod.def.innerType);
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Create a toDto mapper function from a Zod schema.
|
|
14
|
+
*
|
|
15
|
+
* The Zod schema defines which fields exist in the DTO. The config declares
|
|
16
|
+
* how to transform DB-specific types (ObjectId refs, Dates, subdocuments).
|
|
17
|
+
*
|
|
18
|
+
* Handles automatically:
|
|
19
|
+
* - `_id` → `id` (toString)
|
|
20
|
+
* - ObjectId refs → string (toString), with field renaming via `refs`
|
|
21
|
+
* - Date fields → ISO string via `dates`
|
|
22
|
+
* - Subdocument arrays via `subdocs`
|
|
23
|
+
* - Nullable/optional fields → `null` coercion (from `undefined`)
|
|
24
|
+
* - All other fields → passthrough
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const toDto = createDtoMapper<LedgerItemDto>(LedgerItemSchema, {
|
|
29
|
+
* refs: { account: "accountId" },
|
|
30
|
+
* dates: ["date"],
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function createDtoMapper(zodSchema, config = {}) {
|
|
35
|
+
const apiFields = Object.keys(zodSchema.shape);
|
|
36
|
+
const shape = zodSchema.shape;
|
|
37
|
+
// Build reverse lookup: apiField → dbField for refs
|
|
38
|
+
const refByApiField = new Map();
|
|
39
|
+
if (config.refs) {
|
|
40
|
+
for (const [dbField, apiField] of Object.entries(config.refs)) {
|
|
41
|
+
refByApiField.set(apiField, dbField);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const dateSet = new Set(config.dates ?? []);
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
return (doc) => {
|
|
47
|
+
const dto = {};
|
|
48
|
+
for (const field of apiFields) {
|
|
49
|
+
if (field === "id") {
|
|
50
|
+
dto.id = doc._id.toString();
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (refByApiField.has(field)) {
|
|
54
|
+
dto[field] = doc[refByApiField.get(field)].toString();
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (dateSet.has(field)) {
|
|
58
|
+
dto[field] = doc[field].toISOString();
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (config.subdocs?.[field]) {
|
|
62
|
+
dto[field] = (doc[field] ?? []).map(config.subdocs[field]);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
dto[field] = isNullable(shape[field]) ? (doc[field] ?? null) : doc[field];
|
|
66
|
+
}
|
|
67
|
+
return dto;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { RouteConfig } from "@hono/zod-openapi";
|
|
2
|
+
import type { ZodType } from "zod";
|
|
3
|
+
/**
|
|
4
|
+
* Registers a Zod schema as a named entry in `components/schemas`.
|
|
5
|
+
*
|
|
6
|
+
* Use this for shared schemas (e.g. shared error types, reusable response shapes)
|
|
7
|
+
* that aren't directly attached to a specific route. Schemas already registered
|
|
8
|
+
* under the same name are silently skipped.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* export const MySchema = registerSchema("MySchema", z.object({ id: z.string() }));
|
|
12
|
+
*/
|
|
13
|
+
export declare const registerSchema: <T extends ZodType>(name: string, schema: T) => T;
|
|
14
|
+
/**
|
|
15
|
+
* Registers multiple Zod schemas at once as named entries in `components/schemas`.
|
|
16
|
+
* Object keys become the schema names. Returns the same object so you can
|
|
17
|
+
* destructure or re-export the schemas normally.
|
|
18
|
+
*
|
|
19
|
+
* Schemas already registered (e.g. via a prior `registerSchema` call) are skipped.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* export const { LedgerItem, Product } = registerSchemas({
|
|
23
|
+
* LedgerItem: z.object({ id: z.string(), amount: z.number() }),
|
|
24
|
+
* Product: z.object({ id: z.string(), price: z.number() }),
|
|
25
|
+
* });
|
|
26
|
+
*/
|
|
27
|
+
export declare const registerSchemas: <T extends Record<string, ZodType>>(schemas: T) => T;
|
|
28
|
+
/**
|
|
29
|
+
* Auto-registers a module export as a named OpenAPI schema.
|
|
30
|
+
* Used internally by modelSchemas auto-discovery in createApp.
|
|
31
|
+
* Strips a trailing "Schema" suffix from the export name.
|
|
32
|
+
* Skips non-Zod values and already-registered schemas.
|
|
33
|
+
*/
|
|
34
|
+
export declare function maybeAutoRegister(exportName: string, value: unknown): void;
|
|
35
|
+
/**
|
|
36
|
+
* Adds an OpenAPI `security` requirement to a route without affecting TypeScript
|
|
37
|
+
* type inference on the handler. Pass each security scheme as a separate object.
|
|
38
|
+
*
|
|
39
|
+
* Use this instead of inlining `security` in `createRoute(...)` — inlining a
|
|
40
|
+
* field typed as `{ [name: string]: string[] }` breaks `c.req.valid()` inference.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* router.openapi(
|
|
44
|
+
* withSecurity(createRoute({ method: "get", path: "/me", ... }), { cookieAuth: [] }, { userToken: [] }),
|
|
45
|
+
* async (c) => { ... }
|
|
46
|
+
* )
|
|
47
|
+
*/
|
|
48
|
+
export declare const withSecurity: <T extends RouteConfig>(route: T, ...schemes: Array<Record<string, string[]>>) => T;
|
|
49
|
+
/**
|
|
50
|
+
* Drop-in replacement for `createRoute` from `@hono/zod-openapi`.
|
|
51
|
+
*
|
|
52
|
+
* Automatically registers unnamed request body and response schemas as named
|
|
53
|
+
* OpenAPI components so they appear in `components/schemas` instead of being
|
|
54
|
+
* inlined at every use site. Generated names follow the convention:
|
|
55
|
+
*
|
|
56
|
+
* {Method}{PathSegments}Body — request body
|
|
57
|
+
* {Method}{PathSegments}{StatusCode} — response body
|
|
58
|
+
*
|
|
59
|
+
* Schemas already named via `.openapi("Name")` are never overwritten.
|
|
60
|
+
*/
|
|
61
|
+
export declare const createRoute: <T extends RouteConfig>(config: T) => T;
|