strapi-plugin-magic-mail 2.0.1 โ 2.0.3
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 +6 -5
- package/dist/server/index.js +602 -665
- package/dist/server/index.mjs +602 -665
- package/package.json +1 -1
- package/server/src/bootstrap.js +16 -16
- package/server/src/config/features.js +7 -7
- package/server/src/controllers/accounts.js +15 -6
- package/server/src/controllers/analytics.js +56 -42
- package/server/src/controllers/license.js +9 -7
- package/server/src/controllers/oauth.js +9 -9
- package/server/src/controllers/routing-rules.js +18 -11
- package/server/src/controllers/test.js +111 -193
- package/server/src/services/account-manager.js +73 -21
- package/server/src/services/analytics.js +88 -72
- package/server/src/services/email-designer.js +131 -284
- package/server/src/services/email-router.js +69 -43
- package/server/src/services/license-guard.js +24 -24
- package/server/src/services/oauth.js +11 -11
- package/server/src/services/service.js +1 -1
- package/server/src/utils/encryption.js +1 -1
|
@@ -30,12 +30,18 @@ module.exports = ({ strapi }) => ({
|
|
|
30
30
|
templateId = null, // Template Reference ID
|
|
31
31
|
templateData, // Data for template rendering
|
|
32
32
|
data, // Alias for templateData (for native Strapi compatibility)
|
|
33
|
+
skipLinkTracking = false, // Skip link rewriting for sensitive URLs (e.g., Magic Links)
|
|
33
34
|
} = emailData;
|
|
34
35
|
|
|
35
36
|
// Support both 'data' and 'templateData' for backward compatibility
|
|
36
37
|
if (!templateData && data) {
|
|
37
38
|
templateData = data;
|
|
38
39
|
}
|
|
40
|
+
|
|
41
|
+
// Debug log for skipLinkTracking
|
|
42
|
+
if (skipLinkTracking) {
|
|
43
|
+
strapi.log.info(`[magic-mail] [SKIP-TRACK] skipLinkTracking=true received for email to: ${to}`);
|
|
44
|
+
}
|
|
39
45
|
|
|
40
46
|
// NEW: If templateId/templateReferenceId provided, render template
|
|
41
47
|
let renderedTemplate = null;
|
|
@@ -53,14 +59,14 @@ module.exports = ({ strapi }) => ({
|
|
|
53
59
|
const numericTemplateId = Number(templateId);
|
|
54
60
|
|
|
55
61
|
if (!Number.isNaN(numericTemplateId) && Number.isInteger(numericTemplateId)) {
|
|
56
|
-
strapi.log.info(`[magic-mail]
|
|
62
|
+
strapi.log.info(`[magic-mail] [CHECK] Looking up template by ID: ${numericTemplateId}`);
|
|
57
63
|
templateRecord = await strapi
|
|
58
64
|
.plugin('magic-mail')
|
|
59
65
|
.service('email-designer')
|
|
60
66
|
.findOne(numericTemplateId);
|
|
61
67
|
|
|
62
68
|
if (!templateRecord) {
|
|
63
|
-
strapi.log.error(`[magic-mail]
|
|
69
|
+
strapi.log.error(`[magic-mail] [ERROR] Template with ID ${numericTemplateId} not found in database`);
|
|
64
70
|
throw new Error(`Template with ID ${numericTemplateId} not found`);
|
|
65
71
|
}
|
|
66
72
|
|
|
@@ -70,7 +76,7 @@ module.exports = ({ strapi }) => ({
|
|
|
70
76
|
|
|
71
77
|
resolvedTemplateReferenceId = String(templateRecord.templateReferenceId).trim();
|
|
72
78
|
strapi.log.info(
|
|
73
|
-
`[magic-mail]
|
|
79
|
+
`[magic-mail] [SUCCESS] Found template: ID=${templateRecord.id}, referenceId="${resolvedTemplateReferenceId}", name="${templateRecord.name}"`
|
|
74
80
|
);
|
|
75
81
|
} else {
|
|
76
82
|
// templateId was provided but not numeric; treat it directly as reference ID
|
|
@@ -96,7 +102,7 @@ module.exports = ({ strapi }) => ({
|
|
|
96
102
|
type = type || renderedTemplate.category; // Use template category if not specified
|
|
97
103
|
|
|
98
104
|
strapi.log.info(
|
|
99
|
-
`[magic-mail]
|
|
105
|
+
`[magic-mail] [EMAIL] Rendered template reference "${resolvedTemplateReferenceId}" (requested ID: ${templateId ?? 'n/a'}): ${renderedTemplate.templateName}`
|
|
100
106
|
);
|
|
101
107
|
|
|
102
108
|
// Ensure templateId/templateName are populated for logging/analytics
|
|
@@ -105,7 +111,7 @@ module.exports = ({ strapi }) => ({
|
|
|
105
111
|
emailData.templateName = templateRecord?.name || renderedTemplate.templateName;
|
|
106
112
|
}
|
|
107
113
|
} catch (error) {
|
|
108
|
-
strapi.log.error(`[magic-mail]
|
|
114
|
+
strapi.log.error(`[magic-mail] [ERROR] Template rendering failed: ${error.message}`);
|
|
109
115
|
throw new Error(`Template rendering failed: ${error.message}`);
|
|
110
116
|
}
|
|
111
117
|
}
|
|
@@ -139,15 +145,20 @@ module.exports = ({ strapi }) => ({
|
|
|
139
145
|
|
|
140
146
|
recipientHash = analyticsService.generateRecipientHash(emailLog.emailId, to);
|
|
141
147
|
|
|
142
|
-
// Inject tracking pixel
|
|
148
|
+
// Inject tracking pixel (open tracking)
|
|
143
149
|
html = analyticsService.injectTrackingPixel(html, emailLog.emailId, recipientHash);
|
|
144
150
|
|
|
145
|
-
// Rewrite links for click tracking (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
// Rewrite links for click tracking (unless explicitly disabled)
|
|
152
|
+
// skipLinkTracking is useful for sensitive URLs like Magic Links, password resets, etc.
|
|
153
|
+
// where the original URL must remain intact for security/UX reasons
|
|
154
|
+
if (!skipLinkTracking) {
|
|
155
|
+
html = await analyticsService.rewriteLinksForTracking(html, emailLog.emailId, recipientHash);
|
|
156
|
+
strapi.log.info(`[magic-mail] [STATS] Full tracking enabled for email: ${emailLog.emailId}`);
|
|
157
|
+
} else {
|
|
158
|
+
strapi.log.info(`[magic-mail] [STATS] Open tracking enabled, link tracking DISABLED for email: ${emailLog.emailId}`);
|
|
159
|
+
}
|
|
149
160
|
} catch (error) {
|
|
150
|
-
strapi.log.error(`[magic-mail]
|
|
161
|
+
strapi.log.error(`[magic-mail] [WARNING] Tracking setup failed (continuing without tracking):`, error.message);
|
|
151
162
|
// Continue sending email even if tracking fails
|
|
152
163
|
}
|
|
153
164
|
}
|
|
@@ -165,7 +176,7 @@ module.exports = ({ strapi }) => ({
|
|
|
165
176
|
if (priority === 'high') {
|
|
166
177
|
const hasFeature = await licenseGuard.hasFeature('priority-headers');
|
|
167
178
|
if (!hasFeature) {
|
|
168
|
-
strapi.log.warn('[magic-mail]
|
|
179
|
+
strapi.log.warn('[magic-mail] [WARNING] High priority emails require Advanced license - using normal priority');
|
|
169
180
|
emailData.priority = 'normal';
|
|
170
181
|
}
|
|
171
182
|
}
|
|
@@ -203,8 +214,8 @@ module.exports = ({ strapi }) => ({
|
|
|
203
214
|
// Update email log with account info (if tracking enabled)
|
|
204
215
|
if (emailLog) {
|
|
205
216
|
try {
|
|
206
|
-
await strapi.
|
|
207
|
-
|
|
217
|
+
await strapi.documents('plugin::magic-mail.email-log').update({
|
|
218
|
+
documentId: emailLog.documentId,
|
|
208
219
|
data: {
|
|
209
220
|
accountId: account.id,
|
|
210
221
|
accountName: account.name,
|
|
@@ -217,9 +228,9 @@ module.exports = ({ strapi }) => ({
|
|
|
217
228
|
}
|
|
218
229
|
|
|
219
230
|
// Update stats
|
|
220
|
-
await this.updateAccountStats(account.
|
|
231
|
+
await this.updateAccountStats(account.documentId);
|
|
221
232
|
|
|
222
|
-
strapi.log.info(`[magic-mail]
|
|
233
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Email sent to ${to} via ${account.name}`);
|
|
223
234
|
|
|
224
235
|
return {
|
|
225
236
|
success: true,
|
|
@@ -227,7 +238,7 @@ module.exports = ({ strapi }) => ({
|
|
|
227
238
|
messageId: result.messageId,
|
|
228
239
|
};
|
|
229
240
|
} catch (error) {
|
|
230
|
-
strapi.log.error('[magic-mail]
|
|
241
|
+
strapi.log.error('[magic-mail] [ERROR] Email send failed:', error);
|
|
231
242
|
|
|
232
243
|
throw error;
|
|
233
244
|
}
|
|
@@ -237,25 +248,25 @@ module.exports = ({ strapi }) => ({
|
|
|
237
248
|
* Select best account based on rules
|
|
238
249
|
*/
|
|
239
250
|
async selectAccount(type, priority, excludeIds = [], emailData = {}) {
|
|
240
|
-
// Get all active accounts
|
|
241
|
-
const accounts = await strapi.
|
|
251
|
+
// Get all active accounts using Document Service
|
|
252
|
+
const accounts = await strapi.documents('plugin::magic-mail.email-account').findMany({
|
|
242
253
|
filters: {
|
|
243
254
|
isActive: true,
|
|
244
255
|
id: { $notIn: excludeIds },
|
|
245
256
|
},
|
|
246
|
-
sort: { priority: 'desc' },
|
|
257
|
+
sort: [{ priority: 'desc' }],
|
|
247
258
|
});
|
|
248
259
|
|
|
249
260
|
if (!accounts || accounts.length === 0) {
|
|
250
261
|
return null;
|
|
251
262
|
}
|
|
252
263
|
|
|
253
|
-
// Get all active routing rules
|
|
254
|
-
const allRules = await strapi.
|
|
264
|
+
// Get all active routing rules using Document Service
|
|
265
|
+
const allRules = await strapi.documents('plugin::magic-mail.routing-rule').findMany({
|
|
255
266
|
filters: {
|
|
256
267
|
isActive: true,
|
|
257
268
|
},
|
|
258
|
-
sort: { priority: 'desc' },
|
|
269
|
+
sort: [{ priority: 'desc' }],
|
|
259
270
|
});
|
|
260
271
|
|
|
261
272
|
// Check routing rules with different match types
|
|
@@ -292,14 +303,14 @@ module.exports = ({ strapi }) => ({
|
|
|
292
303
|
if (matches) {
|
|
293
304
|
const account = accounts.find(a => a.name === rule.accountName);
|
|
294
305
|
if (account) {
|
|
295
|
-
strapi.log.info(`[magic-mail]
|
|
306
|
+
strapi.log.info(`[magic-mail] [ROUTE] Routing rule matched: ${rule.name} -> ${account.name}`);
|
|
296
307
|
return account;
|
|
297
308
|
}
|
|
298
309
|
// If primary account not found, try fallback
|
|
299
310
|
if (rule.fallbackAccountName) {
|
|
300
311
|
const fallbackAccount = accounts.find(a => a.name === rule.fallbackAccountName);
|
|
301
312
|
if (fallbackAccount) {
|
|
302
|
-
strapi.log.info(`[magic-mail]
|
|
313
|
+
strapi.log.info(`[magic-mail] [FALLBACK] Using fallback account: ${fallbackAccount.name}`);
|
|
303
314
|
return fallbackAccount;
|
|
304
315
|
}
|
|
305
316
|
}
|
|
@@ -426,7 +437,7 @@ module.exports = ({ strapi }) => ({
|
|
|
426
437
|
mailOptions.headers['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click';
|
|
427
438
|
mailOptions.headers['Precedence'] = 'bulk'; // Mark as bulk mail
|
|
428
439
|
} else {
|
|
429
|
-
strapi.log.warn('[magic-mail]
|
|
440
|
+
strapi.log.warn('[magic-mail] [WARNING] Marketing email without unsubscribe URL - may violate GDPR/CAN-SPAM');
|
|
430
441
|
}
|
|
431
442
|
}
|
|
432
443
|
|
|
@@ -490,7 +501,7 @@ module.exports = ({ strapi }) => ({
|
|
|
490
501
|
);
|
|
491
502
|
|
|
492
503
|
currentAccessToken = newTokens.accessToken;
|
|
493
|
-
strapi.log.info('[magic-mail]
|
|
504
|
+
strapi.log.info('[magic-mail] [SUCCESS] Token refreshed successfully');
|
|
494
505
|
|
|
495
506
|
// Update stored tokens
|
|
496
507
|
const { encryptCredentials } = require('../utils/encryption');
|
|
@@ -500,7 +511,8 @@ module.exports = ({ strapi }) => ({
|
|
|
500
511
|
expiresAt: newTokens.expiresAt,
|
|
501
512
|
});
|
|
502
513
|
|
|
503
|
-
await strapi.
|
|
514
|
+
await strapi.documents('plugin::magic-mail.email-account').update({
|
|
515
|
+
documentId: account.documentId,
|
|
504
516
|
data: { oauth: updatedOAuth },
|
|
505
517
|
});
|
|
506
518
|
} catch (refreshErr) {
|
|
@@ -661,14 +673,20 @@ module.exports = ({ strapi }) => ({
|
|
|
661
673
|
}
|
|
662
674
|
|
|
663
675
|
const result = await response.json();
|
|
664
|
-
strapi.log.info('[magic-mail]
|
|
676
|
+
strapi.log.info('[magic-mail] [SUCCESS] Email sent via Gmail API');
|
|
665
677
|
|
|
666
678
|
return {
|
|
667
679
|
messageId: result.id,
|
|
668
680
|
response: 'OK',
|
|
669
681
|
};
|
|
670
682
|
} catch (err) {
|
|
671
|
-
strapi.log.error('[magic-mail] Gmail API send failed:', err);
|
|
683
|
+
strapi.log.error('[magic-mail] Gmail API send failed:', err.message || err);
|
|
684
|
+
strapi.log.error('[magic-mail] Error details:', {
|
|
685
|
+
name: err.name,
|
|
686
|
+
code: err.code,
|
|
687
|
+
cause: err.cause?.message || err.cause,
|
|
688
|
+
stack: err.stack?.split('\n').slice(0, 3).join('\n'),
|
|
689
|
+
});
|
|
672
690
|
throw err;
|
|
673
691
|
}
|
|
674
692
|
},
|
|
@@ -726,7 +744,7 @@ module.exports = ({ strapi }) => ({
|
|
|
726
744
|
);
|
|
727
745
|
|
|
728
746
|
currentAccessToken = newTokens.accessToken;
|
|
729
|
-
strapi.log.info('[magic-mail]
|
|
747
|
+
strapi.log.info('[magic-mail] [SUCCESS] Microsoft token refreshed successfully');
|
|
730
748
|
|
|
731
749
|
// Update stored tokens
|
|
732
750
|
const { encryptCredentials } = require('../utils/encryption');
|
|
@@ -736,7 +754,8 @@ module.exports = ({ strapi }) => ({
|
|
|
736
754
|
expiresAt: newTokens.expiresAt,
|
|
737
755
|
});
|
|
738
756
|
|
|
739
|
-
await strapi.
|
|
757
|
+
await strapi.documents('plugin::magic-mail.email-account').update({
|
|
758
|
+
documentId: account.documentId,
|
|
740
759
|
data: { oauth: updatedOAuth },
|
|
741
760
|
});
|
|
742
761
|
} catch (refreshErr) {
|
|
@@ -886,7 +905,7 @@ module.exports = ({ strapi }) => ({
|
|
|
886
905
|
throw new Error(`Microsoft Graph API error: ${response.status} - ${response.statusText}`);
|
|
887
906
|
}
|
|
888
907
|
|
|
889
|
-
strapi.log.info('[magic-mail]
|
|
908
|
+
strapi.log.info('[magic-mail] [SUCCESS] Email sent via Microsoft Graph API with MIME + custom headers');
|
|
890
909
|
strapi.log.info('[magic-mail] Microsoft adds From/DKIM automatically for DMARC compliance');
|
|
891
910
|
|
|
892
911
|
return {
|
|
@@ -942,7 +961,7 @@ module.exports = ({ strapi }) => ({
|
|
|
942
961
|
);
|
|
943
962
|
|
|
944
963
|
currentAccessToken = newTokens.accessToken;
|
|
945
|
-
strapi.log.info('[magic-mail]
|
|
964
|
+
strapi.log.info('[magic-mail] [SUCCESS] Token refreshed successfully');
|
|
946
965
|
|
|
947
966
|
// Update stored tokens
|
|
948
967
|
const { encryptCredentials } = require('../utils/encryption');
|
|
@@ -952,7 +971,8 @@ module.exports = ({ strapi }) => ({
|
|
|
952
971
|
expiresAt: newTokens.expiresAt,
|
|
953
972
|
});
|
|
954
973
|
|
|
955
|
-
await strapi.
|
|
974
|
+
await strapi.documents('plugin::magic-mail.email-account').update({
|
|
975
|
+
documentId: account.documentId,
|
|
956
976
|
data: { oauth: updatedOAuth },
|
|
957
977
|
});
|
|
958
978
|
} catch (refreshErr) {
|
|
@@ -1010,7 +1030,7 @@ module.exports = ({ strapi }) => ({
|
|
|
1010
1030
|
}
|
|
1011
1031
|
|
|
1012
1032
|
const result = await transporter.sendMail(mailOptions);
|
|
1013
|
-
strapi.log.info('[magic-mail]
|
|
1033
|
+
strapi.log.info('[magic-mail] [SUCCESS] Email sent via Yahoo OAuth');
|
|
1014
1034
|
|
|
1015
1035
|
return {
|
|
1016
1036
|
messageId: result.messageId,
|
|
@@ -1136,7 +1156,7 @@ module.exports = ({ strapi }) => ({
|
|
|
1136
1156
|
throw new Error(`SendGrid API error: ${response.statusText}`);
|
|
1137
1157
|
}
|
|
1138
1158
|
|
|
1139
|
-
strapi.log.info('[magic-mail]
|
|
1159
|
+
strapi.log.info('[magic-mail] [SUCCESS] Email sent via SendGrid API');
|
|
1140
1160
|
|
|
1141
1161
|
// SendGrid returns 202 Accepted with no body on success
|
|
1142
1162
|
return {
|
|
@@ -1253,7 +1273,7 @@ module.exports = ({ strapi }) => ({
|
|
|
1253
1273
|
}
|
|
1254
1274
|
|
|
1255
1275
|
const result = await response.json();
|
|
1256
|
-
strapi.log.info('[magic-mail]
|
|
1276
|
+
strapi.log.info('[magic-mail] [SUCCESS] Email sent via Mailgun API');
|
|
1257
1277
|
|
|
1258
1278
|
return {
|
|
1259
1279
|
messageId: result.id || `mailgun-${Date.now()}`,
|
|
@@ -1280,11 +1300,17 @@ module.exports = ({ strapi }) => ({
|
|
|
1280
1300
|
|
|
1281
1301
|
/**
|
|
1282
1302
|
* Update account statistics
|
|
1303
|
+
* Note: This function now expects documentId
|
|
1283
1304
|
*/
|
|
1284
|
-
async updateAccountStats(
|
|
1285
|
-
const account = await strapi.
|
|
1305
|
+
async updateAccountStats(documentId) {
|
|
1306
|
+
const account = await strapi.documents('plugin::magic-mail.email-account').findOne({
|
|
1307
|
+
documentId,
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
if (!account) return;
|
|
1286
1311
|
|
|
1287
|
-
await strapi.
|
|
1312
|
+
await strapi.documents('plugin::magic-mail.email-account').update({
|
|
1313
|
+
documentId,
|
|
1288
1314
|
data: {
|
|
1289
1315
|
emailsSentToday: (account.emailsSentToday || 0) + 1,
|
|
1290
1316
|
emailsSentThisHour: (account.emailsSentThisHour || 0) + 1,
|
|
@@ -1310,7 +1336,7 @@ module.exports = ({ strapi }) => ({
|
|
|
1310
1336
|
* Get account by name
|
|
1311
1337
|
*/
|
|
1312
1338
|
async getAccountByName(name) {
|
|
1313
|
-
const accounts = await strapi.
|
|
1339
|
+
const accounts = await strapi.documents('plugin::magic-mail.email-account').findMany({
|
|
1314
1340
|
filters: { name, isActive: true },
|
|
1315
1341
|
limit: 1,
|
|
1316
1342
|
});
|
|
@@ -1379,7 +1405,7 @@ module.exports = ({ strapi }) => ({
|
|
|
1379
1405
|
strapi.log.warn('[magic-mail] Email has HTML but no text alternative - may reduce deliverability');
|
|
1380
1406
|
}
|
|
1381
1407
|
|
|
1382
|
-
strapi.log.info('[magic-mail]
|
|
1408
|
+
strapi.log.info('[magic-mail] [SUCCESS] Email security validation passed');
|
|
1383
1409
|
},
|
|
1384
1410
|
|
|
1385
1411
|
/**
|
|
@@ -101,14 +101,14 @@ module.exports = ({ strapi }) => ({
|
|
|
101
101
|
const data = await response.json();
|
|
102
102
|
|
|
103
103
|
if (data.success) {
|
|
104
|
-
strapi.log.info('[magic-mail]
|
|
104
|
+
strapi.log.info('[magic-mail] [SUCCESS] License created:', data.data.licenseKey);
|
|
105
105
|
return data.data;
|
|
106
106
|
} else {
|
|
107
|
-
strapi.log.error('[magic-mail]
|
|
107
|
+
strapi.log.error('[magic-mail] [ERROR] License creation failed:', data);
|
|
108
108
|
return null;
|
|
109
109
|
}
|
|
110
110
|
} catch (error) {
|
|
111
|
-
strapi.log.error('[magic-mail]
|
|
111
|
+
strapi.log.error('[magic-mail] [ERROR] Error creating license:', error);
|
|
112
112
|
return null;
|
|
113
113
|
}
|
|
114
114
|
},
|
|
@@ -140,10 +140,10 @@ module.exports = ({ strapi }) => ({
|
|
|
140
140
|
}
|
|
141
141
|
} catch (error) {
|
|
142
142
|
if (allowGracePeriod) {
|
|
143
|
-
strapi.log.warn('[magic-mail]
|
|
143
|
+
strapi.log.warn('[magic-mail] [WARNING] License verification timeout - grace period active');
|
|
144
144
|
return { valid: true, data: null, gracePeriod: true };
|
|
145
145
|
}
|
|
146
|
-
strapi.log.error('[magic-mail]
|
|
146
|
+
strapi.log.error('[magic-mail] [ERROR] License verification error:', error.message);
|
|
147
147
|
return { valid: false, data: null };
|
|
148
148
|
}
|
|
149
149
|
},
|
|
@@ -206,7 +206,7 @@ module.exports = ({ strapi }) => ({
|
|
|
206
206
|
name: 'magic-mail'
|
|
207
207
|
});
|
|
208
208
|
await pluginStore.set({ key: 'licenseKey', value: licenseKey });
|
|
209
|
-
strapi.log.info(`[magic-mail]
|
|
209
|
+
strapi.log.info(`[magic-mail] [SUCCESS] License key stored: ${licenseKey.substring(0, 8)}...`);
|
|
210
210
|
},
|
|
211
211
|
|
|
212
212
|
startPinging(licenseKey, intervalMinutes = 15) {
|
|
@@ -242,7 +242,7 @@ module.exports = ({ strapi }) => ({
|
|
|
242
242
|
const license = await this.getLicenseByKey(licenseKey);
|
|
243
243
|
return license;
|
|
244
244
|
} catch (error) {
|
|
245
|
-
strapi.log.error(`[magic-mail]
|
|
245
|
+
strapi.log.error(`[magic-mail] [ERROR] Error loading license:`, error);
|
|
246
246
|
return null;
|
|
247
247
|
}
|
|
248
248
|
},
|
|
@@ -298,7 +298,7 @@ module.exports = ({ strapi }) => ({
|
|
|
298
298
|
*/
|
|
299
299
|
async initialize() {
|
|
300
300
|
try {
|
|
301
|
-
strapi.log.info('
|
|
301
|
+
strapi.log.info('[INIT] Initializing License Guard...');
|
|
302
302
|
|
|
303
303
|
// Check if license key exists in plugin store
|
|
304
304
|
const pluginStore = strapi.store({
|
|
@@ -322,23 +322,23 @@ module.exports = ({ strapi }) => ({
|
|
|
322
322
|
strapi.log.info('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
|
|
323
323
|
strapi.log.info(`๐ฆ Plugin Store Check:`);
|
|
324
324
|
if (licenseKey) {
|
|
325
|
-
strapi.log.info(`
|
|
326
|
-
strapi.log.info(`
|
|
325
|
+
strapi.log.info(` [SUCCESS] License Key found: ${licenseKey}`);
|
|
326
|
+
strapi.log.info(` [LICENSE] Key (short): ${licenseKey.substring(0, 10)}...`);
|
|
327
327
|
if (lastValidated) {
|
|
328
328
|
const lastValidatedDate = new Date(lastValidated);
|
|
329
329
|
const hoursAgo = Math.floor((now.getTime() - lastValidatedDate.getTime()) / (1000 * 60 * 60));
|
|
330
|
-
strapi.log.info(`
|
|
330
|
+
strapi.log.info(` [TIME] Last validated: ${hoursAgo}h ago (Grace: ${withinGracePeriod ? 'ACTIVE' : 'EXPIRED'})`);
|
|
331
331
|
} else {
|
|
332
|
-
strapi.log.info(`
|
|
332
|
+
strapi.log.info(` [TIME] Last validated: Never (Grace: ACTIVE for first ${gracePeriodHours}h)`);
|
|
333
333
|
}
|
|
334
334
|
} else {
|
|
335
|
-
strapi.log.info(`
|
|
335
|
+
strapi.log.info(` [ERROR] No license key stored`);
|
|
336
336
|
}
|
|
337
337
|
strapi.log.info('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
|
|
338
338
|
|
|
339
339
|
if (!licenseKey) {
|
|
340
|
-
strapi.log.info('
|
|
341
|
-
strapi.log.info('
|
|
340
|
+
strapi.log.info('[DEMO] No license found - Running in demo mode');
|
|
341
|
+
strapi.log.info('[INFO] Create a license in the admin panel to activate full features');
|
|
342
342
|
return {
|
|
343
343
|
valid: false,
|
|
344
344
|
demo: true,
|
|
@@ -346,7 +346,7 @@ module.exports = ({ strapi }) => ({
|
|
|
346
346
|
};
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
-
strapi.log.info('
|
|
349
|
+
strapi.log.info('[VERIFY] Verifying stored license key...');
|
|
350
350
|
|
|
351
351
|
// Verify license (allow grace period if we have a last validation)
|
|
352
352
|
const verification = await this.verifyLicense(licenseKey, withinGracePeriod);
|
|
@@ -355,7 +355,7 @@ module.exports = ({ strapi }) => ({
|
|
|
355
355
|
// Get license details for display
|
|
356
356
|
const license = await this.getLicenseByKey(licenseKey);
|
|
357
357
|
|
|
358
|
-
strapi.log.info(
|
|
358
|
+
strapi.log.info(`[SUCCESS] License verified online: ACTIVE (Key: ${licenseKey.substring(0, 10)}...)`);
|
|
359
359
|
|
|
360
360
|
// Update last validated timestamp
|
|
361
361
|
await pluginStore.set({
|
|
@@ -363,11 +363,11 @@ module.exports = ({ strapi }) => ({
|
|
|
363
363
|
value: now.toISOString()
|
|
364
364
|
});
|
|
365
365
|
|
|
366
|
-
strapi.log.info('
|
|
366
|
+
strapi.log.info('[SUCCESS] License is valid and active');
|
|
367
367
|
|
|
368
368
|
// Start automatic pinging
|
|
369
369
|
const pingInterval = this.startPinging(licenseKey, 15);
|
|
370
|
-
strapi.log.info('
|
|
370
|
+
strapi.log.info('[PING] Started pinging license every 15 minutes');
|
|
371
371
|
|
|
372
372
|
// Store interval globally so we can clean it up
|
|
373
373
|
strapi.licenseGuardMagicMail = {
|
|
@@ -378,13 +378,13 @@ module.exports = ({ strapi }) => ({
|
|
|
378
378
|
|
|
379
379
|
// Display license info box
|
|
380
380
|
strapi.log.info('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
|
|
381
|
-
strapi.log.info('โ
|
|
381
|
+
strapi.log.info('โ [SUCCESS] MAGIC MAIL PLUGIN LICENSE ACTIVE โ');
|
|
382
382
|
strapi.log.info('โ โ');
|
|
383
383
|
strapi.log.info(`โ License: ${licenseKey.padEnd(38, ' ')}โ`);
|
|
384
384
|
strapi.log.info(`โ User: ${(license?.firstName + ' ' + license?.lastName).padEnd(41, ' ')}โ`);
|
|
385
385
|
strapi.log.info(`โ Email: ${(license?.email || 'N/A').padEnd(40, ' ')}โ`);
|
|
386
386
|
strapi.log.info('โ โ');
|
|
387
|
-
strapi.log.info('โ
|
|
387
|
+
strapi.log.info('โ [AUTO] Pinging every 15 minutes โ');
|
|
388
388
|
strapi.log.info('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
|
|
389
389
|
|
|
390
390
|
return {
|
|
@@ -394,9 +394,9 @@ module.exports = ({ strapi }) => ({
|
|
|
394
394
|
gracePeriod: verification.gracePeriod || false,
|
|
395
395
|
};
|
|
396
396
|
} else {
|
|
397
|
-
strapi.log.error(
|
|
397
|
+
strapi.log.error(`[ERROR] License validation failed (Key: ${licenseKey.substring(0, 10)}...)`);
|
|
398
398
|
strapi.log.info('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
|
|
399
|
-
strapi.log.info('
|
|
399
|
+
strapi.log.info('[WARNING] Running in demo mode with limited features');
|
|
400
400
|
return {
|
|
401
401
|
valid: false,
|
|
402
402
|
demo: true,
|
|
@@ -405,7 +405,7 @@ module.exports = ({ strapi }) => ({
|
|
|
405
405
|
};
|
|
406
406
|
}
|
|
407
407
|
} catch (error) {
|
|
408
|
-
strapi.log.error('
|
|
408
|
+
strapi.log.error('[ERROR] Error initializing License Guard:', error);
|
|
409
409
|
return {
|
|
410
410
|
valid: false,
|
|
411
411
|
demo: true,
|
|
@@ -70,7 +70,7 @@ module.exports = ({ strapi }) => ({
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
const tokens = await response.json();
|
|
73
|
-
strapi.log.info('[magic-mail]
|
|
73
|
+
strapi.log.info('[magic-mail] [SUCCESS] Tokens received from Google');
|
|
74
74
|
|
|
75
75
|
if (!tokens.access_token) {
|
|
76
76
|
throw new Error('No access token received from Google');
|
|
@@ -89,7 +89,7 @@ module.exports = ({ strapi }) => ({
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
const userInfo = await userInfoResponse.json();
|
|
92
|
-
strapi.log.info(`[magic-mail]
|
|
92
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Got user email from Google: ${userInfo.email}`);
|
|
93
93
|
|
|
94
94
|
if (!userInfo.email) {
|
|
95
95
|
strapi.log.error('[magic-mail] userInfo:', userInfo);
|
|
@@ -220,7 +220,7 @@ module.exports = ({ strapi }) => ({
|
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
const tokens = await response.json();
|
|
223
|
-
strapi.log.info('[magic-mail]
|
|
223
|
+
strapi.log.info('[magic-mail] [SUCCESS] Tokens received from Microsoft');
|
|
224
224
|
strapi.log.info('[magic-mail] Has access_token:', !!tokens.access_token);
|
|
225
225
|
strapi.log.info('[magic-mail] Has refresh_token:', !!tokens.refresh_token);
|
|
226
226
|
strapi.log.info('[magic-mail] Has id_token:', !!tokens.id_token);
|
|
@@ -237,7 +237,7 @@ module.exports = ({ strapi }) => ({
|
|
|
237
237
|
const payloadBase64 = tokens.id_token.split('.')[1];
|
|
238
238
|
const payload = JSON.parse(Buffer.from(payloadBase64, 'base64').toString());
|
|
239
239
|
email = payload.email || payload.preferred_username || payload.upn;
|
|
240
|
-
strapi.log.info(`[magic-mail]
|
|
240
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Got email from Microsoft ID token: ${email}`);
|
|
241
241
|
} catch (jwtErr) {
|
|
242
242
|
strapi.log.warn('[magic-mail] Could not decode ID token:', jwtErr.message);
|
|
243
243
|
}
|
|
@@ -264,7 +264,7 @@ module.exports = ({ strapi }) => ({
|
|
|
264
264
|
strapi.log.info('[magic-mail] User info from Graph:', JSON.stringify(userInfo, null, 2));
|
|
265
265
|
|
|
266
266
|
email = userInfo.mail || userInfo.userPrincipalName;
|
|
267
|
-
strapi.log.info(`[magic-mail]
|
|
267
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Got email from Microsoft Graph: ${email}`);
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
if (!email) {
|
|
@@ -317,7 +317,7 @@ module.exports = ({ strapi }) => ({
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
const tokens = await response.json();
|
|
320
|
-
strapi.log.info('[magic-mail]
|
|
320
|
+
strapi.log.info('[magic-mail] [SUCCESS] Microsoft tokens refreshed successfully');
|
|
321
321
|
|
|
322
322
|
return {
|
|
323
323
|
accessToken: tokens.access_token,
|
|
@@ -388,7 +388,7 @@ module.exports = ({ strapi }) => ({
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
const tokens = await response.json();
|
|
391
|
-
strapi.log.info('[magic-mail]
|
|
391
|
+
strapi.log.info('[magic-mail] [SUCCESS] Tokens received from Yahoo');
|
|
392
392
|
|
|
393
393
|
if (!tokens.access_token) {
|
|
394
394
|
throw new Error('No access token received from Yahoo');
|
|
@@ -408,7 +408,7 @@ module.exports = ({ strapi }) => ({
|
|
|
408
408
|
|
|
409
409
|
const userInfo = await userInfoResponse.json();
|
|
410
410
|
const email = userInfo.email;
|
|
411
|
-
strapi.log.info(`[magic-mail]
|
|
411
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Got email from Yahoo: ${email}`);
|
|
412
412
|
|
|
413
413
|
if (!email) {
|
|
414
414
|
throw new Error('Yahoo did not provide email address');
|
|
@@ -486,14 +486,14 @@ module.exports = ({ strapi }) => ({
|
|
|
486
486
|
expiresAt: tokenData.expiresAt,
|
|
487
487
|
});
|
|
488
488
|
|
|
489
|
-
const account = await strapi.
|
|
489
|
+
const account = await strapi.documents('plugin::magic-mail.email-account').create({
|
|
490
490
|
data: {
|
|
491
491
|
name: accountDetails.name,
|
|
492
492
|
description: accountDetails.description || '',
|
|
493
493
|
provider: `${provider}-oauth`,
|
|
494
494
|
config: encryptedConfig, // OAuth app credentials
|
|
495
495
|
oauth: encryptedOAuth, // OAuth tokens
|
|
496
|
-
fromEmail: tokenData.email, //
|
|
496
|
+
fromEmail: tokenData.email, // [SUCCESS] Use email from Google, not from accountDetails
|
|
497
497
|
fromName: accountDetails.fromName || tokenData.email.split('@')[0],
|
|
498
498
|
replyTo: accountDetails.replyTo || tokenData.email,
|
|
499
499
|
isActive: true,
|
|
@@ -507,7 +507,7 @@ module.exports = ({ strapi }) => ({
|
|
|
507
507
|
},
|
|
508
508
|
});
|
|
509
509
|
|
|
510
|
-
strapi.log.info(`[magic-mail]
|
|
510
|
+
strapi.log.info(`[magic-mail] [SUCCESS] OAuth account created: ${accountDetails.name} (${tokenData.email})`);
|
|
511
511
|
|
|
512
512
|
return account;
|
|
513
513
|
},
|
|
@@ -15,7 +15,7 @@ function getEncryptionKey() {
|
|
|
15
15
|
return crypto.createHash('sha256').update(envKey).digest();
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
console.warn('[magic-mail]
|
|
18
|
+
console.warn('[magic-mail] [WARNING] No MAGIC_MAIL_ENCRYPTION_KEY found. Using fallback.');
|
|
19
19
|
return crypto.createHash('sha256').update('magic-mail-default-key').digest();
|
|
20
20
|
}
|
|
21
21
|
|