@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.
@@ -0,0 +1,252 @@
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 { ProxyAddressRow, RoutingAuditRow } from '../../src/types.js';
10
+
11
+ /**
12
+ * In-memory database mock for testing email masking service
13
+ */
14
+ class MockDatabase {
15
+ private proxyAddresses: Map<string, ProxyAddressRow> = new Map();
16
+ private routingAudits: Map<string, RoutingAuditRow> = new Map();
17
+ private proxyEmailIndex: Map<string, string> = new Map(); // proxy_email -> id
18
+
19
+ /**
20
+ * Reset all data (called between tests)
21
+ */
22
+ reset(): void {
23
+ this.proxyAddresses.clear();
24
+ this.routingAudits.clear();
25
+ this.proxyEmailIndex.clear();
26
+ }
27
+
28
+ /**
29
+ * Execute a SQL query (mocked)
30
+ */
31
+ async query<T = any>(sql: string, params: any[] = []): Promise<{ rows: T[] }> {
32
+ const normalizedSql = sql.trim().toLowerCase();
33
+
34
+ // CREATE TABLE statements (schema initialization)
35
+ if (normalizedSql.includes('create table') || normalizedSql.includes('create index')) {
36
+ return { rows: [] };
37
+ }
38
+
39
+ // INSERT INTO proxy_addresses
40
+ if (normalizedSql.includes('insert into proxy_addresses')) {
41
+ const [
42
+ proxyEmail,
43
+ realEmail,
44
+ userId,
45
+ externalEmail,
46
+ conversationId,
47
+ status,
48
+ expiresAt,
49
+ metadata
50
+ ] = params;
51
+
52
+ const id = this.generateUUID();
53
+ const now = new Date().toISOString();
54
+
55
+ const row: ProxyAddressRow = {
56
+ id,
57
+ proxy_email: proxyEmail,
58
+ real_email: realEmail,
59
+ user_id: userId,
60
+ external_email: externalEmail,
61
+ conversation_id: conversationId,
62
+ status,
63
+ created_at: now,
64
+ activated_at: null,
65
+ expires_at: expiresAt ? new Date(expiresAt).toISOString() : null,
66
+ last_used_at: null,
67
+ routing_count: '0',
68
+ metadata: metadata || '{}'
69
+ };
70
+
71
+ this.proxyAddresses.set(id, row);
72
+ this.proxyEmailIndex.set(proxyEmail, id);
73
+
74
+ return { rows: [row] as T[] };
75
+ }
76
+
77
+ // INSERT INTO routing_audit
78
+ if (normalizedSql.includes('insert into routing_audit')) {
79
+ const [
80
+ proxyId,
81
+ direction,
82
+ fromAddress,
83
+ toAddress,
84
+ success,
85
+ error,
86
+ metadata
87
+ ] = params;
88
+
89
+ const id = this.generateUUID();
90
+ const now = new Date().toISOString();
91
+
92
+ const row: RoutingAuditRow = {
93
+ id,
94
+ proxy_id: proxyId,
95
+ direction,
96
+ from_address: fromAddress,
97
+ to_address: toAddress,
98
+ routed_at: now,
99
+ success,
100
+ error: error || null,
101
+ metadata: metadata || '{}'
102
+ };
103
+
104
+ this.routingAudits.set(id, row);
105
+
106
+ return { rows: [{ id }] as T[] };
107
+ }
108
+
109
+ // SELECT * FROM proxy_addresses WHERE id = $1
110
+ if (normalizedSql.includes('select * from proxy_addresses') && normalizedSql.includes('where id')) {
111
+ const [id] = params;
112
+ const row = this.proxyAddresses.get(id);
113
+ return { rows: row ? [row] as T[] : [] };
114
+ }
115
+
116
+ // SELECT * FROM proxy_addresses WHERE proxy_email = $1
117
+ if (normalizedSql.includes('select * from proxy_addresses') && normalizedSql.includes('where proxy_email')) {
118
+ const [proxyEmail] = params;
119
+ const id = this.proxyEmailIndex.get(proxyEmail);
120
+ const row = id ? this.proxyAddresses.get(id) : undefined;
121
+ return { rows: row ? [row] as T[] : [] };
122
+ }
123
+
124
+ // SELECT * FROM proxy_addresses WHERE user_id = $1 AND real_email = $2 AND external_email = $3
125
+ if (normalizedSql.includes('select * from proxy_addresses') &&
126
+ normalizedSql.includes('where user_id') &&
127
+ normalizedSql.includes('real_email') &&
128
+ normalizedSql.includes('external_email')) {
129
+ const [userId, realEmail, externalEmail] = params;
130
+
131
+ let matches = Array.from(this.proxyAddresses.values()).filter(row =>
132
+ row.user_id === userId &&
133
+ row.real_email === realEmail &&
134
+ row.external_email === externalEmail
135
+ );
136
+
137
+ // Sort by created_at DESC if ORDER BY is present
138
+ if (normalizedSql.includes('order by created_at desc')) {
139
+ matches.sort((a, b) => {
140
+ const dateA = new Date(a.created_at).getTime();
141
+ const dateB = new Date(b.created_at).getTime();
142
+ return dateB - dateA; // DESC order
143
+ });
144
+ }
145
+
146
+ return { rows: matches.slice(0, 1) as T[] }; // LIMIT 1
147
+ }
148
+
149
+ // SELECT COUNT(*) FROM proxy_addresses WHERE user_id = $1 AND status = 'active'
150
+ if (normalizedSql.includes('select count(*)') && normalizedSql.includes('from proxy_addresses')) {
151
+ const [userId] = params;
152
+
153
+ const count = Array.from(this.proxyAddresses.values()).filter(row =>
154
+ row.user_id === userId && row.status === 'active'
155
+ ).length;
156
+
157
+ return { rows: [{ count: count.toString() }] as T[] };
158
+ }
159
+
160
+ // UPDATE proxy_addresses SET status = $1 WHERE id = $2
161
+ if (normalizedSql.includes('update proxy_addresses') &&
162
+ normalizedSql.includes('set status') &&
163
+ !normalizedSql.includes('routing_count')) {
164
+ const [status, id] = params;
165
+ const row = this.proxyAddresses.get(id);
166
+
167
+ if (row) {
168
+ row.status = status;
169
+ this.proxyAddresses.set(id, row);
170
+ }
171
+
172
+ return { rows: [] };
173
+ }
174
+
175
+ // UPDATE proxy_addresses SET routing_count = routing_count + 1, last_used_at = NOW() WHERE id = $1
176
+ if (normalizedSql.includes('update proxy_addresses') && normalizedSql.includes('routing_count')) {
177
+ const [id] = params;
178
+ const row = this.proxyAddresses.get(id);
179
+
180
+ if (row) {
181
+ row.routing_count = (parseInt(row.routing_count) + 1).toString();
182
+ row.last_used_at = new Date().toISOString();
183
+ this.proxyAddresses.set(id, row);
184
+ }
185
+
186
+ return { rows: [] };
187
+ }
188
+
189
+ // UPDATE proxy_addresses SET status = 'expired' WHERE expires_at < NOW() AND status = 'active'
190
+ if (normalizedSql.includes('update proxy_addresses') && normalizedSql.includes('expires_at < now()')) {
191
+ const now = new Date();
192
+
193
+ Array.from(this.proxyAddresses.values()).forEach(row => {
194
+ if (row.status === 'active' && row.expires_at) {
195
+ const expiresAt = new Date(row.expires_at);
196
+ if (expiresAt < now) {
197
+ row.status = 'expired';
198
+ }
199
+ }
200
+ });
201
+
202
+ return { rows: [] };
203
+ }
204
+
205
+ // Fallback for unhandled queries
206
+ console.warn('Unhandled mock query:', normalizedSql, params);
207
+ return { rows: [] };
208
+ }
209
+
210
+ /**
211
+ * Generate a UUID v4
212
+ */
213
+ private generateUUID(): string {
214
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
215
+ const r = Math.random() * 16 | 0;
216
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
217
+ return v.toString(16);
218
+ });
219
+ }
220
+ }
221
+
222
+ // Singleton instance
223
+ const mockDb = new MockDatabase();
224
+
225
+ /**
226
+ * Mock Pool class that mimics pg.Pool
227
+ */
228
+ export class Pool {
229
+ async query<T = any>(sql: string, params: any[] = []): Promise<{ rows: T[] }> {
230
+ return mockDb.query<T>(sql, params);
231
+ }
232
+
233
+ async end(): Promise<void> {
234
+ // No-op for mock
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Initialize database schema (no-op in mock)
240
+ */
241
+ export async function initializeDatabase(_pool: Pool): Promise<void> {
242
+ // Schema is implicitly created in memory
243
+ await mockDb.query('CREATE TABLE proxy_addresses');
244
+ await mockDb.query('CREATE TABLE routing_audit');
245
+ }
246
+
247
+ /**
248
+ * Reset mock database between tests
249
+ */
250
+ export function resetMockDatabase(): void {
251
+ mockDb.reset();
252
+ }
@@ -0,0 +1,16 @@
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 { resetMockDatabase } from './__mocks__/database.js';
10
+
11
+ /**
12
+ * Reset mock database before each test
13
+ */
14
+ beforeEach(() => {
15
+ resetMockDatabase();
16
+ });
@@ -0,0 +1,90 @@
1
+ import type { EmailMaskingConfig, ProxyAddress, RoutingResult, CreateProxyOptions } from './types.js';
2
+ /**
3
+ * Email masking and man-in-the-middle routing service
4
+ */
5
+ export declare class EmailMaskingService {
6
+ private config;
7
+ private db;
8
+ private cleanupInterval?;
9
+ constructor(config: EmailMaskingConfig);
10
+ /**
11
+ * Initialize the masking service
12
+ */
13
+ initialize(): Promise<void>;
14
+ /**
15
+ * Cleanup resources
16
+ */
17
+ shutdown(): Promise<void>;
18
+ /**
19
+ * Create a new proxy email address
20
+ */
21
+ createProxy(userId: string, realEmail: string, options?: CreateProxyOptions): Promise<ProxyAddress>;
22
+ /**
23
+ * Route inbound email (external → real address)
24
+ */
25
+ routeInbound(from: string, to: string, _subject: string, _text?: string, _html?: string): Promise<RoutingResult>;
26
+ /**
27
+ * Route outbound email (real address → external via proxy)
28
+ */
29
+ routeOutbound(userId: string, realEmail: string, externalEmail: string, _emailContent: {
30
+ subject: string;
31
+ text?: string;
32
+ html?: string;
33
+ }): Promise<RoutingResult>;
34
+ /**
35
+ * Deactivate a proxy address
36
+ */
37
+ deactivateProxy(proxyId: string): Promise<void>;
38
+ /**
39
+ * Revoke a proxy address permanently
40
+ */
41
+ revokeProxy(proxyId: string): Promise<void>;
42
+ /**
43
+ * Get proxy by ID
44
+ */
45
+ getProxyById(proxyId: string): Promise<ProxyAddress | null>;
46
+ /**
47
+ * Get proxy by email address
48
+ */
49
+ private getProxyByEmail;
50
+ /**
51
+ * Get proxy for specific user-contact pair
52
+ */
53
+ private getProxyForContact;
54
+ /**
55
+ * Update proxy usage statistics
56
+ */
57
+ private updateProxyUsage;
58
+ /**
59
+ * Audit a routing decision
60
+ */
61
+ private auditRouting;
62
+ /**
63
+ * Map database row to ProxyAddress
64
+ */
65
+ private mapProxyRow;
66
+ /**
67
+ * Get user's proxy count
68
+ */
69
+ private getUserProxyCount;
70
+ /**
71
+ * Expire a proxy
72
+ */
73
+ private expireProxy;
74
+ /**
75
+ * Start cleanup job for expired proxies
76
+ */
77
+ private startCleanupJob;
78
+ /**
79
+ * Cleanup expired proxies
80
+ */
81
+ private cleanupExpiredProxies;
82
+ /**
83
+ * Extract proxy address from To field
84
+ */
85
+ private extractProxyAddress;
86
+ /**
87
+ * Generate secure proxy token
88
+ */
89
+ private generateProxyToken;
90
+ }
@@ -0,0 +1,316 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (c) 2025 Bernier LLC
4
+
5
+ This file is licensed to the client under a limited-use license.
6
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
7
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.EmailMaskingService = void 0;
11
+ const pg_1 = require("pg");
12
+ const database_js_1 = require("./database.js");
13
+ /**
14
+ * Email masking and man-in-the-middle routing service
15
+ */
16
+ class EmailMaskingService {
17
+ constructor(config) {
18
+ this.config = config;
19
+ this.db = new pg_1.Pool({
20
+ host: config.database.host,
21
+ port: config.database.port,
22
+ database: config.database.database,
23
+ user: config.database.user,
24
+ password: config.database.password,
25
+ });
26
+ }
27
+ /**
28
+ * Initialize the masking service
29
+ */
30
+ async initialize() {
31
+ // Initialize database schema
32
+ await (0, database_js_1.initializeDatabase)(this.db);
33
+ // Start cleanup job for expired proxies
34
+ this.startCleanupJob();
35
+ }
36
+ /**
37
+ * Cleanup resources
38
+ */
39
+ async shutdown() {
40
+ if (this.cleanupInterval) {
41
+ clearInterval(this.cleanupInterval);
42
+ }
43
+ await this.db.end();
44
+ }
45
+ /**
46
+ * Create a new proxy email address
47
+ */
48
+ async createProxy(userId, realEmail, options) {
49
+ // Validate user's proxy quota
50
+ const userProxies = await this.getUserProxyCount(userId);
51
+ const maxProxies = this.config.maxProxiesPerUser || 0;
52
+ if (maxProxies > 0 && userProxies >= maxProxies) {
53
+ throw new Error(`User ${userId} has reached maximum proxy limit (${maxProxies})`);
54
+ }
55
+ // Generate secure proxy token
56
+ const proxyToken = this.generateProxyToken();
57
+ const proxyEmail = `${userId}-${proxyToken}@${this.config.proxyDomain}`;
58
+ // Calculate expiration
59
+ const ttl = options?.ttlDays !== undefined ? options.ttlDays : (this.config.defaultTTL || 0);
60
+ const expiresAt = ttl !== 0
61
+ ? new Date(Date.now() + ttl * 24 * 60 * 60 * 1000)
62
+ : null;
63
+ // Insert proxy record
64
+ const result = await this.db.query(`INSERT INTO proxy_addresses
65
+ (proxy_email, real_email, user_id, external_email, conversation_id, status, expires_at, metadata)
66
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
67
+ RETURNING *`, [
68
+ proxyEmail,
69
+ realEmail,
70
+ userId,
71
+ options?.externalEmail || null,
72
+ options?.conversationId || null,
73
+ 'active',
74
+ expiresAt,
75
+ JSON.stringify(options?.metadata || {})
76
+ ]);
77
+ return this.mapProxyRow(result.rows[0]);
78
+ }
79
+ /**
80
+ * Route inbound email (external → real address)
81
+ */
82
+ async routeInbound(from, to, _subject, _text, _html) {
83
+ const toAddress = this.extractProxyAddress(to);
84
+ if (!toAddress) {
85
+ return {
86
+ success: false,
87
+ direction: 'inbound',
88
+ error: 'No proxy address found in To field'
89
+ };
90
+ }
91
+ // Look up proxy
92
+ const proxy = await this.getProxyByEmail(toAddress);
93
+ if (!proxy) {
94
+ return {
95
+ success: false,
96
+ direction: 'inbound',
97
+ error: `Proxy address not found: ${toAddress}`
98
+ };
99
+ }
100
+ // Validate proxy status
101
+ if (proxy.status !== 'active') {
102
+ return {
103
+ success: false,
104
+ direction: 'inbound',
105
+ error: `Proxy is ${proxy.status}: ${toAddress}`
106
+ };
107
+ }
108
+ // Check expiration
109
+ if (proxy.expiresAt && proxy.expiresAt < new Date()) {
110
+ await this.expireProxy(proxy.id);
111
+ return {
112
+ success: false,
113
+ direction: 'inbound',
114
+ error: `Proxy expired: ${toAddress}`
115
+ };
116
+ }
117
+ // Update proxy usage
118
+ await this.updateProxyUsage(proxy.id);
119
+ // Audit the routing
120
+ const auditId = await this.auditRouting({
121
+ proxyId: proxy.id,
122
+ direction: 'inbound',
123
+ fromAddress: from,
124
+ toAddress: proxy.realEmail,
125
+ success: true
126
+ });
127
+ return {
128
+ success: true,
129
+ routedTo: proxy.realEmail,
130
+ proxyUsed: toAddress,
131
+ direction: 'inbound',
132
+ auditId
133
+ };
134
+ }
135
+ /**
136
+ * Route outbound email (real address → external via proxy)
137
+ */
138
+ async routeOutbound(userId, realEmail, externalEmail, _emailContent) {
139
+ // Find or create proxy for this user-external pair
140
+ let proxy = await this.getProxyForContact(userId, realEmail, externalEmail);
141
+ if (!proxy) {
142
+ // Auto-create proxy for outbound routing
143
+ proxy = await this.createProxy(userId, realEmail, {
144
+ externalEmail,
145
+ metadata: { autoCreated: true, direction: 'outbound' }
146
+ });
147
+ }
148
+ // Validate proxy status
149
+ if (proxy.status !== 'active') {
150
+ return {
151
+ success: false,
152
+ direction: 'outbound',
153
+ error: `Proxy is ${proxy.status}`
154
+ };
155
+ }
156
+ // Update proxy usage
157
+ await this.updateProxyUsage(proxy.id);
158
+ // Audit the routing
159
+ const auditId = await this.auditRouting({
160
+ proxyId: proxy.id,
161
+ direction: 'outbound',
162
+ fromAddress: realEmail,
163
+ toAddress: externalEmail,
164
+ success: true
165
+ });
166
+ return {
167
+ success: true,
168
+ routedTo: externalEmail,
169
+ proxyUsed: proxy.proxyEmail,
170
+ direction: 'outbound',
171
+ auditId
172
+ };
173
+ }
174
+ /**
175
+ * Deactivate a proxy address
176
+ */
177
+ async deactivateProxy(proxyId) {
178
+ await this.db.query('UPDATE proxy_addresses SET status = $1 WHERE id = $2', ['inactive', proxyId]);
179
+ }
180
+ /**
181
+ * Revoke a proxy address permanently
182
+ */
183
+ async revokeProxy(proxyId) {
184
+ await this.db.query('UPDATE proxy_addresses SET status = $1 WHERE id = $2', ['revoked', proxyId]);
185
+ }
186
+ /**
187
+ * Get proxy by ID
188
+ */
189
+ async getProxyById(proxyId) {
190
+ const result = await this.db.query('SELECT * FROM proxy_addresses WHERE id = $1', [proxyId]);
191
+ return result.rows.length > 0 ? this.mapProxyRow(result.rows[0]) : null;
192
+ }
193
+ /**
194
+ * Get proxy by email address
195
+ */
196
+ async getProxyByEmail(proxyEmail) {
197
+ const result = await this.db.query('SELECT * FROM proxy_addresses WHERE proxy_email = $1', [proxyEmail]);
198
+ return result.rows.length > 0 ? this.mapProxyRow(result.rows[0]) : null;
199
+ }
200
+ /**
201
+ * Get proxy for specific user-contact pair
202
+ */
203
+ async getProxyForContact(userId, realEmail, externalEmail) {
204
+ const result = await this.db.query(`SELECT * FROM proxy_addresses
205
+ WHERE user_id = $1
206
+ AND real_email = $2
207
+ AND external_email = $3
208
+ ORDER BY created_at DESC
209
+ LIMIT 1`, [userId, realEmail, externalEmail]);
210
+ return result.rows.length > 0 ? this.mapProxyRow(result.rows[0]) : null;
211
+ }
212
+ /**
213
+ * Update proxy usage statistics
214
+ */
215
+ async updateProxyUsage(proxyId) {
216
+ await this.db.query(`UPDATE proxy_addresses
217
+ SET routing_count = routing_count + 1, last_used_at = NOW()
218
+ WHERE id = $1`, [proxyId]);
219
+ }
220
+ /**
221
+ * Audit a routing decision
222
+ */
223
+ async auditRouting(audit) {
224
+ if (!this.config.auditEnabled) {
225
+ return '';
226
+ }
227
+ const result = await this.db.query(`INSERT INTO routing_audit
228
+ (proxy_id, direction, from_address, to_address, success, error, metadata)
229
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
230
+ RETURNING id`, [
231
+ audit.proxyId,
232
+ audit.direction,
233
+ audit.fromAddress,
234
+ audit.toAddress,
235
+ audit.success,
236
+ audit.error || null,
237
+ JSON.stringify(audit.metadata || {})
238
+ ]);
239
+ return result.rows[0].id;
240
+ }
241
+ /**
242
+ * Map database row to ProxyAddress
243
+ */
244
+ mapProxyRow(row) {
245
+ return {
246
+ id: row.id,
247
+ proxyEmail: row.proxy_email,
248
+ realEmail: row.real_email,
249
+ userId: row.user_id,
250
+ externalEmail: row.external_email || undefined,
251
+ conversationId: row.conversation_id || undefined,
252
+ status: row.status,
253
+ createdAt: new Date(row.created_at),
254
+ activatedAt: row.activated_at ? new Date(row.activated_at) : undefined,
255
+ expiresAt: row.expires_at ? new Date(row.expires_at) : undefined,
256
+ lastUsedAt: row.last_used_at ? new Date(row.last_used_at) : undefined,
257
+ routingCount: parseInt(row.routing_count) || 0,
258
+ metadata: row.metadata ? JSON.parse(row.metadata) : {}
259
+ };
260
+ }
261
+ /**
262
+ * Get user's proxy count
263
+ */
264
+ async getUserProxyCount(userId) {
265
+ const result = await this.db.query(`SELECT COUNT(*) as count FROM proxy_addresses
266
+ WHERE user_id = $1 AND status = 'active'`, [userId]);
267
+ return parseInt(result.rows[0].count) || 0;
268
+ }
269
+ /**
270
+ * Expire a proxy
271
+ */
272
+ async expireProxy(proxyId) {
273
+ await this.db.query('UPDATE proxy_addresses SET status = $1 WHERE id = $2', ['expired', proxyId]);
274
+ }
275
+ /**
276
+ * Start cleanup job for expired proxies
277
+ */
278
+ startCleanupJob() {
279
+ // Run every hour
280
+ this.cleanupInterval = setInterval(() => {
281
+ void this.cleanupExpiredProxies();
282
+ }, 60 * 60 * 1000); // 1 hour
283
+ }
284
+ /**
285
+ * Cleanup expired proxies
286
+ */
287
+ async cleanupExpiredProxies() {
288
+ await this.db.query(`UPDATE proxy_addresses
289
+ SET status = 'expired'
290
+ WHERE expires_at < NOW() AND status = 'active'`);
291
+ }
292
+ /**
293
+ * Extract proxy address from To field
294
+ */
295
+ extractProxyAddress(to) {
296
+ const proxyDomain = this.config.proxyDomain;
297
+ if (to.includes(`@${proxyDomain}`)) {
298
+ // Extract email from "Name <email>" format
299
+ const match = to.match(/<(.+?)>/) || [null, to];
300
+ return match[1];
301
+ }
302
+ return null;
303
+ }
304
+ /**
305
+ * Generate secure proxy token
306
+ */
307
+ generateProxyToken() {
308
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
309
+ let token = '';
310
+ for (let i = 0; i < 16; i++) {
311
+ token += chars.charAt(Math.floor(Math.random() * chars.length));
312
+ }
313
+ return token;
314
+ }
315
+ }
316
+ exports.EmailMaskingService = EmailMaskingService;
@@ -0,0 +1,5 @@
1
+ import { Pool } from 'pg';
2
+ /**
3
+ * Initialize database schema for email masking
4
+ */
5
+ export declare function initializeDatabase(pool: Pool): Promise<void>;