// D3 negative access (LIVE): mint a NON-MEMBER token → raw Firestore REST → expect 403/PERMISSION_DENIED. const admin = require('firebase-admin') const path = require('path') const SA = process.env.SA_JSON || path.join(__dirname, '..', 'closer-app-22014-firebase-adminsdk-fbsvc-ed20bf6003.json') const PROJECT = 'closer-app-22014' const APIKEY = 'AIzaSyDAD7FnEYzhMsil41SzJ1XMjUNnJWmjie8' const COUPLE = process.env.COUPLE_ID || 'Xal3Kw3gjSdn0niERYKJ' const FAKE_UID = 'qa-nonmember-' + Date.now() admin.initializeApp({ credential: admin.credential.cert(require(SA)) }) const docBase = `https://firestore.googleapis.com/v1/projects/${PROJECT}/databases/(default)/documents` async function getIdToken() { const custom = await admin.auth().createCustomToken(FAKE_UID) const r = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${APIKEY}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: custom, returnSecureToken: true }) }) const j = await r.json() if (!j.idToken) throw new Error('no idToken: ' + JSON.stringify(j)) return j.idToken } async function tryGet(token, p, label) { const r = await fetch(`${docBase}/${p}`, { headers: { Authorization: `Bearer ${token}` } }) const ok = r.status === 200 const verdict = ok ? '❌❌ LEAK (200)' : `✅ DENIED (${r.status})` console.log(` ${label}: ${verdict} [${p}]`) return ok } async function trySelfGrant(token, uid) { const r = await fetch(`${docBase}/users/${uid}/entitlements/premium?updateMask.fieldPaths=active`, { method: 'PATCH', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ fields: { active: { booleanValue: true } } }) }) console.log(` self-grant premium: ${r.status === 200 ? '❌❌ ALLOWED (200)' : `✅ DENIED (${r.status})`}`) return r.status === 200 } (async () => { console.log('=== D3 non-member negative access (uid', FAKE_UID, ') ===') const tok = await getIdToken() let leaks = 0 if (await tryGet(tok, `couples/${COUPLE}`, 'read couple doc')) leaks++ if (await tryGet(tok, `couples/${COUPLE}/conversations/main/messages`, 'list messages')) leaks++ if (await tryGet(tok, `couples/${COUPLE}/capsules`, 'list capsules')) leaks++ if (await tryGet(tok, `couples/${COUPLE}/desire_sync`, 'list desire_sync')) leaks++ if (await trySelfGrant(tok, FAKE_UID)) leaks++ // cleanup the throwaway auth user await admin.auth().deleteUser(FAKE_UID).catch(() => {}) console.log(leaks === 0 ? '=== D3 PASS: all denied ===' : `=== D3 FAIL: ${leaks} LEAK(S) — P0 ===`) process.exit(leaks === 0 ? 0 : 1) })().catch(e => { console.error('FATAL', e.message); process.exit(2) })