Skip to content

feat: Supabase auth, legal pages, and backend env fix - #2

Merged
afrinxnahar merged 5 commits into
anode-ai:mainfrom
afrinxnahar:feat/auth-and-legal-pages
Aug 23, 2026
Merged

afrinxnahar merged 5 commits into
anode-ai:mainfrom
afrinxnahar:feat/auth-and-legal-pages

Conversation

@afrinxnahar

Copy link
Copy Markdown
Contributor

What

Three pieces of work, each in its own commit.

Supabase auth — sign up, login, forgot password and reset password, with a guard over the dashboard. Email/password and Google OAuth.

Privacy Policy and Terms of Service at /privacy and /terms, linked from the footer.

Backend boot fix — the API crashed on startup because it looked for .env in the wrong directory.

Auth notes

Sessions are cookie-based via @supabase/ssr. The middleware refreshes them on every request and authorizes with getClaims(), which verifies the JWT signature — getSession() is not safe for this on the server. The dashboard layout re-checks server-side so the guard sits next to the data it protects rather than relying on routing alone.

Security choices worth reviewing:

  • Credentials are validated with zod inside the server actions, so bypassing the browser form hits the same checks. Verified by stripping required/type off the form and submitting garbage — the server still rejected it.
  • Sign up and password reset return identical responses whether or not an account exists, so neither can be used to enumerate registered emails.
  • ?next= is restricted to same-origin paths, so it cannot be turned into an open redirect. Covered by e2e/auth-guard.spec.ts.
  • With no Supabase credentials configured the guard fails closed — protected routes redirect, the marketing site keeps working.

A single callback route handles all three link styles Supabase can send (OAuth and email confirmation via ?code=, recovery via ?token_hash=) rather than separate confirm and callback routes.

Backend fix

The API died on boot with Drizzle database connection instance is not initialized yet!. @anode/supabase reads DATABASE_URL when it is first required, and the env was never loaded: main.ts resolved .env from process.cwd(), which is apps/backend under pnpm --filter backend dev. Everything else in the repo expects it at the monorepo root — drizzle.config.ts uses '../../.env', ConfigModule uses __dirname/../../../.env, and both READMEs say project root.

Fixed on the cheaper side by resolving from __dirname in main.ts, which lines it up with the rest and works from src and dist alike. Also adds the root .env.example the README already tells you to copy, and reworks the DI error so it names DATABASE_URL instead of implying a lifecycle race.

Verified

  • Both /dashboard and /reset-password redirect to /login when signed out; static assets and the public widget API still bypass the guard
  • Server-side validation holds when browser validation is stripped
  • 5/5 e2e/auth-guard.spec.ts tests pass
  • Backend boots clean with a root .env, and gives the new actionable error without one
  • No console errors, no horizontal overflow at 375px or 1280px on any new page
  • Design tokens confirmed against the existing system: accent oklch(0.7 0.2 45), 0px radius, Bebas headings, mono uppercase labels

Reviewer notes

  • The legal copy is a starting template, not legal advice. It describes how Anode actually works rather than generic boilerplate, but it has not been lawyer-reviewed — the jurisdiction-sensitive parts (liability cap, governing law, GDPR/CCPA specifics) need counsel. There is a note to that effect at the top of lib/legal-content.ts.
  • Auth does nothing until NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY are set, and Google sign-in additionally needs the provider enabled in Supabase with /auth/callback allow-listed there and in the Google Cloud OAuth client.
  • apps/frontend/lib/supabase.ts is dead code — zero importers, and it does not typecheck (@anode/supabase is not a frontend dependency). Left alone deliberately; happy to delete it in a follow-up.
  • The footer's Product links are bare hashes (#modes), so they do nothing from /privacy and /terms. That is why the legal layout carries its own back-to-site link instead of reusing AnodeFooter. Changing them to /#modes would fix it globally.

Adds sign up, login, forgot password and reset password pages backed by
Supabase, plus a route guard over the dashboard.

Sessions are cookie-based via @supabase/ssr. The middleware refreshes them
on every request and uses getClaims(), which verifies the JWT signature —
getSession() is not safe for authorization on the server. The dashboard
layout re-checks server-side so the guard sits next to the data it protects
rather than relying on routing alone. When Supabase credentials are absent
the guard fails closed: protected routes redirect, the marketing site keeps
working.

Credentials are validated with zod in the server actions, so bypassing the
browser form still hits the same checks. The password reset and sign up
flows return identical responses whether or not an account exists, to avoid
leaking which emails are registered. Redirect targets are restricted to
same-origin paths so ?next= cannot be used as an open redirect.

One callback route handles all three link styles Supabase can send — OAuth
and email confirmation via ?code=, and recovery via ?token_hash= — instead
of separate confirm and callback routes.

Pages follow the existing editorial styling: zero radius, accent orange,
Bebas headings and mono uppercase labels.
Both documents describe how Anode actually works — Supabase, Gemini,
Hugging Face embeddings, Vercel analytics, the setup-fee pricing model and
Google sign-in — rather than generic boilerplate.

Copy lives as data in lib/legal-content.ts, matching the existing
anode-data.ts pattern, and both pages share one renderer. Adding a clause
is a data edit and the two documents cannot drift apart in styling.

Layout reuses the site's numbered-section idiom: accent eyebrow, Bebas
heading, zero-padded numbering in the left gutter and mono body copy.

The text is a starting template and has not been reviewed by a lawyer;
there is a note to that effect at the top of the content file.
Turns the hero's secondary CTA from "Try The Demo" into "Get Started"
pointing at /signup, keeping the scramble-on-hover treatment.

Adds an Account column to the footer for sign in and create account, and
puts the privacy policy and terms links in the bottom bar beside the
copyright. The footer grid goes to five columns on desktop and still pairs
cleanly at two columns on mobile.
The backend crashed on boot with "Drizzle database connection instance is
not initialized yet!" because @anode/supabase reads DATABASE_URL when it is
first required, and the env was never loaded.

main.ts resolved .env from process.cwd(), which is apps/backend under
pnpm --filter backend dev. Everything else in the repo expects it at the
monorepo root: drizzle.config.ts uses '../../.env', ConfigModule uses
__dirname/../../../.env, and both READMEs say project root. Resolving from
__dirname lines main.ts up with the rest and works from src and dist alike.

Also adds the root .env.example the README already tells you to copy, and
reworks the DI error so it names DATABASE_URL instead of implying a
lifecycle race.
Running the e2e suite drops test-results/ and playwright-report/ into the
repo root. Neither belongs in version control.
@afrinxnahar
afrinxnahar merged commit ad854dc into anode-ai:main Aug 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant