6.2 KiB
Production 403 Errors - Root Cause & Fix
Diagnosis Summary
Issue: All API endpoints returning 302 redirects to Pangolin authentication page Root Cause: Pangolin tunnel resources configured with authentication enabled (should be "Not Protected") Status: CORS configuration ✅ FIXED | Pangolin resources ❌ NEEDS MANUAL FIX
What Was Fixed
✅ CORS Configuration (COMPLETED)
File: /home/bunker-admin/changemaker.lite/.env
Changes applied:
# Changed from development to production
NODE_ENV=production
# Added production domain to CORS whitelist
CORS_ORIGINS=http://app.betteredmonton.org,https://app.betteredmonton.org,http://localhost:3000,http://localhost
API container restarted: ✅ Done
What Still Needs Manual Fix
❌ Pangolin Resource Authentication (REQUIRES MANUAL ACTION)
Problem: Resources are configured with authentication, causing 302 redirects to auth page.
Evidence:
$ curl -I https://api.betteredmonton.org/api/health
HTTP/2 302
location: https://pangolin.bnkserve.org/auth/resource/68488f80-b055-41ea-bc1b-0ab905fb8a53?redirect=...
Fix Required: Change authentication setting for ALL Pangolin resources to "Not Protected"
Step-by-Step Fix Instructions
1. Log in to Pangolin Dashboard
URL: https://api.bnkserve.org (remove /v1 from API URL)
2. Navigate to Resources
Dashboard → Resources → Public
3. Edit Each Resource
For EACH of these critical resources:
- ✅ app.betteredmonton.org (Admin GUI + Public Pages)
- ✅ api.betteredmonton.org (Main API)
- ✅ media.betteredmonton.org (Media API)
- db.betteredmonton.org (NocoDB)
- docs.betteredmonton.org (MkDocs)
- code.betteredmonton.org (Code Server)
- git.betteredmonton.org (Gitea)
- n8n.betteredmonton.org (n8n)
- grafana.betteredmonton.org (Grafana)
- listmonk.betteredmonton.org (Listmonk)
- qr.betteredmonton.org (Mini QR)
- home.betteredmonton.org (Homepage)
Most critical (fix these first):
- api.betteredmonton.org - Main API (all endpoints fail without this)
- app.betteredmonton.org - Admin GUI (login page won't work)
- media.betteredmonton.org - Media API (video library features)
4. Change Authentication Setting
For each resource:
- Click Edit (pencil icon)
- Find Authentication or Access Policy section
- Change from "Protected" or "Authenticated" to:
- "Not Protected" OR
- "Public Access" OR
- "No Authentication" (exact wording depends on Pangolin UI version)
- Click Save
5. Verify Fix
After changing authentication settings, test each endpoint:
Test API:
curl https://api.betteredmonton.org/api/health
# Expected: {"status":"healthy","checks":{"database":"ok","redis":"ok"}}
# NOT: 302 redirect
Test Public Campaigns:
curl https://api.betteredmonton.org/api/campaigns/public
# Expected: JSON array of campaigns
# NOT: 302 redirect
Test Admin GUI: Visit https://app.betteredmonton.org in browser
- Should see login page
- NO redirect to Pangolin auth page
Why This Happened
- Pangolin resources default to "Protected" - requires manual change to "Not Protected"
- Manual setup process - automated setup was removed, so resources must be configured manually
- No API enforcement - Pangolin API doesn't enforce "Not Protected" when creating resources programmatically
Resource Configuration Reference
Correct settings for ALL resources:
- Protocol: HTTPS (SSL enabled)
- Target: nginx:80 (all services route through nginx)
- Authentication: Not Protected ← THIS IS CRITICAL
- SSL/TLS: Enabled
Troubleshooting
Still seeing 302 redirects after changing settings?
- Clear browser cache - old redirects may be cached
- Try incognito/private window
- Wait 30-60 seconds - Pangolin may need time to update routing
- Check resource status - ensure resource shows as "Active" in Pangolin dashboard
- Verify target - should point to
nginx:80(not individual service ports)
API works locally but not via tunnel?
Confirm:
- Newt container is running:
docker compose ps newt - Newt logs show connection:
docker compose logs newt --tail 50 - PANGOLIN_SITE_ID, PANGOLIN_NEWT_ID, PANGOLIN_NEWT_SECRET are set in .env
- Nginx is running:
docker compose ps nginx
Health endpoint works but other endpoints fail?
Check in this order:
- Test public endpoints (no auth):
/api/campaigns/public,/api/shifts/public - Test protected endpoints with valid JWT:
/api/campaigns,/api/users - Check auth store in browser DevTools: localStorage should have
auth-storagewith tokens - Verify JWT secrets haven't changed (would invalidate existing tokens)
Post-Fix Verification Checklist
After changing Pangolin resource authentication to "Not Protected":
- Health endpoint returns JSON (not 302):
curl https://api.betteredmonton.org/api/health - Public campaigns endpoint works:
curl https://api.betteredmonton.org/api/campaigns/public - Admin GUI loads: visit https://app.betteredmonton.org
- Login works: can authenticate with admin credentials
- Campaign management page loads data (no console errors)
- Representative lookup functions
- Public campaign page accessible: https://app.betteredmonton.org/campaigns
- Map page loads: https://app.betteredmonton.org/map
- Shifts page works: https://app.betteredmonton.org/shifts
Summary
What was done:
- ✅ Updated
.envwith production CORS origins - ✅ Set NODE_ENV to production
- ✅ Restarted API container
- ✅ Verified API works locally
What you need to do:
- ❌ Log in to Pangolin dashboard at https://api.bnkserve.org
- ❌ Edit each resource and set Authentication to "Not Protected"
- ❌ Verify endpoints no longer return 302 redirects
- ❌ Test application is fully functional
Time estimate: 5-10 minutes to update all 12 resources
Contact
If you encounter issues after following these steps:
- Check Pangolin documentation: https://pangolin.bnkserve.org/docs (if available)
- Review Newt container logs:
docker compose logs newt - Verify nginx routing:
docker compose logs nginx | grep betteredmonton.org