@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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
class LruCache {
|
|
2
|
+
_map = new Map();
|
|
3
|
+
_maxSize;
|
|
4
|
+
_ttlMs;
|
|
5
|
+
constructor(maxSize, ttlMs) {
|
|
6
|
+
this._maxSize = maxSize;
|
|
7
|
+
this._ttlMs = ttlMs;
|
|
8
|
+
}
|
|
9
|
+
get(key) {
|
|
10
|
+
const entry = this._map.get(key);
|
|
11
|
+
if (!entry)
|
|
12
|
+
return undefined; // cache miss
|
|
13
|
+
if (entry.expiresAt <= Date.now()) {
|
|
14
|
+
this._map.delete(key);
|
|
15
|
+
return undefined; // expired
|
|
16
|
+
}
|
|
17
|
+
// Move to end (most recently used)
|
|
18
|
+
this._map.delete(key);
|
|
19
|
+
this._map.set(key, entry);
|
|
20
|
+
return entry.value;
|
|
21
|
+
}
|
|
22
|
+
set(key, value) {
|
|
23
|
+
// Remove first if exists (for re-insertion at end)
|
|
24
|
+
this._map.delete(key);
|
|
25
|
+
// Evict oldest if at capacity
|
|
26
|
+
if (this._map.size >= this._maxSize) {
|
|
27
|
+
const oldest = this._map.keys().next().value;
|
|
28
|
+
if (oldest !== undefined)
|
|
29
|
+
this._map.delete(oldest);
|
|
30
|
+
}
|
|
31
|
+
this._map.set(key, { value, expiresAt: Date.now() + this._ttlMs });
|
|
32
|
+
}
|
|
33
|
+
delete(key) {
|
|
34
|
+
this._map.delete(key);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Exported cache invalidation (used by tenant provisioning helpers)
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
let _cache = null;
|
|
41
|
+
export const invalidateTenantCache = (tenantId) => {
|
|
42
|
+
_cache?.delete(tenantId);
|
|
43
|
+
};
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Tenant resolution middleware
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
const DEFAULT_EXEMPT = ["/health", "/docs", "/openapi.json", "/auth/"];
|
|
48
|
+
function extractTenantId(c, config) {
|
|
49
|
+
if (config.resolution === "header") {
|
|
50
|
+
const headerName = config.headerName ?? "x-tenant-id";
|
|
51
|
+
return c.req.header(headerName) ?? null;
|
|
52
|
+
}
|
|
53
|
+
if (config.resolution === "subdomain") {
|
|
54
|
+
const host = c.req.header("host") ?? "";
|
|
55
|
+
// Extract first subdomain: "acme.myapp.com" → "acme"
|
|
56
|
+
const parts = host.split(".");
|
|
57
|
+
if (parts.length < 3)
|
|
58
|
+
return null; // no subdomain
|
|
59
|
+
return parts[0] || null;
|
|
60
|
+
}
|
|
61
|
+
if (config.resolution === "path") {
|
|
62
|
+
const segmentIndex = config.pathSegment ?? 0;
|
|
63
|
+
// Path: "/acme/api/users" → segments after split: ["", "acme", "api", "users"]
|
|
64
|
+
const segments = c.req.path.split("/").filter(Boolean);
|
|
65
|
+
return segments[segmentIndex] ?? null;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
export const createTenantMiddleware = (config) => {
|
|
70
|
+
const exemptPaths = [...DEFAULT_EXEMPT, ...(config.exemptPaths ?? [])];
|
|
71
|
+
const rejectionStatus = config.rejectionStatus ?? 403;
|
|
72
|
+
const cacheTtlMs = config.cacheTtlMs ?? 60_000;
|
|
73
|
+
const cacheMaxSize = config.cacheMaxSize ?? 500;
|
|
74
|
+
// Initialize LRU cache if caching is enabled and onResolve is provided
|
|
75
|
+
if (config.onResolve && cacheTtlMs > 0) {
|
|
76
|
+
_cache = new LruCache(cacheMaxSize, cacheTtlMs);
|
|
77
|
+
}
|
|
78
|
+
return async (c, next) => {
|
|
79
|
+
const path = c.req.path;
|
|
80
|
+
// Check exempt paths using startsWith
|
|
81
|
+
for (const exempt of exemptPaths) {
|
|
82
|
+
if (path === exempt || path.startsWith(exempt)) {
|
|
83
|
+
c.set("tenantId", null);
|
|
84
|
+
c.set("tenantConfig", null);
|
|
85
|
+
return next();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const tenantId = extractTenantId(c, config);
|
|
89
|
+
if (!tenantId) {
|
|
90
|
+
return c.json({ error: "Tenant ID required" }, 400);
|
|
91
|
+
}
|
|
92
|
+
// Validate via onResolve (with caching)
|
|
93
|
+
if (config.onResolve) {
|
|
94
|
+
let tenantConfig;
|
|
95
|
+
if (_cache) {
|
|
96
|
+
tenantConfig = _cache.get(tenantId);
|
|
97
|
+
}
|
|
98
|
+
// undefined = cache miss, null = onResolve returned null (rejected)
|
|
99
|
+
if (tenantConfig === undefined) {
|
|
100
|
+
tenantConfig = await config.onResolve(tenantId);
|
|
101
|
+
_cache?.set(tenantId, tenantConfig);
|
|
102
|
+
}
|
|
103
|
+
if (tenantConfig === null) {
|
|
104
|
+
return c.json({ error: "Access denied" }, rejectionStatus);
|
|
105
|
+
}
|
|
106
|
+
c.set("tenantId", tenantId);
|
|
107
|
+
c.set("tenantConfig", tenantConfig);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// No onResolve — trust the tenant ID
|
|
111
|
+
c.set("tenantId", tenantId);
|
|
112
|
+
c.set("tenantConfig", null);
|
|
113
|
+
}
|
|
114
|
+
return next();
|
|
115
|
+
};
|
|
116
|
+
};
|
|
@@ -8,6 +8,14 @@ interface IAuthUser {
|
|
|
8
8
|
roles: string[];
|
|
9
9
|
/** Whether the user's email address has been verified. */
|
|
10
10
|
emailVerified: boolean;
|
|
11
|
+
/** TOTP secret for MFA. Null when MFA is not set up. */
|
|
12
|
+
mfaSecret?: string | null;
|
|
13
|
+
/** Whether MFA is enabled (secret stored + confirmed via TOTP code). */
|
|
14
|
+
mfaEnabled?: boolean;
|
|
15
|
+
/** SHA-256 hashed recovery codes for MFA. */
|
|
16
|
+
recoveryCodes?: string[];
|
|
17
|
+
/** MFA methods enabled for this user (e.g., ["totp"], ["emailOtp"], ["totp", "emailOtp"]). */
|
|
18
|
+
mfaMethods?: string[];
|
|
11
19
|
}
|
|
12
20
|
type AuthUserDocument = IAuthUser & Document;
|
|
13
21
|
export declare const AuthUser: Model<AuthUserDocument, {}, {}, {}, Document<unknown, {}, AuthUserDocument, {}, import("mongoose").DefaultSchemaOptions> & IAuthUser & Document<import("mongoose").Types.ObjectId, any, any, Record<string, any>, {}> & Required<{
|
package/dist/models/AuthUser.js
CHANGED
|
@@ -14,6 +14,14 @@ function getAuthUser() {
|
|
|
14
14
|
roles: [{ type: String }],
|
|
15
15
|
/** Whether the user's email address has been verified. */
|
|
16
16
|
emailVerified: { type: Boolean, default: false },
|
|
17
|
+
/** TOTP secret for MFA. */
|
|
18
|
+
mfaSecret: { type: String, default: null },
|
|
19
|
+
/** Whether MFA is enabled. */
|
|
20
|
+
mfaEnabled: { type: Boolean, default: false },
|
|
21
|
+
/** SHA-256 hashed recovery codes for MFA. */
|
|
22
|
+
recoveryCodes: [{ type: String }],
|
|
23
|
+
/** MFA methods enabled for this user. */
|
|
24
|
+
mfaMethods: [{ type: String }],
|
|
17
25
|
}, { timestamps: true });
|
|
18
26
|
schema.index({ providerIds: 1 });
|
|
19
27
|
_AuthUser = authConnection.model("AuthUser", schema);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Document, Model } from "mongoose";
|
|
2
|
+
interface ITenantRole {
|
|
3
|
+
userId: string;
|
|
4
|
+
tenantId: string;
|
|
5
|
+
roles: string[];
|
|
6
|
+
}
|
|
7
|
+
type TenantRoleDocument = ITenantRole & Document;
|
|
8
|
+
export declare const TenantRole: Model<TenantRoleDocument, {}, {}, {}, Document<unknown, {}, TenantRoleDocument, {}, import("mongoose").DefaultSchemaOptions> & ITenantRole & Document<import("mongoose").Types.ObjectId, any, any, Record<string, any>, {}> & Required<{
|
|
9
|
+
_id: import("mongoose").Types.ObjectId;
|
|
10
|
+
}> & {
|
|
11
|
+
__v: number;
|
|
12
|
+
} & {
|
|
13
|
+
id: string;
|
|
14
|
+
}, any, TenantRoleDocument>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { authConnection, mongoose } from "../lib/mongo";
|
|
2
|
+
let _TenantRole = null;
|
|
3
|
+
function getTenantRole() {
|
|
4
|
+
if (!_TenantRole) {
|
|
5
|
+
const { Schema } = mongoose;
|
|
6
|
+
const schema = new Schema({
|
|
7
|
+
userId: { type: String, required: true },
|
|
8
|
+
tenantId: { type: String, required: true },
|
|
9
|
+
roles: [{ type: String }],
|
|
10
|
+
}, { timestamps: true });
|
|
11
|
+
schema.index({ userId: 1, tenantId: 1 }, { unique: true });
|
|
12
|
+
schema.index({ tenantId: 1 });
|
|
13
|
+
_TenantRole = authConnection.model("TenantRole", schema);
|
|
14
|
+
}
|
|
15
|
+
return _TenantRole;
|
|
16
|
+
}
|
|
17
|
+
export const TenantRole = new Proxy({}, {
|
|
18
|
+
get(_, prop) {
|
|
19
|
+
const model = getTenantRole();
|
|
20
|
+
const val = model[prop];
|
|
21
|
+
return typeof val === "function" ? val.bind(model) : val;
|
|
22
|
+
},
|
|
23
|
+
});
|
package/dist/routes/auth.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { PrimaryField, EmailVerificationConfig, PasswordResetConfig } from "../lib/appConfig";
|
|
2
|
-
import type { AuthRateLimitConfig } from "../app";
|
|
1
|
+
import type { PrimaryField, EmailVerificationConfig, PasswordResetConfig, RefreshTokenConfig } from "../lib/appConfig";
|
|
2
|
+
import type { AuthRateLimitConfig, AccountDeletionConfig } from "../app";
|
|
3
3
|
export interface AuthRouterOptions {
|
|
4
4
|
primaryField: PrimaryField;
|
|
5
5
|
emailVerification?: EmailVerificationConfig;
|
|
6
6
|
passwordReset?: PasswordResetConfig;
|
|
7
7
|
rateLimit?: AuthRateLimitConfig;
|
|
8
|
+
accountDeletion?: AccountDeletionConfig;
|
|
9
|
+
refreshTokens?: RefreshTokenConfig;
|
|
8
10
|
}
|
|
9
|
-
export declare const createAuthRouter: ({ primaryField, emailVerification, passwordReset, rateLimit }: AuthRouterOptions) => import("@hono/zod-openapi").OpenAPIHono<import("../lib/context").AppEnv, {}, "/">;
|
|
11
|
+
export declare const createAuthRouter: ({ primaryField, emailVerification, passwordReset, rateLimit, accountDeletion, refreshTokens }: AuthRouterOptions) => import("@hono/zod-openapi").OpenAPIHono<import("../lib/context").AppEnv, {}, "/">;
|