Back to Experiments

Experiment 017

Live Supabase Auth, Postgres, Edge Functions, RLS, 10 verified captures

Four Apps, One Door, Zero Funnel — Tracing Outages and Moving Auth Plus Data to Supabase

Can a home-based multi-app stack survive a broken public access path, and what does moving auth and data to a managed platform actually fix?

mimo v2.6 flash via opencode · Supabase Auth, Postgres, Edge Functions · 10 verified captures · 0 VPS bill · 29 Sep 2026

My three apps now log in through Supabase Auth, not the shared backend of EXP 011 anymore. I did not copy auth, I moved it. Old token keys jobTracker.token, crm.token, and content.token are retired, so you log in one more time and then you are done. The old shared auth detail still lives in EXP 011. The :7002 backend stays alive for cv screening only.

Source Code

The source code is public. Prove it yourself.

The backend API and the database moved to Supabase. The repo that got the real update is the jobtracker backend. The other two backend repos stay as they were, almost untouched, and the frontend lives inside my porto repo.

github.com/robbyaliasaakbar/backend-api-services-jobtracker (updated)

github.com/robbyaliasaakbar/contentos-backend-api-service (as is)

github.com/robbyaliasaakbar/mini-leads-service-api--backend (as is)

No secrets inside any README. The updated backend README carries the receipts.

System Requirements

Live view

Modern browser + internet

Live login

Supabase Auth, email code

Live apps

/jobtracker/, /contentOS/, /miniLeads/

Cloud home

Supabase free tier, 500MB, 50k MAU

Data moved

2050 leads, content rows, lamaran rows

Still at home

n8n, evolution, postgres, auth :7002

Test gate

RLS negative 0, E2E green, 0 funnel hits

Model

mimo v2.6 flash via opencode

Four apps live on GitHub Pages, three with cloud login. My home PC now only carries the side paths, n8n, WhatsApp, postgres, and the old auth for cv screening.

Screenshots

Blocked First Then Terminal Green At The End

I captured ten shots in strict order: the broken door first, the locks and the moved data in the middle, green receipts at the end. Terminals, Supabase dashboards, and one live login, all 800 by 500, all checked before upload.

1

Blocked - Four Countries, Zero Bytes

public-blocked.webp
Terminal showing forced public requests answered not found while a control request works - Robby Aliasa Akbar
Blocked - the public door answers not found, 29 Sep 2026
2

DNS Back - Door Still Shut

dns-health.webp
Terminal showing DNS over HTTPS answers with two public IP addresses after the outage - Robby Aliasa Akbar
DNS back - names resolve again while the door stays shut
3

Locked - Anonymous Query Denied

rls-deny.webp
Supabase SQL editor with row level policies and an empty count for the anonymous role - Robby Aliasa Akbar
Locked - anonymous query comes back empty
4

Moved - Two Thousand Fifty Rows Clean

seed-2050.webp
Supabase SQL editor showing status counts after the leads seed moved - Robby Aliasa Akbar
Moved - counts read back from the new home
5

Cross User - Other Account Sees Zero

cross-user.webp
Supabase SQL editor showing zero rows for an account that does not own the data - Robby Aliasa Akbar
Cross user - another account sees zero rows
6

Closed - Edge Function Wants JWT

edge401.webp
Terminal calling the extract edge function without a token - Robby Aliasa Akbar
Closed - edge function asked for a token first
7

Code Only - No Link For Scanners

otp-code.webp
Supabase password reset email template with code only body and no link - Robby Aliasa Akbar
Code only - bare code in the body, link removed
8

Open - Login From The Real Internet

login-page-live.webp
JobTracker login page live with sample ledger panel marked as demo data - Robby Aliasa Akbar
Open - live login, right side is sample data
9

Detached - Zero Funnel Hits

nol-funnel.webp
Terminal grepping the live bundle for old funnel and localhost addresses - Robby Aliasa Akbar
Detached - zero hits for the old funnel in the live bundle
10

