Architecture
A signed-in dashboard request flows through three trust boundaries: the browser, the Next.js server (route handlers + middleware), and the upstream registry at zns01.zynd.ai. Each boundary has its own auth and data flow.
Request lifecycle
Three trust boundaries
| Boundary | Auth | Notes |
|---|---|---|
| Browser → Next.js server | Supabase JWT in HttpOnly cookie | Refreshed by middleware.ts on every request. |
| Next.js server → Postgres | Prisma + Supabase service role | Service role bypasses Postgres RLS — the route handler is the gatekeeper. |
| Next.js server → registry | Ed25519 signed payloads | Developer's private key is decrypted server-side from developer_keys.private_key_enc for each signed call. |
The browser never sees the developer's private key during normal operations. The download-key flow in /dashboard/settings is the only path that decrypts and streams the key out.
Auth flow
src/middleware.ts runs on every request:
- Reads the Supabase auth cookie.
- Calls
supabase.auth.getUser()— refreshes the access token if expired. - Writes the (possibly rotated) cookie back on the response.
- For routes under
/dashboard/*, redirects to/authif no session.
src/app/auth/callback/route.ts is the OAuth landing page. It exchanges the OAuth code for a Supabase session, then:
- Looks up
developer_keysfor the user_id. - If absent, redirects to
/onboardto claim a handle and generate a key. - Otherwise, redirects to
/dashboard.
useAuth.ts is the client-side hook that exposes user, loading, and signOut to the UI tree.
Onboarding
/onboard is the first-run flow:
- Pick a handle — typed in
/onboard/page.tsx, debounced check viaGET /api/developer/username-check?username=...(which proxiesGET /v1/handles/{handle}/availableon the registry). - Submit —
POST /api/developer/registerruns server-side:- Generates an Ed25519 keypair via
tweetnacl. - Encrypts the private key with AES-256-GCM using
PKI_ENCRYPTION_KEY(see Data Model — Key encryption). - Inserts a
developer_keysrow. - POSTs to the registry's onboarding webhook with
Authorization: Bearer ${AGENTDNS_WEBHOOK_SECRET}.
- Generates an Ed25519 keypair via
- Redirects to
/onboard/setupfor any additional setup (role, country), then/dashboard.
Registry client
src/lib/api/ wraps every /v1/... HTTP call to zns01.zynd.ai:
- Public reads (
/v1/search,/v1/categories,/v1/tags,/v1/network/...) — no auth. - Signed writes (
POST /v1/entities,PUT /v1/entities/{id},DELETE /v1/entities/{id},POST /v1/names) — server-side: load encrypted private key, decrypt, sign canonical payload, send. - Webhook calls (developer registration on restricted registries) —
Authorization: Bearer ${AGENTDNS_WEBHOOK_SECRET}.
The base URL comes from NEXT_PUBLIC_AGENTDNS_URL for browser-readable reads and AGENTDNS_REGISTRY_URL for server-side writes (they're usually the same, but separating them lets you point reads at a CDN and writes at the canonical node).
Data caching
The dashboard mirrors the user's own entities into local Postgres for two reasons:
- Speed —
/dashboard/entitieslists from Prisma without round-tripping to the registry. - Edit buffer — drafts can be saved before pushing to the registry.
/api/entities/sync is the reconciliation point. It fetches GET /v1/entities?developer_id=... and upserts into entities. Called on /dashboard/entities mount and after every create / edit.
Components
components/dashboard/sidebar.tsx,top-nav.tsx,credential-card.tsx— authenticated layout chrome.components/entities/entity-form.tsx— the create / edit entity form. Used by both/dashboard/entities/createand/dashboard/entities/[id]/edit.components/AgentDetailPage.tsx,RegistryPage.tsx— public registry browser used at/registryand/registry/[id].components/ui/— primitives (buttons, modals, tooltips).
State
- Server state — Supabase session, Prisma rows, registry responses. Fetched per request.
- Client state —
store/global.store.ts(Zustand) for cross-component UI state.hooks/useEntities.tsxfor SWR-style entity caching.
Background concerns
- Marketing pages (
/,/blogs/*,/privacy-policy,/terms-of-service) — fully static, no auth checks. Pulled out of the auth middleware via path matchers. - Sitemap —
src/app/sitemap.tsgenerates a sitemap from blog posts and static routes for SEO. - Newsletter —
POST /api/subscribewrites to thesubscriberstable.
Next
- API Routes — every internal Next.js route handler.
- Data Model — Prisma schema and identity flow.
- Self-Host — env vars and deployment.