Skip to content

Add supabase authentication#16

Merged
lojzatran merged 5 commits into
mainfrom
add-auth
Apr 22, 2026
Merged

Add supabase authentication#16
lojzatran merged 5 commits into
mainfrom
add-auth

Conversation

@lojzatran
Copy link
Copy Markdown
Owner

@lojzatran lojzatran commented Apr 22, 2026

Summary by CodeRabbit

  • New Features
    • Full Supabase-based auth: email/password and OAuth, login page, account menu with sign-out, and guarded routes with safe redirect handling
  • Documentation
    • Added detailed integration guide outlining patterns, prerequisites, and quality checks
  • Tests
    • Added unit tests validating safe redirect path handling

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Warning

Rate limit exceeded

@lojzatran has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 49 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 1 minutes and 49 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6ea80475-e177-4d5e-8f66-bdabc3ed66e8

📥 Commits

Reviewing files that changed from the base of the PR and between a4d741f and 4ddaf12.

📒 Files selected for processing (10)
  • README.md
  • apps/website/next.config.js
  • apps/website/specs/index.spec.tsx
  • apps/website/src/app/components/AuthMenu.tsx
  • apps/website/src/app/lib/redirect.spec.ts
  • apps/website/src/app/lib/redirect.ts
  • apps/website/src/app/lib/supabase/server.ts
  • apps/website/src/app/login/components/LoginForm.tsx
  • apps/website/src/app/login/page.tsx
  • apps/website/src/proxy.ts
📝 Walkthrough

Walkthrough

Integrates Supabase Auth into the Next.js website: adds SSR/browser Supabase clients, env validation, login UI and OAuth callback, auth-aware proxy/middleware, safe redirect utility, account menu, and tests for redirect sanitization.

Changes

Cohort / File(s) Summary
Documentation
.agents/skills/nextjs-supabase-auth/SKILL.md
New skill doc describing Supabase Auth + Next.js App Router integration patterns, quality checklist, and example workflows.
Dependencies
apps/website/package.json
Added @supabase/ssr (^0.10.2) and @supabase/supabase-js (^2.104.0).
Env & Config
apps/website/src/app/utils/env.ts, apps/website/src/app/lib/supabase/config.ts
Added Supabase env vars to validation schema; helpers to read/validate NEXT_PUBLIC_SUPABASE_URL and anon/publishable keys with fallback.
Supabase Clients
apps/website/src/app/lib/supabase/server.ts, apps/website/src/app/lib/supabase/client.ts
Implemented SSR server client with cookies adapter and a client-side singleton browser client factory.
Auth Routes & Pages
apps/website/src/app/auth/callback/route.ts, apps/website/src/app/login/page.tsx, apps/website/src/app/login/components/LoginForm.tsx
Added OAuth callback route to exchange code for session; login page + client LoginForm supporting email/password and Facebook OAuth flows, safe next-path handling.
Redirect Utility & Tests
apps/website/src/app/lib/redirect.ts, apps/website/src/app/lib/redirect.spec.ts
Added getSafeNextPath to sanitize redirect targets and corresponding unit tests covering safe/unsafe inputs.
UI: Account Menu Integration
apps/website/src/app/components/AuthMenu.tsx, apps/website/src/app/page.tsx, apps/website/src/app/applications/page.tsx
Added AuthMenu component that queries auth state and displays account/email with sign-out; integrated into main and applications page headers.
Route Protection / Proxy
apps/website/src/proxy.ts
Added a Next.js route handler acting as global auth gate: reads server Supabase client, enforces allowlist, returns 401 for API routes or redirects to /login with safe next, and syncs cookie updates.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant NextServer as Next.js Server
    participant SupaAuth as Supabase Auth

    rect rgba(100, 150, 200, 0.5)
    note over Browser, SupaAuth: OAuth Sign-In Flow
    Browser->>Browser: Click OAuth (Facebook) in LoginForm
    Browser->>SupaAuth: signInWithOAuth requested (browser)
    SupaAuth-->>Browser: Authorization URL
    Browser-->>Browser: Redirect to provider, user authenticates
    Browser->>NextServer: GET /auth/callback?code=...
    NextServer->>SupaAuth: exchangeCodeForSession(code) (server)
    SupaAuth-->>NextServer: Session + Set-Cookie
    NextServer-->>Browser: Redirect to safe next path (cookies set)
    end
