From 02cf96f3eb6511e9e0bcfb7eb52d9c62643b2b4f Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Tue, 24 Jun 2025 22:28:53 +1000 Subject: [PATCH 1/3] feat: Fix using hosts --- .env.example | 1 + server.js | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 29ff9fa..2edf134 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ # Server settings PORT=3000 +DASHBOARD_HOST=ipfs.hnshosting.au # IPFS settings IPFS_GATEWAY=https://ipfs.io diff --git a/server.js b/server.js index 17a353f..ec6409a 100644 --- a/server.js +++ b/server.js @@ -14,6 +14,10 @@ app.use(cors()); app.use(morgan('dev')); app.use(express.json()); +// Define the main dashboard host +// This should be configurable via environment variable +const DASHBOARD_HOST = process.env.DASHBOARD_HOST || '127.0.0.1:3000'; + // Define reserved paths that should not be treated as Handshake domains const RESERVED_PATHS = [ 'api', @@ -54,13 +58,14 @@ function injectTrackingScript(content, mimeType) { } // Helper function to make links absolute in HTML content -function makeLinksAbsolute(content, mimeType, domain, subPath = '') { +function makeLinksAbsolute(content, mimeType, domain, subPath = '', isDirectAccess = false) { if (!mimeType || !mimeType.includes('text/html')) { return content; } let htmlContent = content.toString(); - const baseUrl = `/${domain}`; + // If direct access (via subdomain), don't prefix links with domain + const baseUrl = isDirectAccess ? '' : `/${domain}`; // Create base directory for proper path resolution let basePath = '/'; @@ -121,6 +126,79 @@ function makeLinksAbsolute(content, mimeType, domain, subPath = '') { return htmlContent; } +// Helper function to handle direct domain access +async function handleDirectDomainAccess(req, res, domain) { + try { + const subPath = req.path || ''; + + console.log(`Direct access for domain: ${domain}, path: ${subPath}`); + + // Resolve Handshake domain to get IPFS CID + const cid = await resolveHandshake(domain); + + if (!cid) { + console.warn(`No IPFS CID found for domain: ${domain}`); + return res.status(404).json({ + error: 'Domain not found or has no IPFS record', + domain: domain + }); + } + + console.log(`Resolved ${domain} to IPFS CID: ${cid}`); + + // Fetch content from IPFS + const path = subPath.startsWith('/') ? subPath.substring(1) : subPath; + const content = await fetchFromIpfs(cid, path); + + if (!content) { + return res.status(404).json({ + error: 'Content not found on IPFS network', + cid: cid, + path: path + }); + } + + // Set appropriate content type + if (content.mimeType) { + res.setHeader('Content-Type', content.mimeType); + } + + // Process HTML content: make links absolute and inject tracking script + // Note: isDirectAccess=true so links won't be prefixed with domain + let processedContent = content.data; + if (content.mimeType && content.mimeType.includes('text/html')) { + processedContent = makeLinksAbsolute(processedContent, content.mimeType, domain, path, true); + processedContent = injectTrackingScript(processedContent, content.mimeType); + } + + // Return the content + return res.send(processedContent); + } catch (error) { + console.error('Error handling direct domain access:', error); + return res.status(500).json({ + error: 'Server error processing request', + message: error.message + }); + } +} + +// Middleware to check if request is for direct domain access +app.use(async (req, res, next) => { + const host = req.get('host'); + + // If this is not the main dashboard host, treat it as direct domain access + if (host && host !== DASHBOARD_HOST) { + // Extract domain from the hostname + // This assumes the domain is the full hostname or a subdomain of your gateway + const domain = host.split(':')[0]; // Remove port if present + + return handleDirectDomainAccess(req, res, domain); + } + + // Continue with normal processing for dashboard host + next(); +}); + // Status endpoint app.get('/api/status', (req, res) => { res.json({ status: 'online', version: '1.0.0' }); @@ -395,4 +473,5 @@ app.get('*', (req, res) => { // Start server app.listen(PORT, () => { console.log(`Fire Portal server running on port ${PORT}`); + console.log(`Dashboard host: ${DASHBOARD_HOST}`); }); -- 2.49.1 From 3d40a7152f2b1a6297e6750a0be8d82689e58973 Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Tue, 24 Jun 2025 22:43:56 +1000 Subject: [PATCH 2/3] fix: Update the direct host proxy --- .env.example | 2 +- server.js | 145 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 109 insertions(+), 38 deletions(-) diff --git a/.env.example b/.env.example index 2edf134..a0f6846 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ # Server settings PORT=3000 -DASHBOARD_HOST=ipfs.hnshosting.au +DASHBOARD_HOST=ipfs.hnsproxy.au # IPFS settings IPFS_GATEWAY=https://ipfs.io diff --git a/server.js b/server.js index ec6409a..817266e 100644 --- a/server.js +++ b/server.js @@ -14,6 +14,18 @@ app.use(cors()); app.use(morgan('dev')); app.use(express.json()); +// Add a request logger middleware at the very beginning +app.use((req, res, next) => { + console.log('\n----- NEW REQUEST -----'); + console.log(`${req.method} ${req.url}`); + console.log(`Headers: ${JSON.stringify({ + host: req.get('host'), + referer: req.get('referer'), + 'user-agent': req.get('user-agent') + }, null, 2)}`); + next(); +}); + // Define the main dashboard host // This should be configurable via environment variable const DASHBOARD_HOST = process.env.DASHBOARD_HOST || '127.0.0.1:3000'; @@ -31,7 +43,53 @@ const RESERVED_PATHS = [ 'favicon.ico' ]; -// Serve static files +// Helper function to normalize host strings for comparison +function normalizeHost(host) { + if (!host) return ''; + // Remove port if present and convert to lowercase + const normalized = host.split(':')[0].toLowerCase(); + console.log(`Normalizing host: ${host} -> ${normalized}`); + return normalized; +} + +// IMPORTANT: Move direct domain access middleware before static file serving +// Middleware to check if request is for direct domain access +app.use(async (req, res, next) => { + const host = req.get('host'); + const normalizedHost = normalizeHost(host); + const normalizedDashboardHost = normalizeHost(DASHBOARD_HOST); + + console.log(`[HOST CHECK] Request host: ${host}, Normalized: ${normalizedHost}, Dashboard: ${normalizedDashboardHost}`); + + // Special handling for curl requests with Host header + if (normalizedHost !== normalizedDashboardHost) { + console.log(`[HOST CHECK] Host ${normalizedHost} doesn't match dashboard ${normalizedDashboardHost}, treating as direct access`); + + // Skip direct domain handling for API endpoints + if (req.path.startsWith('/api/')) { + console.log(`[HOST CHECK] Skipping direct domain handling for API endpoint: ${req.path}`); + return next(); + } + + // Extract domain from the hostname + const domain = normalizedHost; + console.log(`[DIRECT ACCESS] Using domain: ${domain} from host: ${host}`); + + try { + return await handleDirectDomainAccess(req, res, domain); + } catch (error) { + console.error(`[HOST CHECK] Error handling direct access, falling back to normal processing: ${error}`); + // Fall back to normal processing if direct access fails + return next(); + } + } + + console.log(`[HOST CHECK] Host ${normalizedHost} matches dashboard ${normalizedDashboardHost}, continuing with normal processing`); + // Continue with normal processing for dashboard host + next(); +}); + +// Serve static files AFTER checking for direct domain access app.use(express.static(path.join(__dirname, 'public'))); // Helper function to inject tracking script into HTML content @@ -128,77 +186,85 @@ function makeLinksAbsolute(content, mimeType, domain, subPath = '', isDirectAcce // Helper function to handle direct domain access async function handleDirectDomainAccess(req, res, domain) { + console.log(`[DIRECT ACCESS] Starting direct domain access handler for: ${domain}`); try { - const subPath = req.path || ''; + // Remove trailing slash from path for consistency + let subPath = req.path || ''; + if (subPath === '/' || subPath === '') { + subPath = ''; + } - console.log(`Direct access for domain: ${domain}, path: ${subPath}`); + console.log(`[DIRECT ACCESS] Domain: ${domain}, Path: '${subPath}'`); // Resolve Handshake domain to get IPFS CID + console.log(`[DIRECT ACCESS] Resolving Handshake domain: ${domain}`); const cid = await resolveHandshake(domain); if (!cid) { - console.warn(`No IPFS CID found for domain: ${domain}`); + console.warn(`[DIRECT ACCESS] No IPFS CID found for domain: ${domain}`); return res.status(404).json({ error: 'Domain not found or has no IPFS record', domain: domain }); } - console.log(`Resolved ${domain} to IPFS CID: ${cid}`); + console.log(`[DIRECT ACCESS] Resolved ${domain} to IPFS CID: ${cid}`); - // Fetch content from IPFS - const path = subPath.startsWith('/') ? subPath.substring(1) : subPath; - const content = await fetchFromIpfs(cid, path); + // Fetch content from IPFS - handle root path specially + const path = subPath === '' ? '' : (subPath.startsWith('/') ? subPath.substring(1) : subPath); + console.log(`[DIRECT ACCESS] Fetching IPFS content for CID: ${cid}, path: '${path}'`); + let content = await fetchFromIpfs(cid, path); if (!content) { - return res.status(404).json({ - error: 'Content not found on IPFS network', - cid: cid, - path: path - }); + console.log(`[DIRECT ACCESS] No content found for path: '${path}'`); + // Try index.html for empty paths + if (path === '') { + console.log('[DIRECT ACCESS] Trying index.html for root path'); + const indexContent = await fetchFromIpfs(cid, 'index.html'); + if (indexContent) { + console.log('[DIRECT ACCESS] Found index.html content, using that instead'); + content = indexContent; + } else { + console.log('[DIRECT ACCESS] No index.html found either'); + } + } + + // If still no content, return 404 + if (!content) { + console.log(`[DIRECT ACCESS] Returning 404 for CID: ${cid}, path: '${path}'`); + return res.status(404).json({ + error: 'Content not found on IPFS network', + cid: cid, + path: path + }); + } } // Set appropriate content type if (content.mimeType) { + console.log(`[DIRECT ACCESS] Setting content type: ${content.mimeType}`); res.setHeader('Content-Type', content.mimeType); } // Process HTML content: make links absolute and inject tracking script - // Note: isDirectAccess=true so links won't be prefixed with domain + console.log('[DIRECT ACCESS] Processing content'); let processedContent = content.data; if (content.mimeType && content.mimeType.includes('text/html')) { + console.log('[DIRECT ACCESS] HTML content detected, making links absolute'); processedContent = makeLinksAbsolute(processedContent, content.mimeType, domain, path, true); + console.log('[DIRECT ACCESS] Injecting tracking script'); processedContent = injectTrackingScript(processedContent, content.mimeType); } // Return the content + console.log('[DIRECT ACCESS] Sending processed content'); return res.send(processedContent); } catch (error) { - console.error('Error handling direct domain access:', error); - return res.status(500).json({ - error: 'Server error processing request', - message: error.message - }); + console.error('[DIRECT ACCESS] Error handling direct domain access:', error); + throw error; // Rethrow so middleware can fall back to normal processing } } -// Middleware to check if request is for direct domain access -app.use(async (req, res, next) => { - const host = req.get('host'); - - // If this is not the main dashboard host, treat it as direct domain access - if (host && host !== DASHBOARD_HOST) { - // Extract domain from the hostname - // This assumes the domain is the full hostname or a subdomain of your gateway - const domain = host.split(':')[0]; // Remove port if present - - return handleDirectDomainAccess(req, res, domain); - } - - // Continue with normal processing for dashboard host - next(); -}); - // Status endpoint app.get('/api/status', (req, res) => { res.json({ status: 'online', version: '1.0.0' }); @@ -208,13 +274,16 @@ app.get('/api/status', (req, res) => { app.get('/:domain', async (req, res, next) => { const domain = req.params.domain; + console.log(`[DASHBOARD ROUTE] Processing /:domain request for: ${domain}`); + // Skip this handler for reserved paths if (RESERVED_PATHS.includes(domain)) { + console.log(`[DASHBOARD ROUTE] Domain '${domain}' is in reserved paths, skipping`); return next(); } try { - console.log(`Processing request for domain root: ${domain}`); + console.log(`[DASHBOARD ROUTE] Processing request for domain root: ${domain}`); // Resolve Handshake domain to get IPFS CID const cid = await resolveHandshake(domain); @@ -472,6 +541,8 @@ app.get('*', (req, res) => { // Start server app.listen(PORT, () => { + console.log(`\n==================================`); console.log(`Fire Portal server running on port ${PORT}`); console.log(`Dashboard host: ${DASHBOARD_HOST}`); + console.log(`==================================\n`); }); -- 2.49.1 From c78d5f5d6321eeb938e4a088b0edf3f56a9cfb48 Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Wed, 25 Jun 2025 13:18:48 +1000 Subject: [PATCH 3/3] feat: Update NGINX config in the README --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 331c24b..3ae0292 100644 --- a/README.md +++ b/README.md @@ -84,12 +84,13 @@ To proxy Handshake domains to IPFS, you can use the following nginx configuratio ```nginx server { listen 80; - server_name ~^(?.+)$; + server_name _; # Catch-all for all domains location / { - proxy_pass http://127.0.0.1:3000/$domain; + proxy_pass http://127.0.0.1:3000; + proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; -- 2.49.1