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.
- package/README.md +25 -10
- package/dist/{s3db.cjs.js → s3db.cjs} +38801 -32446
- package/dist/s3db.cjs.map +1 -0
- package/dist/s3db.es.js +38653 -32291
- package/dist/s3db.es.js.map +1 -1
- package/package.json +218 -22
- package/src/concerns/id.js +90 -6
- package/src/concerns/index.js +2 -1
- package/src/concerns/password-hashing.js +150 -0
- package/src/database.class.js +6 -2
- package/src/plugins/api/auth/basic-auth.js +40 -10
- package/src/plugins/api/auth/index.js +49 -3
- package/src/plugins/api/auth/oauth2-auth.js +171 -0
- package/src/plugins/api/auth/oidc-auth.js +789 -0
- package/src/plugins/api/auth/oidc-client.js +462 -0
- package/src/plugins/api/auth/path-auth-matcher.js +284 -0
- package/src/plugins/api/concerns/event-emitter.js +134 -0
- package/src/plugins/api/concerns/failban-manager.js +651 -0
- package/src/plugins/api/concerns/guards-helpers.js +402 -0
- package/src/plugins/api/concerns/metrics-collector.js +346 -0
- package/src/plugins/api/index.js +510 -57
- package/src/plugins/api/middlewares/failban.js +305 -0
- package/src/plugins/api/middlewares/rate-limit.js +301 -0
- package/src/plugins/api/middlewares/request-id.js +74 -0
- package/src/plugins/api/middlewares/security-headers.js +120 -0
- package/src/plugins/api/middlewares/session-tracking.js +194 -0
- package/src/plugins/api/routes/auth-routes.js +119 -78
- package/src/plugins/api/routes/resource-routes.js +73 -30
- package/src/plugins/api/server.js +1139 -45
- package/src/plugins/api/utils/custom-routes.js +102 -0
- package/src/plugins/api/utils/guards.js +213 -0
- package/src/plugins/api/utils/mime-types.js +154 -0
- package/src/plugins/api/utils/openapi-generator.js +91 -12
- package/src/plugins/api/utils/path-matcher.js +173 -0
- package/src/plugins/api/utils/static-filesystem.js +262 -0
- package/src/plugins/api/utils/static-s3.js +231 -0
- package/src/plugins/api/utils/template-engine.js +188 -0
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
- package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
- package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
- package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
- package/src/plugins/cloud-inventory/index.js +20 -0
- package/src/plugins/cloud-inventory/registry.js +146 -0
- package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
- package/src/plugins/cloud-inventory.plugin.js +1333 -0
- package/src/plugins/concerns/plugin-dependencies.js +62 -2
- package/src/plugins/eventual-consistency/analytics.js +1 -0
- package/src/plugins/eventual-consistency/consolidation.js +2 -2
- package/src/plugins/eventual-consistency/garbage-collection.js +2 -2
- package/src/plugins/eventual-consistency/install.js +2 -2
- package/src/plugins/identity/README.md +335 -0
- package/src/plugins/identity/concerns/mfa-manager.js +204 -0
- package/src/plugins/identity/concerns/password.js +138 -0
- package/src/plugins/identity/concerns/resource-schemas.js +273 -0
- package/src/plugins/identity/concerns/token-generator.js +172 -0
- package/src/plugins/identity/email-service.js +422 -0
- package/src/plugins/identity/index.js +1052 -0
- package/src/plugins/identity/oauth2-server.js +1033 -0
- package/src/plugins/identity/oidc-discovery.js +285 -0
- package/src/plugins/identity/rsa-keys.js +323 -0
- package/src/plugins/identity/server.js +500 -0
- package/src/plugins/identity/session-manager.js +453 -0
- package/src/plugins/identity/ui/layouts/base.js +251 -0
- package/src/plugins/identity/ui/middleware.js +135 -0
- package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
- package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
- package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
- package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
- package/src/plugins/identity/ui/pages/admin/users.js +263 -0
- package/src/plugins/identity/ui/pages/consent.js +262 -0
- package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
- package/src/plugins/identity/ui/pages/login.js +144 -0
- package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
- package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
- package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
- package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
- package/src/plugins/identity/ui/pages/profile.js +361 -0
- package/src/plugins/identity/ui/pages/register.js +226 -0
- package/src/plugins/identity/ui/pages/reset-password.js +128 -0
- package/src/plugins/identity/ui/pages/verify-email.js +172 -0
- package/src/plugins/identity/ui/routes.js +2541 -0
- package/src/plugins/identity/ui/styles/main.css +465 -0
- package/src/plugins/index.js +4 -1
- package/src/plugins/ml/base-model.class.js +65 -16
- package/src/plugins/ml/classification-model.class.js +1 -1
- package/src/plugins/ml/timeseries-model.class.js +3 -1
- package/src/plugins/ml.plugin.js +584 -31
- package/src/plugins/shared/error-handler.js +147 -0
- package/src/plugins/shared/index.js +9 -0
- package/src/plugins/shared/middlewares/compression.js +117 -0
- package/src/plugins/shared/middlewares/cors.js +49 -0
- package/src/plugins/shared/middlewares/index.js +11 -0
- package/src/plugins/shared/middlewares/logging.js +54 -0
- package/src/plugins/shared/middlewares/rate-limit.js +73 -0
- package/src/plugins/shared/middlewares/security.js +158 -0
- package/src/plugins/shared/response-formatter.js +264 -0
- package/src/plugins/state-machine.plugin.js +57 -2
- package/src/resource.class.js +140 -12
- package/src/schema.class.js +30 -1
- package/src/validator.class.js +57 -6
- 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(/ /g, ' ')
|
|
159
|
+
.replace(/&/g, '&')
|
|
160
|
+
.replace(/</g, '<')
|
|
161
|
+
.replace(/>/g, '>')
|
|
162
|
+
.replace(/"/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>© ${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;
|