Loading
sequenceDiagram
    participant Browser
    participant NextServer as Next.js Server
    participant SupaAuth as Supabase Auth

    rect rgba(150, 200, 100, 0.5)
    note over Browser, NextServer: Protected Route Access via Proxy
    Browser->>NextServer: GET /protected-route (with cookies)
    NextServer->>NextServer: proxy: createServerClient(cookies) -> getUser()
    alt Authenticated
        NextServer-->>Browser: Serve page
    else Unauthenticated
        NextServer-->>Browser: Redirect to /login?next=/protected-route
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Add docker support #14: Modifies apps/website/src/app/utils/env.ts schema—closely related to the Supabase env variable additions in this PR.

Poem

🐰 A rabbit hops through cookie fields at night,
Exchanging codes for sessions under moonlight.
Middleware guards the burrow tight,
Redirects safe and tokens right,
Supabase and Next—what a merry sight! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add supabase authentication' clearly and directly describes the main change—integrating Supabase authentication throughout the application.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-auth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
apps/website/src/app/lib/supabase/config.ts (1)

3-11: Runtime check is redundant given z.url() in the schema.

NEXT_PUBLIC_SUPABASE_URL is validated as a required URL in websiteEnvSchema, so env.NEXT_PUBLIC_SUPABASE_URL cannot be empty here (except when SKIP_ENV_VALIDATION=true). The extra guard is harmless; optional cleanup only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/app/lib/supabase/config.ts` around lines 3 - 11, The runtime
null-check in getSupabaseUrl duplicates validation already enforced by
websiteEnvSchema; remove the redundant guard and simply return
env.NEXT_PUBLIC_SUPABASE_URL (or use a non-null assertion) so the function
becomes a thin accessor; keep a short comment referencing websiteEnvSchema or
SKIP_ENV_VALIDATION if you want to document the exceptional bypass.
apps/website/src/app/lib/supabase/client.ts (1)

6-14: Module-scoped singleton is fine; consider a Database generic.

For end-to-end typing of supabase.from(...), parameterize createBrowserClient<Database>() once types are generated. Non-blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/app/lib/supabase/client.ts` around lines 6 - 14, The
module-scoped singleton should instantiate the Supabase client with the
generated Database types: update the browserClient initialization in
createClient to call createBrowserClient with the Database generic (e.g.,
createBrowserClient<Database>(...)) and type browserClient as ReturnType<typeof
createBrowserClient<Database>> | undefined so all supabase.from(...) calls are
fully typed; ensure you import or reference the generated Database type and
adjust the browserClient declaration and createClient function accordingly.
apps/website/src/app/login/components/LoginForm.tsx (1)

27-50: Nit: reuse getCallbackUrl() in the sign-up flow.

getCallbackUrl() already builds ${origin}/auth/callback?next=... but sign-up duplicates the expression inline. Using the helper keeps the two callback URLs in lockstep.

♻️ Proposed fix
         : await supabase.auth.signUp({
             email: trimmedEmail,
             password,
             options: {
-              emailRedirectTo: `${window.location.origin}/auth/callback?next=${encodeURIComponent(safeNextPath)}`,
+              emailRedirectTo: getCallbackUrl(),
             },
           });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/app/login/components/LoginForm.tsx` around lines 27 - 50,
The sign-up branch in handleSubmit duplicates the callback URL expression
instead of reusing getCallbackUrl(); update the options.emailRedirectTo passed
to supabase.auth.signUp to call getCallbackUrl() (same as the sign-in path uses
getCallbackUrl) so both flows use the single helper; modify the code in
handleSubmit where supabase.auth.signUp is called to replace the inline
`${window.location.origin}/auth/callback?next=...` with getCallbackUrl().
apps/website/src/app/components/AuthMenu.tsx (1)

19-50: Optional: handle loadUser() rejection so the UI doesn't get stuck on "Loading...".

loadUser() is invoked fire-and-forget. If supabase.auth.getUser() rejects (network error, etc.), isLoading is never cleared and the UI sticks on "Loading...". The subsequent onAuthStateChange fires on sign-in/out but not for the initial failed fetch, so the stuck state can persist until the user acts.

♻️ Proposed fix
     async function loadUser() {
-      const {
-        data: { user: currentUser },
-      } = await supabase.auth.getUser();
-
-      if (!isMounted) {
-        return;
-      }
-
-      setUser(currentUser);
-      setIsLoading(false);
+      try {
+        const {
+          data: { user: currentUser },
+        } = await supabase.auth.getUser();
+        if (!isMounted) return;
+        setUser(currentUser);
+      } finally {
+        if (isMounted) setIsLoading(false);
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/app/components/AuthMenu.tsx` around lines 19 - 50, The
loadUser() async call inside the useEffect can throw and leave the component
stuck in loading; update loadUser to catch/reject errors (wrap the
supabase.auth.getUser() call in try/catch) and ensure setIsLoading(false) runs
in both success and failure paths (respecting the isMounted guard) and
optionally log the error; also ensure the cleanup still unsubscribes the
subscription in the return. Target the loadUser function, the useEffect cleanup
block, and the calls to supabase.auth.getUser(), setUser, and setIsLoading.
.agents/skills/nextjs-supabase-auth/SKILL.md (1)

