strapi-plugin-magic-mail 1.0.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.
- package/COPYRIGHT_NOTICE.txt +13 -0
- package/LICENSE +22 -0
- package/README.md +1420 -0
- package/admin/jsconfig.json +10 -0
- package/admin/src/components/AddAccountModal.jsx +1943 -0
- package/admin/src/components/Initializer.jsx +14 -0
- package/admin/src/components/LicenseGuard.jsx +475 -0
- package/admin/src/components/PluginIcon.jsx +5 -0
- package/admin/src/hooks/useAuthRefresh.js +44 -0
- package/admin/src/hooks/useLicense.js +158 -0
- package/admin/src/index.js +86 -0
- package/admin/src/pages/Analytics.jsx +762 -0
- package/admin/src/pages/App.jsx +111 -0
- package/admin/src/pages/EmailDesigner/EditorPage.jsx +1405 -0
- package/admin/src/pages/EmailDesigner/TemplateList.jsx +1807 -0
- package/admin/src/pages/HomePage.jsx +1233 -0
- package/admin/src/pages/LicensePage.jsx +424 -0
- package/admin/src/pages/RoutingRules.jsx +1141 -0
- package/admin/src/pages/Settings.jsx +603 -0
- package/admin/src/pluginId.js +3 -0
- package/admin/src/translations/de.json +71 -0
- package/admin/src/translations/en.json +70 -0
- package/admin/src/translations/es.json +71 -0
- package/admin/src/translations/fr.json +71 -0
- package/admin/src/translations/pt.json +71 -0
- package/admin/src/utils/fetchWithRetry.js +123 -0
- package/admin/src/utils/getTranslation.js +5 -0
- package/dist/_chunks/App-B-Gp4Vbr.js +7568 -0
- package/dist/_chunks/App-BymMjoGM.mjs +7543 -0
- package/dist/_chunks/LicensePage-Bl02myMx.mjs +342 -0
- package/dist/_chunks/LicensePage-CJXwPnEe.js +344 -0
- package/dist/_chunks/Settings-C_TmKwcz.mjs +400 -0
- package/dist/_chunks/Settings-zuFQ3pnn.js +402 -0
- package/dist/_chunks/de-CN-G9j1S.js +64 -0
- package/dist/_chunks/de-DS04rP54.mjs +64 -0
- package/dist/_chunks/en-BDc7Jk8u.js +64 -0
- package/dist/_chunks/en-BEFQJXvR.mjs +64 -0
- package/dist/_chunks/es-BpV1MIdm.js +64 -0
- package/dist/_chunks/es-DQHwzPpP.mjs +64 -0
- package/dist/_chunks/fr-BG1WfEVm.mjs +64 -0
- package/dist/_chunks/fr-vpziIpRp.js +64 -0
- package/dist/_chunks/pt-CMoGrOib.mjs +64 -0
- package/dist/_chunks/pt-ODpAhDNa.js +64 -0
- package/dist/admin/index.js +89 -0
- package/dist/admin/index.mjs +90 -0
- package/dist/server/index.js +6214 -0
- package/dist/server/index.mjs +6208 -0
- package/package.json +113 -0
- package/server/jsconfig.json +10 -0
- package/server/src/bootstrap.js +153 -0
- package/server/src/config/features.js +260 -0
- package/server/src/config/index.js +6 -0
- package/server/src/content-types/email-account/schema.json +93 -0
- package/server/src/content-types/email-event/index.js +8 -0
- package/server/src/content-types/email-event/schema.json +57 -0
- package/server/src/content-types/email-link/index.js +8 -0
- package/server/src/content-types/email-link/schema.json +49 -0
- package/server/src/content-types/email-log/index.js +8 -0
- package/server/src/content-types/email-log/schema.json +106 -0
- package/server/src/content-types/email-template/schema.json +74 -0
- package/server/src/content-types/email-template-version/schema.json +60 -0
- package/server/src/content-types/index.js +33 -0
- package/server/src/content-types/routing-rule/schema.json +59 -0
- package/server/src/controllers/accounts.js +220 -0
- package/server/src/controllers/analytics.js +347 -0
- package/server/src/controllers/controller.js +26 -0
- package/server/src/controllers/email-designer.js +474 -0
- package/server/src/controllers/index.js +21 -0
- package/server/src/controllers/license.js +267 -0
- package/server/src/controllers/oauth.js +474 -0
- package/server/src/controllers/routing-rules.js +122 -0
- package/server/src/controllers/test.js +383 -0
- package/server/src/destroy.js +23 -0
- package/server/src/index.js +25 -0
- package/server/src/middlewares/index.js +3 -0
- package/server/src/policies/index.js +3 -0
- package/server/src/register.js +5 -0
- package/server/src/routes/admin.js +469 -0
- package/server/src/routes/content-api.js +37 -0
- package/server/src/routes/index.js +9 -0
- package/server/src/services/account-manager.js +277 -0
- package/server/src/services/analytics.js +496 -0
- package/server/src/services/email-designer.js +870 -0
- package/server/src/services/email-router.js +1420 -0
- package/server/src/services/index.js +17 -0
- package/server/src/services/license-guard.js +418 -0
- package/server/src/services/oauth.js +515 -0
- package/server/src/services/service.js +7 -0
- package/server/src/utils/encryption.js +81 -0
- package/strapi-admin.js +4 -0
- package/strapi-server.js +4 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OAuth Controller
|
|
5
|
+
* Handles OAuth authentication flows
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
/**
|
|
10
|
+
* Initiate Gmail OAuth flow
|
|
11
|
+
*/
|
|
12
|
+
async gmailAuth(ctx) {
|
|
13
|
+
try {
|
|
14
|
+
const { clientId } = ctx.query;
|
|
15
|
+
|
|
16
|
+
if (!clientId) {
|
|
17
|
+
return ctx.badRequest('Client ID is required');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const oauthService = strapi.plugin('magic-mail').service('oauth');
|
|
21
|
+
const state = Buffer.from(JSON.stringify({
|
|
22
|
+
timestamp: Date.now(),
|
|
23
|
+
clientId,
|
|
24
|
+
})).toString('base64');
|
|
25
|
+
|
|
26
|
+
const authUrl = oauthService.getGmailAuthUrl(clientId, state);
|
|
27
|
+
|
|
28
|
+
ctx.body = {
|
|
29
|
+
authUrl,
|
|
30
|
+
message: 'Redirect user to this URL to authorize',
|
|
31
|
+
};
|
|
32
|
+
} catch (err) {
|
|
33
|
+
strapi.log.error('[magic-mail] Gmail OAuth init error:', err);
|
|
34
|
+
ctx.throw(500, err.message);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Handle Gmail OAuth callback
|
|
40
|
+
*/
|
|
41
|
+
async gmailCallback(ctx) {
|
|
42
|
+
try {
|
|
43
|
+
const { code, state, error } = ctx.query;
|
|
44
|
+
|
|
45
|
+
if (error) {
|
|
46
|
+
// OAuth was denied or failed
|
|
47
|
+
ctx.type = 'html';
|
|
48
|
+
ctx.body = `
|
|
49
|
+
<!DOCTYPE html>
|
|
50
|
+
<html>
|
|
51
|
+
<head>
|
|
52
|
+
<title>OAuth Failed</title>
|
|
53
|
+
<style>
|
|
54
|
+
body { font-family: system-ui; text-align: center; padding: 50px; }
|
|
55
|
+
.error { color: #ef4444; font-size: 24px; margin: 20px 0; }
|
|
56
|
+
</style>
|
|
57
|
+
</head>
|
|
58
|
+
<body>
|
|
59
|
+
<div class="error">❌ OAuth Authorization Failed</div>
|
|
60
|
+
<p>Error: ${error}</p>
|
|
61
|
+
<p>You can close this window and try again.</p>
|
|
62
|
+
<script>
|
|
63
|
+
setTimeout(() => window.close(), 3000);
|
|
64
|
+
</script>
|
|
65
|
+
</body>
|
|
66
|
+
</html>
|
|
67
|
+
`;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!code) {
|
|
72
|
+
return ctx.badRequest('No authorization code received');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Success - send code to parent window and close popup
|
|
76
|
+
ctx.type = 'html';
|
|
77
|
+
ctx.body = `
|
|
78
|
+
<!DOCTYPE html>
|
|
79
|
+
<html>
|
|
80
|
+
<head>
|
|
81
|
+
<title>OAuth Success</title>
|
|
82
|
+
<style>
|
|
83
|
+
body {
|
|
84
|
+
font-family: system-ui;
|
|
85
|
+
text-align: center;
|
|
86
|
+
padding: 50px;
|
|
87
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
88
|
+
color: white;
|
|
89
|
+
}
|
|
90
|
+
.success { font-size: 72px; margin: 20px 0; }
|
|
91
|
+
.message { font-size: 24px; font-weight: 600; }
|
|
92
|
+
.note { font-size: 14px; opacity: 0.9; margin-top: 20px; }
|
|
93
|
+
</style>
|
|
94
|
+
</head>
|
|
95
|
+
<body>
|
|
96
|
+
<div class="success">✅</div>
|
|
97
|
+
<div class="message">Gmail OAuth Authorized!</div>
|
|
98
|
+
<div class="note">Closing window...</div>
|
|
99
|
+
<script>
|
|
100
|
+
if (window.opener) {
|
|
101
|
+
// Send data to parent window
|
|
102
|
+
window.opener.postMessage({
|
|
103
|
+
type: 'gmail-oauth-success',
|
|
104
|
+
code: '${code}',
|
|
105
|
+
state: '${state}'
|
|
106
|
+
}, window.location.origin);
|
|
107
|
+
|
|
108
|
+
setTimeout(() => window.close(), 1500);
|
|
109
|
+
} else {
|
|
110
|
+
// Fallback: redirect to admin panel
|
|
111
|
+
setTimeout(() => {
|
|
112
|
+
window.location.href = '/admin/plugins/magic-mail?oauth_code=${code}&oauth_state=${state}';
|
|
113
|
+
}, 2000);
|
|
114
|
+
}
|
|
115
|
+
</script>
|
|
116
|
+
</body>
|
|
117
|
+
</html>
|
|
118
|
+
`;
|
|
119
|
+
} catch (err) {
|
|
120
|
+
strapi.log.error('[magic-mail] Gmail OAuth callback error:', err);
|
|
121
|
+
ctx.throw(500, err.message);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Initiate Microsoft OAuth flow
|
|
127
|
+
*/
|
|
128
|
+
async microsoftAuth(ctx) {
|
|
129
|
+
try {
|
|
130
|
+
const { clientId, tenantId } = ctx.query;
|
|
131
|
+
|
|
132
|
+
if (!clientId) {
|
|
133
|
+
return ctx.badRequest('Client ID is required');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!tenantId) {
|
|
137
|
+
return ctx.badRequest('Tenant ID is required');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const oauthService = strapi.plugin('magic-mail').service('oauth');
|
|
141
|
+
const state = Buffer.from(JSON.stringify({
|
|
142
|
+
timestamp: Date.now(),
|
|
143
|
+
clientId,
|
|
144
|
+
tenantId,
|
|
145
|
+
})).toString('base64');
|
|
146
|
+
|
|
147
|
+
const authUrl = oauthService.getMicrosoftAuthUrl(clientId, tenantId, state);
|
|
148
|
+
|
|
149
|
+
ctx.body = {
|
|
150
|
+
authUrl,
|
|
151
|
+
message: 'Redirect user to this URL to authorize',
|
|
152
|
+
};
|
|
153
|
+
} catch (err) {
|
|
154
|
+
strapi.log.error('[magic-mail] Microsoft OAuth init error:', err);
|
|
155
|
+
ctx.throw(500, err.message);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Handle Microsoft OAuth callback
|
|
161
|
+
*/
|
|
162
|
+
async microsoftCallback(ctx) {
|
|
163
|
+
try {
|
|
164
|
+
const { code, state, error } = ctx.query;
|
|
165
|
+
|
|
166
|
+
if (error) {
|
|
167
|
+
// OAuth was denied or failed
|
|
168
|
+
ctx.type = 'html';
|
|
169
|
+
ctx.body = `
|
|
170
|
+
<!DOCTYPE html>
|
|
171
|
+
<html>
|
|
172
|
+
<head>
|
|
173
|
+
<title>OAuth Failed</title>
|
|
174
|
+
<style>
|
|
175
|
+
body { font-family: system-ui; text-align: center; padding: 50px; }
|
|
176
|
+
.error { color: #ef4444; font-size: 24px; margin: 20px 0; }
|
|
177
|
+
</style>
|
|
178
|
+
</head>
|
|
179
|
+
<body>
|
|
180
|
+
<div class="error">❌ OAuth Authorization Failed</div>
|
|
181
|
+
<p>Error: ${error}</p>
|
|
182
|
+
<p>You can close this window and try again.</p>
|
|
183
|
+
<script>
|
|
184
|
+
setTimeout(() => window.close(), 3000);
|
|
185
|
+
</script>
|
|
186
|
+
</body>
|
|
187
|
+
</html>
|
|
188
|
+
`;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!code) {
|
|
193
|
+
return ctx.badRequest('No authorization code received');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Success - send code to parent window and close popup
|
|
197
|
+
ctx.type = 'html';
|
|
198
|
+
ctx.body = `
|
|
199
|
+
<!DOCTYPE html>
|
|
200
|
+
<html>
|
|
201
|
+
<head>
|
|
202
|
+
<title>OAuth Success</title>
|
|
203
|
+
<style>
|
|
204
|
+
body {
|
|
205
|
+
font-family: system-ui;
|
|
206
|
+
text-align: center;
|
|
207
|
+
padding: 50px;
|
|
208
|
+
background: linear-gradient(135deg, #00A4EF 0%, #0078D4 100%);
|
|
209
|
+
color: white;
|
|
210
|
+
}
|
|
211
|
+
.success { font-size: 72px; margin: 20px 0; }
|
|
212
|
+
.message { font-size: 24px; font-weight: 600; }
|
|
213
|
+
.note { font-size: 14px; opacity: 0.9; margin-top: 20px; }
|
|
214
|
+
</style>
|
|
215
|
+
</head>
|
|
216
|
+
<body>
|
|
217
|
+
<div class="success">✅</div>
|
|
218
|
+
<div class="message">Microsoft OAuth Authorized!</div>
|
|
219
|
+
<div class="note">Closing window...</div>
|
|
220
|
+
<script>
|
|
221
|
+
if (window.opener) {
|
|
222
|
+
// Send data to parent window
|
|
223
|
+
window.opener.postMessage({
|
|
224
|
+
type: 'microsoft-oauth-success',
|
|
225
|
+
code: '${code}',
|
|
226
|
+
state: '${state}'
|
|
227
|
+
}, window.location.origin);
|
|
228
|
+
|
|
229
|
+
setTimeout(() => window.close(), 1500);
|
|
230
|
+
} else {
|
|
231
|
+
// Fallback: redirect to admin panel
|
|
232
|
+
setTimeout(() => {
|
|
233
|
+
window.location.href = '/admin/plugins/magic-mail?oauth_code=${code}&oauth_state=${state}';
|
|
234
|
+
}, 2000);
|
|
235
|
+
}
|
|
236
|
+
</script>
|
|
237
|
+
</body>
|
|
238
|
+
</html>
|
|
239
|
+
`;
|
|
240
|
+
} catch (err) {
|
|
241
|
+
strapi.log.error('[magic-mail] Microsoft OAuth callback error:', err);
|
|
242
|
+
ctx.throw(500, err.message);
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Initiate Yahoo OAuth flow
|
|
248
|
+
*/
|
|
249
|
+
async yahooAuth(ctx) {
|
|
250
|
+
try {
|
|
251
|
+
const { clientId } = ctx.query;
|
|
252
|
+
|
|
253
|
+
if (!clientId) {
|
|
254
|
+
return ctx.badRequest('Client ID is required');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const oauthService = strapi.plugin('magic-mail').service('oauth');
|
|
258
|
+
const state = Buffer.from(JSON.stringify({
|
|
259
|
+
timestamp: Date.now(),
|
|
260
|
+
clientId,
|
|
261
|
+
})).toString('base64');
|
|
262
|
+
|
|
263
|
+
const authUrl = oauthService.getYahooAuthUrl(clientId, state);
|
|
264
|
+
|
|
265
|
+
ctx.body = {
|
|
266
|
+
authUrl,
|
|
267
|
+
message: 'Redirect user to this URL to authorize',
|
|
268
|
+
};
|
|
269
|
+
} catch (err) {
|
|
270
|
+
strapi.log.error('[magic-mail] Yahoo OAuth init error:', err);
|
|
271
|
+
ctx.throw(500, err.message);
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Handle Yahoo OAuth callback
|
|
277
|
+
*/
|
|
278
|
+
async yahooCallback(ctx) {
|
|
279
|
+
try {
|
|
280
|
+
const { code, state, error } = ctx.query;
|
|
281
|
+
|
|
282
|
+
if (error) {
|
|
283
|
+
// OAuth was denied or failed
|
|
284
|
+
ctx.type = 'html';
|
|
285
|
+
ctx.body = `
|
|
286
|
+
<!DOCTYPE html>
|
|
287
|
+
<html>
|
|
288
|
+
<head>
|
|
289
|
+
<title>OAuth Failed</title>
|
|
290
|
+
<style>
|
|
291
|
+
body { font-family: system-ui; text-align: center; padding: 50px; }
|
|
292
|
+
.error { color: #ef4444; font-size: 24px; margin: 20px 0; }
|
|
293
|
+
</style>
|
|
294
|
+
</head>
|
|
295
|
+
<body>
|
|
296
|
+
<div class="error">❌ OAuth Authorization Failed</div>
|
|
297
|
+
<p>Error: ${error}</p>
|
|
298
|
+
<p>You can close this window and try again.</p>
|
|
299
|
+
<script>
|
|
300
|
+
setTimeout(() => window.close(), 3000);
|
|
301
|
+
</script>
|
|
302
|
+
</body>
|
|
303
|
+
</html>
|
|
304
|
+
`;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!code) {
|
|
309
|
+
return ctx.badRequest('No authorization code received');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Success - send code to parent window and close popup
|
|
313
|
+
ctx.type = 'html';
|
|
314
|
+
ctx.body = `
|
|
315
|
+
<!DOCTYPE html>
|
|
316
|
+
<html>
|
|
317
|
+
<head>
|
|
318
|
+
<title>OAuth Success</title>
|
|
319
|
+
<style>
|
|
320
|
+
body {
|
|
321
|
+
font-family: system-ui;
|
|
322
|
+
text-align: center;
|
|
323
|
+
padding: 50px;
|
|
324
|
+
background: linear-gradient(135deg, #6001D2 0%, #410096 100%);
|
|
325
|
+
color: white;
|
|
326
|
+
}
|
|
327
|
+
.success { font-size: 72px; margin: 20px 0; }
|
|
328
|
+
.message { font-size: 24px; font-weight: 600; }
|
|
329
|
+
.note { font-size: 14px; opacity: 0.9; margin-top: 20px; }
|
|
330
|
+
</style>
|
|
331
|
+
</head>
|
|
332
|
+
<body>
|
|
333
|
+
<div class="success">✅</div>
|
|
334
|
+
<div class="message">Yahoo Mail OAuth Authorized!</div>
|
|
335
|
+
<div class="note">Closing window...</div>
|
|
336
|
+
<script>
|
|
337
|
+
if (window.opener) {
|
|
338
|
+
// Send data to parent window
|
|
339
|
+
window.opener.postMessage({
|
|
340
|
+
type: 'yahoo-oauth-success',
|
|
341
|
+
code: '${code}',
|
|
342
|
+
state: '${state}'
|
|
343
|
+
}, window.location.origin);
|
|
344
|
+
|
|
345
|
+
setTimeout(() => window.close(), 1500);
|
|
346
|
+
} else {
|
|
347
|
+
// Fallback: redirect to admin panel
|
|
348
|
+
setTimeout(() => {
|
|
349
|
+
window.location.href = '/admin/plugins/magic-mail?oauth_code=${code}&oauth_state=${state}';
|
|
350
|
+
}, 2000);
|
|
351
|
+
}
|
|
352
|
+
</script>
|
|
353
|
+
</body>
|
|
354
|
+
</html>
|
|
355
|
+
`;
|
|
356
|
+
} catch (err) {
|
|
357
|
+
strapi.log.error('[magic-mail] Yahoo OAuth callback error:', err);
|
|
358
|
+
ctx.throw(500, err.message);
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Create account from OAuth tokens
|
|
364
|
+
*/
|
|
365
|
+
async createOAuthAccount(ctx) {
|
|
366
|
+
try {
|
|
367
|
+
const { provider, code, state, accountDetails } = ctx.request.body;
|
|
368
|
+
|
|
369
|
+
strapi.log.info('[magic-mail] Creating OAuth account...');
|
|
370
|
+
strapi.log.info('[magic-mail] Provider:', provider);
|
|
371
|
+
strapi.log.info('[magic-mail] Account name:', accountDetails?.name);
|
|
372
|
+
|
|
373
|
+
if (provider !== 'gmail' && provider !== 'microsoft' && provider !== 'yahoo') {
|
|
374
|
+
return ctx.badRequest('Only Gmail, Microsoft and Yahoo OAuth supported');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!code) {
|
|
378
|
+
return ctx.badRequest('OAuth code is required');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// License check for OAuth provider
|
|
382
|
+
const licenseGuard = strapi.plugin('magic-mail').service('license-guard');
|
|
383
|
+
const providerKey = `${provider}-oauth`;
|
|
384
|
+
const providerAllowed = await licenseGuard.isProviderAllowed(providerKey);
|
|
385
|
+
|
|
386
|
+
if (!providerAllowed) {
|
|
387
|
+
ctx.throw(403, `OAuth provider "${provider}" requires a Premium license or higher. Please upgrade your license.`);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Check account limit
|
|
392
|
+
const currentAccounts = await strapi.entityService.count('plugin::magic-mail.email-account');
|
|
393
|
+
const maxAccounts = await licenseGuard.getMaxAccounts();
|
|
394
|
+
|
|
395
|
+
if (maxAccounts !== -1 && currentAccounts >= maxAccounts) {
|
|
396
|
+
ctx.throw(403, `Account limit reached (${maxAccounts}). Upgrade your license to add more accounts.`);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Decode state to get clientId
|
|
401
|
+
const stateData = JSON.parse(Buffer.from(state, 'base64').toString());
|
|
402
|
+
|
|
403
|
+
if (!accountDetails.config?.clientId || !accountDetails.config?.clientSecret) {
|
|
404
|
+
return ctx.badRequest('Client ID and Secret are required');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const oauthService = strapi.plugin('magic-mail').service('oauth');
|
|
408
|
+
|
|
409
|
+
// Exchange code for tokens
|
|
410
|
+
let tokenData;
|
|
411
|
+
if (provider === 'gmail') {
|
|
412
|
+
strapi.log.info('[magic-mail] Calling exchangeGoogleCode...');
|
|
413
|
+
tokenData = await oauthService.exchangeGoogleCode(
|
|
414
|
+
code,
|
|
415
|
+
accountDetails.config.clientId,
|
|
416
|
+
accountDetails.config.clientSecret
|
|
417
|
+
);
|
|
418
|
+
} else if (provider === 'microsoft') {
|
|
419
|
+
strapi.log.info('[magic-mail] Calling exchangeMicrosoftCode...');
|
|
420
|
+
|
|
421
|
+
if (!accountDetails.config.tenantId) {
|
|
422
|
+
throw new Error('Tenant ID is required for Microsoft OAuth');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
tokenData = await oauthService.exchangeMicrosoftCode(
|
|
426
|
+
code,
|
|
427
|
+
accountDetails.config.clientId,
|
|
428
|
+
accountDetails.config.clientSecret,
|
|
429
|
+
accountDetails.config.tenantId
|
|
430
|
+
);
|
|
431
|
+
} else if (provider === 'yahoo') {
|
|
432
|
+
strapi.log.info('[magic-mail] Calling exchangeYahooCode...');
|
|
433
|
+
tokenData = await oauthService.exchangeYahooCode(
|
|
434
|
+
code,
|
|
435
|
+
accountDetails.config.clientId,
|
|
436
|
+
accountDetails.config.clientSecret
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
strapi.log.info('[magic-mail] Token data received:', {
|
|
441
|
+
email: tokenData.email,
|
|
442
|
+
hasAccessToken: !!tokenData.accessToken,
|
|
443
|
+
hasRefreshToken: !!tokenData.refreshToken,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
if (!tokenData.email) {
|
|
447
|
+
strapi.log.error('[magic-mail] No email in tokenData!');
|
|
448
|
+
throw new Error(`Failed to get email from ${provider} OAuth`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Store account
|
|
452
|
+
strapi.log.info('[magic-mail] Calling storeOAuthAccount...');
|
|
453
|
+
const account = await oauthService.storeOAuthAccount(
|
|
454
|
+
provider,
|
|
455
|
+
tokenData,
|
|
456
|
+
accountDetails,
|
|
457
|
+
accountDetails.config // contains clientId and clientSecret
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
strapi.log.info('[magic-mail] ✅ OAuth account created successfully');
|
|
461
|
+
|
|
462
|
+
ctx.body = {
|
|
463
|
+
success: true,
|
|
464
|
+
data: account,
|
|
465
|
+
message: 'OAuth account created successfully',
|
|
466
|
+
};
|
|
467
|
+
} catch (err) {
|
|
468
|
+
strapi.log.error('[magic-mail] Create OAuth account error:', err);
|
|
469
|
+
strapi.log.error('[magic-mail] Error stack:', err.stack);
|
|
470
|
+
ctx.throw(500, err.message);
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Routing Rules Controller
|
|
5
|
+
* Manages email routing rules CRUD operations
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
/**
|
|
10
|
+
* Get all routing rules
|
|
11
|
+
*/
|
|
12
|
+
async getAll(ctx) {
|
|
13
|
+
try {
|
|
14
|
+
const rules = await strapi.entityService.findMany('plugin::magic-mail.routing-rule', {
|
|
15
|
+
sort: { priority: 'desc' },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
ctx.body = {
|
|
19
|
+
data: rules,
|
|
20
|
+
meta: { count: rules.length },
|
|
21
|
+
};
|
|
22
|
+
} catch (err) {
|
|
23
|
+
strapi.log.error('[magic-mail] Error getting routing rules:', err);
|
|
24
|
+
ctx.throw(500, 'Error fetching routing rules');
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get single routing rule
|
|
30
|
+
*/
|
|
31
|
+
async getOne(ctx) {
|
|
32
|
+
try {
|
|
33
|
+
const { ruleId } = ctx.params;
|
|
34
|
+
const rule = await strapi.entityService.findOne('plugin::magic-mail.routing-rule', ruleId);
|
|
35
|
+
|
|
36
|
+
if (!rule) {
|
|
37
|
+
ctx.throw(404, 'Routing rule not found');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
ctx.body = {
|
|
41
|
+
data: rule,
|
|
42
|
+
};
|
|
43
|
+
} catch (err) {
|
|
44
|
+
strapi.log.error('[magic-mail] Error getting routing rule:', err);
|
|
45
|
+
ctx.throw(500, 'Error fetching routing rule');
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create new routing rule
|
|
51
|
+
*/
|
|
52
|
+
async create(ctx) {
|
|
53
|
+
try {
|
|
54
|
+
const licenseGuard = strapi.plugin('magic-mail').service('license-guard');
|
|
55
|
+
|
|
56
|
+
// Check routing rule limit
|
|
57
|
+
const currentRules = await strapi.entityService.count('plugin::magic-mail.routing-rule');
|
|
58
|
+
const maxRules = await licenseGuard.getMaxRoutingRules();
|
|
59
|
+
|
|
60
|
+
if (maxRules !== -1 && currentRules >= maxRules) {
|
|
61
|
+
ctx.throw(403, `Routing rule limit reached (${maxRules}). Upgrade to Advanced license for unlimited rules.`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const rule = await strapi.entityService.create('plugin::magic-mail.routing-rule', {
|
|
66
|
+
data: ctx.request.body,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
ctx.body = {
|
|
70
|
+
data: rule,
|
|
71
|
+
message: 'Routing rule created successfully',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
strapi.log.info(`[magic-mail] ✅ Routing rule created: ${rule.name}`);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
strapi.log.error('[magic-mail] Error creating routing rule:', err);
|
|
77
|
+
ctx.throw(err.status || 500, err.message || 'Error creating routing rule');
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Update routing rule
|
|
83
|
+
*/
|
|
84
|
+
async update(ctx) {
|
|
85
|
+
try {
|
|
86
|
+
const { ruleId } = ctx.params;
|
|
87
|
+
const rule = await strapi.entityService.update('plugin::magic-mail.routing-rule', ruleId, {
|
|
88
|
+
data: ctx.request.body,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
ctx.body = {
|
|
92
|
+
data: rule,
|
|
93
|
+
message: 'Routing rule updated successfully',
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
strapi.log.info(`[magic-mail] ✅ Routing rule updated: ${rule.name}`);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
strapi.log.error('[magic-mail] Error updating routing rule:', err);
|
|
99
|
+
ctx.throw(500, err.message || 'Error updating routing rule');
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Delete routing rule
|
|
105
|
+
*/
|
|
106
|
+
async delete(ctx) {
|
|
107
|
+
try {
|
|
108
|
+
const { ruleId } = ctx.params;
|
|
109
|
+
await strapi.entityService.delete('plugin::magic-mail.routing-rule', ruleId);
|
|
110
|
+
|
|
111
|
+
ctx.body = {
|
|
112
|
+
message: 'Routing rule deleted successfully',
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
strapi.log.info(`[magic-mail] Routing rule deleted: ${ruleId}`);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
strapi.log.error('[magic-mail] Error deleting routing rule:', err);
|
|
118
|
+
ctx.throw(500, 'Error deleting routing rule');
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|