s3db.js 13.5.1 → 13.6.1

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.
Files changed (108) hide show
  1. package/README.md +89 -19
  2. package/dist/{s3db.cjs.js → s3db.cjs} +29780 -24384
  3. package/dist/s3db.cjs.map +1 -0
  4. package/dist/s3db.es.js +24263 -18860
  5. package/dist/s3db.es.js.map +1 -1
  6. package/package.json +227 -21
  7. package/src/concerns/id.js +90 -6
  8. package/src/concerns/index.js +2 -1
  9. package/src/concerns/password-hashing.js +150 -0
  10. package/src/database.class.js +4 -0
  11. package/src/plugins/api/auth/basic-auth.js +23 -1
  12. package/src/plugins/api/auth/index.js +49 -3
  13. package/src/plugins/api/auth/oauth2-auth.js +171 -0
  14. package/src/plugins/api/auth/oidc-auth.js +789 -0
  15. package/src/plugins/api/auth/oidc-client.js +462 -0
  16. package/src/plugins/api/auth/path-auth-matcher.js +284 -0
  17. package/src/plugins/api/concerns/event-emitter.js +134 -0
  18. package/src/plugins/api/concerns/failban-manager.js +651 -0
  19. package/src/plugins/api/concerns/guards-helpers.js +402 -0
  20. package/src/plugins/api/concerns/metrics-collector.js +346 -0
  21. package/src/plugins/api/concerns/opengraph-helper.js +116 -0
  22. package/src/plugins/api/concerns/state-machine.js +288 -0
  23. package/src/plugins/api/index.js +514 -54
  24. package/src/plugins/api/middlewares/failban.js +305 -0
  25. package/src/plugins/api/middlewares/rate-limit.js +301 -0
  26. package/src/plugins/api/middlewares/request-id.js +74 -0
  27. package/src/plugins/api/middlewares/security-headers.js +120 -0
  28. package/src/plugins/api/middlewares/session-tracking.js +194 -0
  29. package/src/plugins/api/routes/auth-routes.js +23 -3
  30. package/src/plugins/api/routes/resource-routes.js +71 -29
  31. package/src/plugins/api/server.js +1017 -94
  32. package/src/plugins/api/utils/guards.js +213 -0
  33. package/src/plugins/api/utils/mime-types.js +154 -0
  34. package/src/plugins/api/utils/openapi-generator.js +44 -11
  35. package/src/plugins/api/utils/path-matcher.js +173 -0
  36. package/src/plugins/api/utils/static-filesystem.js +262 -0
  37. package/src/plugins/api/utils/static-s3.js +231 -0
  38. package/src/plugins/api/utils/template-engine.js +262 -0
  39. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
  40. package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
  41. package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
  42. package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
  43. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
  44. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
  45. package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
  46. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
  47. package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
  48. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
  49. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
  50. package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
  51. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
  52. package/src/plugins/cloud-inventory/index.js +20 -0
  53. package/src/plugins/cloud-inventory/registry.js +146 -0
  54. package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
  55. package/src/plugins/cloud-inventory.plugin.js +1333 -0
  56. package/src/plugins/concerns/plugin-dependencies.js +61 -1
  57. package/src/plugins/eventual-consistency/analytics.js +1 -0
  58. package/src/plugins/identity/README.md +335 -0
  59. package/src/plugins/identity/concerns/mfa-manager.js +204 -0
  60. package/src/plugins/identity/concerns/password.js +138 -0
  61. package/src/plugins/identity/concerns/resource-schemas.js +273 -0
  62. package/src/plugins/identity/concerns/token-generator.js +172 -0
  63. package/src/plugins/identity/email-service.js +422 -0
  64. package/src/plugins/identity/index.js +1052 -0
  65. package/src/plugins/identity/oauth2-server.js +1033 -0
  66. package/src/plugins/identity/oidc-discovery.js +285 -0
  67. package/src/plugins/identity/rsa-keys.js +323 -0
  68. package/src/plugins/identity/server.js +500 -0
  69. package/src/plugins/identity/session-manager.js +453 -0
  70. package/src/plugins/identity/ui/layouts/base.js +251 -0
  71. package/src/plugins/identity/ui/middleware.js +135 -0
  72. package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
  73. package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
  74. package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
  75. package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
  76. package/src/plugins/identity/ui/pages/admin/users.js +263 -0
  77. package/src/plugins/identity/ui/pages/consent.js +262 -0
  78. package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
  79. package/src/plugins/identity/ui/pages/login.js +144 -0
  80. package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
  81. package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
  82. package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
  83. package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
  84. package/src/plugins/identity/ui/pages/profile.js +361 -0
  85. package/src/plugins/identity/ui/pages/register.js +226 -0
  86. package/src/plugins/identity/ui/pages/reset-password.js +128 -0
  87. package/src/plugins/identity/ui/pages/verify-email.js +172 -0
  88. package/src/plugins/identity/ui/routes.js +2541 -0
  89. package/src/plugins/identity/ui/styles/main.css +465 -0
  90. package/src/plugins/index.js +4 -1
  91. package/src/plugins/ml/base-model.class.js +32 -7
  92. package/src/plugins/ml/classification-model.class.js +1 -1
  93. package/src/plugins/ml/timeseries-model.class.js +3 -1
  94. package/src/plugins/ml.plugin.js +124 -32
  95. package/src/plugins/shared/error-handler.js +147 -0
  96. package/src/plugins/shared/index.js +9 -0
  97. package/src/plugins/shared/middlewares/compression.js +117 -0
  98. package/src/plugins/shared/middlewares/cors.js +49 -0
  99. package/src/plugins/shared/middlewares/index.js +11 -0
  100. package/src/plugins/shared/middlewares/logging.js +54 -0
  101. package/src/plugins/shared/middlewares/rate-limit.js +73 -0
  102. package/src/plugins/shared/middlewares/security.js +158 -0
  103. package/src/plugins/shared/response-formatter.js +264 -0
  104. package/src/plugins/tfstate/README.md +126 -126
  105. package/src/resource.class.js +140 -12
  106. package/src/schema.class.js +30 -1
  107. package/src/validator.class.js +57 -6
  108. package/dist/s3db.cjs.js.map +0 -1
