s3db.js 13.4.0 → 13.6.0

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 (110) hide show
  1. package/README.md +25 -10
  2. package/dist/{s3db.cjs.js → s3db.cjs} +38801 -32446
  3. package/dist/s3db.cjs.map +1 -0
  4. package/dist/s3db.es.js +38653 -32291
  5. package/dist/s3db.es.js.map +1 -1
  6. package/package.json +218 -22
  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 +6 -2
  11. package/src/plugins/api/auth/basic-auth.js +40 -10
  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/index.js +510 -57
  22. package/src/plugins/api/middlewares/failban.js +305 -0
  23. package/src/plugins/api/middlewares/rate-limit.js +301 -0
  24. package/src/plugins/api/middlewares/request-id.js +74 -0
  25. package/src/plugins/api/middlewares/security-headers.js +120 -0
  26. package/src/plugins/api/middlewares/session-tracking.js +194 -0
  27. package/src/plugins/api/routes/auth-routes.js +119 -78
  28. package/src/plugins/api/routes/resource-routes.js +73 -30
  29. package/src/plugins/api/server.js +1139 -45
  30. package/src/plugins/api/utils/custom-routes.js +102 -0
  31. package/src/plugins/api/utils/guards.js +213 -0
  32. package/src/plugins/api/utils/mime-types.js +154 -0
  33. package/src/plugins/api/utils/openapi-generator.js +91 -12
  34. package/src/plugins/api/utils/path-matcher.js +173 -0
  35. package/src/plugins/api/utils/static-filesystem.js +262 -0
  36. package/src/plugins/api/utils/static-s3.js +231 -0
  37. package/src/plugins/api/utils/template-engine.js +188 -0
  38. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
  39. package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
  40. package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
  41. package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
  42. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
  43. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
  44. package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
  45. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
  46. package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
  47. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
  48. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
  49. package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
  50. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
  51. package/src/plugins/cloud-inventory/index.js +20 -0
  52. package/src/plugins/cloud-inventory/registry.js +146 -0
  53. package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
  54. package/src/plugins/cloud-inventory.plugin.js +1333 -0
  55. package/src/plugins/concerns/plugin-dependencies.js +62 -2
  56. package/src/plugins/eventual-consistency/analytics.js +1 -0
  57. package/src/plugins/eventual-consistency/consolidation.js +2 -2
  58. package/src/plugins/eventual-consistency/garbage-collection.js +2 -2
  59. package/src/plugins/eventual-consistency/install.js +2 -2
  60. package/src/plugins/identity/README.md +335 -0
  61. package/src/plugins/identity/concerns/mfa-manager.js +204 -0
  62. package/src/plugins/identity/concerns/password.js +138 -0
  63. package/src/plugins/identity/concerns/resource-schemas.js +273 -0
  64. package/src/plugins/identity/concerns/token-generator.js +172 -0
  65. package/src/plugins/identity/email-service.js +422 -0
  66. package/src/plugins/identity/index.js +1052 -0
  67. package/src/plugins/identity/oauth2-server.js +1033 -0
  68. package/src/plugins/identity/oidc-discovery.js +285 -0
  69. package/src/plugins/identity/rsa-keys.js +323 -0
  70. package/src/plugins/identity/server.js +500 -0
  71. package/src/plugins/identity/session-manager.js +453 -0
  72. package/src/plugins/identity/ui/layouts/base.js +251 -0
  73. package/src/plugins/identity/ui/middleware.js +135 -0
  74. package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
  75. package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
  76. package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
  77. package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
  78. package/src/plugins/identity/ui/pages/admin/users.js +263 -0
  79. package/src/plugins/identity/ui/pages/consent.js +262 -0
  80. package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
  81. package/src/plugins/identity/ui/pages/login.js +144 -0
  82. package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
  83. package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
  84. package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
  85. package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
  86. package/src/plugins/identity/ui/pages/profile.js +361 -0
  87. package/src/plugins/identity/ui/pages/register.js +226 -0
  88. package/src/plugins/identity/ui/pages/reset-password.js +128 -0
  89. package/src/plugins/identity/ui/pages/verify-email.js +172 -0
  90. package/src/plugins/identity/ui/routes.js +2541 -0
  91. package/src/plugins/identity/ui/styles/main.css +465 -0
  92. package/src/plugins/index.js +4 -1
  93. package/src/plugins/ml/base-model.class.js +65 -16
  94. package/src/plugins/ml/classification-model.class.js +1 -1
  95. package/src/plugins/ml/timeseries-model.class.js +3 -1
  96. package/src/plugins/ml.plugin.js +584 -31
  97. package/src/plugins/shared/error-handler.js +147 -0
  98. package/src/plugins/shared/index.js +9 -0
  99. package/src/plugins/shared/middlewares/compression.js +117 -0
  100. package/src/plugins/shared/middlewares/cors.js +49 -0
  101. package/src/plugins/shared/middlewares/index.js +11 -0
  102. package/src/plugins/shared/middlewares/logging.js +54 -0
  103. package/src/plugins/shared/middlewares/rate-limit.js +73 -0
  104. package/src/plugins/shared/middlewares/security.js +158 -0
  105. package/src/plugins/shared/response-formatter.js +264 -0
  106. package/src/plugins/state-machine.plugin.js +57 -2
  107. package/src/resource.class.js +140 -12
  108. package/src/schema.class.js +30 -1
  109. package/src/validator.class.js +57 -6
  110. package/dist/s3db.cjs.js.map +0 -1
