@realtimex/email-automator 2.26.0 → 2.28.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/api/server.ts +19 -4
- package/api/src/routes/auth.ts +45 -18
- package/dist/api/server.js +18 -4
- package/dist/api/src/routes/auth.js +42 -17
- package/dist/assets/{index-BiE9QzV0.js → index-DpVG-8N2.js} +27 -27
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/supabase/migrations/20260117000003_add_action_constraint.sql +4 -0
- package/supabase/migrations/20260131170000_add_rule_categories.sql +65 -0
- package/supabase/migrations/20260206000010_fix_trigger_naming_conflict.sql +49 -4
- package/supabase/migrations/20260206000016_update_init_function_negative_conditions.sql +95 -78
- package/supabase/migrations/20260117000003_seed_default_rules.sql +0 -77
- package/supabase/migrations/20260120100000_fix_auth_triggers.sql +0 -44
- package/supabase/migrations/20260131110000_auto_init_user_data.sql +0 -90
- package/supabase/migrations/20260131150000_fix_trigger_error_handling.sql +0 -71
- package/supabase/migrations/20260131170000_fix_rule_categories.sql +0 -140
package/api/server.ts
CHANGED
|
@@ -21,9 +21,15 @@ import { setEncryptionKey, getEncryptionKeyHex } from './src/utils/encryption.js
|
|
|
21
21
|
async function initializePersistenceEncryption() {
|
|
22
22
|
try {
|
|
23
23
|
const supabase = getServiceRoleSupabase();
|
|
24
|
+
|
|
24
25
|
if (!supabase) {
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
// BYOK mode: Supabase not configured at startup (credentials come via HTTP headers)
|
|
27
|
+
// Generate encryption key anyway and keep it in memory
|
|
28
|
+
logger.info('Supabase not configured yet (BYOK mode)');
|
|
29
|
+
logger.info('Generating encryption key in memory - will persist when Supabase becomes available');
|
|
30
|
+
const newKey = crypto.randomBytes(32).toString('hex');
|
|
31
|
+
setEncryptionKey(newKey);
|
|
32
|
+
logger.info('✓ Encryption key generated and loaded in memory');
|
|
27
33
|
return;
|
|
28
34
|
}
|
|
29
35
|
|
|
@@ -37,6 +43,10 @@ async function initializePersistenceEncryption() {
|
|
|
37
43
|
|
|
38
44
|
if (error) {
|
|
39
45
|
logger.warn('Failed to query user_settings for encryption key', { error });
|
|
46
|
+
// Still generate a key for in-memory use
|
|
47
|
+
const newKey = crypto.randomBytes(32).toString('hex');
|
|
48
|
+
setEncryptionKey(newKey);
|
|
49
|
+
logger.info('✓ Generated fallback encryption key due to DB error');
|
|
40
50
|
return;
|
|
41
51
|
}
|
|
42
52
|
|
|
@@ -74,12 +84,17 @@ async function initializePersistenceEncryption() {
|
|
|
74
84
|
logger.info('✓ Encryption key saved to database');
|
|
75
85
|
} else {
|
|
76
86
|
logger.info('No users found yet, encryption key loaded in memory');
|
|
77
|
-
logger.info('Key will be persisted when
|
|
87
|
+
logger.info('Key will be persisted when users are created');
|
|
78
88
|
}
|
|
79
89
|
|
|
80
90
|
} catch (err) {
|
|
81
91
|
logger.error('Error initializing encryption:', err);
|
|
82
|
-
|
|
92
|
+
// Always ensure we have a key, even if there was an error
|
|
93
|
+
if (!getEncryptionKeyHex()) {
|
|
94
|
+
logger.warn('Generating emergency fallback encryption key');
|
|
95
|
+
const fallbackKey = crypto.randomBytes(32).toString('hex');
|
|
96
|
+
setEncryptionKey(fallbackKey);
|
|
97
|
+
}
|
|
83
98
|
}
|
|
84
99
|
}
|
|
85
100
|
|
package/api/src/routes/auth.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { getGmailService } from '../services/gmail.js';
|
|
|
7
7
|
import { getMicrosoftService } from '../services/microsoft.js';
|
|
8
8
|
import { getImapService } from '../services/imap-service.js';
|
|
9
9
|
import { createLogger } from '../utils/logger.js';
|
|
10
|
-
import { getEncryptionKeyHex } from '../utils/encryption.js';
|
|
10
|
+
import { getEncryptionKeyHex, setEncryptionKey } from '../utils/encryption.js';
|
|
11
11
|
|
|
12
12
|
const router = Router();
|
|
13
13
|
const logger = createLogger('AuthRoutes');
|
|
@@ -24,25 +24,52 @@ router.post('/imap/connect',
|
|
|
24
24
|
smtpHost, smtpPort, smtpSecure
|
|
25
25
|
} = req.body;
|
|
26
26
|
|
|
27
|
-
// CRITICAL FIX:
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
// CRITICAL FIX: Sync encryption key between server memory and database
|
|
28
|
+
// In BYOK mode, database trigger generates key but server has random key in memory
|
|
29
|
+
// We need to sync them: prefer database key (persistent) over server key (ephemeral)
|
|
30
|
+
const { data: userSettings, error: fetchError } = await req.supabase!
|
|
31
|
+
.from('user_settings')
|
|
32
|
+
.select('encryption_key')
|
|
33
|
+
.eq('user_id', req.user!.id)
|
|
34
|
+
.single();
|
|
35
|
+
|
|
36
|
+
if (fetchError) {
|
|
37
|
+
logger.error('Failed to fetch user settings', { error: fetchError });
|
|
38
|
+
throw new ValidationError('Failed to load user settings. Please try again.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const currentServerKey = getEncryptionKeyHex();
|
|
42
|
+
const databaseKey = userSettings?.encryption_key;
|
|
43
|
+
|
|
44
|
+
logger.info('Encryption key sync check', {
|
|
45
|
+
hasServerKey: !!currentServerKey,
|
|
46
|
+
hasDatabaseKey: !!databaseKey,
|
|
47
|
+
userId: req.user!.id
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (databaseKey) {
|
|
51
|
+
// Database has a key - use it (it's the source of truth)
|
|
52
|
+
if (currentServerKey !== databaseKey) {
|
|
53
|
+
logger.info('Loading encryption key from database (overriding server memory)');
|
|
54
|
+
setEncryptionKey(databaseKey);
|
|
55
|
+
}
|
|
56
|
+
} else if (currentServerKey) {
|
|
57
|
+
// Server has key but database doesn't - persist to database
|
|
58
|
+
logger.info('Database missing encryption key, persisting server key');
|
|
59
|
+
const { error: updateError } = await req.supabase!
|
|
32
60
|
.from('user_settings')
|
|
33
|
-
.
|
|
34
|
-
.eq('user_id', req.user!.id)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
logger.info('User missing encryption key, persisting immediately', { userId: req.user!.id });
|
|
40
|
-
await req.supabase!
|
|
41
|
-
.from('user_settings')
|
|
42
|
-
.update({ encryption_key: currentKey })
|
|
43
|
-
.eq('user_id', req.user!.id);
|
|
44
|
-
logger.info('✓ Encryption key persisted to user');
|
|
61
|
+
.update({ encryption_key: currentServerKey })
|
|
62
|
+
.eq('user_id', req.user!.id);
|
|
63
|
+
|
|
64
|
+
if (updateError) {
|
|
65
|
+
logger.error('Failed to persist encryption key', { error: updateError });
|
|
66
|
+
throw new ValidationError('Failed to initialize encryption. Please try again.');
|
|
45
67
|
}
|
|
68
|
+
logger.info('✓ Encryption key persisted to database');
|
|
69
|
+
} else {
|
|
70
|
+
// Neither server nor database has key - this should never happen with new trigger
|
|
71
|
+
logger.error('No encryption key found in server OR database!');
|
|
72
|
+
throw new ValidationError('Encryption not initialized. Please contact support.');
|
|
46
73
|
}
|
|
47
74
|
|
|
48
75
|
const imapService = getImapService();
|
package/dist/api/server.js
CHANGED
|
@@ -20,8 +20,13 @@ async function initializePersistenceEncryption() {
|
|
|
20
20
|
try {
|
|
21
21
|
const supabase = getServiceRoleSupabase();
|
|
22
22
|
if (!supabase) {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
// BYOK mode: Supabase not configured at startup (credentials come via HTTP headers)
|
|
24
|
+
// Generate encryption key anyway and keep it in memory
|
|
25
|
+
logger.info('Supabase not configured yet (BYOK mode)');
|
|
26
|
+
logger.info('Generating encryption key in memory - will persist when Supabase becomes available');
|
|
27
|
+
const newKey = crypto.randomBytes(32).toString('hex');
|
|
28
|
+
setEncryptionKey(newKey);
|
|
29
|
+
logger.info('✓ Encryption key generated and loaded in memory');
|
|
25
30
|
return;
|
|
26
31
|
}
|
|
27
32
|
// 1. Check if ANY user has an encryption key stored
|
|
@@ -33,6 +38,10 @@ async function initializePersistenceEncryption() {
|
|
|
33
38
|
.limit(1);
|
|
34
39
|
if (error) {
|
|
35
40
|
logger.warn('Failed to query user_settings for encryption key', { error });
|
|
41
|
+
// Still generate a key for in-memory use
|
|
42
|
+
const newKey = crypto.randomBytes(32).toString('hex');
|
|
43
|
+
setEncryptionKey(newKey);
|
|
44
|
+
logger.info('✓ Generated fallback encryption key due to DB error');
|
|
36
45
|
return;
|
|
37
46
|
}
|
|
38
47
|
if (users && users.length > 0 && users[0].encryption_key) {
|
|
@@ -63,12 +72,17 @@ async function initializePersistenceEncryption() {
|
|
|
63
72
|
}
|
|
64
73
|
else {
|
|
65
74
|
logger.info('No users found yet, encryption key loaded in memory');
|
|
66
|
-
logger.info('Key will be persisted when
|
|
75
|
+
logger.info('Key will be persisted when users are created');
|
|
67
76
|
}
|
|
68
77
|
}
|
|
69
78
|
catch (err) {
|
|
70
79
|
logger.error('Error initializing encryption:', err);
|
|
71
|
-
|
|
80
|
+
// Always ensure we have a key, even if there was an error
|
|
81
|
+
if (!getEncryptionKeyHex()) {
|
|
82
|
+
logger.warn('Generating emergency fallback encryption key');
|
|
83
|
+
const fallbackKey = crypto.randomBytes(32).toString('hex');
|
|
84
|
+
setEncryptionKey(fallbackKey);
|
|
85
|
+
}
|
|
72
86
|
}
|
|
73
87
|
}
|
|
74
88
|
// Periodic sync: Persist in-memory encryption key to users without one
|
|
@@ -7,31 +7,56 @@ import { getGmailService } from '../services/gmail.js';
|
|
|
7
7
|
import { getMicrosoftService } from '../services/microsoft.js';
|
|
8
8
|
import { getImapService } from '../services/imap-service.js';
|
|
9
9
|
import { createLogger } from '../utils/logger.js';
|
|
10
|
-
import { getEncryptionKeyHex } from '../utils/encryption.js';
|
|
10
|
+
import { getEncryptionKeyHex, setEncryptionKey } from '../utils/encryption.js';
|
|
11
11
|
const router = Router();
|
|
12
12
|
const logger = createLogger('AuthRoutes');
|
|
13
13
|
// IMAP/SMTP Connection
|
|
14
14
|
router.post('/imap/connect', connectionRateLimit, // More lenient for connection testing (30 attempts / 15 min)
|
|
15
15
|
authMiddleware, validateBody(schemas.imapConnect), asyncHandler(async (req, res) => {
|
|
16
16
|
const { email, password, imapHost, imapPort, imapSecure, smtpHost, smtpPort, smtpSecure } = req.body;
|
|
17
|
-
// CRITICAL FIX:
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
// CRITICAL FIX: Sync encryption key between server memory and database
|
|
18
|
+
// In BYOK mode, database trigger generates key but server has random key in memory
|
|
19
|
+
// We need to sync them: prefer database key (persistent) over server key (ephemeral)
|
|
20
|
+
const { data: userSettings, error: fetchError } = await req.supabase
|
|
21
|
+
.from('user_settings')
|
|
22
|
+
.select('encryption_key')
|
|
23
|
+
.eq('user_id', req.user.id)
|
|
24
|
+
.single();
|
|
25
|
+
if (fetchError) {
|
|
26
|
+
logger.error('Failed to fetch user settings', { error: fetchError });
|
|
27
|
+
throw new ValidationError('Failed to load user settings. Please try again.');
|
|
28
|
+
}
|
|
29
|
+
const currentServerKey = getEncryptionKeyHex();
|
|
30
|
+
const databaseKey = userSettings?.encryption_key;
|
|
31
|
+
logger.info('Encryption key sync check', {
|
|
32
|
+
hasServerKey: !!currentServerKey,
|
|
33
|
+
hasDatabaseKey: !!databaseKey,
|
|
34
|
+
userId: req.user.id
|
|
35
|
+
});
|
|
36
|
+
if (databaseKey) {
|
|
37
|
+
// Database has a key - use it (it's the source of truth)
|
|
38
|
+
if (currentServerKey !== databaseKey) {
|
|
39
|
+
logger.info('Loading encryption key from database (overriding server memory)');
|
|
40
|
+
setEncryptionKey(databaseKey);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if (currentServerKey) {
|
|
44
|
+
// Server has key but database doesn't - persist to database
|
|
45
|
+
logger.info('Database missing encryption key, persisting server key');
|
|
46
|
+
const { error: updateError } = await req.supabase
|
|
22
47
|
.from('user_settings')
|
|
23
|
-
.
|
|
24
|
-
.eq('user_id', req.user.id)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
logger.info('User missing encryption key, persisting immediately', { userId: req.user.id });
|
|
29
|
-
await req.supabase
|
|
30
|
-
.from('user_settings')
|
|
31
|
-
.update({ encryption_key: currentKey })
|
|
32
|
-
.eq('user_id', req.user.id);
|
|
33
|
-
logger.info('✓ Encryption key persisted to user');
|
|
48
|
+
.update({ encryption_key: currentServerKey })
|
|
49
|
+
.eq('user_id', req.user.id);
|
|
50
|
+
if (updateError) {
|
|
51
|
+
logger.error('Failed to persist encryption key', { error: updateError });
|
|
52
|
+
throw new ValidationError('Failed to initialize encryption. Please try again.');
|
|
34
53
|
}
|
|
54
|
+
logger.info('✓ Encryption key persisted to database');
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Neither server nor database has key - this should never happen with new trigger
|
|
58
|
+
logger.error('No encryption key found in server OR database!');
|
|
59
|
+
throw new ValidationError('Encryption not initialized. Please contact support.');
|
|
35
60
|
}
|
|
36
61
|
const imapService = getImapService();
|
|
37
62
|
const imapConfig = {
|