emailengine-app 2.61.1 → 2.61.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/CHANGELOG.md +17 -0
- package/data/google-crawlers.json +1 -1
- package/lib/account/account-state.js +248 -0
- package/lib/account.js +45 -193
- package/lib/api-routes/account-routes.js +1023 -0
- package/lib/api-routes/message-routes.js +1377 -0
- package/lib/consts.js +12 -2
- package/lib/email-client/base-client.js +282 -771
- package/lib/email-client/gmail/gmail-api.js +243 -0
- package/lib/email-client/gmail-client.js +145 -53
- package/lib/email-client/imap/mailbox.js +24 -698
- package/lib/email-client/imap/sync-operations.js +812 -0
- package/lib/email-client/imap-client.js +1 -1
- package/lib/email-client/message-builder.js +566 -0
- package/lib/email-client/notification-handler.js +314 -0
- package/lib/email-client/outlook/graph-api.js +326 -0
- package/lib/email-client/outlook-client.js +159 -113
- package/lib/email-client/smtp-pool-manager.js +196 -0
- package/lib/imapproxy/imap-server.js +3 -12
- package/lib/oauth/gmail.js +4 -4
- package/lib/oauth/mail-ru.js +30 -5
- package/lib/oauth/outlook.js +57 -3
- package/lib/oauth/pubsub/google.js +30 -11
- package/lib/oauth/scope-checker.js +202 -0
- package/lib/oauth2-apps.js +8 -4
- package/lib/redis-operations.js +484 -0
- package/lib/routes-ui.js +283 -2582
- package/lib/tools.js +4 -196
- package/lib/ui-routes/account-routes.js +1931 -0
- package/lib/ui-routes/admin-config-routes.js +1233 -0
- package/lib/ui-routes/admin-entities-routes.js +2367 -0
- package/lib/ui-routes/oauth-routes.js +992 -0
- package/lib/utils/network.js +237 -0
- package/package.json +10 -10
- package/sbom.json +1 -1
- package/static/js/app.js +5 -5
- package/static/licenses.html +79 -19
- package/translations/de.mo +0 -0
- package/translations/de.po +97 -86
- package/translations/en.mo +0 -0
- package/translations/en.po +80 -75
- package/translations/et.mo +0 -0
- package/translations/et.po +96 -86
- package/translations/fr.mo +0 -0
- package/translations/fr.po +97 -86
- package/translations/ja.mo +0 -0
- package/translations/ja.po +96 -86
- package/translations/messages.pot +105 -91
- package/translations/nl.mo +0 -0
- package/translations/nl.po +98 -86
- package/translations/pl.mo +0 -0
- package/translations/pl.po +96 -86
- package/views/account/security.hbs +4 -4
- package/views/accounts/account.hbs +13 -13
- package/views/accounts/register/imap-server.hbs +12 -12
- package/views/config/document-store/pre-processing/index.hbs +4 -2
- package/views/config/oauth/app.hbs +6 -7
- package/views/config/oauth/index.hbs +2 -2
- package/views/config/service.hbs +3 -4
- package/views/dashboard.hbs +5 -7
- package/views/error.hbs +22 -7
- package/views/gateways/gateway.hbs +2 -2
- package/views/partials/add_account_modal.hbs +7 -10
- package/views/partials/document_store_header.hbs +1 -1
- package/views/partials/editor_scope_info.hbs +0 -1
- package/views/partials/oauth_config_header.hbs +1 -1
- package/views/partials/side_menu.hbs +3 -3
- package/views/partials/webhook_form.hbs +2 -2
- package/views/templates/index.hbs +1 -1
- package/views/templates/template.hbs +8 -8
- package/views/tokens/index.hbs +6 -6
- package/views/tokens/new.hbs +1 -1
- package/views/webhooks/index.hbs +4 -4
- package/views/webhooks/webhook.hbs +7 -7
- package/workers/api.js +148 -2436
- package/workers/smtp.js +2 -1
- package/lib/imapproxy/imap-core/test/client.js +0 -46
- package/lib/imapproxy/imap-core/test/fixtures/append.eml +0 -1196
- package/lib/imapproxy/imap-core/test/fixtures/chunks.js +0 -44
- package/lib/imapproxy/imap-core/test/fixtures/fix1.eml +0 -6
- package/lib/imapproxy/imap-core/test/fixtures/fix2.eml +0 -599
- package/lib/imapproxy/imap-core/test/fixtures/fix3.eml +0 -32
- package/lib/imapproxy/imap-core/test/fixtures/fix4.eml +0 -6
- package/lib/imapproxy/imap-core/test/fixtures/mimetorture.eml +0 -599
- package/lib/imapproxy/imap-core/test/fixtures/mimetorture.js +0 -2740
- package/lib/imapproxy/imap-core/test/fixtures/mimetorture.json +0 -1411
- package/lib/imapproxy/imap-core/test/fixtures/mimetree.js +0 -85
- package/lib/imapproxy/imap-core/test/fixtures/nodemailer.eml +0 -582
- package/lib/imapproxy/imap-core/test/fixtures/ryan_finnie_mime_torture.eml +0 -599
- package/lib/imapproxy/imap-core/test/fixtures/simple.eml +0 -42
- package/lib/imapproxy/imap-core/test/fixtures/simple.json +0 -164
- package/lib/imapproxy/imap-core/test/imap-compile-stream-test.js +0 -671
- package/lib/imapproxy/imap-core/test/imap-compiler-test.js +0 -272
- package/lib/imapproxy/imap-core/test/imap-indexer-test.js +0 -236
- package/lib/imapproxy/imap-core/test/imap-parser-test.js +0 -922
- package/lib/imapproxy/imap-core/test/memory-notifier.js +0 -129
- package/lib/imapproxy/imap-core/test/prepare.sh +0 -74
- package/lib/imapproxy/imap-core/test/protocol-test.js +0 -1756
- package/lib/imapproxy/imap-core/test/search-test.js +0 -1356
- package/lib/imapproxy/imap-core/test/test-client.js +0 -152
- package/lib/imapproxy/imap-core/test/test-server.js +0 -623
- package/lib/imapproxy/imap-core/test/tools-test.js +0 -22
- package/test/api-test.js +0 -899
- package/test/autoreply-test.js +0 -327
- package/test/bounce-test.js +0 -151
- package/test/complaint-test.js +0 -256
- package/test/fixtures/autoreply/LICENSE +0 -27
- package/test/fixtures/autoreply/rfc3834-01.eml +0 -23
- package/test/fixtures/autoreply/rfc3834-02.eml +0 -24
- package/test/fixtures/autoreply/rfc3834-03.eml +0 -26
- package/test/fixtures/autoreply/rfc3834-04.eml +0 -48
- package/test/fixtures/autoreply/rfc3834-05.eml +0 -19
- package/test/fixtures/autoreply/rfc3834-06.eml +0 -59
- package/test/fixtures/bounces/163.eml +0 -2521
- package/test/fixtures/bounces/fastmail.eml +0 -242
- package/test/fixtures/bounces/gmail.eml +0 -252
- package/test/fixtures/bounces/hotmail.eml +0 -655
- package/test/fixtures/bounces/mailru.eml +0 -121
- package/test/fixtures/bounces/outlook.eml +0 -1107
- package/test/fixtures/bounces/postfix.eml +0 -101
- package/test/fixtures/bounces/rambler.eml +0 -116
- package/test/fixtures/bounces/workmail.eml +0 -142
- package/test/fixtures/bounces/yahoo.eml +0 -139
- package/test/fixtures/bounces/zoho.eml +0 -83
- package/test/fixtures/bounces/zonemta.eml +0 -100
- package/test/fixtures/complaints/LICENSE +0 -27
- package/test/fixtures/complaints/amazonses.eml +0 -72
- package/test/fixtures/complaints/dmarc.eml +0 -59
- package/test/fixtures/complaints/hotmail.eml +0 -49
- package/test/fixtures/complaints/optout.eml +0 -40
- package/test/fixtures/complaints/standard-arf.eml +0 -68
- package/test/fixtures/complaints/yahoo.eml +0 -68
- package/test/oauth2-apps-test.js +0 -301
- package/test/sendonly-test.js +0 -160
- package/test/test-config.js +0 -34
- package/test/webhooks-server.js +0 -39
package/test/oauth2-apps-test.js
DELETED
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
require('dotenv').config({ quiet: true });
|
|
4
|
-
|
|
5
|
-
const test = require('node:test');
|
|
6
|
-
const assert = require('node:assert').strict;
|
|
7
|
-
|
|
8
|
-
const { formatExtraScopes } = require('../lib/oauth2-apps');
|
|
9
|
-
const { Account } = require('../lib/account');
|
|
10
|
-
|
|
11
|
-
test('formatExtraScopes', async t => {
|
|
12
|
-
t.after(async () => {
|
|
13
|
-
// force close because we loaded ../lib/oauth2-apps that spawns the db connection and queues
|
|
14
|
-
setTimeout(() => process.exit(), 5000).unref();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
await t.test('should filter out Gmail scopes using short form', async () => {
|
|
18
|
-
const defaultScopes = [
|
|
19
|
-
'https://www.googleapis.com/auth/gmail.modify',
|
|
20
|
-
'https://www.googleapis.com/auth/gmail.send',
|
|
21
|
-
'https://www.googleapis.com/auth/gmail.labels'
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
const result = formatExtraScopes([], null, defaultScopes, ['gmail.modify'], null);
|
|
25
|
-
|
|
26
|
-
assert.deepStrictEqual(result, ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.labels']);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
await t.test('should filter out Gmail scopes using full URL', async () => {
|
|
30
|
-
const defaultScopes = [
|
|
31
|
-
'https://www.googleapis.com/auth/gmail.modify',
|
|
32
|
-
'https://www.googleapis.com/auth/gmail.send',
|
|
33
|
-
'https://www.googleapis.com/auth/gmail.labels'
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const result = formatExtraScopes([], null, defaultScopes, ['https://www.googleapis.com/auth/gmail.modify'], null);
|
|
37
|
-
|
|
38
|
-
assert.deepStrictEqual(result, ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.labels']);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
await t.test('should filter multiple Gmail scopes', async () => {
|
|
42
|
-
const defaultScopes = [
|
|
43
|
-
'https://www.googleapis.com/auth/gmail.modify',
|
|
44
|
-
'https://www.googleapis.com/auth/gmail.readonly',
|
|
45
|
-
'https://www.googleapis.com/auth/gmail.send',
|
|
46
|
-
'https://www.googleapis.com/auth/gmail.labels'
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
const result = formatExtraScopes([], null, defaultScopes, ['gmail.modify', 'gmail.readonly', 'gmail.labels'], null);
|
|
50
|
-
|
|
51
|
-
assert.deepStrictEqual(result, ['https://www.googleapis.com/auth/gmail.send']);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
await t.test('should add extra Gmail scopes', async () => {
|
|
55
|
-
const defaultScopes = ['https://www.googleapis.com/auth/gmail.modify'];
|
|
56
|
-
|
|
57
|
-
const result = formatExtraScopes(['https://www.googleapis.com/auth/gmail.send'], null, defaultScopes, [], null);
|
|
58
|
-
|
|
59
|
-
assert.deepStrictEqual(result, ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.modify']);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
await t.test('should add extra scopes and filter out unwanted ones', async () => {
|
|
63
|
-
const defaultScopes = ['https://www.googleapis.com/auth/gmail.modify', 'https://www.googleapis.com/auth/gmail.labels'];
|
|
64
|
-
|
|
65
|
-
const result = formatExtraScopes(['https://www.googleapis.com/auth/gmail.send'], null, defaultScopes, ['gmail.modify'], null);
|
|
66
|
-
|
|
67
|
-
assert.deepStrictEqual(result, ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.labels']);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
await t.test('should handle send-only configuration', async () => {
|
|
71
|
-
const defaultScopes = ['https://www.googleapis.com/auth/gmail.modify'];
|
|
72
|
-
|
|
73
|
-
const result = formatExtraScopes(['https://www.googleapis.com/auth/gmail.send'], null, defaultScopes, ['gmail.modify'], null);
|
|
74
|
-
|
|
75
|
-
assert.deepStrictEqual(result, ['https://www.googleapis.com/auth/gmail.send']);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
await t.test('should filter out Outlook scopes using short form', async () => {
|
|
79
|
-
const defaultScopes = [
|
|
80
|
-
'https://outlook.office.com/IMAP.AccessAsUser.All',
|
|
81
|
-
'https://outlook.office.com/SMTP.Send',
|
|
82
|
-
'https://outlook.office.com/User.Read'
|
|
83
|
-
];
|
|
84
|
-
|
|
85
|
-
const result = formatExtraScopes([], null, defaultScopes, ['IMAP.AccessAsUser.All'], null);
|
|
86
|
-
|
|
87
|
-
assert.deepStrictEqual(result, ['https://outlook.office.com/SMTP.Send', 'https://outlook.office.com/User.Read']);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
await t.test('should filter out Microsoft Graph scopes', async () => {
|
|
91
|
-
const defaultScopes = ['https://graph.microsoft.com/Mail.ReadWrite', 'https://graph.microsoft.com/Mail.Send', 'https://graph.microsoft.com/User.Read'];
|
|
92
|
-
|
|
93
|
-
const result = formatExtraScopes([], null, defaultScopes, ['Mail.ReadWrite'], null);
|
|
94
|
-
|
|
95
|
-
assert.deepStrictEqual(result, ['https://graph.microsoft.com/Mail.Send', 'https://graph.microsoft.com/User.Read']);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
await t.test('should filter out full URL Outlook scopes', async () => {
|
|
99
|
-
const defaultScopes = ['https://outlook.office.com/IMAP.AccessAsUser.All', 'https://outlook.office.com/SMTP.Send'];
|
|
100
|
-
|
|
101
|
-
const result = formatExtraScopes([], null, defaultScopes, ['https://outlook.office.com/IMAP.AccessAsUser.All'], null);
|
|
102
|
-
|
|
103
|
-
assert.deepStrictEqual(result, ['https://outlook.office.com/SMTP.Send']);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
await t.test('should return default scopes when no extras or skips', async () => {
|
|
107
|
-
const defaultScopes = ['scope1', 'scope2'];
|
|
108
|
-
|
|
109
|
-
const result = formatExtraScopes(null, null, defaultScopes, [], null);
|
|
110
|
-
|
|
111
|
-
assert.deepStrictEqual(result, ['scope1', 'scope2']);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
await t.test('should handle empty skipScopes array', async () => {
|
|
115
|
-
const defaultScopes = ['scope1', 'scope2'];
|
|
116
|
-
|
|
117
|
-
const result = formatExtraScopes(['scope3'], null, defaultScopes, [], null);
|
|
118
|
-
|
|
119
|
-
assert.deepStrictEqual(result, ['scope3', 'scope1', 'scope2']);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
await t.test('should not duplicate scopes already in defaults', async () => {
|
|
123
|
-
const defaultScopes = ['scope1', 'scope2'];
|
|
124
|
-
|
|
125
|
-
const result = formatExtraScopes(['scope1', 'scope3'], null, defaultScopes, [], null);
|
|
126
|
-
|
|
127
|
-
assert.deepStrictEqual(result, ['scope3', 'scope1', 'scope2']);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
await t.test('should handle scopePrefix correctly', async () => {
|
|
131
|
-
const defaultScopes = ['prefix/scope1', 'prefix/scope2'];
|
|
132
|
-
|
|
133
|
-
const result = formatExtraScopes(['scope1', 'scope3'], null, defaultScopes, [], 'prefix');
|
|
134
|
-
|
|
135
|
-
assert.deepStrictEqual(result, ['scope3', 'prefix/scope1', 'prefix/scope2']);
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('checkAccountScopes - Outlook', async t => {
|
|
140
|
-
let account;
|
|
141
|
-
|
|
142
|
-
t.beforeEach(() => {
|
|
143
|
-
// Create a mock account instance with minimal required properties
|
|
144
|
-
account = new Account({
|
|
145
|
-
redis: {},
|
|
146
|
-
account: 'test-account',
|
|
147
|
-
secret: 'test-secret',
|
|
148
|
-
logger: {
|
|
149
|
-
warn: () => {},
|
|
150
|
-
error: () => {},
|
|
151
|
-
info: () => {},
|
|
152
|
-
debug: () => {}
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
await t.test('should detect send-only Outlook account (global cloud)', async () => {
|
|
158
|
-
const scopes = ['https://graph.microsoft.com/Mail.Send', 'offline_access'];
|
|
159
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
160
|
-
|
|
161
|
-
assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: false });
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
await t.test('should detect full-access Outlook account (global cloud)', async () => {
|
|
165
|
-
const scopes = ['https://graph.microsoft.com/Mail.ReadWrite', 'https://graph.microsoft.com/Mail.Send', 'offline_access'];
|
|
166
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
167
|
-
|
|
168
|
-
assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: true });
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
await t.test('should detect send-only Outlook account (GCC-High cloud)', async () => {
|
|
172
|
-
const scopes = ['https://graph.microsoft.us/Mail.Send', 'offline_access'];
|
|
173
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
174
|
-
|
|
175
|
-
assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: false });
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
await t.test('should detect full-access Outlook account (GCC-High cloud)', async () => {
|
|
179
|
-
const scopes = ['https://graph.microsoft.us/Mail.ReadWrite', 'https://graph.microsoft.us/Mail.Send', 'offline_access'];
|
|
180
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
181
|
-
|
|
182
|
-
assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: true });
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
await t.test('should detect send-only Outlook account (DoD cloud)', async () => {
|
|
186
|
-
const scopes = ['https://dod-graph.microsoft.us/Mail.Send', 'offline_access'];
|
|
187
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
188
|
-
|
|
189
|
-
assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: false });
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
await t.test('should detect full-access Outlook account (DoD cloud)', async () => {
|
|
193
|
-
const scopes = ['https://dod-graph.microsoft.us/Mail.ReadWrite', 'https://dod-graph.microsoft.us/Mail.Send', 'offline_access'];
|
|
194
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
195
|
-
|
|
196
|
-
assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: true });
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
await t.test('should detect send-only Outlook account (China cloud)', async () => {
|
|
200
|
-
const scopes = ['https://microsoftgraph.chinacloudapi.cn/Mail.Send', 'offline_access'];
|
|
201
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
202
|
-
|
|
203
|
-
assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: false });
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
await t.test('should detect full-access Outlook account (China cloud)', async () => {
|
|
207
|
-
const scopes = ['https://microsoftgraph.chinacloudapi.cn/Mail.ReadWrite', 'https://microsoftgraph.chinacloudapi.cn/Mail.Send', 'offline_access'];
|
|
208
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
209
|
-
|
|
210
|
-
assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: true });
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
await t.test('should detect read-only Outlook account with Mail.Read', async () => {
|
|
214
|
-
const scopes = ['https://graph.microsoft.com/Mail.Read', 'offline_access'];
|
|
215
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
216
|
-
|
|
217
|
-
assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: true });
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
await t.test('should handle scopes with trailing slashes', async () => {
|
|
221
|
-
const scopes = ['https://graph.microsoft.com/Mail.Send/', 'offline_access'];
|
|
222
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
223
|
-
|
|
224
|
-
assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: false });
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
await t.test('should handle scopes with query parameters', async () => {
|
|
228
|
-
const scopes = ['https://graph.microsoft.com/Mail.Send?foo=bar', 'offline_access'];
|
|
229
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
230
|
-
|
|
231
|
-
assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: false });
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
await t.test('should handle scopes with fragments', async () => {
|
|
235
|
-
const scopes = ['https://graph.microsoft.com/Mail.Send#section', 'offline_access'];
|
|
236
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
237
|
-
|
|
238
|
-
assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: false });
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
await t.test('should handle plain scope names', async () => {
|
|
242
|
-
const scopes = ['offline_access', 'openid', 'profile'];
|
|
243
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
244
|
-
|
|
245
|
-
assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
await t.test('should handle invalid protocol (http instead of https)', async () => {
|
|
249
|
-
const scopes = ['http://graph.microsoft.com/Mail.Send', 'offline_access'];
|
|
250
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
251
|
-
|
|
252
|
-
assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
await t.test('should handle non-Microsoft Graph domains', async () => {
|
|
256
|
-
const scopes = ['https://evil.com/Mail.Send', 'offline_access'];
|
|
257
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
258
|
-
|
|
259
|
-
assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
await t.test('should handle malformed URLs', async () => {
|
|
263
|
-
const scopes = ['not-a-url', 'offline_access'];
|
|
264
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
265
|
-
|
|
266
|
-
assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
await t.test('should handle empty scopes array', async () => {
|
|
270
|
-
const scopes = [];
|
|
271
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
272
|
-
|
|
273
|
-
assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
await t.test('should handle null scopes', async () => {
|
|
277
|
-
const result = account.checkAccountScopes('outlook', null);
|
|
278
|
-
|
|
279
|
-
assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
await t.test('should handle undefined scopes', async () => {
|
|
283
|
-
const result = account.checkAccountScopes('outlook', undefined);
|
|
284
|
-
|
|
285
|
-
assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
await t.test('should handle mixed cloud scopes', async () => {
|
|
289
|
-
const scopes = ['https://graph.microsoft.com/Mail.Send', 'https://graph.microsoft.us/Mail.ReadWrite', 'offline_access'];
|
|
290
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
291
|
-
|
|
292
|
-
assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: true });
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
await t.test('should handle User.Read scope (not mail-related)', async () => {
|
|
296
|
-
const scopes = ['https://graph.microsoft.com/User.Read', 'offline_access'];
|
|
297
|
-
const result = account.checkAccountScopes('outlook', scopes);
|
|
298
|
-
|
|
299
|
-
assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
|
|
300
|
-
});
|
|
301
|
-
});
|
package/test/sendonly-test.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
require('dotenv').config({ quiet: true });
|
|
4
|
-
|
|
5
|
-
const config = require('@zone-eu/wild-config');
|
|
6
|
-
const testConfig = require('./test-config');
|
|
7
|
-
const supertest = require('supertest');
|
|
8
|
-
const test = require('node:test');
|
|
9
|
-
const assert = require('node:assert').strict;
|
|
10
|
-
const webhooksServer = require('./webhooks-server');
|
|
11
|
-
|
|
12
|
-
const accessToken = '2aa97ad0456d6624a55d30780aa2ff61bfb7edc6fa00935b40814b271e718660';
|
|
13
|
-
const server = supertest.agent(`http://127.0.0.1:${config.api.port}`).auth(accessToken, { type: 'bearer' });
|
|
14
|
-
|
|
15
|
-
const gmailSendOnlyAccountId = 'gmail-sendonly-test';
|
|
16
|
-
|
|
17
|
-
// Helper function for polling with timeout
|
|
18
|
-
async function waitForCondition(checkFn, options = {}) {
|
|
19
|
-
const { interval = testConfig.POLL_INTERVAL, timeout = testConfig.DEFAULT_TIMEOUT, message = 'Condition not met within timeout' } = options;
|
|
20
|
-
|
|
21
|
-
const startTime = Date.now();
|
|
22
|
-
|
|
23
|
-
while (Date.now() - startTime < timeout) {
|
|
24
|
-
const result = await checkFn();
|
|
25
|
-
if (result) {
|
|
26
|
-
return result;
|
|
27
|
-
}
|
|
28
|
-
await new Promise(r => setTimeout(r, interval));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
throw new Error(`Timeout: ${message}`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
test('Gmail send-only account - isolated send test', async t => {
|
|
35
|
-
let oauth2PubsubId;
|
|
36
|
-
let oauth2SendOnlyAppId;
|
|
37
|
-
|
|
38
|
-
t.before(async () => {
|
|
39
|
-
await webhooksServer.init();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
t.after(async () => {
|
|
43
|
-
await webhooksServer.quit();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
await t.test('Create Gmail API OAuth2 service project', { timeout: 30000 }, async () => {
|
|
47
|
-
let gmailServiceData = {
|
|
48
|
-
name: 'Gmail API Pub/Sub',
|
|
49
|
-
provider: 'gmailService',
|
|
50
|
-
baseScopes: 'pubsub',
|
|
51
|
-
googleProjectId: process.env.GMAIL_API_PROJECT_ID,
|
|
52
|
-
serviceClient: process.env.GMAIL_API_SERVICE_CLIENT,
|
|
53
|
-
serviceClientEmail: process.env.GMAIL_API_SERVICE_EMAIL,
|
|
54
|
-
serviceKey: process.env.GMAIL_API_SERVICE_KEY
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const response = await server.post(`/v1/oauth2`).send(gmailServiceData).expect(200);
|
|
58
|
-
|
|
59
|
-
oauth2PubsubId = response.body.id;
|
|
60
|
-
assert.ok(oauth2PubsubId);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
await t.test('Create Gmail send-only OAuth2 client project', { timeout: 30000 }, async () => {
|
|
64
|
-
let gmailSendOnlyClientData = {
|
|
65
|
-
name: 'Gmail API Send-Only Client',
|
|
66
|
-
provider: 'gmail',
|
|
67
|
-
baseScopes: 'api',
|
|
68
|
-
googleProjectId: process.env.GMAIL_SENDONLY_PROJECT_ID,
|
|
69
|
-
clientId: process.env.GMAIL_SENDONLY_CLIENT_ID,
|
|
70
|
-
clientSecret: process.env.GMAIL_SENDONLY_CLIENT_SECRET,
|
|
71
|
-
extraScopes: ['gmail.send'],
|
|
72
|
-
skipScopes: ['gmail.modify'],
|
|
73
|
-
redirectUrl: 'http://127.0.0.1:3000/oauth'
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const response = await server.post(`/v1/oauth2`).send(gmailSendOnlyClientData).expect(200);
|
|
77
|
-
|
|
78
|
-
oauth2SendOnlyAppId = response.body.id;
|
|
79
|
-
assert.ok(oauth2SendOnlyAppId);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
await t.test('Register Gmail send-only account', { timeout: 30000 }, async () => {
|
|
83
|
-
const response = await server
|
|
84
|
-
.post(`/v1/account`)
|
|
85
|
-
.send({
|
|
86
|
-
account: gmailSendOnlyAccountId,
|
|
87
|
-
name: 'Gmail Send-Only Test User',
|
|
88
|
-
email: process.env.GMAIL_SENDONLY_ACCOUNT_EMAIL,
|
|
89
|
-
oauth2: {
|
|
90
|
-
provider: oauth2SendOnlyAppId,
|
|
91
|
-
auth: {
|
|
92
|
-
user: process.env.GMAIL_SENDONLY_ACCOUNT_EMAIL
|
|
93
|
-
},
|
|
94
|
-
refreshToken: process.env.GMAIL_SENDONLY_ACCOUNT_REFRESH
|
|
95
|
-
}
|
|
96
|
-
})
|
|
97
|
-
.expect(200);
|
|
98
|
-
|
|
99
|
-
assert.strictEqual(response.body.state, 'new');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
await t.test('Wait until Gmail send-only account is connected', { timeout: 180000 }, async () => {
|
|
103
|
-
await waitForCondition(
|
|
104
|
-
async () => {
|
|
105
|
-
const response = await server.get(`/v1/account/${gmailSendOnlyAccountId}`).expect(200);
|
|
106
|
-
switch (response.body.state) {
|
|
107
|
-
case 'authenticationError':
|
|
108
|
-
case 'connectError':
|
|
109
|
-
throw new Error('Invalid account state ' + response.body.state);
|
|
110
|
-
case 'connected':
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
return false;
|
|
114
|
-
},
|
|
115
|
-
{ timeout: testConfig.GMAIL_TIMEOUT, message: `Gmail send-only account connection timeout` }
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
// Verify account type is 'sending'
|
|
119
|
-
const response = await server.get(`/v1/account/${gmailSendOnlyAccountId}`).expect(200);
|
|
120
|
-
assert.strictEqual(response.body.sendOnly, true, 'Account should be detected as send-only');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
await t.test('Submit email from send-only account (no receive check)', { timeout: 60000 }, async () => {
|
|
124
|
-
let messageId = `<sendonly-isolated-test-${Date.now()}@example.com>`;
|
|
125
|
-
|
|
126
|
-
const response = await server
|
|
127
|
-
.post(`/v1/account/${gmailSendOnlyAccountId}/submit`)
|
|
128
|
-
.send({
|
|
129
|
-
to: [
|
|
130
|
-
{
|
|
131
|
-
name: 'Test Recipient',
|
|
132
|
-
address: process.env.GMAIL_API_ACCOUNT_EMAIL_2 || 'test@example.com'
|
|
133
|
-
}
|
|
134
|
-
],
|
|
135
|
-
subject: 'Isolated send-only test',
|
|
136
|
-
text: 'This is a test message from send-only account - isolated test',
|
|
137
|
-
html: '<p>This is a test message from <strong>send-only</strong> account - isolated test</p>',
|
|
138
|
-
messageId
|
|
139
|
-
})
|
|
140
|
-
.expect(200);
|
|
141
|
-
|
|
142
|
-
assert.ok(response.body.messageId, 'Should have messageId in response');
|
|
143
|
-
assert.ok(response.body.queueId, 'Should have queueId in response');
|
|
144
|
-
|
|
145
|
-
// Wait for messageSent webhook only - don't check for receive
|
|
146
|
-
const messageSentWebhook = await waitForCondition(
|
|
147
|
-
async () => {
|
|
148
|
-
let webhooks = webhooksServer.webhooks.get(gmailSendOnlyAccountId);
|
|
149
|
-
return webhooks?.find(wh => wh.event === 'messageSent' && wh.data.originalMessageId === messageId);
|
|
150
|
-
},
|
|
151
|
-
{ timeout: 30000, message: 'Gmail send-only message sent webhook timeout' }
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
assert.ok(messageSentWebhook, 'Should receive messageSent webhook');
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
await t.test('Cleanup - delete send-only account', { timeout: 10000 }, async () => {
|
|
158
|
-
await server.delete(`/v1/account/${gmailSendOnlyAccountId}`).expect(200);
|
|
159
|
-
});
|
|
160
|
-
});
|
package/test/test-config.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Test configuration for EmailEngine test suite
|
|
5
|
-
* Timeout values are configured to handle Gmail API operations
|
|
6
|
-
* which may take longer due to network latency and API processing
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
module.exports = {
|
|
10
|
-
// Default timeout for standard operations
|
|
11
|
-
DEFAULT_TIMEOUT: 30000, // 30 seconds
|
|
12
|
-
|
|
13
|
-
// Timeout for Gmail API operations
|
|
14
|
-
GMAIL_TIMEOUT: 90000, // 90 seconds
|
|
15
|
-
|
|
16
|
-
// Timeout for waiting for account connections
|
|
17
|
-
CONNECTION_TIMEOUT: 60000, // 60 seconds
|
|
18
|
-
|
|
19
|
-
// Timeout for webhook notifications
|
|
20
|
-
WEBHOOK_TIMEOUT: 30000, // 30 seconds
|
|
21
|
-
|
|
22
|
-
// Polling interval for checking conditions
|
|
23
|
-
POLL_INTERVAL: 1000, // 1 second
|
|
24
|
-
|
|
25
|
-
// Environment-specific overrides
|
|
26
|
-
...(process.env.CI
|
|
27
|
-
? {
|
|
28
|
-
// Increase timeouts in CI environment
|
|
29
|
-
GMAIL_TIMEOUT: 120000, // 2 minutes in CI
|
|
30
|
-
CONNECTION_TIMEOUT: 90000, // 90 seconds in CI
|
|
31
|
-
WEBHOOK_TIMEOUT: 60000 // 60 seconds in CI
|
|
32
|
-
}
|
|
33
|
-
: {})
|
|
34
|
-
};
|
package/test/webhooks-server.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const config = require('@zone-eu/wild-config');
|
|
4
|
-
const Hapi = require('@hapi/hapi');
|
|
5
|
-
|
|
6
|
-
const webhooks = new Map();
|
|
7
|
-
|
|
8
|
-
const server = Hapi.server({
|
|
9
|
-
port: config.webhooksServer.port,
|
|
10
|
-
host: '0.0.0.0'
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
server.route({
|
|
14
|
-
method: 'POST',
|
|
15
|
-
path: '/webhooks',
|
|
16
|
-
handler: async (request, h) => {
|
|
17
|
-
let account = request.payload.account || '';
|
|
18
|
-
if (!webhooks.has(account)) {
|
|
19
|
-
webhooks.set(account, []);
|
|
20
|
-
}
|
|
21
|
-
console.log('WEBHOOK', JSON.stringify(request.payload));
|
|
22
|
-
webhooks.get(account).push(request.payload);
|
|
23
|
-
return h.response('OK').code(200);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const init = async () => {
|
|
28
|
-
await server.start();
|
|
29
|
-
console.log('Webhooks Server running on %s', server.info.uri);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
module.exports = {
|
|
33
|
-
init,
|
|
34
|
-
webhooks,
|
|
35
|
-
async quit() {
|
|
36
|
-
await server.stop({ timeout: 5 * 1000 });
|
|
37
|
-
console.log('Webhooks Server closed');
|
|
38
|
-
}
|
|
39
|
-
};
|