@@ -0,0 +1,422 @@
1
+ /**
2
+ * Email Service for Identity Provider
3
+ * Handles email sending via SMTP with template support
4
+ */
5
+
6
+ /**
7
+ * Email Service class
8
+ * @class
9
+ */
10
+ export class EmailService {
11
+ /**
12
+ * Create Email Service instance
13
+ * @param {Object} options - Email service configuration
14
+ */
15
+ constructor(options = {}) {
16
+ this.config = {
17
+ enabled: options.enabled !== false,
18
+ from: options.from || 'noreply@s3db.identity',
19
+ replyTo: options.replyTo || null,
20
+
21
+ // SMTP configuration
22
+ smtp: {
23
+ host: options.smtp?.host || 'localhost',
24
+ port: options.smtp?.port || 587,
25
+ secure: options.smtp?.secure || false, // true for 465, false for other ports
26
+ auth: {
27
+ user: options.smtp?.auth?.user || '',
28
+ pass: options.smtp?.auth?.pass || ''
29
+ },
30
+ // Optional TLS options
31
+ tls: {
32
+ rejectUnauthorized: options.smtp?.tls?.rejectUnauthorized !== false
33
+ }
34
+ },
35
+
36
+ // Template configuration
37
+ templates: {
38
+ baseUrl: options.templates?.baseUrl || 'http://localhost:4000',
39
+ brandName: options.templates?.brandName || 'S3DB Identity',
40
+ brandLogo: options.templates?.brandLogo || null,
41
+ brandColor: options.templates?.brandColor || '#007bff',
42
+ supportEmail: options.templates?.supportEmail || null,
43
+ customFooter: options.templates?.customFooter || null
44
+ },
45
+
46
+ verbose: options.verbose || false
47
+ };
48
+
49
+ this.transporter = null;
50
+ this.initialized = false;
51
+ }
52
+
53
+ /**
54
+ * Initialize email service (lazy initialization)
55
+ * @private
56
+ */
57
+ async _initialize() {
58
+ if (this.initialized || !this.config.enabled) {
59
+ return;
60
+ }
61
+
62
+ try {
63
+ // Dynamic import of nodemailer
64
+ const nodemailer = await import('nodemailer');
65
+
66
+ // Create SMTP transporter
67
+ this.transporter = nodemailer.default.createTransport({
68
+ host: this.config.smtp.host,
69
+ port: this.config.smtp.port,
70
+ secure: this.config.smtp.secure,
71
+ auth: this.config.smtp.auth,
72
+ tls: this.config.smtp.tls
73
+ });
74
+
75
+ // Verify connection
76
+ if (this.config.verbose) {
77
+ await this.transporter.verify();
78
+ console.log('[EmailService] SMTP connection verified');
79
+ }
80
+
81
+ this.initialized = true;
82
+ } catch (error) {
83
+ console.error('[EmailService] Failed to initialize:', error);
84
+ throw new Error(`Failed to initialize email service: ${error.message}`);
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Send an email
90
+ * @param {Object} options - Email options
91
+ * @param {string} options.to - Recipient email address
92
+ * @param {string} options.subject - Email subject
93
+ * @param {string} options.html - HTML email body
94
+ * @param {string} [options.text] - Plain text email body (fallback)
95
+ * @param {string} [options.from] - Override sender address
96
+ * @param {string} [options.replyTo] - Reply-to address
97
+ * @returns {Promise<Object>} Send result
98
+ */
99
+ async sendEmail(options) {
100
+ if (!this.config.enabled) {
101
+ if (this.config.verbose) {
102
+ console.log('[EmailService] Email service disabled, skipping send');
103
+ }
104
+ return { success: false, reason: 'disabled' };
105
+ }
106
+
107
+ // Initialize if needed
108
+ if (!this.initialized) {
109
+ await this._initialize();
110
+ }
111
+
112
+ const { to, subject, html, text, from, replyTo } = options;
113
+
114
+ if (!to || !subject || !html) {
115
+ throw new Error('Email requires to, subject, and html fields');
116
+ }
117
+
118
+ try {
119
+ const info = await this.transporter.sendMail({
120
+ from: from || this.config.from,
121
+ to,
122
+ subject,
123
+ text: text || this._htmlToText(html),
124
+ html,
125
+ replyTo: replyTo || this.config.replyTo
126
+ });
127
+
128
+ if (this.config.verbose) {
129
+ console.log('[EmailService] Email sent successfully:', info.messageId);
130
+ }
131
+
132
+ return {
133
+ success: true,
134
+ messageId: info.messageId,
135
+ accepted: info.accepted,
136
+ rejected: info.rejected
137
+ };
138
+ } catch (error) {
139
+ console.error('[EmailService] Failed to send email:', error);
140
+ return {
141
+ success: false,
142
+ error: error.message
143
+ };
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Convert HTML to plain text (simple implementation)
149
+ * @param {string} html - HTML content
150
+ * @returns {string} Plain text
151
+ * @private
152
+ */
153
+ _htmlToText(html) {
154
+ return html
155
+ .replace(/<br\s*\/?>/gi, '\n')
156
+ .replace(/<\/p>/gi, '\n\n')
157
+ .replace(/<[^>]+>/g, '')
158
+ .replace(/&nbsp;/g, ' ')
159
+ .replace(/&amp;/g, '&')
160
+ .replace(/&lt;/g, '<')
161
+ .replace(/&gt;/g, '>')
162
+ .replace(/&quot;/g, '"')
163
+ .trim();
164
+ }
165
+
166
+ /**
167
+ * Base email template wrapper
168
+ * @param {Object} options - Template options
169
+ * @param {string} options.title - Email title
170
+ * @param {string} options.preheader - Email preheader (preview text)
171
+ * @param {string} options.content - Email content (HTML)
172
+ * @returns {string} HTML email
173
+ * @private
174
+ */
175
+ _baseTemplate({ title, preheader, content }) {
176
+ const { brandName, brandLogo, brandColor, supportEmail, customFooter } = this.config.templates;
177
+
178
+ return `<!DOCTYPE html>
179
+ <html lang="en">
180
+ <head>
181
+ <meta charset="UTF-8">
182
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
183
+ <title>${title}</title>
184
+ <style>
185
+ body {
186
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
187
+ line-height: 1.6;
188
+ color: #333;
189
+ margin: 0;
190
+ padding: 0;
191
+ background-color: #f4f4f4;
192
+ }
193
+ .email-wrapper {
194
+ max-width: 600px;
195
+ margin: 0 auto;
196
+ background-color: #ffffff;
197
+ }
198
+ .email-header {
199
+ background-color: ${brandColor};
200
+ padding: 30px 20px;
201
+ text-align: center;
202
+ }
203
+ .email-header h1 {
204
+ color: #ffffff;
205
+ margin: 0;
206
+ font-size: 24px;
207
+ }
208
+ .email-body {
209
+ padding: 40px 20px;
210
+ }
211
+ .email-footer {
212
+ background-color: #f8f9fa;
213
+ padding: 20px;
214
+ text-align: center;
215
+ font-size: 12px;
216
+ color: #6c757d;
217
+ border-top: 1px solid #dee2e6;
218
+ }
219
+ .button {
220
+ display: inline-block;
221
+ padding: 12px 30px;
222
+ background-color: ${brandColor};
223
+ color: #ffffff !important;
224
+ text-decoration: none;
225
+ border-radius: 4px;
226
+ font-weight: 500;
227
+ margin: 20px 0;
228
+ }
229
+ .button:hover {
230
+ background-color: ${brandColor}dd;
231
+ }
232
+ .info-box {
233
+ background-color: #f8f9fa;
234
+ border-left: 4px solid ${brandColor};
235
+ padding: 15px;
236
+ margin: 20px 0;
237
+ }
238
+ .preheader {
239
+ display: none;
240
+ font-size: 1px;
241
+ color: #ffffff;
242
+ line-height: 1px;
243
+ max-height: 0;
244
+ max-width: 0;
245
+ opacity: 0;
246
+ overflow: hidden;
247
+ }
248
+ </style>
249
+ </head>
250
+ <body>
251
+ <span class="preheader">${preheader || ''}</span>
252
+ <div class="email-wrapper">
253
+ <div class="email-header">
254
+ ${brandLogo ? `<img src="${brandLogo}" alt="${brandName}" height="40" style="margin-bottom: 10px;">` : ''}
255
+ <h1>${brandName}</h1>
256
+ </div>
257
+ <div class="email-body">
258
+ ${content}
259
+ </div>
260
+ <div class="email-footer">
261
+ ${customFooter || `
262
+ <p>This email was sent from ${brandName}</p>
263
+ ${supportEmail ? `<p>Need help? Contact us at <a href="mailto:${supportEmail}">${supportEmail}</a></p>` : ''}
264
+ <p>&copy; ${new Date().getFullYear()} ${brandName}. All rights reserved.</p>
265
+ `}
266
+ </div>
267
+ </div>
268
+ </body>
269
+ </html>`;
270
+ }
271
+
272
+ /**
273
+ * Send password reset email
274
+ * @param {Object} options - Email options
275
+ * @param {string} options.to - Recipient email
276
+ * @param {string} options.name - Recipient name
277
+ * @param {string} options.resetToken - Password reset token
278
+ * @param {number} [options.expiresIn] - Token expiration in minutes (default: 60)
279
+ * @returns {Promise<Object>} Send result
280
+ */
281
+ async sendPasswordResetEmail({ to, name, resetToken, expiresIn = 60 }) {
282
+ const { baseUrl } = this.config.templates;
283
+ const resetUrl = `${baseUrl}/reset-password?token=${resetToken}`;
284
+
285
+ const content = `
286
+ <h2>Password Reset Request</h2>
287
+ <p>Hi ${name},</p>
288
+ <p>We received a request to reset your password. If you didn't make this request, you can safely ignore this email.</p>
289
+ <p>To reset your password, click the button below:</p>
290
+ <p style="text-align: center;">
291
+ <a href="${resetUrl}" class="button">Reset Password</a>
292
+ </p>
293
+ <div class="info-box">
294
+ <p><strong>⏰ This link will expire in ${expiresIn} minutes.</strong></p>
295
+ <p>If the button doesn't work, copy and paste this link into your browser:</p>
296
+ <p style="word-break: break-all;"><a href="${resetUrl}">${resetUrl}</a></p>
297
+ </div>
298
+ <p>If you didn't request a password reset, please ignore this email or contact support if you have concerns.</p>
299
+ <p>Best regards,<br>The ${this.config.templates.brandName} Team</p>
300
+ `;
301
+
302
+ const html = this._baseTemplate({
303
+ title: 'Reset Your Password',
304
+ preheader: 'Click here to reset your password',
305
+ content
306
+ });
307
+
308
+ return this.sendEmail({
309
+ to,
310
+ subject: 'Reset Your Password',
311
+ html
312
+ });
313
+ }
314
+
315
+ /**
316
+ * Send email verification email
317
+ * @param {Object} options - Email options
318
+ * @param {string} options.to - Recipient email
319
+ * @param {string} options.name - Recipient name
320
+ * @param {string} options.verificationToken - Email verification token
321
+ * @param {number} [options.expiresIn] - Token expiration in hours (default: 24)
322
+ * @returns {Promise<Object>} Send result
323
+ */
324
+ async sendEmailVerificationEmail({ to, name, verificationToken, expiresIn = 24 }) {
325
+ const { baseUrl } = this.config.templates;
326
+ const verifyUrl = `${baseUrl}/verify-email?token=${verificationToken}`;
327
+
328
+ const content = `
329
+ <h2>Verify Your Email Address</h2>
330
+ <p>Hi ${name},</p>
331
+ <p>Thank you for creating an account with ${this.config.templates.brandName}! To complete your registration, please verify your email address.</p>
332
+ <p style="text-align: center;">
333
+ <a href="${verifyUrl}" class="button">Verify Email Address</a>
334
+ </p>
335
+ <div class="info-box">
336
+ <p><strong>⏰ This link will expire in ${expiresIn} hours.</strong></p>
337
+ <p>If the button doesn't work, copy and paste this link into your browser:</p>
338
+ <p style="word-break: break-all;"><a href="${verifyUrl}">${verifyUrl}</a></p>
339
+ </div>
340
+ <p>If you didn't create an account with us, you can safely ignore this email.</p>
341
+ <p>Welcome aboard!<br>The ${this.config.templates.brandName} Team</p>
342
+ `;
343
+
344
+ const html = this._baseTemplate({
345
+ title: 'Verify Your Email',
346
+ preheader: 'Verify your email address to get started',
347
+ content
348
+ });
349
+
350
+ return this.sendEmail({
351
+ to,
352
+ subject: 'Verify Your Email Address',
353
+ html
354
+ });
355
+ }
356
+
357
+ /**
358
+ * Send welcome email after successful registration
359
+ * @param {Object} options - Email options
360
+ * @param {string} options.to - Recipient email
361
+ * @param {string} options.name - Recipient name
362
+ * @returns {Promise<Object>} Send result
363
+ */
364
+ async sendWelcomeEmail({ to, name }) {
365
+ const { baseUrl } = this.config.templates;
366
+
367
+ const content = `
368
+ <h2>Welcome to ${this.config.templates.brandName}!</h2>
369
+ <p>Hi ${name},</p>
370
+ <p>Your account is now active and you're ready to get started.</p>
371
+ <p style="text-align: center;">
372
+ <a href="${baseUrl}/profile" class="button">Go to Your Profile</a>
373
+ </p>
374
+ <p>If you have any questions or need help, don't hesitate to reach out to our support team.</p>
375
+ <p>Best regards,<br>The ${this.config.templates.brandName} Team</p>
376
+ `;
377
+
378
+ const html = this._baseTemplate({
379
+ title: 'Welcome!',
380
+ preheader: 'Your account is ready',
381
+ content
382
+ });
383
+
384
+ return this.sendEmail({
385
+ to,
386
+ subject: `Welcome to ${this.config.templates.brandName}!`,
387
+ html
388
+ });
389
+ }
390
+
391
+ /**
392
+ * Test email service connection
393
+ * @returns {Promise<boolean>} True if connection is valid
394
+ */
395
+ async testConnection() {
396
+ if (!this.config.enabled) {
397
+ return false;
398
+ }
399
+
400
+ try {
401
+ await this._initialize();
402
+ await this.transporter.verify();
403
+ return true;
404
+ } catch (error) {
405
+ console.error('[EmailService] Connection test failed:', error);
406
+ return false;
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Close transporter connection
412
+ */
413
+ async close() {
414
+ if (this.transporter) {
415
+ this.transporter.close();
416
+ this.transporter = null;
417
+ this.initialized = false;
418
+ }
419
+ }
420
+ }
421
+
422
+ export default EmailService;