diff --git a/src/app/api/index/plot/route.ts b/src/app/api/index/plot/route.ts index 5c6a479c..650ce342 100644 --- a/src/app/api/index/plot/route.ts +++ b/src/app/api/index/plot/route.ts @@ -74,24 +74,31 @@ export async function POST(req: Request) { decoded.args; // 4. Fetch content from IPFS (with fallback) - let content: string; + let content: string | null = null; try { const ipfsRes = await fetch(`${IPFS_GATEWAY}${contentCID}`, { signal: AbortSignal.timeout(IPFS_TIMEOUT_MS), }); if (!ipfsRes.ok) throw new Error(`IPFS status ${ipfsRes.status}`); - content = await ipfsRes.text(); + const ipfsContent = await ipfsRes.text(); + // Verify IPFS content hash matches on-chain hash + if (hashContent(ipfsContent) === contentHash) { + content = ipfsContent; + } + // If hash mismatches, fall through to fallback content below } catch { - if (!fallbackContent) { - return error("IPFS fetch failed and no fallback content provided", 502); + // IPFS fetch failed — fall through to fallback content below + } + + // 5. Try fallback content if IPFS content was unavailable or hash-mismatched + if (!content && fallbackContent) { + if (hashContent(fallbackContent) === contentHash) { + content = fallbackContent; } - content = fallbackContent; } - // 5. Verify content hash - const computedHash = hashContent(content); - if (computedHash !== contentHash) { - return error("Content hash mismatch"); + if (!content) { + return error("Content hash mismatch (IPFS and fallback both failed)"); } // 6. Get block timestamp diff --git a/src/app/api/index/storyline/route.ts b/src/app/api/index/storyline/route.ts index 97b90eb6..fd125e18 100644 --- a/src/app/api/index/storyline/route.ts +++ b/src/app/api/index/storyline/route.ts @@ -100,27 +100,31 @@ export async function POST(req: Request) { const writerType = await detectWriterType(writer); // 6. Fetch genesis plot content from IPFS (with fallback) - let genesisContent: string; + let genesisContent: string | null = null; try { const ipfsRes = await fetch(`${IPFS_GATEWAY}${openingCID}`, { signal: AbortSignal.timeout(IPFS_TIMEOUT_MS), }); if (!ipfsRes.ok) throw new Error(`IPFS status ${ipfsRes.status}`); - genesisContent = await ipfsRes.text(); + const ipfsContent = await ipfsRes.text(); + // Verify IPFS content hash matches on-chain hash + if (hashContent(ipfsContent) === openingHash) { + genesisContent = ipfsContent; + } + // If hash mismatches, fall through to fallback content below } catch { - if (!fallbackContent) { - return error( - "IPFS fetch failed and no fallback content provided", - 502 - ); + // IPFS fetch failed — fall through to fallback content below + } + + // 7. Try fallback content if IPFS content was unavailable or hash-mismatched + if (!genesisContent && fallbackContent) { + if (hashContent(fallbackContent) === openingHash) { + genesisContent = fallbackContent; } - genesisContent = fallbackContent; } - // 7. Verify genesis content hash - const computedHash = hashContent(genesisContent); - if (computedHash !== openingHash) { - return error("Genesis content hash mismatch"); + if (!genesisContent) { + return error("Genesis content hash mismatch (IPFS and fallback both failed)"); } // 8. Upsert storyline to Supabase