s3db.js 13.6.1 → 14.0.2
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 +56 -15
- package/dist/s3db.cjs +72446 -39022
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72172 -38790
- package/dist/s3db.es.js.map +1 -1
- package/mcp/lib/base-handler.js +157 -0
- package/mcp/lib/handlers/connection-handler.js +280 -0
- package/mcp/lib/handlers/query-handler.js +533 -0
- package/mcp/lib/handlers/resource-handler.js +428 -0
- package/mcp/lib/tool-registry.js +336 -0
- package/mcp/lib/tools/connection-tools.js +161 -0
- package/mcp/lib/tools/query-tools.js +267 -0
- package/mcp/lib/tools/resource-tools.js +404 -0
- package/package.json +85 -50
- package/src/clients/memory-client.class.js +346 -191
- package/src/clients/memory-storage.class.js +300 -84
- package/src/clients/s3-client.class.js +7 -6
- package/src/concerns/geo-encoding.js +19 -2
- package/src/concerns/ip.js +59 -9
- package/src/concerns/money.js +8 -1
- package/src/concerns/password-hashing.js +49 -8
- package/src/concerns/plugin-storage.js +186 -18
- package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
- package/src/database.class.js +139 -29
- package/src/errors.js +332 -42
- package/src/plugins/api/auth/oidc-auth.js +66 -17
- package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
- package/src/plugins/api/auth/strategies/factory.class.js +63 -0
- package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
- package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
- package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
- package/src/plugins/api/concerns/failban-manager.js +106 -57
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/index.js +168 -40
- package/src/plugins/api/routes/auth-routes.js +198 -30
- package/src/plugins/api/routes/resource-routes.js +19 -4
- package/src/plugins/api/server/health-manager.class.js +163 -0
- package/src/plugins/api/server/middleware-chain.class.js +310 -0
- package/src/plugins/api/server/router.class.js +472 -0
- package/src/plugins/api/server.js +280 -1303
- package/src/plugins/api/utils/custom-routes.js +17 -5
- package/src/plugins/api/utils/guards.js +76 -17
- package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
- package/src/plugins/api/utils/openapi-generator.js +7 -6
- package/src/plugins/audit.plugin.js +30 -8
- package/src/plugins/backup.plugin.js +110 -14
- package/src/plugins/cache/cache.class.js +22 -5
- package/src/plugins/cache/filesystem-cache.class.js +116 -19
- package/src/plugins/cache/memory-cache.class.js +211 -57
- package/src/plugins/cache/multi-tier-cache.class.js +371 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
- package/src/plugins/cache/redis-cache.class.js +552 -0
- package/src/plugins/cache/s3-cache.class.js +17 -8
- package/src/plugins/cache.plugin.js +176 -61
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
- package/src/plugins/cloud-inventory/index.js +29 -8
- package/src/plugins/cloud-inventory/registry.js +64 -42
- package/src/plugins/cloud-inventory.plugin.js +240 -138
- package/src/plugins/concerns/plugin-dependencies.js +54 -0
- package/src/plugins/concerns/resource-names.js +100 -0
- package/src/plugins/consumers/index.js +10 -2
- package/src/plugins/consumers/sqs-consumer.js +12 -2
- package/src/plugins/cookie-farm-suite.plugin.js +278 -0
- package/src/plugins/cookie-farm.errors.js +73 -0
- package/src/plugins/cookie-farm.plugin.js +869 -0
- package/src/plugins/costs.plugin.js +7 -1
- package/src/plugins/eventual-consistency/analytics.js +94 -19
- package/src/plugins/eventual-consistency/config.js +15 -7
- package/src/plugins/eventual-consistency/consolidation.js +29 -11
- package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
- package/src/plugins/eventual-consistency/helpers.js +39 -14
- package/src/plugins/eventual-consistency/install.js +21 -2
- package/src/plugins/eventual-consistency/utils.js +32 -10
- package/src/plugins/fulltext.plugin.js +38 -11
- package/src/plugins/geo.plugin.js +61 -9
- package/src/plugins/identity/concerns/config.js +61 -0
- package/src/plugins/identity/concerns/mfa-manager.js +15 -2
- package/src/plugins/identity/concerns/rate-limit.js +124 -0
- package/src/plugins/identity/concerns/resource-schemas.js +9 -1
- package/src/plugins/identity/concerns/token-generator.js +29 -4
- package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
- package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
- package/src/plugins/identity/drivers/index.js +18 -0
- package/src/plugins/identity/drivers/password-driver.js +122 -0
- package/src/plugins/identity/email-service.js +17 -2
- package/src/plugins/identity/index.js +413 -69
- package/src/plugins/identity/oauth2-server.js +413 -30
- package/src/plugins/identity/oidc-discovery.js +16 -8
- package/src/plugins/identity/rsa-keys.js +115 -35
- package/src/plugins/identity/server.js +166 -45
- package/src/plugins/identity/session-manager.js +53 -7
- package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
- package/src/plugins/identity/ui/routes.js +363 -255
- package/src/plugins/importer/index.js +153 -20
- package/src/plugins/index.js +9 -2
- package/src/plugins/kubernetes-inventory/index.js +6 -0
- package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
- package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
- package/src/plugins/kubernetes-inventory.plugin.js +980 -0
- package/src/plugins/metrics.plugin.js +64 -16
- package/src/plugins/ml/base-model.class.js +25 -15
- package/src/plugins/ml/regression-model.class.js +1 -1
- package/src/plugins/ml.errors.js +57 -25
- package/src/plugins/ml.plugin.js +28 -4
- package/src/plugins/namespace.js +210 -0
- package/src/plugins/plugin.class.js +180 -8
- package/src/plugins/puppeteer/console-monitor.js +729 -0
- package/src/plugins/puppeteer/cookie-manager.js +492 -0
- package/src/plugins/puppeteer/network-monitor.js +816 -0
- package/src/plugins/puppeteer/performance-manager.js +746 -0
- package/src/plugins/puppeteer/proxy-manager.js +478 -0
- package/src/plugins/puppeteer/stealth-manager.js +556 -0
- package/src/plugins/puppeteer.errors.js +81 -0
- package/src/plugins/puppeteer.plugin.js +1327 -0
- package/src/plugins/queue-consumer.plugin.js +69 -14
- package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
- package/src/plugins/recon/concerns/command-runner.js +148 -0
- package/src/plugins/recon/concerns/diff-detector.js +372 -0
- package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
- package/src/plugins/recon/concerns/process-manager.js +338 -0
- package/src/plugins/recon/concerns/report-generator.js +478 -0
- package/src/plugins/recon/concerns/security-analyzer.js +571 -0
- package/src/plugins/recon/concerns/target-normalizer.js +68 -0
- package/src/plugins/recon/config/defaults.js +321 -0
- package/src/plugins/recon/config/resources.js +370 -0
- package/src/plugins/recon/index.js +778 -0
- package/src/plugins/recon/managers/dependency-manager.js +174 -0
- package/src/plugins/recon/managers/scheduler-manager.js +179 -0
- package/src/plugins/recon/managers/storage-manager.js +745 -0
- package/src/plugins/recon/managers/target-manager.js +274 -0
- package/src/plugins/recon/stages/asn-stage.js +314 -0
- package/src/plugins/recon/stages/certificate-stage.js +84 -0
- package/src/plugins/recon/stages/dns-stage.js +107 -0
- package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
- package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
- package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
- package/src/plugins/recon/stages/http-stage.js +89 -0
- package/src/plugins/recon/stages/latency-stage.js +148 -0
- package/src/plugins/recon/stages/massdns-stage.js +302 -0
- package/src/plugins/recon/stages/osint-stage.js +1373 -0
- package/src/plugins/recon/stages/ports-stage.js +169 -0
- package/src/plugins/recon/stages/screenshot-stage.js +94 -0
- package/src/plugins/recon/stages/secrets-stage.js +514 -0
- package/src/plugins/recon/stages/subdomains-stage.js +295 -0
- package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
- package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
- package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
- package/src/plugins/recon/stages/whois-stage.js +349 -0
- package/src/plugins/recon.plugin.js +75 -0
- package/src/plugins/recon.plugin.js.backup +2635 -0
- package/src/plugins/relation.errors.js +87 -14
- package/src/plugins/replicator.plugin.js +514 -137
- package/src/plugins/replicators/base-replicator.class.js +89 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
- package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mysql-replicator.class.js +52 -17
- package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
- package/src/plugins/replicators/postgres-replicator.class.js +62 -27
- package/src/plugins/replicators/s3db-replicator.class.js +25 -18
- package/src/plugins/replicators/schema-sync.helper.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +8 -2
- package/src/plugins/replicators/turso-replicator.class.js +23 -3
- package/src/plugins/replicators/webhook-replicator.class.js +42 -4
- package/src/plugins/s3-queue.plugin.js +464 -65
- package/src/plugins/scheduler.plugin.js +20 -6
- package/src/plugins/state-machine.plugin.js +40 -9
- package/src/plugins/tfstate/base-driver.js +28 -4
- package/src/plugins/tfstate/errors.js +65 -10
- package/src/plugins/tfstate/filesystem-driver.js +52 -8
- package/src/plugins/tfstate/index.js +163 -90
- package/src/plugins/tfstate/s3-driver.js +64 -6
- package/src/plugins/ttl.plugin.js +72 -17
- package/src/plugins/vector/distances.js +18 -12
- package/src/plugins/vector/kmeans.js +26 -4
- package/src/resource.class.js +115 -19
- package/src/testing/factory.class.js +20 -3
- package/src/testing/seeder.class.js +7 -1
- package/src/clients/memory-client.md +0 -917
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router - Handles all route mounting for API server
|
|
3
|
+
*
|
|
4
|
+
* Separates routing concerns from server lifecycle management.
|
|
5
|
+
* Responsible for mounting:
|
|
6
|
+
* - Resource routes (auto-generated CRUD)
|
|
7
|
+
* - Authentication routes
|
|
8
|
+
* - Static file serving
|
|
9
|
+
* - Custom user routes
|
|
10
|
+
* - Relational routes (if RelationPlugin active)
|
|
11
|
+
* - Admin routes (failban, metrics)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createResourceRoutes, createRelationalRoutes } from '../routes/resource-routes.js';
|
|
15
|
+
import { createAuthRoutes } from '../routes/auth-routes.js';
|
|
16
|
+
import { mountCustomRoutes } from '../utils/custom-routes.js';
|
|
17
|
+
import * as formatter from '../../shared/response-formatter.js';
|
|
18
|
+
import { createFilesystemHandler, validateFilesystemConfig } from '../utils/static-filesystem.js';
|
|
19
|
+
import { createS3Handler, validateS3Config } from '../utils/static-s3.js';
|
|
20
|
+
import { createFailbanAdminRoutes } from '../middlewares/failban.js';
|
|
21
|
+
|
|
22
|
+
export class Router {
|
|
23
|
+
constructor({ database, resources, routes, versionPrefix, auth, static: staticConfigs, failban, metrics, relationsPlugin, authMiddleware, verbose, Hono }) {
|
|
24
|
+
this.database = database;
|
|
25
|
+
this.resources = resources || {};
|
|
26
|
+
this.routes = routes || {};
|
|
27
|
+
this.versionPrefix = versionPrefix;
|
|
28
|
+
this.auth = auth;
|
|
29
|
+
this.staticConfigs = staticConfigs || [];
|
|
30
|
+
this.failban = failban;
|
|
31
|
+
this.metrics = metrics;
|
|
32
|
+
this.relationsPlugin = relationsPlugin;
|
|
33
|
+
this.authMiddleware = authMiddleware;
|
|
34
|
+
this.verbose = verbose;
|
|
35
|
+
this.Hono = Hono;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Mount all routes on Hono app
|
|
40
|
+
* @param {Hono} app - Hono application instance
|
|
41
|
+
* @param {Object} events - Event emitter
|
|
42
|
+
*/
|
|
43
|
+
mount(app, events) {
|
|
44
|
+
// Static files first (give them priority)
|
|
45
|
+
this.mountStaticRoutes(app);
|
|
46
|
+
|
|
47
|
+
// Resource routes
|
|
48
|
+
this.mountResourceRoutes(app, events);
|
|
49
|
+
|
|
50
|
+
// Authentication routes
|
|
51
|
+
this.mountAuthRoutes(app);
|
|
52
|
+
|
|
53
|
+
// Relational routes (if RelationPlugin is active)
|
|
54
|
+
this.mountRelationalRoutes(app);
|
|
55
|
+
|
|
56
|
+
// Plugin-level custom routes
|
|
57
|
+
this.mountCustomRoutes(app);
|
|
58
|
+
|
|
59
|
+
// Admin routes (failban, metrics)
|
|
60
|
+
this.mountAdminRoutes(app);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Mount resource routes (auto-generated CRUD)
|
|
65
|
+
* @private
|
|
66
|
+
*/
|
|
67
|
+
mountResourceRoutes(app, events) {
|
|
68
|
+
const databaseResources = this.database.resources;
|
|
69
|
+
|
|
70
|
+
for (const [name, resource] of Object.entries(databaseResources)) {
|
|
71
|
+
const resourceConfig = this.resources[name];
|
|
72
|
+
const isPluginResource = name.startsWith('plg_');
|
|
73
|
+
|
|
74
|
+
// Internal plugin resources require explicit opt-in
|
|
75
|
+
if (isPluginResource && !resourceConfig) {
|
|
76
|
+
if (this.verbose) {
|
|
77
|
+
console.log(`[API Router] Skipping internal resource '${name}' (not included in config.resources)`);
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Allow explicit disabling via config
|
|
83
|
+
if (resourceConfig?.enabled === false) {
|
|
84
|
+
if (this.verbose) {
|
|
85
|
+
console.log(`[API Router] Resource '${name}' disabled via config.resources`);
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Determine version
|
|
91
|
+
const version = resource.config?.currentVersion || resource.version || 'v1';
|
|
92
|
+
|
|
93
|
+
// Determine version prefix (resource-level overrides global)
|
|
94
|
+
let versionPrefixConfig;
|
|
95
|
+
if (resourceConfig && resourceConfig.versionPrefix !== undefined) {
|
|
96
|
+
versionPrefixConfig = resourceConfig.versionPrefix;
|
|
97
|
+
} else if (resource.config && resource.config.versionPrefix !== undefined) {
|
|
98
|
+
versionPrefixConfig = resource.config.versionPrefix;
|
|
99
|
+
} else if (this.versionPrefix !== undefined) {
|
|
100
|
+
versionPrefixConfig = this.versionPrefix;
|
|
101
|
+
} else {
|
|
102
|
+
versionPrefixConfig = false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Calculate the actual prefix to use
|
|
106
|
+
let prefix = '';
|
|
107
|
+
if (versionPrefixConfig === true) {
|
|
108
|
+
prefix = version;
|
|
109
|
+
} else if (versionPrefixConfig === false) {
|
|
110
|
+
prefix = '';
|
|
111
|
+
} else if (typeof versionPrefixConfig === 'string') {
|
|
112
|
+
prefix = versionPrefixConfig;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Prepare custom middleware
|
|
116
|
+
const middlewares = [];
|
|
117
|
+
|
|
118
|
+
// Add global authentication middleware unless explicitly disabled
|
|
119
|
+
const authDisabled = resourceConfig?.auth === false;
|
|
120
|
+
|
|
121
|
+
if (this.authMiddleware && !authDisabled) {
|
|
122
|
+
middlewares.push(this.authMiddleware);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Add resource-specific middleware from config
|
|
126
|
+
const extraMiddleware = resourceConfig?.customMiddleware;
|
|
127
|
+
if (extraMiddleware) {
|
|
128
|
+
const toRegister = Array.isArray(extraMiddleware) ? extraMiddleware : [extraMiddleware];
|
|
129
|
+
|
|
130
|
+
for (const middleware of toRegister) {
|
|
131
|
+
if (typeof middleware === 'function') {
|
|
132
|
+
middlewares.push(middleware);
|
|
133
|
+
} else if (this.verbose) {
|
|
134
|
+
console.warn(`[API Router] Ignoring non-function middleware for resource '${name}'`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Normalize HTTP methods
|
|
140
|
+
let methods = resourceConfig?.methods || resource.config?.methods;
|
|
141
|
+
if (!Array.isArray(methods) || methods.length === 0) {
|
|
142
|
+
methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
|
|
143
|
+
} else {
|
|
144
|
+
methods = methods
|
|
145
|
+
.filter(Boolean)
|
|
146
|
+
.map(method => typeof method === 'string' ? method.toUpperCase() : method);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Determine validation toggle
|
|
150
|
+
const enableValidation = resourceConfig?.validation !== undefined
|
|
151
|
+
? resourceConfig.validation !== false
|
|
152
|
+
: resource.config?.validation !== false;
|
|
153
|
+
|
|
154
|
+
// Create resource routes
|
|
155
|
+
const resourceApp = createResourceRoutes(resource, version, {
|
|
156
|
+
methods,
|
|
157
|
+
customMiddleware: middlewares,
|
|
158
|
+
enableValidation,
|
|
159
|
+
versionPrefix: prefix,
|
|
160
|
+
events
|
|
161
|
+
}, this.Hono);
|
|
162
|
+
|
|
163
|
+
// Mount resource routes
|
|
164
|
+
const mountPath = prefix ? `/${prefix}/${name}` : `/${name}`;
|
|
165
|
+
app.route(mountPath, resourceApp);
|
|
166
|
+
|
|
167
|
+
if (this.verbose) {
|
|
168
|
+
console.log(`[API Router] Mounted routes for resource '${name}' at ${mountPath}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Mount custom routes for this resource
|
|
172
|
+
if (resource.config?.routes) {
|
|
173
|
+
const routeContext = {
|
|
174
|
+
resource,
|
|
175
|
+
database: this.database,
|
|
176
|
+
resourceName: name,
|
|
177
|
+
version
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
mountCustomRoutes(resourceApp, resource.config.routes, routeContext, this.verbose);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Mount authentication routes
|
|
187
|
+
* @private
|
|
188
|
+
*/
|
|
189
|
+
mountAuthRoutes(app) {
|
|
190
|
+
const { drivers, resource: resourceName, usernameField, passwordField, registration, loginThrottle } = this.auth;
|
|
191
|
+
|
|
192
|
+
const identityPlugin = this.database?.plugins?.identity || this.database?.plugins?.Identity;
|
|
193
|
+
if (identityPlugin) {
|
|
194
|
+
if (this.verbose) {
|
|
195
|
+
console.warn('[API Router] IdentityPlugin detected. Skipping built-in auth routes.');
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Find first JWT driver
|
|
201
|
+
const jwtDriver = drivers?.find(d => d.driver === 'jwt');
|
|
202
|
+
|
|
203
|
+
if (!jwtDriver) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Get auth resource
|
|
208
|
+
const authResource = this.database.resources[resourceName];
|
|
209
|
+
if (!authResource) {
|
|
210
|
+
console.error(`[API Router] Auth resource '${resourceName}' not found. Skipping auth routes.`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const driverConfig = jwtDriver.config || {};
|
|
215
|
+
const registrationConfig = {
|
|
216
|
+
enabled: driverConfig.allowRegistration === true ||
|
|
217
|
+
driverConfig.registration?.enabled === true ||
|
|
218
|
+
registration?.enabled === true,
|
|
219
|
+
allowedFields: Array.isArray(driverConfig.registration?.allowedFields)
|
|
220
|
+
? driverConfig.registration.allowedFields
|
|
221
|
+
: Array.isArray(registration?.allowedFields)
|
|
222
|
+
? registration.allowedFields
|
|
223
|
+
: [],
|
|
224
|
+
defaultRole: driverConfig.registration?.defaultRole ??
|
|
225
|
+
registration?.defaultRole ??
|
|
226
|
+
'user'
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const driverLoginThrottle = driverConfig.loginThrottle || {};
|
|
230
|
+
const loginThrottleConfig = {
|
|
231
|
+
enabled: driverLoginThrottle.enabled ?? loginThrottle?.enabled ?? true,
|
|
232
|
+
maxAttempts: driverLoginThrottle.maxAttempts || loginThrottle?.maxAttempts || 5,
|
|
233
|
+
windowMs: driverLoginThrottle.windowMs || loginThrottle?.windowMs || 60_000,
|
|
234
|
+
blockDurationMs: driverLoginThrottle.blockDurationMs || loginThrottle?.blockDurationMs || 300_000,
|
|
235
|
+
maxEntries: driverLoginThrottle.maxEntries || loginThrottle?.maxEntries || 10_000
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Prepare auth config for routes
|
|
239
|
+
const authConfig = {
|
|
240
|
+
driver: 'jwt',
|
|
241
|
+
usernameField,
|
|
242
|
+
passwordField,
|
|
243
|
+
jwtSecret: driverConfig.jwtSecret || driverConfig.secret,
|
|
244
|
+
jwtExpiresIn: driverConfig.jwtExpiresIn || driverConfig.expiresIn || '7d',
|
|
245
|
+
passphrase: driverConfig.passphrase || 'secret',
|
|
246
|
+
allowRegistration: registrationConfig.enabled,
|
|
247
|
+
registration: registrationConfig,
|
|
248
|
+
loginThrottle: loginThrottleConfig
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// Create auth routes
|
|
252
|
+
const authApp = createAuthRoutes(authResource, authConfig);
|
|
253
|
+
|
|
254
|
+
// Mount auth routes at /auth
|
|
255
|
+
app.route('/auth', authApp);
|
|
256
|
+
|
|
257
|
+
if (this.verbose) {
|
|
258
|
+
console.log('[API Router] Mounted auth routes (driver: jwt) at /auth');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Mount static file serving routes
|
|
264
|
+
* @private
|
|
265
|
+
*/
|
|
266
|
+
mountStaticRoutes(app) {
|
|
267
|
+
if (!this.staticConfigs || this.staticConfigs.length === 0) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!Array.isArray(this.staticConfigs)) {
|
|
272
|
+
throw new Error('Static config must be an array of mount points');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
for (const [index, config] of this.staticConfigs.entries()) {
|
|
276
|
+
try {
|
|
277
|
+
// Validate required fields
|
|
278
|
+
if (!config.driver) {
|
|
279
|
+
throw new Error(`static[${index}]: "driver" is required (filesystem or s3)`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!config.path) {
|
|
283
|
+
throw new Error(`static[${index}]: "path" is required (mount path)`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (!config.path.startsWith('/')) {
|
|
287
|
+
throw new Error(`static[${index}]: "path" must start with / (got: ${config.path})`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const driverConfig = config.config || {};
|
|
291
|
+
|
|
292
|
+
// Create handler based on driver
|
|
293
|
+
let handler;
|
|
294
|
+
|
|
295
|
+
if (config.driver === 'filesystem') {
|
|
296
|
+
validateFilesystemConfig({ ...config, ...driverConfig });
|
|
297
|
+
|
|
298
|
+
handler = createFilesystemHandler({
|
|
299
|
+
root: config.root,
|
|
300
|
+
index: driverConfig.index,
|
|
301
|
+
fallback: driverConfig.fallback,
|
|
302
|
+
maxAge: driverConfig.maxAge,
|
|
303
|
+
dotfiles: driverConfig.dotfiles,
|
|
304
|
+
etag: driverConfig.etag,
|
|
305
|
+
cors: driverConfig.cors
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
} else if (config.driver === 's3') {
|
|
309
|
+
validateS3Config({ ...config, ...driverConfig });
|
|
310
|
+
|
|
311
|
+
const s3Client = this.database?.client?.client;
|
|
312
|
+
|
|
313
|
+
if (!s3Client) {
|
|
314
|
+
throw new Error(`static[${index}]: S3 driver requires database with S3 client`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
handler = createS3Handler({
|
|
318
|
+
s3Client,
|
|
319
|
+
bucket: config.bucket,
|
|
320
|
+
prefix: config.prefix,
|
|
321
|
+
streaming: driverConfig.streaming,
|
|
322
|
+
signedUrlExpiry: driverConfig.signedUrlExpiry,
|
|
323
|
+
maxAge: driverConfig.maxAge,
|
|
324
|
+
cacheControl: driverConfig.cacheControl,
|
|
325
|
+
contentDisposition: driverConfig.contentDisposition,
|
|
326
|
+
etag: driverConfig.etag,
|
|
327
|
+
cors: driverConfig.cors
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
} else {
|
|
331
|
+
throw new Error(
|
|
332
|
+
`static[${index}]: invalid driver "${config.driver}". Valid drivers: filesystem, s3`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Mount handler
|
|
337
|
+
const mountPath = config.path === '/' ? '/*' : `${config.path}/*`;
|
|
338
|
+
app.get(mountPath, handler);
|
|
339
|
+
app.head(mountPath, handler);
|
|
340
|
+
|
|
341
|
+
if (this.verbose) {
|
|
342
|
+
console.log(
|
|
343
|
+
`[API Router] Mounted static files (${config.driver}) at ${config.path}` +
|
|
344
|
+
(config.driver === 'filesystem' ? ` -> ${config.root}` : ` -> s3://${config.bucket}/${config.prefix || ''}`)
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
} catch (err) {
|
|
349
|
+
console.error(`[API Router] Failed to setup static files for index ${index}:`, err.message);
|
|
350
|
+
throw err;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Mount relational routes (if RelationPlugin is active)
|
|
357
|
+
* @private
|
|
358
|
+
*/
|
|
359
|
+
mountRelationalRoutes(app) {
|
|
360
|
+
if (!this.relationsPlugin || !this.relationsPlugin.relations) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const relations = this.relationsPlugin.relations;
|
|
365
|
+
|
|
366
|
+
if (this.verbose) {
|
|
367
|
+
console.log('[API Router] Setting up relational routes...');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
for (const [resourceName, relationsDef] of Object.entries(relations)) {
|
|
371
|
+
const resource = this.database.resources[resourceName];
|
|
372
|
+
if (!resource) {
|
|
373
|
+
if (this.verbose) {
|
|
374
|
+
console.warn(`[API Router] Resource '${resourceName}' not found for relational routes`);
|
|
375
|
+
}
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Skip plugin resources unless explicitly included
|
|
380
|
+
if (resourceName.startsWith('plg_') && !this.resources[resourceName]) {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const version = resource.config?.currentVersion || resource.version || 'v1';
|
|
385
|
+
|
|
386
|
+
for (const [relationName, relationConfig] of Object.entries(relationsDef)) {
|
|
387
|
+
// Skip belongsTo relations
|
|
388
|
+
if (relationConfig.type === 'belongsTo') {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Check if relation should be exposed
|
|
393
|
+
const resourceConfig = this.resources[resourceName];
|
|
394
|
+
const exposeRelation = resourceConfig?.relations?.[relationName]?.expose !== false;
|
|
395
|
+
|
|
396
|
+
if (!exposeRelation) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Create relational routes
|
|
401
|
+
const relationalApp = createRelationalRoutes(
|
|
402
|
+
resource,
|
|
403
|
+
relationName,
|
|
404
|
+
relationConfig,
|
|
405
|
+
version,
|
|
406
|
+
this.Hono
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
// Mount relational routes
|
|
410
|
+
app.route(`/${version}/${resourceName}/:id/${relationName}`, relationalApp);
|
|
411
|
+
|
|
412
|
+
if (this.verbose) {
|
|
413
|
+
console.log(
|
|
414
|
+
`[API Router] Mounted relational route: /${version}/${resourceName}/:id/${relationName} ` +
|
|
415
|
+
`(${relationConfig.type} -> ${relationConfig.resource})`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Mount plugin-level custom routes
|
|
424
|
+
* @private
|
|
425
|
+
*/
|
|
426
|
+
mountCustomRoutes(app) {
|
|
427
|
+
if (!this.routes || Object.keys(this.routes).length === 0) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const context = {
|
|
432
|
+
database: this.database,
|
|
433
|
+
plugins: this.database?.plugins || {}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
mountCustomRoutes(app, this.routes, context, this.verbose);
|
|
437
|
+
|
|
438
|
+
if (this.verbose) {
|
|
439
|
+
console.log(`[API Router] Mounted ${Object.keys(this.routes).length} plugin-level custom routes`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Mount admin routes (failban, metrics)
|
|
445
|
+
* @private
|
|
446
|
+
*/
|
|
447
|
+
mountAdminRoutes(app) {
|
|
448
|
+
// Metrics endpoint
|
|
449
|
+
const metricsEnabled = this.metrics?.options?.enabled ?? false;
|
|
450
|
+
if (metricsEnabled) {
|
|
451
|
+
app.get('/metrics', (c) => {
|
|
452
|
+
const summary = this.metrics.getSummary();
|
|
453
|
+
const response = formatter.success(summary);
|
|
454
|
+
return c.json(response);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
if (this.verbose) {
|
|
458
|
+
console.log('[API Router] Metrics endpoint enabled at /metrics');
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Failban admin endpoints
|
|
463
|
+
if (this.failban) {
|
|
464
|
+
const failbanAdminRoutes = createFailbanAdminRoutes(this.Hono, this.failban);
|
|
465
|
+
app.route('/admin/security', failbanAdminRoutes);
|
|
466
|
+
|
|
467
|
+
if (this.verbose) {
|
|
468
|
+
console.log('[API Router] Failban admin endpoints enabled at /admin/security');
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|