@@ -0,0 +1,346 @@
1
+ /**
2
+ * Metrics Collector
3
+ *
4
+ * Collects and aggregates API metrics for monitoring and observability.
5
+ *
6
+ * Metrics Collected:
7
+ * - Request counts by method, path, status
8
+ * - Request duration percentiles (p50, p95, p99)
9
+ * - Auth success/failure counts
10
+ * - Resource operation counts (created, updated, deleted)
11
+ * - User activity (logins, new users)
12
+ * - Error rates
13
+ *
14
+ * @example
15
+ * const metrics = new MetricsCollector({ enabled: true });
16
+ *
17
+ * // Record request
18
+ * metrics.recordRequest({
19
+ * method: 'GET',
20
+ * path: '/users',
21
+ * status: 200,
22
+ * duration: 45
23
+ * });
24
+ *
25
+ * // Get summary
26
+ * const summary = metrics.getSummary();
27
+ */
28
+
29
+ export class MetricsCollector {
30
+ constructor(options = {}) {
31
+ this.options = {
32
+ enabled: options.enabled !== false, // Enabled by default
33
+ verbose: options.verbose || false,
34
+ maxPathsTracked: options.maxPathsTracked || 100, // Limit memory usage
35
+ resetInterval: options.resetInterval || 300000 // Reset every 5 minutes
36
+ };
37
+
38
+ this.metrics = this._createEmptyMetrics();
39
+ this.startTime = Date.now();
40
+
41
+ // Auto-reset metrics periodically to prevent memory growth
42
+ if (this.options.resetInterval > 0) {
43
+ this.resetTimer = setInterval(() => {
44
+ if (this.options.verbose) {
45
+ console.log('[Metrics] Auto-resetting metrics');
46
+ }
47
+ this.reset();
48
+ }, this.options.resetInterval);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Create empty metrics structure
54
+ * @private
55
+ */
56
+ _createEmptyMetrics() {
57
+ return {
58
+ requests: {
59
+ total: 0,
60
+ byMethod: {},
61
+ byStatus: {},
62
+ byPath: {},
63
+ durations: []
64
+ },
65
+ auth: {
66
+ success: 0,
67
+ failure: 0,
68
+ byMethod: {}
69
+ },
70
+ resources: {
71
+ created: 0,
72
+ updated: 0,
73
+ deleted: 0,
74
+ byResource: {}
75
+ },
76
+ users: {
77
+ logins: 0,
78
+ newUsers: 0
79
+ },
80
+ errors: {
81
+ total: 0,
82
+ byType: {}
83
+ }
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Record request metrics
89
+ * @param {Object} data - Request data
90
+ */
91
+ recordRequest({ method, path, status, duration }) {
92
+ if (!this.options.enabled) return;
93
+
94
+ const metrics = this.metrics.requests;
95
+
96
+ metrics.total++;
97
+
98
+ // By method
99
+ metrics.byMethod[method] = (metrics.byMethod[method] || 0) + 1;
100
+
101
+ // By status
102
+ const statusGroup = `${Math.floor(status / 100)}xx`;
103
+ metrics.byStatus[statusGroup] = (metrics.byStatus[statusGroup] || 0) + 1;
104
+
105
+ // By path (limit tracking to prevent memory growth)
106
+ if (Object.keys(metrics.byPath).length < this.options.maxPathsTracked || metrics.byPath[path]) {
107
+ if (!metrics.byPath[path]) {
108
+ metrics.byPath[path] = { count: 0, totalDuration: 0, errors: 0 };
109
+ }
110
+ metrics.byPath[path].count++;
111
+ metrics.byPath[path].totalDuration += duration;
112
+ if (status >= 400) {
113
+ metrics.byPath[path].errors++;
114
+ }
115
+ }
116
+
117
+ // Store duration for percentile calculation
118
+ metrics.durations.push(duration);
119
+
120
+ // Keep only last 1000 durations to prevent memory growth
121
+ if (metrics.durations.length > 1000) {
122
+ metrics.durations.shift();
123
+ }
124
+
125
+ if (this.options.verbose) {
126
+ console.log(`[Metrics] Request: ${method} ${path} ${status} (${duration}ms)`);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Record auth metrics
132
+ * @param {Object} data - Auth data
133
+ */
134
+ recordAuth({ success, method }) {
135
+ if (!this.options.enabled) return;
136
+
137
+ const metrics = this.metrics.auth;
138
+
139
+ if (success) {
140
+ metrics.success++;
141
+ } else {
142
+ metrics.failure++;
143
+ }
144
+
145
+ // By method
146
+ if (!metrics.byMethod[method]) {
147
+ metrics.byMethod[method] = { success: 0, failure: 0 };
148
+ }
149
+
150
+ if (success) {
151
+ metrics.byMethod[method].success++;
152
+ } else {
153
+ metrics.byMethod[method].failure++;
154
+ }
155
+
156
+ if (this.options.verbose) {
157
+ console.log(`[Metrics] Auth: ${method} ${success ? 'success' : 'failure'}`);
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Record resource operation metrics
163
+ * @param {Object} data - Resource operation data
164
+ */
165
+ recordResourceOperation({ action, resource }) {
166
+ if (!this.options.enabled) return;
167
+
168
+ const metrics = this.metrics.resources;
169
+
170
+ // Total by action
171
+ if (action === 'created') metrics.created++;
172
+ else if (action === 'updated') metrics.updated++;
173
+ else if (action === 'deleted') metrics.deleted++;
174
+
175
+ // By resource
176
+ if (!metrics.byResource[resource]) {
177
+ metrics.byResource[resource] = { created: 0, updated: 0, deleted: 0 };
178
+ }
179
+ metrics.byResource[resource][action]++;
180
+
181
+ if (this.options.verbose) {
182
+ console.log(`[Metrics] Resource: ${resource} ${action}`);
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Record user event metrics
188
+ * @param {Object} data - User event data
189
+ */
190
+ recordUserEvent({ action }) {
191
+ if (!this.options.enabled) return;
192
+
193
+ const metrics = this.metrics.users;
194
+
195
+ if (action === 'login') {
196
+ metrics.logins++;
197
+ } else if (action === 'created') {
198
+ metrics.newUsers++;
199
+ }
200
+
201
+ if (this.options.verbose) {
202
+ console.log(`[Metrics] User: ${action}`);
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Record error metrics
208
+ * @param {Object} data - Error data
209
+ */
210
+ recordError({ error, type = 'unknown' }) {
211
+ if (!this.options.enabled) return;
212
+
213
+ const metrics = this.metrics.errors;
214
+
215
+ metrics.total++;
216
+ metrics.byType[type] = (metrics.byType[type] || 0) + 1;
217
+
218
+ if (this.options.verbose) {
219
+ console.log(`[Metrics] Error: ${type} - ${error}`);
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Calculate percentile from sorted array
225
+ * @private
226
+ */
227
+ _percentile(arr, p) {
228
+ if (arr.length === 0) return 0;
229
+ const sorted = [...arr].sort((a, b) => a - b);
230
+ const index = Math.ceil((p / 100) * sorted.length) - 1;
231
+ return sorted[Math.max(0, index)];
232
+ }
233
+
234
+ /**
235
+ * Get metrics summary
236
+ * @returns {Object} Metrics summary
237
+ */
238
+ getSummary() {
239
+ const uptime = Date.now() - this.startTime;
240
+
241
+ return {
242
+ uptime: {
243
+ milliseconds: uptime,
244
+ seconds: Math.floor(uptime / 1000),
245
+ formatted: this._formatDuration(uptime)
246
+ },
247
+ requests: {
248
+ total: this.metrics.requests.total,
249
+ rps: (this.metrics.requests.total / (uptime / 1000)).toFixed(2),
250
+ byMethod: this.metrics.requests.byMethod,
251
+ byStatus: this.metrics.requests.byStatus,
252
+ topPaths: this._getTopPaths(),
253
+ duration: {
254
+ p50: this._percentile(this.metrics.requests.durations, 50),
255
+ p95: this._percentile(this.metrics.requests.durations, 95),
256
+ p99: this._percentile(this.metrics.requests.durations, 99),
257
+ avg: this.metrics.requests.durations.length > 0
258
+ ? (this.metrics.requests.durations.reduce((a, b) => a + b, 0) / this.metrics.requests.durations.length).toFixed(2)
259
+ : 0
260
+ }
261
+ },
262
+ auth: {
263
+ total: this.metrics.auth.success + this.metrics.auth.failure,
264
+ success: this.metrics.auth.success,
265
+ failure: this.metrics.auth.failure,
266
+ successRate: this._calculateRate(this.metrics.auth.success, this.metrics.auth.success + this.metrics.auth.failure),
267
+ byMethod: this.metrics.auth.byMethod
268
+ },
269
+ resources: {
270
+ total: this.metrics.resources.created + this.metrics.resources.updated + this.metrics.resources.deleted,
271
+ created: this.metrics.resources.created,
272
+ updated: this.metrics.resources.updated,
273
+ deleted: this.metrics.resources.deleted,
274
+ byResource: this.metrics.resources.byResource
275
+ },
276
+ users: this.metrics.users,
277
+ errors: {
278
+ total: this.metrics.errors.total,
279
+ rate: this._calculateRate(this.metrics.errors.total, this.metrics.requests.total),
280
+ byType: this.metrics.errors.byType
281
+ }
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Get top paths by request count
287
+ * @private
288
+ */
289
+ _getTopPaths(limit = 10) {
290
+ return Object.entries(this.metrics.requests.byPath)
291
+ .map(([path, data]) => ({
292
+ path,
293
+ count: data.count,
294
+ avgDuration: (data.totalDuration / data.count).toFixed(2),
295
+ errors: data.errors,
296
+ errorRate: this._calculateRate(data.errors, data.count)
297
+ }))
298
+ .sort((a, b) => b.count - a.count)
299
+ .slice(0, limit);
300
+ }
301
+
302
+ /**
303
+ * Calculate rate as percentage
304
+ * @private
305
+ */
306
+ _calculateRate(numerator, denominator) {
307
+ if (denominator === 0) return '0.00%';
308
+ return ((numerator / denominator) * 100).toFixed(2) + '%';
309
+ }
310
+
311
+ /**
312
+ * Format duration in human-readable form
313
+ * @private
314
+ */
315
+ _formatDuration(ms) {
316
+ const seconds = Math.floor(ms / 1000);
317
+ const minutes = Math.floor(seconds / 60);
318
+ const hours = Math.floor(minutes / 60);
319
+ const days = Math.floor(hours / 24);
320
+
321
+ if (days > 0) return `${days}d ${hours % 24}h ${minutes % 60}m`;
322
+ if (hours > 0) return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
323
+ if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
324
+ return `${seconds}s`;
325
+ }
326
+
327
+ /**
328
+ * Reset metrics
329
+ */
330
+ reset() {
331
+ this.metrics = this._createEmptyMetrics();
332
+ this.startTime = Date.now();
333
+ }
334
+
335
+ /**
336
+ * Stop metrics collection and cleanup
337
+ */
338
+ stop() {
339
+ if (this.resetTimer) {
340
+ clearInterval(this.resetTimer);
341
+ this.resetTimer = null;
342
+ }
343
+ }
344
+ }
345
+
346
+ export default MetricsCollector;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * OpenGraph Helper
3
+ *
4
+ * Generates OpenGraph and Twitter Card meta tags for social media previews.
5
+ *
6
+ * @example
7
+ * const og = new OpenGraphHelper({
8
+ * siteName: 'My Site',
9
+ * locale: 'en_US',
10
+ * twitterSite: '@mysite'
11
+ * });
12
+ *
13
+ * const tags = og.generateTags({
14
+ * title: 'Page Title',
15
+ * description: 'Page description',
16
+ * image: '/og-image.jpg',
17
+ * url: 'https://example.com/page'
18
+ * });
19
+ */
20
+ export class OpenGraphHelper {
21
+ constructor(defaults = {}) {
22
+ this.defaults = {
23
+ siteName: defaults.siteName || 'My Site',
24
+ locale: defaults.locale || 'en_US',
25
+ type: defaults.type || 'website',
26
+ twitterCard: defaults.twitterCard || 'summary_large_image',
27
+ twitterSite: defaults.twitterSite || null,
28
+ defaultImage: defaults.defaultImage || null
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Generate OpenGraph meta tags
34
+ *
35
+ * @param {Object} data - OpenGraph data
36
+ * @param {string} data.title - Page title
37
+ * @param {string} data.description - Page description
38
+ * @param {string} data.image - Image URL (absolute or relative)
39
+ * @param {string} data.url - Canonical URL
40
+ * @param {string} data.type - Content type (default: 'website')
41
+ * @param {string} data.locale - Locale (default: from defaults)
42
+ * @param {string} data.siteName - Site name (default: from defaults)
43
+ * @param {string} data.imageAlt - Image alt text
44
+ * @param {number} data.imageWidth - Image width in pixels
45
+ * @param {number} data.imageHeight - Image height in pixels
46
+ * @param {string} data.twitterCard - Twitter card type (default: from defaults)
47
+ * @param {string} data.twitterSite - Twitter @username (default: from defaults)
48
+ * @param {string} data.twitterCreator - Twitter creator @username
49
+ * @returns {string} HTML meta tags
50
+ */
51
+ generateTags(data = {}) {
52
+ const og = { ...this.defaults, ...data };
53
+
54
+ // Use default image if none provided
55
+ const image = og.image || og.defaultImage;
56
+
57
+ const tags = [
58
+ // Basic OpenGraph
59
+ og.title && `<meta property="og:title" content="${this._escape(og.title)}">`,
60
+ og.description && `<meta property="og:description" content="${this._escape(og.description)}">`,
61
+ image && `<meta property="og:image" content="${this._escape(image)}">`,
62
+ og.url && `<meta property="og:url" content="${this._escape(og.url)}">`,
63
+ `<meta property="og:type" content="${this._escape(og.type)}">`,
64
+ `<meta property="og:site_name" content="${this._escape(og.siteName)}">`,
65
+ `<meta property="og:locale" content="${this._escape(og.locale)}">`,
66
+
67
+ // Image metadata
68
+ og.imageAlt && `<meta property="og:image:alt" content="${this._escape(og.imageAlt)}">`,
69
+ og.imageWidth && `<meta property="og:image:width" content="${og.imageWidth}">`,
70
+ og.imageHeight && `<meta property="og:image:height" content="${og.imageHeight}">`,
71
+
72
+ // Twitter Cards
73
+ `<meta name="twitter:card" content="${this._escape(og.twitterCard)}">`,
74
+ og.twitterSite && `<meta name="twitter:site" content="${this._escape(og.twitterSite)}">`,
75
+ og.twitterCreator && `<meta name="twitter:creator" content="${this._escape(og.twitterCreator)}">`,
76
+ og.title && `<meta name="twitter:title" content="${this._escape(og.title)}">`,
77
+ og.description && `<meta name="twitter:description" content="${this._escape(og.description)}">`,
78
+ image && `<meta name="twitter:image" content="${this._escape(image)}">`,
79
+ ];
80
+
81
+ return tags.filter(Boolean).join('\n ');
82
+ }
83
+
84
+ /**
85
+ * Create Hono middleware that injects OG helper into context
86
+ *
87
+ * @example
88
+ * app.use('*', ogHelper.middleware());
89
+ *
90
+ * // In route handler:
91
+ * const ogTags = c.get('og')({ title: 'My Page', ... });
92
+ */
93
+ middleware() {
94
+ return async (c, next) => {
95
+ c.set('og', (data) => this.generateTags(data));
96
+ await next();
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Escape HTML entities to prevent XSS
102
+ * @private
103
+ */
104
+ _escape(str) {
105
+ if (str === null || str === undefined) return '';
106
+
107
+ return String(str)
108
+ .replace(/&/g, '&amp;')
109
+ .replace(/</g, '&lt;')
110
+ .replace(/>/g, '&gt;')
111
+ .replace(/"/g, '&quot;')
112
+ .replace(/'/g, '&#039;');
113
+ }
114
+ }
115
+
116
+ export default OpenGraphHelper;