broker_links 400 — Cloud JWT vs Self-Hosted JWT mismatch (08/jul/2026)
Symptom
POST /rest/v1/broker_links 400 (Bad Request) no DevTools ao adicionar link na página /admin do main site (rochasalesseguros.com.br).
Root Cause — TWO problems
Problema 1: Cloud anon key no bundle principal
- - Arquivo:
/opt/rochasales/dist/assets/index-oCPVfdCr.js(740KB) - - Bundle tinha a Cloud anon key (
iat: 1781619347, Jun 2026,ref: rochasales) que é incompatível com o PostgREST self-hosted - - O PostgREST usa
PGRST_DB_VERIFY_JWT=falsemas mesmo assim rejeita Cloud JWT comPGRST301 JWSInvalidSignature - - Fix: Substituiu a Cloud anon key pela self-hosted anon key diretamente no bundle:
- - Tabela
broker_linkstemtokencomoNOT NULLsem valor default e sem trigger - - Auth JWT funciona corretamente (retorna user_id corretamente via trigger existente
trg_inject_broker_links_user_id) - - Fix: Criou trigger para auto-gerar token UUID:
- - Limpar cache do browser: DevTools → Application → Clear site data → Ctrl+Shift+R
`
# Backup
cp /opt/rochasales/dist/assets/index-oCPVfdCr.js /opt/rochasales/dist/assets/index-oCPVfdCr.js.bak4
# Substituir (old → new):
# OLD: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJvY2hhc2FsZXMiLCJyb2xlIjoiYW5vbiIsImlhdCI6MTc4MTYxOTM0NywiZXhwIjoyMDk2OTc5MzQ3fQ.eYHq36jgVmU4CyW23CG4tVAzYoXbmBLBgc-E3rjJmmI
# NEW: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJvY2hhc2FsZXMiLCAicm9sZSI6ICJhbm9uIiwgImlhdCI6IDE3ODM0MzM3MjQsICJleHAiOiAxNzg0NjQzMzI0fQ.qfgKlvwdMkvKZp3CubkLC73yPtd5Pv5jOkil1BDsW2w
`
Problema 2: Coluna token NOT NULL sem trigger/default
`sql
CREATE OR REPLACE FUNCTION set_broker_links_token()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
IF NEW.token IS NULL THEN
NEW.token := gen_random_uuid()::text;
END IF;
RETURN NEW;
END;
$$;
DROP TRIGGER IF EXISTS trg_set_broker_links_token ON broker_links;
CREATE TRIGGER trg_set_broker_links_token
BEFORE INSERT ON broker_links
FOR EACH ROW EXECUTE FUNCTION set_broker_links_token();
`
Debug Commands
`bash
Verificar anon keys no bundle
sshpass -p 'Marcia19671951@' ssh root@31.97.243.106 \
"grep -o 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.[a-zA-Z0-9_-]\.[a-zA-Z0-9_-]' \
/opt/rochasales/dist/assets/index-oCPVfdCr.js | sort -u"
Testar POST broker_links com JWT real (via curl + auth real)
TOKEN=$(curl -s "https://rochasalesseguros.com.br/auth/v1/token?grant_type=password" \
-H "Content-Type: application/json" \
-H "apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJvY2hhc2FsZXMiLCAicm9sZSI6ICJhbm9uIiwgImlhdCI6IDE3ODM0MzM3MjQsICJleHAiOiAxNzg0NjQzMzI0fQ.qfgKlvwdMkvKZp3CubkLC73yPtd5Pv5jOkil1BDsW2w" \
-d '{"email":"tecrochasales@gmail.com","password":"acesso@rochasales"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
curl -s -X POST "https://rochasalesseguros.com.br/rest/v1/broker_links" \
-H "apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d '{"title":"Teste","url":"https://teste.com","is_active":true,"display_order":99}'
Ver triggers da tabela
docker exec deploy-vps-db-1 psql -U supabase_admin -d postgres -c \
"SELECT trigger_name, action_statement FROM information_schema.triggers WHERE event_object_table = 'broker_links';"
Verificar PostgREST JWT config
docker exec deploy-vps-rest-1 sh -c 'echo $PGRST_DB_VERIFY_JWT $PGRST_JWT_SECRET'
`
Self-hosted Anon Key (atual, Jul 2026)
`
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJvY2hhc2FsZXMiLCAicm9sZSI6ICJhbm9uIiwgImlhdCI6IDE3ODM0MzM3MjQsICJleHAiOiAxNzg0NjQzMzI0fQ.qfgKlvwdMkvKZp3CubkLC73yPtd5Pv5jOkil1BDsW2w
`
(iat: 1783433724 = Jul 2026, ref: rochasales)
PostgREST Config
`
PGRST_DB_VERIFY_JWT=false
PGRST_JWT_SECRET=OyIoGm...xXs4 (JWT_SECRET do GoTrue)
PGRST_DB_ANON_ROLE=anon
`
Lessons
1. PGRST_DB_VERIFY_JWT=false não significa "aceita qualquer JWT" — PostgREST ainda tenta decodificar o JWT e rejeita se o formato/claims forem incompatíveis
2. Mesmo com JWT correto, constraint NOT NULL sem default/trigger quebra INSERTs
3. Bundle do main site (/opt/rochasales/dist/) é SEPARADO do ComercialRS (/var/www/comercialrs/dist/) — cada um tem seu próprio JS com suas próprias chaves
4. Two separate docker-compose networks: deploy-vps_ containers vs rochasales- containers — cada um com seu próprio Kong + PostgREST