Skip to Content
Investor OverviewAuthentication Model

Authentication Model — Auth-First for All

Onoots is an authenticated-from-the-start app. There is no anonymous, identity-less visitor: the moment someone lands on onoots.com, the app opens a session for them. Every interaction with the platform — chatting with an agent, sending a lead, saving a dream, requesting a showing, joining a call — is tied to a real identity.

There is no login wall. The session is created transparently in the background. Visitors browse the public catalog exactly as before — they just also carry a real session from their first request.

Transparent anonymous sessions

The enabler is Supabase Anonymous Sign-Ins. On a real browser’s first navigation, the middleware mints an anonymous session:

visitor → middleware → no session? → signInAnonymously() → real auth.users row (is_anonymous = true) + JWT
  • The session is a real auth.users row with is_anonymous = true and a normal JWT — not a fake/guest token. So auth.uid() is available everywhere (RLS, API routes, calls).
  • A profiles row is auto-created by the handle_new_user trigger (it tolerates the null email/metadata of an anonymous user).
  • Crawlers are excluded. Googlebot/bingbot/social/AI bots (by User-Agent) never get a session minted — the public catalog stays server-rendered for anon, fully indexable, and auth.users isn’t inflated by crawl traffic.
  • Graceful degradation. If anonymous sign-ins are ever unavailable, the middleware sets a short-lived onoots-anon-off cookie and the catalog keeps serving — nothing breaks.

Bound identity

Because every visitor has a uid, everything they create is attributable:

InteractionBound via
Lead (chat / form / showing request)leads.user_id = auth.uid()
Dreamdreams.user_id = auth.uid()
Buyer conversationconversation_participants client keyed by auth.uid()

The buyer being a participant keyed by auth.uid() is what lets them read their own thread via RLS and start LiveKit voice/video calls with their real session — no opaque bearer token.

Anti-abuse is unchanged. An anonymous session does not stop bots, so Cloudflare Turnstile + per-IP rate limits still guard every public write.

Upgrade to a permanent account

When a visitor registers, the app upgrades the existing anonymous session in place instead of creating a brand-new user:

anonymous session → updateUser({ email, password }) → same uid, now permanent

The uid is preserved, so all of their history carries over — leads, dreams, and conversations stay attached. Registration is a promotion, not a fresh start.

Gated areas

The public catalog is open to everyone (anonymous included), but operator surfaces require a permanent account:

  • /dashboard and /crm redirect to /auth/login when the session is missing or anonymous.
  • Agent-only APIs reject anyone without a row in agents — an anonymous visitor never qualifies.

Housekeeping

Most transparent sessions never engage. A daily anonymous sweep cron (/api/cron/anon-sweep) hard-deletes anonymous, email-less users older than 30 days that have no lead, dream, or conversation — keeping auth.users bounded. Engaged or upgraded users are structurally excluded.

Why

This removed the ill-defined “anonymous buyer”: previously the buyer chat ran without a session (service-role + an opaque conversation bearer), which blocked authenticated features like calls. With auth-first, the buyer is always a real, identifiable participant — and the communication component (voice/video/call) builds directly on top.

Last updated on