From d781022b630c7dbd7a98647d33e3c182997a9172 Mon Sep 17 00:00:00 2001 From: Michelle Date: Fri, 27 Feb 2026 16:12:41 +0100 Subject: [PATCH] add timeouts --- server/config/federation.js | 20 +++++++++++--------- server/config/mailer.js | 3 +++ server/routes/federation.js | 21 +++++++++++---------- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/server/config/federation.js b/server/config/federation.js index b6b6cdd..cca4106 100644 --- a/server/config/federation.js +++ b/server/config/federation.js @@ -35,8 +35,9 @@ if (FEDERATION_DOMAIN) { publicKeyPem = crypto.createPublicKey(currentPrivateKey).export({ type: 'spki', format: 'pem' }); } -// Instance discovery cache (domain → { baseUrl, publicKey }) +// Instance discovery cache (domain → { baseUrl, publicKey, cachedAt }) const discoveryCache = new Map(); +const DISCOVERY_TTL_MS = 5 * 60 * 1000; // 5 minutes /** * Get this instance's federation domain. @@ -101,19 +102,20 @@ export function verifyPayload(payload, signature, remotePublicKeyPem) { * @returns {Promise<{ baseUrl: string, publicKey: string }>} */ export async function discoverInstance(domain) { - if (discoveryCache.has(domain)) { - return discoveryCache.get(domain); + const cached = discoveryCache.get(domain); + if (cached && (Date.now() - cached.cachedAt) < DISCOVERY_TTL_MS) { + return cached; } const wellKnownUrl = `https://${domain}/.well-known/redlight`; + const TIMEOUT_MS = 10_000; // 10 seconds try { - // Since we test locally, allow http fallback if the request fails (optional but good for testing) let response; try { - response = await fetch(wellKnownUrl); + response = await fetch(wellKnownUrl, { signal: AbortSignal.timeout(TIMEOUT_MS) }); } catch (e) { if (e.message.includes('fetch') && wellKnownUrl.startsWith('https://localhost')) { - response = await fetch(`http://${domain}/.well-known/redlight`); + response = await fetch(`http://${domain}/.well-known/redlight`, { signal: AbortSignal.timeout(TIMEOUT_MS) }); } else throw e; } @@ -127,17 +129,17 @@ export async function discoverInstance(domain) { } const baseUrl = `https://${domain}${data.federation_api || '/api/federation'}`; - // Optionally handle local testing gracefully for baseUrl const result = { baseUrl: baseUrl.replace('https://localhost', 'http://localhost'), - publicKey: data.public_key + publicKey: data.public_key, + cachedAt: Date.now(), }; discoveryCache.set(domain, result); return result; } catch (error) { console.error(`Federation discovery failed for ${domain}:`, error.message); - throw new Error(`Could not discover Redlight instance at ${domain}`); + throw new Error(`Could not discover Redlight instance at ${domain}: ${error.message}`); } } diff --git a/server/config/mailer.js b/server/config/mailer.js index 7cba35e..b8420ee 100644 --- a/server/config/mailer.js +++ b/server/config/mailer.js @@ -18,6 +18,9 @@ export function initMailer() { port, secure: port === 465, auth: { user, pass }, + connectionTimeout: 10_000, // 10 s to establish TCP connection + greetingTimeout: 10_000, // 10 s to receive SMTP greeting + socketTimeout: 15_000, // 15 s of inactivity before abort }); console.log('✅ SMTP mailer configured'); diff --git a/server/routes/federation.js b/server/routes/federation.js index ad71853..84bb0e5 100644 --- a/server/routes/federation.js +++ b/server/routes/federation.js @@ -100,6 +100,7 @@ router.post('/invite', authenticateToken, async (req, res) => { 'X-Federation-Origin': getFederationDomain(), }, body: JSON.stringify(payload), + signal: AbortSignal.timeout(15_000), // 15 second timeout }); if (!response.ok) { @@ -192,17 +193,17 @@ router.post('/receive', async (req, res) => { } catch { /* column may not exist on very old installs */ } } - // Send notification email (fire-and-forget, don't fail the request if mail fails) - try { - const appUrl = process.env.APP_URL || ''; - const inboxUrl = `${appUrl}/federation/inbox`; - const appName = process.env.APP_NAME || 'Redlight'; - await sendFederationInviteEmail( - targetUser.email, targetUser.name, from_user, - room_name, message || null, inboxUrl, appName - ); - } catch (mailErr) { + // Send notification email (truly fire-and-forget – never blocks the response) + if (targetUser.email) { + const appUrl = process.env.APP_URL || ''; + const inboxUrl = `${appUrl}/federation/inbox`; + const appName = process.env.APP_NAME || 'Redlight'; + sendFederationInviteEmail( + targetUser.email, targetUser.name, from_user, + room_name, message || null, inboxUrl, appName + ).catch(mailErr => { console.warn('Federation invite mail failed (non-fatal):', mailErr.message); + }); } res.json({ success: true });