diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index b2d6e21e..2caf3638 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -532,6 +532,7 @@ jobs: env: NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }} GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }} + PAYLOAD_SECRET: ${{ secrets.PAYLOAD_SECRET || vars.PAYLOAD_SECRET }} PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium run: pnpm run check:forms diff --git a/scripts/check-forms.ts b/scripts/check-forms.ts index ce877c45..8ccb5ba0 100644 --- a/scripts/check-forms.ts +++ b/scripts/check-forms.ts @@ -348,13 +348,23 @@ async function main() { // 5. Cleanup: Delete test submissions from Payload CMS console.log(`\n🧹 Starting cleanup of test submissions...`); + const payloadSecret = process.env.PAYLOAD_SECRET; + + if (!payloadSecret) { + console.warn(` ⚠️ PAYLOAD_SECRET not found in environment. Cleanup will likely fail with 403.`); + } + try { const apiUrl = `${targetUrl.replace(/\/$/, '')}/api/form-submissions`; - const searchUrl = `${apiUrl}?where[email][equals]=testing@mintel.me`; + // We fetch ALL submissions matching the test email to clean up potential lefovers from previous runs + const searchUrl = `${apiUrl}?where[email][equals]=testing@mintel.me&limit=100`; - // Fetch test submissions + // Fetch test submissions with bypass header const searchResponse = await axios.get(searchUrl, { - headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` }, + headers: { + Cookie: `klz_gatekeeper_session=${gatekeeperPassword}`, + 'x-e2e-secret': payloadSecret || '', + }, }); const testSubmissions = searchResponse.data.docs || []; @@ -363,25 +373,30 @@ async function main() { for (const doc of testSubmissions) { try { await axios.delete(`${apiUrl}/${doc.id}`, { - headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` }, + headers: { + Cookie: `klz_gatekeeper_session=${gatekeeperPassword}`, + 'x-e2e-secret': payloadSecret || '', + }, }); - console.log(` ✅ Deleted submission: ${doc.id}`); + console.log(` ✅ Deleted submission: ${doc.id} (${doc.name})`); } catch (delErr: any) { - // Log but don't fail, 403s on Directus / Payload APIs for guest Gatekeeper sessions are normal console.warn( - ` ⚠️ Cleanup attempt on ${doc.id} returned an error, typically due to API Auth separation: ${delErr.message}`, + ` ⚠️ Cleanup attempt on ${doc.id} failed: ${delErr.message}`, ); } } + + if (testSubmissions.length > 0) { + console.log(`✅ Cleanup completed successfully.`); + } } catch (err: any) { if (err.response?.status === 403) { - console.warn( - ` ⚠️ Cleanup fetch failed with 403 Forbidden. This is expected if the runner lacks admin API credentials. Test submissions remain in the database.`, + console.error( + ` ❌ Cleanup failed with 403 Forbidden. Ensure FormSubmissions access control and PAYLOAD_SECRET are aligned.`, ); } else { console.error(` ❌ Cleanup fetch failed: ${err.message}`); } - // Don't mark the whole test as failed just because cleanup failed } await browser.close(); diff --git a/src/payload/collections/FormSubmissions.ts b/src/payload/collections/FormSubmissions.ts index eee43c7f..17ea1d72 100644 --- a/src/payload/collections/FormSubmissions.ts +++ b/src/payload/collections/FormSubmissions.ts @@ -9,9 +9,19 @@ export const FormSubmissions: CollectionConfig = { }, access: { // Only Admins can view and delete leads via dashboard. - read: ({ req: { user } }) => Boolean(user) || process.env.NODE_ENV === 'development', - update: ({ req: { user } }) => Boolean(user) || process.env.NODE_ENV === 'development', - delete: ({ req: { user } }) => Boolean(user) || process.env.NODE_ENV === 'development', + // E2E scripts can bypass if they provide the correct secret header. + read: ({ req }) => { + const isE2E = req.headers.get('x-e2e-secret') === process.env.PAYLOAD_SECRET; + return Boolean(req.user) || isE2E || process.env.NODE_ENV === 'development'; + }, + update: ({ req }) => { + const isE2E = req.headers.get('x-e2e-secret') === process.env.PAYLOAD_SECRET; + return Boolean(req.user) || isE2E || process.env.NODE_ENV === 'development'; + }, + delete: ({ req }) => { + const isE2E = req.headers.get('x-e2e-secret') === process.env.PAYLOAD_SECRET; + return Boolean(req.user) || isE2E || process.env.NODE_ENV === 'development'; + }, // Next.js server actions handle secure inserts natively. No public client create access. create: () => false, },