278-291: Nit: specify a language on the workflow fenced code blocks (MD040).

markdownlint flags these as missing a language. Use ```text (or similar) for the plain-text workflow listings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/nextjs-supabase-auth/SKILL.md around lines 278 - 291, The
fenced code blocks in SKILL.md that list the numbered workflow and the Workflow:
plain-text section lack a language specifier; update each opening
triple-backtick to include a language such as text (e.g., change ``` to ```text)
for the blocks containing "1. Database setup (supabase-backend)..." and the
Workflow block so markdownlint MD040 is satisfied.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/website/src/app/lib/redirect.ts`:
- Around line 1-9: getSafeNextPath allows values like "/\\evil.com" because it
only checks for '//' and not backslashes or control characters; update the
function signature to export function getSafeNextPath(nextPath: string |
string[] | undefined): string and add stricter validation: normalize to the
first string, trim it, reject if empty, then ensure it starts with a single
forward slash (value[0] === '/') but not a double slash (value.startsWith('//'))
and also reject if it starts with "/\" or contains any backslash characters
(value.includes('\\')) or any control/whitespace characters (use a regex to
detect [\u0000-\u001F\s]); return '/' for any failing case and return the
original value otherwise, referencing the getSafeNextPath function and the local
variable value in the diff.

In `@apps/website/src/app/lib/supabase/server.ts`:
- Around line 1-20: The setAll implementation in createClient can throw when
called from a Server Component because cookieStore.set may throw in that
environment; wrap the body of setAll in a try-catch and swallow or log the error
(do not rethrow) so server-side renders won't crash; specifically update
createClient's cookies.setAll (the function that iterates cookiesToSet and calls
cookieStore.set) to catch any exceptions and no-op (or log) them, preserving the
existing behavior for getAll and relying on your middleware (proxy.ts) to
refresh sessions.

In `@apps/website/src/app/login/page.tsx`:
- Around line 7-24: LoginPage currently treats searchParams as a plain object
but in Next.js 16 it is a Promise; update the LoginPageProps to type
searchParams?: Promise<{ next?: string; error?: string }> and inside the async
LoginPage function await it (e.g. const resolvedSearchParams = await
searchParams) then use resolvedSearchParams when calling getSafeNextPath and
when checking/displaying the error; ensure all references to searchParams
(including the redirect call using getSafeNextPath and the later error display)
are switched to the awaited resolvedSearchParams variable.

In `@apps/website/src/proxy.ts`:
- Around line 20-60: The early-return branches create new NextResponse objects
(the API error via NextResponse.json, the unauthenticated redirect, and the
post-login redirect) and thus drop any refreshed cookies written into the local
response by the supabase client (setAll callback after supabase.auth.getUser());
fix by copying all cookies from the original response to the new response before
returning, e.g., implement a small helper like withRefreshedCookies(newResp,
response) that iterates response.cookies and sets them onto newResp, and call it
in the branches that return NextResponse.json or NextResponse.redirect (the
branches using isApiPath(...), the unauthenticated redirect that builds
loginUrl, and the redirect when user && pathname === '/login').

---

Nitpick comments:
In @.agents/skills/nextjs-supabase-auth/SKILL.md:
- Around line 278-291: The fenced code blocks in SKILL.md that list the numbered
workflow and the Workflow: plain-text section lack a language specifier; update
each opening triple-backtick to include a language such as text (e.g., change
``` to ```text) for the blocks containing "1. Database setup
(supabase-backend)..." and the Workflow block so markdownlint MD040 is
satisfied.

In `@apps/website/src/app/components/AuthMenu.tsx`:
- Around line 19-50: The loadUser() async call inside the useEffect can throw
and leave the component stuck in loading; update loadUser to catch/reject errors
(wrap the supabase.auth.getUser() call in try/catch) and ensure
setIsLoading(false) runs in both success and failure paths (respecting the
isMounted guard) and optionally log the error; also ensure the cleanup still
unsubscribes the subscription in the return. Target the loadUser function, the
useEffect cleanup block, and the calls to supabase.auth.getUser(), setUser, and
setIsLoading.

