@bernierllc/email-mitm-masking 1.0.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/.eslintrc.js +21 -0
- package/README.md +405 -0
- package/TESTING.md +237 -0
- package/__tests__/EmailMaskingService.test.ts +470 -0
- package/__tests__/__fixtures__/test-config.ts +24 -0
- package/__tests__/__mocks__/database.ts +252 -0
- package/__tests__/setup.ts +16 -0
- package/dist/EmailMaskingService.d.ts +90 -0
- package/dist/EmailMaskingService.js +316 -0
- package/dist/database.d.ts +5 -0
- package/dist/database.js +69 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -0
- package/dist/types.d.ts +159 -0
- package/dist/types.js +9 -0
- package/jest.config.cjs +28 -0
- package/package.json +56 -0
- package/src/EmailMaskingService.ts +425 -0
- package/src/database.ts +69 -0
- package/src/index.ts +20 -0
- package/src/types.ts +200 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { EmailMaskingService } from '../src/EmailMaskingService.js';
|
|
10
|
+
import { testConfig } from './__fixtures__/test-config.js';
|
|
11
|
+
import type { ProxyAddress } from '../src/types.js';
|
|
12
|
+
|
|
13
|
+
describe('EmailMaskingService', () => {
|
|
14
|
+
let service: EmailMaskingService;
|
|
15
|
+
|
|
16
|
+
beforeEach(async () => {
|
|
17
|
+
service = new EmailMaskingService(testConfig);
|
|
18
|
+
await service.initialize();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(async () => {
|
|
22
|
+
await service.shutdown();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('createProxy', () => {
|
|
26
|
+
it('should create proxy with default TTL', async () => {
|
|
27
|
+
const proxy = await service.createProxy('user123', 'user@real.com');
|
|
28
|
+
|
|
29
|
+
expect(proxy.proxyEmail).toMatch(/user123-[a-z0-9]+@proxy\.test\.com/);
|
|
30
|
+
expect(proxy.realEmail).toBe('user@real.com');
|
|
31
|
+
expect(proxy.userId).toBe('user123');
|
|
32
|
+
expect(proxy.status).toBe('active');
|
|
33
|
+
expect(proxy.expiresAt).toBeDefined();
|
|
34
|
+
expect(proxy.routingCount).toBe(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should create proxy with custom TTL', async () => {
|
|
38
|
+
const proxy = await service.createProxy('user123', 'user@real.com', {
|
|
39
|
+
ttlDays: 90
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(proxy.expiresAt).toBeDefined();
|
|
43
|
+
if (proxy.expiresAt) {
|
|
44
|
+
const expectedExpiry = new Date(Date.now() + 90 * 24 * 60 * 60 * 1000);
|
|
45
|
+
const diff = Math.abs(proxy.expiresAt.getTime() - expectedExpiry.getTime());
|
|
46
|
+
expect(diff).toBeLessThan(5000); // Within 5 seconds
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should create proxy with external email for per-contact masking', async () => {
|
|
51
|
+
const proxy = await service.createProxy('user123', 'user@real.com', {
|
|
52
|
+
externalEmail: 'contact@external.com'
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(proxy.externalEmail).toBe('contact@external.com');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should create proxy with conversation ID', async () => {
|
|
59
|
+
const proxy = await service.createProxy('user123', 'user@real.com', {
|
|
60
|
+
conversationId: 'conv-abc123'
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(proxy.conversationId).toBe('conv-abc123');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should create proxy with metadata', async () => {
|
|
67
|
+
const proxy = await service.createProxy('user123', 'user@real.com', {
|
|
68
|
+
metadata: { source: 'test', priority: 'high' }
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(proxy.metadata).toEqual({ source: 'test', priority: 'high' });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should enforce user quota limits', async () => {
|
|
75
|
+
// Create 10 proxies (max)
|
|
76
|
+
for (let i = 0; i < 10; i++) {
|
|
77
|
+
await service.createProxy('user123', `user${i}@real.com`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 11th should fail
|
|
81
|
+
await expect(
|
|
82
|
+
service.createProxy('user123', 'user11@real.com')
|
|
83
|
+
).rejects.toThrow(/maximum proxy limit/);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should create unlimited proxies when quota is 0', async () => {
|
|
87
|
+
const serviceNoQuota = new EmailMaskingService({
|
|
88
|
+
...testConfig,
|
|
89
|
+
maxProxiesPerUser: 0
|
|
90
|
+
});
|
|
91
|
+
await serviceNoQuota.initialize();
|
|
92
|
+
|
|
93
|
+
// Should be able to create many proxies
|
|
94
|
+
for (let i = 0; i < 15; i++) {
|
|
95
|
+
const proxy = await serviceNoQuota.createProxy('user456', `user${i}@real.com`);
|
|
96
|
+
expect(proxy).toBeDefined();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
await serviceNoQuota.shutdown();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('routeInbound', () => {
|
|
104
|
+
let proxy: ProxyAddress;
|
|
105
|
+
|
|
106
|
+
beforeEach(async () => {
|
|
107
|
+
proxy = await service.createProxy('user123', 'user@real.com');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should route inbound email to real address', async () => {
|
|
111
|
+
const result = await service.routeInbound(
|
|
112
|
+
'external@example.com',
|
|
113
|
+
proxy.proxyEmail,
|
|
114
|
+
'Test Email',
|
|
115
|
+
'Hello!',
|
|
116
|
+
'<p>Hello!</p>'
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
expect(result.success).toBe(true);
|
|
120
|
+
expect(result.routedTo).toBe('user@real.com');
|
|
121
|
+
expect(result.proxyUsed).toBe(proxy.proxyEmail);
|
|
122
|
+
expect(result.direction).toBe('inbound');
|
|
123
|
+
expect(result.auditId).toBeDefined();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should route inbound email with Name <email> format', async () => {
|
|
127
|
+
const result = await service.routeInbound(
|
|
128
|
+
'external@example.com',
|
|
129
|
+
`John Doe <${proxy.proxyEmail}>`,
|
|
130
|
+
'Test Email',
|
|
131
|
+
'Hello!'
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
expect(result.success).toBe(true);
|
|
135
|
+
expect(result.routedTo).toBe('user@real.com');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should reject unknown proxy', async () => {
|
|
139
|
+
const result = await service.routeInbound(
|
|
140
|
+
'external@example.com',
|
|
141
|
+
'unknown-abc123@proxy.test.com',
|
|
142
|
+
'Test Email',
|
|
143
|
+
'Hello!'
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
expect(result.success).toBe(false);
|
|
147
|
+
expect(result.error).toMatch(/not found/);
|
|
148
|
+
expect(result.direction).toBe('inbound');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should reject inactive proxy', async () => {
|
|
152
|
+
await service.deactivateProxy(proxy.id);
|
|
153
|
+
|
|
154
|
+
const result = await service.routeInbound(
|
|
155
|
+
'external@example.com',
|
|
156
|
+
proxy.proxyEmail,
|
|
157
|
+
'Test Email',
|
|
158
|
+
'Hello!'
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
expect(result.success).toBe(false);
|
|
162
|
+
expect(result.error).toMatch(/inactive/);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should reject revoked proxy', async () => {
|
|
166
|
+
await service.revokeProxy(proxy.id);
|
|
167
|
+
|
|
168
|
+
const result = await service.routeInbound(
|
|
169
|
+
'external@example.com',
|
|
170
|
+
proxy.proxyEmail,
|
|
171
|
+
'Test Email',
|
|
172
|
+
'Hello!'
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
expect(result.success).toBe(false);
|
|
176
|
+
expect(result.error).toMatch(/revoked/);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should reject expired proxy', async () => {
|
|
180
|
+
// Create proxy with expiration in the past
|
|
181
|
+
const expiredProxy = await service.createProxy('user789', 'user@real.com', {
|
|
182
|
+
ttlDays: -1 // Already expired
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const result = await service.routeInbound(
|
|
186
|
+
'external@example.com',
|
|
187
|
+
expiredProxy.proxyEmail,
|
|
188
|
+
'Test Email',
|
|
189
|
+
'Hello!'
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
expect(result.success).toBe(false);
|
|
193
|
+
expect(result.error).toMatch(/expired/);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should handle missing proxy address in To field', async () => {
|
|
197
|
+
const result = await service.routeInbound(
|
|
198
|
+
'external@example.com',
|
|
199
|
+
'notaproxy@example.com',
|
|
200
|
+
'Test Email',
|
|
201
|
+
'Hello!'
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
expect(result.success).toBe(false);
|
|
205
|
+
expect(result.error).toMatch(/No proxy address found/);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should update proxy usage statistics', async () => {
|
|
209
|
+
await service.routeInbound(
|
|
210
|
+
'external@example.com',
|
|
211
|
+
proxy.proxyEmail,
|
|
212
|
+
'Test Email',
|
|
213
|
+
'Hello!'
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const updatedProxy = await service.getProxyById(proxy.id);
|
|
217
|
+
expect(updatedProxy?.routingCount).toBe(1);
|
|
218
|
+
expect(updatedProxy?.lastUsedAt).toBeDefined();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('routeOutbound', () => {
|
|
223
|
+
it('should auto-create proxy for new contact', async () => {
|
|
224
|
+
const result = await service.routeOutbound(
|
|
225
|
+
'user123',
|
|
226
|
+
'user@real.com',
|
|
227
|
+
'external@example.com',
|
|
228
|
+
{
|
|
229
|
+
subject: 'Test',
|
|
230
|
+
text: 'Hello!',
|
|
231
|
+
html: '<p>Hello!</p>'
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
expect(result.success).toBe(true);
|
|
236
|
+
expect(result.routedTo).toBe('external@example.com');
|
|
237
|
+
expect(result.proxyUsed).toMatch(/@proxy\.test\.com$/);
|
|
238
|
+
expect(result.direction).toBe('outbound');
|
|
239
|
+
expect(result.auditId).toBeDefined();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should reuse existing proxy for known contact', async () => {
|
|
243
|
+
// First outbound
|
|
244
|
+
const result1 = await service.routeOutbound(
|
|
245
|
+
'user123',
|
|
246
|
+
'user@real.com',
|
|
247
|
+
'external@example.com',
|
|
248
|
+
{ subject: 'Test 1', text: 'Hello!' }
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Second outbound (should reuse proxy)
|
|
252
|
+
const result2 = await service.routeOutbound(
|
|
253
|
+
'user123',
|
|
254
|
+
'user@real.com',
|
|
255
|
+
'external@example.com',
|
|
256
|
+
{ subject: 'Test 2', text: 'Hello again!' }
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
expect(result1.proxyUsed).toBe(result2.proxyUsed);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should handle quota limits during auto-creation', async () => {
|
|
263
|
+
// Create 10 proxies manually to hit limit
|
|
264
|
+
for (let i = 0; i < 10; i++) {
|
|
265
|
+
await service.createProxy('user123', `user${i}@real.com`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Auto-create should fail due to quota
|
|
269
|
+
await expect(
|
|
270
|
+
service.routeOutbound(
|
|
271
|
+
'user123',
|
|
272
|
+
'user@real.com',
|
|
273
|
+
'external@example.com',
|
|
274
|
+
{ subject: 'Test', text: 'Hello!' }
|
|
275
|
+
)
|
|
276
|
+
).rejects.toThrow(/maximum proxy limit/);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should reject inactive proxy during outbound routing', async () => {
|
|
280
|
+
// Create and then deactivate proxy
|
|
281
|
+
const proxy = await service.createProxy('user123', 'user@real.com', {
|
|
282
|
+
externalEmail: 'external@example.com'
|
|
283
|
+
});
|
|
284
|
+
await service.deactivateProxy(proxy.id);
|
|
285
|
+
|
|
286
|
+
const result = await service.routeOutbound(
|
|
287
|
+
'user123',
|
|
288
|
+
'user@real.com',
|
|
289
|
+
'external@example.com',
|
|
290
|
+
{ subject: 'Test', text: 'Hello!' }
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
expect(result.success).toBe(false);
|
|
294
|
+
expect(result.error).toMatch(/inactive/);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should update proxy usage statistics', async () => {
|
|
298
|
+
const result = await service.routeOutbound(
|
|
299
|
+
'user123',
|
|
300
|
+
'user@real.com',
|
|
301
|
+
'external@example.com',
|
|
302
|
+
{ subject: 'Test', text: 'Hello!' }
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Get the proxy that was created
|
|
306
|
+
const proxyEmail = result.proxyUsed;
|
|
307
|
+
if (!proxyEmail) {
|
|
308
|
+
throw new Error('Proxy email not returned');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Route again to increment count
|
|
312
|
+
await service.routeOutbound(
|
|
313
|
+
'user123',
|
|
314
|
+
'user@real.com',
|
|
315
|
+
'external@example.com',
|
|
316
|
+
{ subject: 'Test 2', text: 'Hello again!' }
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
// Find the proxy and check usage - we need to query directly since we don't have the ID
|
|
320
|
+
// The proxy should have routingCount = 2
|
|
321
|
+
const result3 = await service.routeOutbound(
|
|
322
|
+
'user123',
|
|
323
|
+
'user@real.com',
|
|
324
|
+
'external@example.com',
|
|
325
|
+
{ subject: 'Test 3', text: 'Hello once more!' }
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
expect(result3.success).toBe(true);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('deactivateProxy', () => {
|
|
333
|
+
it('should deactivate a proxy', async () => {
|
|
334
|
+
const proxy = await service.createProxy('user123', 'user@real.com');
|
|
335
|
+
|
|
336
|
+
await service.deactivateProxy(proxy.id);
|
|
337
|
+
|
|
338
|
+
const updatedProxy = await service.getProxyById(proxy.id);
|
|
339
|
+
expect(updatedProxy?.status).toBe('inactive');
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
describe('revokeProxy', () => {
|
|
344
|
+
it('should revoke a proxy permanently', async () => {
|
|
345
|
+
const proxy = await service.createProxy('user123', 'user@real.com');
|
|
346
|
+
|
|
347
|
+
await service.revokeProxy(proxy.id);
|
|
348
|
+
|
|
349
|
+
const updatedProxy = await service.getProxyById(proxy.id);
|
|
350
|
+
expect(updatedProxy?.status).toBe('revoked');
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
describe('getProxyById', () => {
|
|
355
|
+
it('should retrieve proxy by ID', async () => {
|
|
356
|
+
const proxy = await service.createProxy('user123', 'user@real.com');
|
|
357
|
+
|
|
358
|
+
const retrieved = await service.getProxyById(proxy.id);
|
|
359
|
+
|
|
360
|
+
expect(retrieved).toBeDefined();
|
|
361
|
+
expect(retrieved?.id).toBe(proxy.id);
|
|
362
|
+
expect(retrieved?.proxyEmail).toBe(proxy.proxyEmail);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('should return null for non-existent proxy', async () => {
|
|
366
|
+
const retrieved = await service.getProxyById('00000000-0000-0000-0000-000000000000');
|
|
367
|
+
|
|
368
|
+
expect(retrieved).toBeNull();
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe('audit logging', () => {
|
|
373
|
+
it('should create audit entries when enabled', async () => {
|
|
374
|
+
const proxy = await service.createProxy('user123', 'user@real.com');
|
|
375
|
+
|
|
376
|
+
const result = await service.routeInbound(
|
|
377
|
+
'external@example.com',
|
|
378
|
+
proxy.proxyEmail,
|
|
379
|
+
'Test Email',
|
|
380
|
+
'Hello!'
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
expect(result.auditId).toBeDefined();
|
|
384
|
+
expect(result.auditId).not.toBe('');
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('should not create audit entries when disabled', async () => {
|
|
388
|
+
const serviceNoAudit = new EmailMaskingService({
|
|
389
|
+
...testConfig,
|
|
390
|
+
auditEnabled: false
|
|
391
|
+
});
|
|
392
|
+
await serviceNoAudit.initialize();
|
|
393
|
+
|
|
394
|
+
const proxy = await serviceNoAudit.createProxy('user123', 'user@real.com');
|
|
395
|
+
|
|
396
|
+
const result = await serviceNoAudit.routeInbound(
|
|
397
|
+
'external@example.com',
|
|
398
|
+
proxy.proxyEmail,
|
|
399
|
+
'Test Email',
|
|
400
|
+
'Hello!'
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
expect(result.auditId).toBe('');
|
|
404
|
+
|
|
405
|
+
await serviceNoAudit.shutdown();
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
describe('configuration', () => {
|
|
410
|
+
it('should use default TTL when not specified', async () => {
|
|
411
|
+
const proxy = await service.createProxy('user123', 'user@real.com');
|
|
412
|
+
|
|
413
|
+
expect(proxy.expiresAt).toBeDefined();
|
|
414
|
+
if (proxy.expiresAt) {
|
|
415
|
+
const expectedExpiry = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
|
416
|
+
const diff = Math.abs(proxy.expiresAt.getTime() - expectedExpiry.getTime());
|
|
417
|
+
expect(diff).toBeLessThan(5000); // Within 5 seconds
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('should create unlimited TTL when set to 0', async () => {
|
|
422
|
+
const serviceUnlimited = new EmailMaskingService({
|
|
423
|
+
...testConfig,
|
|
424
|
+
defaultTTL: 0
|
|
425
|
+
});
|
|
426
|
+
await serviceUnlimited.initialize();
|
|
427
|
+
|
|
428
|
+
const proxy = await serviceUnlimited.createProxy('user123', 'user@real.com');
|
|
429
|
+
|
|
430
|
+
expect(proxy.expiresAt).toBeUndefined();
|
|
431
|
+
|
|
432
|
+
await serviceUnlimited.shutdown();
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('should use correct proxy domain', async () => {
|
|
436
|
+
const proxy = await service.createProxy('user123', 'user@real.com');
|
|
437
|
+
|
|
438
|
+
expect(proxy.proxyEmail).toContain('@proxy.test.com');
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
describe('edge cases', () => {
|
|
443
|
+
it('should handle concurrent proxy creation requests', async () => {
|
|
444
|
+
const promises = Array.from({ length: 5 }, (_, i) =>
|
|
445
|
+
service.createProxy('user123', `user${i}@real.com`)
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
const proxies = await Promise.all(promises);
|
|
449
|
+
|
|
450
|
+
expect(proxies).toHaveLength(5);
|
|
451
|
+
const emails = proxies.map(p => p.proxyEmail);
|
|
452
|
+
const uniqueEmails = new Set(emails);
|
|
453
|
+
expect(uniqueEmails.size).toBe(5); // All unique
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('should handle empty metadata', async () => {
|
|
457
|
+
const proxy = await service.createProxy('user123', 'user@real.com', {
|
|
458
|
+
metadata: {}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
expect(proxy.metadata).toEqual({});
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('should handle special characters in email addresses', async () => {
|
|
465
|
+
const proxy = await service.createProxy('user123', 'user+tag@real.com');
|
|
466
|
+
|
|
467
|
+
expect(proxy.realEmail).toBe('user+tag@real.com');
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { EmailMaskingConfig } from '../../src/types.js';
|
|
10
|
+
|
|
11
|
+
export const testConfig: EmailMaskingConfig = {
|
|
12
|
+
proxyDomain: 'proxy.test.com',
|
|
13
|
+
database: {
|
|
14
|
+
host: process.env.POSTGRES_HOST || 'localhost',
|
|
15
|
+
port: parseInt(process.env.POSTGRES_PORT || '5432'),
|
|
16
|
+
database: process.env.POSTGRES_DB || 'email_masking_test',
|
|
17
|
+
user: process.env.POSTGRES_USER || 'postgres',
|
|
18
|
+
password: process.env.POSTGRES_PASSWORD || 'postgres'
|
|
19
|
+
},
|
|
20
|
+
defaultTTL: 30,
|
|
21
|
+
maxProxiesPerUser: 10,
|
|
22
|
+
auditEnabled: true,
|
|
23
|
+
strategy: 'per-contact'
|
|
24
|
+
};
|