@realtimex/email-automator 2.28.1 → 2.28.2
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/src/middleware/auth.ts +14 -14
- package/api/src/services/encryptionInit.ts +50 -36
- package/dist/api/src/middleware/auth.js +8 -11
- package/dist/api/src/services/encryptionInit.js +43 -30
- package/dist/assets/{index-Xu8f_mT0.js → index-CRx9ZPz1.js} +3 -3
- package/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -57,20 +57,6 @@ export async function authMiddleware(
|
|
|
57
57
|
const supabaseUrl = isEnvUrlValid ? envUrl : (headerConfig?.url || '');
|
|
58
58
|
const supabaseAnonKey = isEnvKeyValid ? envKey : (headerConfig?.anonKey || '');
|
|
59
59
|
|
|
60
|
-
// If encryption is not ready, try to initialize it using available Supabase config
|
|
61
|
-
if (!isEncryptionReady() && supabaseUrl && supabaseAnonKey) {
|
|
62
|
-
// Note: Ideally we use service role to read encryption_key from user_settings,
|
|
63
|
-
// but even with anon key it might work if RLS allows or if we just use it
|
|
64
|
-
// to check for existence of keys.
|
|
65
|
-
const initClient = createClient(supabaseUrl, supabaseAnonKey, {
|
|
66
|
-
auth: { autoRefreshToken: false, persistSession: false },
|
|
67
|
-
});
|
|
68
|
-
// Run in background to not block auth
|
|
69
|
-
initializePersistenceEncryption(initClient).catch(err =>
|
|
70
|
-
logger.warn('Failed to initialize encryption in auth middleware', { error: err.message })
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
60
|
// Development bypass: skip auth if DISABLE_AUTH=true in non-production
|
|
75
61
|
if (config.security.disableAuth && !config.isProduction) {
|
|
76
62
|
logger.warn('Auth disabled for development - creating mock user');
|
|
@@ -97,6 +83,13 @@ export async function authMiddleware(
|
|
|
97
83
|
req.supabase = supabase;
|
|
98
84
|
// Initialize logger persistence for mock user
|
|
99
85
|
Logger.setPersistence(supabase, req.user.id);
|
|
86
|
+
|
|
87
|
+
// If encryption is not ready, try to initialize it now
|
|
88
|
+
if (!isEncryptionReady()) {
|
|
89
|
+
initializePersistenceEncryption(supabase).catch(err =>
|
|
90
|
+
logger.warn('Failed to initialize encryption in dev mode', { error: err.message })
|
|
91
|
+
);
|
|
92
|
+
}
|
|
100
93
|
} else {
|
|
101
94
|
throw new AuthenticationError('Supabase not configured. Please set up Supabase in the app or provide SUPABASE_URL/ANON_KEY in .env');
|
|
102
95
|
}
|
|
@@ -133,6 +126,13 @@ export async function authMiddleware(
|
|
|
133
126
|
throw new AuthenticationError('Invalid or expired token');
|
|
134
127
|
}
|
|
135
128
|
|
|
129
|
+
// If encryption is not ready, initialize it now with the authenticated client
|
|
130
|
+
if (!isEncryptionReady()) {
|
|
131
|
+
initializePersistenceEncryption(supabase).catch(err =>
|
|
132
|
+
logger.warn('Failed to initialize encryption with authenticated client', { error: err.message })
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
136
|
// Initialize logger persistence for this request
|
|
137
137
|
Logger.setPersistence(supabase, user.id);
|
|
138
138
|
|
|
@@ -21,15 +21,24 @@ export async function initializePersistenceEncryption(providedSupabase?: any) {
|
|
|
21
21
|
// BYOK mode: Supabase not configured at startup (credentials come via HTTP headers)
|
|
22
22
|
// If we don't have a key yet, generate a temporary one in memory
|
|
23
23
|
if (!getEncryptionKeyHex()) {
|
|
24
|
-
logger.info('Supabase not configured yet (BYOK mode)');
|
|
25
|
-
logger.info('Generating temporary encryption key in memory - will be reconciled when Supabase becomes available');
|
|
24
|
+
logger.info('Supabase not configured yet (BYOK mode) - using temporary key');
|
|
26
25
|
const newKey = crypto.randomBytes(32).toString('hex');
|
|
27
26
|
setEncryptionKey(newKey);
|
|
28
|
-
logger.info('✓ Temporary encryption key generated and loaded in memory');
|
|
29
27
|
}
|
|
30
28
|
return;
|
|
31
29
|
}
|
|
32
30
|
|
|
31
|
+
// Check client type for logging
|
|
32
|
+
const isServiceRole = !!(supabase as any).supabaseServiceRoleKey || !(supabase as any).auth?.session;
|
|
33
|
+
const hasToken = !!(supabase as any).realtime?.accessToken; // Simple check for authenticated client
|
|
34
|
+
|
|
35
|
+
if (!isServiceRole && !hasToken) {
|
|
36
|
+
logger.debug('Skipping encryption reconciliation with unauthenticated anon client');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
logger.info(`Reconciling encryption key with database (${isServiceRole ? 'Service Role' : 'Authenticated User'})`);
|
|
41
|
+
|
|
33
42
|
// 1. Check if ANY user has an encryption key stored
|
|
34
43
|
// In sandbox mode, encryption key is always in database
|
|
35
44
|
const { data: users, error } = await supabase
|
|
@@ -64,42 +73,47 @@ export async function initializePersistenceEncryption(providedSupabase?: any) {
|
|
|
64
73
|
return;
|
|
65
74
|
}
|
|
66
75
|
|
|
67
|
-
// 2. No key in database -
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// 3. Persist to all existing users in database
|
|
79
|
-
const { data: allUsers } = await supabase
|
|
80
|
-
.from('user_settings')
|
|
81
|
-
.select('user_id')
|
|
82
|
-
.limit(100);
|
|
83
|
-
|
|
84
|
-
if (allUsers && allUsers.length > 0) {
|
|
85
|
-
logger.info(`Saving encryption key to database for ${allUsers.length} user(s)...`);
|
|
86
|
-
|
|
87
|
-
// Update all users with the new key
|
|
88
|
-
const updates = allUsers.map((user: any) =>
|
|
89
|
-
supabase
|
|
90
|
-
.from('user_settings')
|
|
91
|
-
.update({ encryption_key: finalKey })
|
|
92
|
-
.eq('user_id', user.user_id)
|
|
93
|
-
);
|
|
76
|
+
// 2. No key in database - only generate and persist if we are the "Master" (Service Role)
|
|
77
|
+
// or if we've explicitly decided this is a fresh setup.
|
|
78
|
+
if (isServiceRole) {
|
|
79
|
+
let finalKey = getEncryptionKeyHex();
|
|
80
|
+
if (!finalKey) {
|
|
81
|
+
logger.info('No encryption key found in database or memory, generating new key...');
|
|
82
|
+
finalKey = crypto.randomBytes(32).toString('hex');
|
|
83
|
+
setEncryptionKey(finalKey);
|
|
84
|
+
} else {
|
|
85
|
+
logger.info('Persisting in-memory encryption key to database...');
|
|
86
|
+
}
|
|
94
87
|
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
// 3. Persist to all existing users in database
|
|
89
|
+
const { data: allUsers } = await supabase
|
|
90
|
+
.from('user_settings')
|
|
91
|
+
.select('user_id')
|
|
92
|
+
.limit(100);
|
|
93
|
+
|
|
94
|
+
if (allUsers && allUsers.length > 0) {
|
|
95
|
+
logger.info(`Saving encryption key to database for ${allUsers.length} user(s)...`);
|
|
96
|
+
|
|
97
|
+
// Update all users with the new key
|
|
98
|
+
const updates = allUsers.map((user: any) =>
|
|
99
|
+
supabase
|
|
100
|
+
.from('user_settings')
|
|
101
|
+
.update({ encryption_key: finalKey })
|
|
102
|
+
.eq('user_id', user.user_id)
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
await Promise.all(updates);
|
|
106
|
+
logger.info('✓ Encryption key saved to database');
|
|
107
|
+
isEncryptionInitialized = true;
|
|
108
|
+
} else {
|
|
109
|
+
logger.info('No users found yet, encryption key loaded in memory');
|
|
110
|
+
logger.info('Key will be persisted when users are created');
|
|
111
|
+
// We don't set isEncryptionInitialized = true here because we still want
|
|
112
|
+
// to try reconciling once a user is actually created/logged in
|
|
113
|
+
}
|
|
97
114
|
} else {
|
|
98
|
-
logger.
|
|
99
|
-
logger.info('Key will be persisted when users are created');
|
|
115
|
+
logger.warn('No encryption key found in database, but cannot persist one with user-restricted client.');
|
|
100
116
|
}
|
|
101
|
-
|
|
102
|
-
isEncryptionInitialized = true;
|
|
103
117
|
} catch (err) {
|
|
104
118
|
logger.error('Error initializing encryption:', err);
|
|
105
119
|
// Always ensure we have a key, even if there was an error
|
|
@@ -33,17 +33,6 @@ export async function authMiddleware(req, _res, next) {
|
|
|
33
33
|
const isEnvKeyValid = !!envKey && envKey.length > 0;
|
|
34
34
|
const supabaseUrl = isEnvUrlValid ? envUrl : (headerConfig?.url || '');
|
|
35
35
|
const supabaseAnonKey = isEnvKeyValid ? envKey : (headerConfig?.anonKey || '');
|
|
36
|
-
// If encryption is not ready, try to initialize it using available Supabase config
|
|
37
|
-
if (!isEncryptionReady() && supabaseUrl && supabaseAnonKey) {
|
|
38
|
-
// Note: Ideally we use service role to read encryption_key from user_settings,
|
|
39
|
-
// but even with anon key it might work if RLS allows or if we just use it
|
|
40
|
-
// to check for existence of keys.
|
|
41
|
-
const initClient = createClient(supabaseUrl, supabaseAnonKey, {
|
|
42
|
-
auth: { autoRefreshToken: false, persistSession: false },
|
|
43
|
-
});
|
|
44
|
-
// Run in background to not block auth
|
|
45
|
-
initializePersistenceEncryption(initClient).catch(err => logger.warn('Failed to initialize encryption in auth middleware', { error: err.message }));
|
|
46
|
-
}
|
|
47
36
|
// Development bypass: skip auth if DISABLE_AUTH=true in non-production
|
|
48
37
|
if (config.security.disableAuth && !config.isProduction) {
|
|
49
38
|
logger.warn('Auth disabled for development - creating mock user');
|
|
@@ -67,6 +56,10 @@ export async function authMiddleware(req, _res, next) {
|
|
|
67
56
|
req.supabase = supabase;
|
|
68
57
|
// Initialize logger persistence for mock user
|
|
69
58
|
Logger.setPersistence(supabase, req.user.id);
|
|
59
|
+
// If encryption is not ready, try to initialize it now
|
|
60
|
+
if (!isEncryptionReady()) {
|
|
61
|
+
initializePersistenceEncryption(supabase).catch(err => logger.warn('Failed to initialize encryption in dev mode', { error: err.message }));
|
|
62
|
+
}
|
|
70
63
|
}
|
|
71
64
|
else {
|
|
72
65
|
throw new AuthenticationError('Supabase not configured. Please set up Supabase in the app or provide SUPABASE_URL/ANON_KEY in .env');
|
|
@@ -95,6 +88,10 @@ export async function authMiddleware(req, _res, next) {
|
|
|
95
88
|
logger.debug('Auth failed', { error: error?.message });
|
|
96
89
|
throw new AuthenticationError('Invalid or expired token');
|
|
97
90
|
}
|
|
91
|
+
// If encryption is not ready, initialize it now with the authenticated client
|
|
92
|
+
if (!isEncryptionReady()) {
|
|
93
|
+
initializePersistenceEncryption(supabase).catch(err => logger.warn('Failed to initialize encryption with authenticated client', { error: err.message }));
|
|
94
|
+
}
|
|
98
95
|
// Initialize logger persistence for this request
|
|
99
96
|
Logger.setPersistence(supabase, user.id);
|
|
100
97
|
// Attach user and supabase client to request
|
|
@@ -17,14 +17,20 @@ export async function initializePersistenceEncryption(providedSupabase) {
|
|
|
17
17
|
// BYOK mode: Supabase not configured at startup (credentials come via HTTP headers)
|
|
18
18
|
// If we don't have a key yet, generate a temporary one in memory
|
|
19
19
|
if (!getEncryptionKeyHex()) {
|
|
20
|
-
logger.info('Supabase not configured yet (BYOK mode)');
|
|
21
|
-
logger.info('Generating temporary encryption key in memory - will be reconciled when Supabase becomes available');
|
|
20
|
+
logger.info('Supabase not configured yet (BYOK mode) - using temporary key');
|
|
22
21
|
const newKey = crypto.randomBytes(32).toString('hex');
|
|
23
22
|
setEncryptionKey(newKey);
|
|
24
|
-
logger.info('✓ Temporary encryption key generated and loaded in memory');
|
|
25
23
|
}
|
|
26
24
|
return;
|
|
27
25
|
}
|
|
26
|
+
// Check client type for logging
|
|
27
|
+
const isServiceRole = !!supabase.supabaseServiceRoleKey || !supabase.auth?.session;
|
|
28
|
+
const hasToken = !!supabase.realtime?.accessToken; // Simple check for authenticated client
|
|
29
|
+
if (!isServiceRole && !hasToken) {
|
|
30
|
+
logger.debug('Skipping encryption reconciliation with unauthenticated anon client');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
logger.info(`Reconciling encryption key with database (${isServiceRole ? 'Service Role' : 'Authenticated User'})`);
|
|
28
34
|
// 1. Check if ANY user has an encryption key stored
|
|
29
35
|
// In sandbox mode, encryption key is always in database
|
|
30
36
|
const { data: users, error } = await supabase
|
|
@@ -54,37 +60,44 @@ export async function initializePersistenceEncryption(providedSupabase) {
|
|
|
54
60
|
isEncryptionInitialized = true;
|
|
55
61
|
return;
|
|
56
62
|
}
|
|
57
|
-
// 2. No key in database -
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.select('user_id')
|
|
72
|
-
.limit(100);
|
|
73
|
-
if (allUsers && allUsers.length > 0) {
|
|
74
|
-
logger.info(`Saving encryption key to database for ${allUsers.length} user(s)...`);
|
|
75
|
-
// Update all users with the new key
|
|
76
|
-
const updates = allUsers.map((user) => supabase
|
|
63
|
+
// 2. No key in database - only generate and persist if we are the "Master" (Service Role)
|
|
64
|
+
// or if we've explicitly decided this is a fresh setup.
|
|
65
|
+
if (isServiceRole) {
|
|
66
|
+
let finalKey = getEncryptionKeyHex();
|
|
67
|
+
if (!finalKey) {
|
|
68
|
+
logger.info('No encryption key found in database or memory, generating new key...');
|
|
69
|
+
finalKey = crypto.randomBytes(32).toString('hex');
|
|
70
|
+
setEncryptionKey(finalKey);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
logger.info('Persisting in-memory encryption key to database...');
|
|
74
|
+
}
|
|
75
|
+
// 3. Persist to all existing users in database
|
|
76
|
+
const { data: allUsers } = await supabase
|
|
77
77
|
.from('user_settings')
|
|
78
|
-
.
|
|
79
|
-
.
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
.select('user_id')
|
|
79
|
+
.limit(100);
|
|
80
|
+
if (allUsers && allUsers.length > 0) {
|
|
81
|
+
logger.info(`Saving encryption key to database for ${allUsers.length} user(s)...`);
|
|
82
|
+
// Update all users with the new key
|
|
83
|
+
const updates = allUsers.map((user) => supabase
|
|
84
|
+
.from('user_settings')
|
|
85
|
+
.update({ encryption_key: finalKey })
|
|
86
|
+
.eq('user_id', user.user_id));
|
|
87
|
+
await Promise.all(updates);
|
|
88
|
+
logger.info('✓ Encryption key saved to database');
|
|
89
|
+
isEncryptionInitialized = true;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
logger.info('No users found yet, encryption key loaded in memory');
|
|
93
|
+
logger.info('Key will be persisted when users are created');
|
|
94
|
+
// We don't set isEncryptionInitialized = true here because we still want
|
|
95
|
+
// to try reconciling once a user is actually created/logged in
|
|
96
|
+
}
|
|
82
97
|
}
|
|
83
98
|
else {
|
|
84
|
-
logger.
|
|
85
|
-
logger.info('Key will be persisted when users are created');
|
|
99
|
+
logger.warn('No encryption key found in database, but cannot persist one with user-restricted client.');
|
|
86
100
|
}
|
|
87
|
-
isEncryptionInitialized = true;
|
|
88
101
|
}
|
|
89
102
|
catch (err) {
|
|
90
103
|
logger.error('Error initializing encryption:', err);
|