In `@apps/website/src/app/lib/supabase/client.ts`:
- Around line 6-14: The module-scoped singleton should instantiate the Supabase
client with the generated Database types: update the browserClient
initialization in createClient to call createBrowserClient with the Database
generic (e.g., createBrowserClient<Database>(...)) and type browserClient as
ReturnType<typeof createBrowserClient<Database>> | undefined so all
supabase.from(...) calls are fully typed; ensure you import or reference the
generated Database type and adjust the browserClient declaration and
createClient function accordingly.

In `@apps/website/src/app/lib/supabase/config.ts`:
- Around line 3-11: The runtime null-check in getSupabaseUrl duplicates
validation already enforced by websiteEnvSchema; remove the redundant guard and
simply return env.NEXT_PUBLIC_SUPABASE_URL (or use a non-null assertion) so the
function becomes a thin accessor; keep a short comment referencing
websiteEnvSchema or SKIP_ENV_VALIDATION if you want to document the exceptional
bypass.

In `@apps/website/src/app/login/components/LoginForm.tsx`:
- Around line 27-50: The sign-up branch in handleSubmit duplicates the callback
URL expression instead of reusing getCallbackUrl(); update the
options.emailRedirectTo passed to supabase.auth.signUp to call getCallbackUrl()
(same as the sign-in path uses getCallbackUrl) so both flows use the single
helper; modify the code in handleSubmit where supabase.auth.signUp is called to
replace the inline `${window.location.origin}/auth/callback?next=...` with
getCallbackUrl().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c04b9d38-fc57-44cf-905c-507767ba081e

📥 Commits

Reviewing files that changed from the base of the PR and between faf0b15 and a23749f.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • .agents/skills/nextjs-supabase-auth/SKILL.md
  • apps/website/package.json
  • apps/website/src/app/applications/page.tsx
  • apps/website/src/app/auth/callback/route.ts
  • apps/website/src/app/components/AuthMenu.tsx
  • apps/website/src/app/lib/redirect.ts
  • apps/website/src/app/lib/supabase/client.ts
  • apps/website/src/app/lib/supabase/config.ts
  • apps/website/src/app/lib/supabase/server.ts
  • apps/website/src/app/login/components/LoginForm.tsx
  • apps/website/src/app/login/page.tsx
  • apps/website/src/app/page.tsx
  • apps/website/src/app/utils/env.ts
  • apps/website/src/proxy.ts

Comment thread apps/website/src/app/lib/redirect.ts Outdated
Comment thread apps/website/src/app/lib/supabase/server.ts
Comment thread apps/website/src/app/login/page.tsx
Comment thread apps/website/src/proxy.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/website/src/app/lib/redirect.spec.ts`:
- Around line 8-15: The test block in redirect.spec.ts misplaces the array
assertion: move the expect(getSafeNextPath([' /jobs ',
'/ignored'])).toBe('/jobs') out of the "returns root for unsafe paths" it-block
and into the "returns safe value" test (or create a new it-case named e.g.
"normalizes array input"), so each test description matches its assertions;
while there, add extra assertions covering other guarded cases
(expect(getSafeNextPath(undefined)).toBe('/'),
expect(getSafeNextPath('jobs')).toBe('/'),
expect(getSafeNextPath(['//evil.com'])).toBe('/'), and
expect(getSafeNextPath('/path\twithtab')).toBe('/')) to fully lock down
getSafeNextPath's contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1b51faad-0abb-4621-8b3c-28c166a4e70c

📥 Commits

Reviewing files that changed from the base of the PR and between a23749f and a4d741f.

📒 Files selected for processing (6)
  • apps/website/src/app/lib/redirect.spec.ts
  • apps/website/src/app/lib/redirect.ts
  • apps/website/src/app/lib/supabase/config.ts
  • apps/website/src/app/lib/supabase/server.ts
  • apps/website/src/app/login/page.tsx
  • apps/website/src/proxy.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/website/src/app/lib/supabase/config.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/website/src/app/lib/redirect.ts
  • apps/website/src/app/lib/supabase/server.ts
  • apps/website/src/app/login/page.tsx
  • apps/website/src/proxy.ts

Comment thread apps/website/src/app/lib/redirect.spec.ts
@lojzatran lojzatran force-pushed the add-auth branch 3 times, most recently from 5e93d08 to 45035bf Compare April 22, 2026 20:51
@lojzatran lojzatran merged commit 1fc5f93 into main Apr 22, 2026
4 checks passed
@lojzatran lojzatran deleted the add-auth branch April 22, 2026 21:15
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