Retired - Old Servers Stay Down

old-backend-die.webp
Terminal checking stopped backend containers and the auth port that stays up - Robby Aliasa Akbar
Retired - old backend containers are stopped

Live Demo

Four apps live, three on cloud auth, no VPS bill

All four apps run on GitHub Pages, and three of them log in through Supabase now. Try any app, log in once, and check that the rows you see belong to you alone. My laptop can be off, your login still works.

Caution - the one app that has not moved

cv screening is the only app still on the old path. Its webhook runs through n8n on my home PC behind the Tailscale port, so uploads need my laptop awake, and its login still uses the shared auth on :7002. Everything else on this card works with the laptop off.

Old token keys are retired, so your first login after the move asks for email and code one more time. Free tier limits apply. Side paths like n8n and the old auth for cv screening still wake my home PC, which is expected and written in the status below.

For those of you unable to read this data from technical standpoint, here is the conclusion:

1. My four apps shared one front door, when the door broke nobody from outside could get in

2. I checked the pipes one by one, thirteen problems written down with receipts

3. Then I moved login and data to Supabase, the cloud does the guarding with row locks

4. Old data came along, two thousand fifty leads arrived clean, counts match the old box

5. The three apps now work without my laptop being open, still zero VPS bill

6. Everything that broke is written here, no hidden magic

Experiment Details

Tracing The Broken Door, Then Moving The House

This experiment pairs two stories, the tracing of a broken public path and the move to a managed platform. Here are the five decisions I made to get four apps through both without changing how they behave.

Trace Before You Touch

K01 to K08, three way test

I traced eight findings before I changed anything. DNS was dead first, then the funnel door, then I found my own test lying to me because it ran from inside the network. I fixed the method, DoH for names, forced public IP with curl resolve, remote nodes on check host. Every claim on this page passed that three way gate.

I developed this page with the hosted model mimo v2.6 flash via opencode. I led research, architecture, review, and test, while Udin typed code and suggestions.

Option B, Move Everything

Supabase auth, database

I weighed three options, keep the home backend, move only auth, or move it all. I chose the full move. Frontend on GitHub Pages now talks straight to Supabase, so the broken funnel door and the laptop dependency leave the critical path. The rate bucket problem retires with the old backend too.

Supabase is a service, yes, but it is open source and self hostable later, free tier covers 500MB database, 50k monthly users, and 2GB egress. No monthly bill, same as my home setup.

Row Locks From Minute One

RLS, own rows or admin

The tables got row locks before any data landed. The rule translates my old backend exactly, a user reads their own rows, an admin reads all, the stranger reads nothing. I checked the negative path twice, anonymous role came back empty, and a normal account cannot promote itself to admin.

My proof is the SQL editor shots and the policy list. Row locks live in the database, not in my frontend, so even a stolen anon key opens nothing.

Same Shape, New Home

RPC and Edge Functions keep the shape

Not everything is a plain table read. CSV export, dashboard counts, ingest with dedup, and two parsing helpers needed real logic. I moved them to database functions and two Edge Functions, and I kept the response shape identical to my old Express routes, so the frontend only swapped the auth layer and the base URL, no screen rewrite.

One behavior did change on purpose. The extract helper used to be open, now it demands a signed token and answers 401 without it. I logged that as a fix, not as an accident.

One App At A Time

cutover, verify, rollback path open

No big bang. Each app moved only after its checks passed live, seed counts, filters, export, and row locks. The old backend stayed available as my rollback the whole time, and only after three apps passed I stopped the old containers with restart turned off, so they never wake up on reboot.

Old token keys died with the old backend, so every user logs in one more time. That cost was small and I wrote it down instead of hiding it.

Evidence Log

Standalone Evidence Log: Locks, Moved Data, Zero Funnel

I back every claim on this page with verifiable command line outputs from 29 Sep 2026. I never show passwords or full tokens, raw status and counts remain exact, and the positive control proves the greps really ran.

E1. Names Resolve, Door Still Shut

