add timeouts
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m8s

This commit is contained in:
2026-02-27 16:12:41 +01:00
parent 2762df3e57
commit d781022b63
3 changed files with 25 additions and 19 deletions

View File

@@ -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}`);
}
}

View File

@@ -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');

View File

@@ -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 });