NOERROR Then 404

DNS over HTTPS answers from two providers, then a forced public request still misses the app.

dig @ns1.dnsimple.com aispec.tail06293c.ts.net A
status: NOERROR, answers: 103.84.155.217, 103.84.155.153

curl -H 'accept: application/dns-json' 'https://cloudflare-dns.com/dns-query?name=aispec.tail06293c.ts.net&type=A'
Status: 0

curl --resolve aispec.tail06293c.ts.net:443:103.84.155.153 https://aispec.tail06293c.ts.net/
HTTP 404 {"error":"Not found"}

Result: names answer again, the app behind the door still does not open.

E2. Row Locks Deny The Stranger

Anon 0 Rows

Anonymous role reads nothing, and a normal account cannot raise its own role.

select tablename, policyname from pg_policies where schemaname = 'public'
leads  own rows or admin
profiles  own rows or admin

set local role anon
select count(*) from leads
0 rows, denied

update profiles set role = 'admin' where id = auth.uid()
0 rows affected

Result: stranger sees zero, self promotion blocked, same rule as the old backend.

E3. Seed Arrived Clean

2050 / 0

Total rows and rows without an owner, then the status spread for comparison with the old box.

select count(*) as total,
       count(*) filter (where user_id is null) as tanpa_user
from leads
2050 / 0

select lead_status, count(*) from leads group by 1 order by 2 desc
7 status values, counts match the old box

Result: two thousand fifty rows moved, none without owner, counts match.

E4. Edge Function Wants A Token

401 No Token

The extract helper used to be open, now it rejects callers without a signed token.

curl -s -X POST 'https://yvnrzfgxsjzjsxkgjcfn.supabase.co/functions/v1/leads-extract' \
  -H 'Content-Type: application/json' -d '{"text":"..."}'
HTTP:401
{"code":"UNAUTHORIZED_NO_AUTH_HEADER","message":"Missing authorization header"}

Result: no token, no parse, closed door with a clear reason.

E5. Old Servers Down, Bundle Clean

3 Stopped, 0 Hits

Three old backends stopped with restart turned off, and the live bundle greps clean with a positive control.

docker ps -a | grep -E 'infra-backend-api|contentos-backend|minileads-backend'
infra-backend-api-1                 Exited (0)     restart=no
backend-server-contentos-backend-1  Exited (137)   restart=no
backend-server-minileads-backend-1  Exited (137)   restart=no

grep -oE 'localhost|tail06293c|:7002|:7012' bundle.js | wc -l
0
grep -oE 'supabase|yvnrzfg' bundle.js | wc -l
4

Result: old backends stay down, zero old addresses, positive hits for the new home.

Failure Log

Thirteen Failures Written Honestly

I logged thirteen findings from the outage and the move. Five deep dives below, the rest as an honest list after. Three were found before any user hit them, and every fix carries a receipt.

Tier 1 Incidents Five Deep Dive Case Studies

1. The Public Door Answers Nothing

K02 Infra OUTAGE

Symptom: Names resolve fine, HTTPS from the internet dies with a broken pipe, every public URL answers nothing.

Diagnosis: The funnel ingress cut the TLS stream, tracked upstream in the vendor issue I opened with support.

Fix: I did not wait for the door. I moved auth and data to Supabase so the apps no longer need that path to work.

Lesson: A working name is not a working door, always test the TLS path itself.

2. The Test That Lied To Me

K03 False Positive HIGH

Symptom: My first verdict said public access worked.

Diagnosis: The check ran from inside the same network, and MagicDNS answered like home, so the broken path never got tested.

Fix: Three way method from then on, DoH for names, forced public IP with curl resolve, remote nodes on check host.

Lesson: Never trust a check that lives on the same network as the service, and treat every green result as a hypothesis.

3. One Rate Bucket For The Whole World

K04 Auth GLOBAL

Symptom: Five logins per minute, then everyone on earth gets locked out together.

Diagnosis: The backend counted the proxy address, so all visitors shared one bucket.

Fix: For the three moved apps the problem retired with the old backend, the local fix got cancelled as no longer needed.

Lesson: Rate limits must count real clients, not the pipe they arrive through.

4. The Reset Link That Was Ignored

K09 Auth PIVOT

Symptom: The new auth service ignored my redirect after password reset, the flow landed on its own page.

Diagnosis: The platform default does not honor my old redirect the way my PHP did.

Fix: I pivoted to plain OTP codes, the user copies the code instead of clicking through.

Lesson: Study the defaults of the platform you move into before you promise the old behavior.

5. The Link And The Code Together

K13 Email PREFETCH

Symptom: The code died before the user touched it, first use said already used.

Diagnosis: Email scanners clicked the link inside my message, the single use code burned itself on the scan.

Fix: The template body now carries the bare code only, no link at all.

Lesson: Assume every email gets scanned by a machine before a human reads it.

Tier 2 Honest List Eight Short Entries and Recap

K01 DNS never provisioned at first, now resolved, names answer again from two providers.

K05 Three index files sat in dev mode, restored to the built version before push.

K06 The cv screening webhook still depends on my laptop, decision still open, written here not hidden.

K07 The funnel accepted odd ports without a warning, moved to standard caps so silence cannot lie again.

K08 Old Node backend answered any origin with a wildcard, logged as low risk, not touched yet.

K10 A cached bundle made a live test fail for fake reasons, fixed by reading the body length instead of trusting the label.

K11 A reset file in the temp folder went stale after the user reset, temp path rethink needed.

K12 The custom mail service locked my email template, unlocked after an app password was set.

Recap: thirteen on record, three found before any user hit them, every entry here has a receipt or a reason.

FAQ

Frequently Asked Questions

Is this a new login? ▼

Yes, one more time. The three apps now use Supabase Auth, old token keys are retired, you log in once and you are done.

Did my old data move? ▼

Yes. Two thousand fifty leads and the app rows came across, counts match, old boxes kept until checks passed.

Why Supabase instead of a VPS? ▼

Free tier covers it, row locks come built in, and the stack is open source so I can self host later. No monthly bill.

Why row locks from minute one? ▼

The stranger sees nothing, a user sees their own rows, an admin sees all. Same rule as my old backend.

Why cut over one app at a time? ▼

Each app had to pass live checks before the next one moved. My rollback path stayed open the whole time.

Why write down every failure? ▼

Thirteen on record so far. The receipts are the proof, and the lessons only count if they are written.

Live Status

Live Status - Cloud Auth, Home PC On The Side

What works now

  • Three apps live on GitHub Pages with Supabase login
  • Row locks on, anonymous reads nothing, other accounts read nothing
  • Seed verified, 2050 rows, zero without owner
  • Edge functions answer 401 without a token
  • Live bundles show zero old funnel addresses

What is not production yet

  • cv screening still on the old auth :7002, that decision is open
  • Funnel leftovers still answer on the side, the outage is not closed upstream
  • Home PC still runs n8n, WhatsApp, postgres, and the old auth
  • Free tier limits, hardening is a separate project
Three apps live with cloud auth and row locks. My home PC now only carries the side paths, n8n, WhatsApp, postgres, and the old auth for cv screening. The outage shots are stamped 29 Sep 2026 as it happened, a later check the same day already answered 200 on the jobtracker path, so every blocked claim on this page carries its date. No passwords, tokens, or OTP codes ever shown.

Disclaimer

Disclaimer - Cloud Auth, Home Side Paths, Not Production-Hardened

Built with mimo v2.6 flash via opencode. Snapshots taken 29 Sep 2026. Passwords, tokens, and OTP codes are never shown. Row locks and free tier limits stand as written.

Shared auth old story: EXP 011 (:7002) · v1 jobtracker: EXP 010 (vanilla) · Previous: EXP 016 (v2 enterprise) · This: EXP 017 (Supabase move) · Machine: EXP 001