Skip to content

fix(security): restrict Supabase redirect wildcard to project namespace#195

Closed
EmiyaKiritsugu3 wants to merge 23 commits into
mainfrom
feat/aluno-ui-10-fixes
Closed

fix(security): restrict Supabase redirect wildcard to project namespace#195
EmiyaKiritsugu3 wants to merge 23 commits into
mainfrom
feat/aluno-ui-10-fixes

Conversation

@EmiyaKiritsugu3

@EmiyaKiritsugu3 EmiyaKiritsugu3 commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Problem

** glob in additional_redirect_urls (**.vercel.app) matches every Vercel deployment worldwide.
Latent risk: if any auth flow ever passes user-influenced redirectTo, tokens could be intercepted.

Fix

https://smartmanagementsystem-*-emiyakiritsugu3s-projects.vercel.app/auth/callback

* matches non-separator chars only (the hash portion). Team slug suffix is literal.
Only our project's Vercel preview deploys match — not any arbitrary vercel.app subdomain.

Already synced to cloud

supabase config push applied to production project (zqrlsupezgltofwotven).


Summary by cubic

Restricts Supabase Auth redirects to our Vercel project namespace to prevent token interception. Adds a validated /auth/callback flow and safer callback URL resolution for magic-link and OAuth.

  • Bug Fixes

    • Scoped supabase/config.toml additional_redirect_urls to https://smartmanagementsystem-*-emiyakiritsugu3s-projects.vercel.app/auth/callback plus the explicit production URL; removed the global vercel.app wildcard.
    • Added /auth/callback route and shared validateNext helper to block open redirects, traversal, and protocol-relative URLs; middleware bypass for this route.
    • Server actions for magic link and OAuth build callback URLs from NEXT_PUBLIC_APP_URL with NEXT_PUBLIC_VERCEL_URL and localhost fallbacks.
    • .env.example updated with preview/production guidance; auth/login tests and E2E selectors adjusted to the new flow.
  • Migration

    • Set NEXT_PUBLIC_APP_URL to https://smartmanagementsystem.vercel.app in production; leave unset on previews to use NEXT_PUBLIC_VERCEL_URL.
    • No change for local dev; http://localhost:3000 is allowlisted.
    • Supabase cloud allowlist has been updated.

Written for commit 641d57b. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added Aluno portal sign-in options, including magic-link email login and Google, GitHub, and Apple sign-in.
    • Added a profile page entry and a new workout session flow with start, finish, and cancel actions.
    • Expanded the student workouts view to show exercise names and a cleaner landing page title.
  • Bug Fixes

    • Improved login handling to avoid exposing account-related errors and reduce redirect issues.
    • Strengthened auth redirect behavior to keep users on safe, expected destinations.
  • Documentation

    • Added and updated design and status docs for the latest UI and auth changes.

EmiyaKiritsugu3 and others added 23 commits July 7, 2026 10:47
Q01: aluno-header aria-label 'Perfil do usuario' → 'Menu do aluno'
Q02: dashboard achievements justify-around → justify-center
Q03: landing page 'Academia Five Star' → 'Five Star Gym'
cleanup: remove stale .claude/workflows/ and old audit plan
A01: CardTitle <div> → <h3> semantic heading
A02: login email/password autocomplete + type attributes
A03: auth errors inline with role=alert (replaces toast)
A04: circular-progress SVG viewBox + aria-hidden
U01: card-treino press feedback (active:scale + focus-visible ring)
U02: meus-treinos exercise names truncated below count
U03: enable Perfil dropdown + /aluno/perfil stub page
…-md buttons

P01: align button spec (rounded-full → rounded-md matches code)
P02: document Inter/Outfit font split, remove stale SF Pro reference
- --font-headline: 'Outfit', 'Inter', sans-serif
- Typography table: add dedicated Headlines row (Outfit)
…b/magic-link, middleware bypass

Wave 1: AUTH_CALLBACK_PATH constant, middleware excludes /auth/callback
Wave 2: signInWithMagicLink, signInWithGoogle, signInWithGitHub Server Actions
+ WorkoutSession integration on aluno dashboard
/aluno/login: social buttons (inline SVGs) + magic link form + separators
/login: social buttons between form and footer
Student + admin login pages now offer Google, GitHub, and Apple
All OAuth providers share the same architecture (Server Action → callback)
- .env.example: fix NEXT_PUBLIC_APP_URL port 3000→3001 (prevents auth break)
- admin login: replace Globe/Code with brand SVGs (matches student page)
- admin login: add setError() in OAuth handlers (was silently swallowing errors)
- route.test.ts: 5 tests (code exchange, redirect validation, open-redirect)
- auth.test.ts: 9 tests (magic link, Google/GitHub/Apple OAuth)
signInWithGoogle/GitHub/Apple now pass next=/dashboard so callback
redirects correctly after authentication
…header

- page.test: 'Academia Five Star' → 'Five Star Gym' (matches Q03 brand rename)
- aluno-header.test: mock useRouter from next/navigation (UserMenu now uses router for Perfil)
P0: Extract shared `validateNext` to `src/lib/auth-redirect.ts` so the
OAuth callback route and auth server actions cannot drift — normalizes
`next` via `URL.pathname` (defeats `/aluno/../admin` traversal), enforces
path-segment boundaries (`/aluno$` ok, `/alunox` rejected), and blocks
protocol-relative `//` open redirects. Add Sentry.captureException on
every auth failure path (magic-link + 3 OAuth providers + callback
exchange). Add Zod email validation in `signInWithMagicLink`.

P1: Wrap magic-link submit in try/finally so `setMagicLinkLoading(false)`
always runs even if the server action throws (button no longer stuck
disabled). Stack admin social buttons (drop `sm:flex-row`) — `max-w-sm`
card (384px) cannot fit 3 long-label buttons in a row without overflow.

P2: Dedup 3 OAuth handlers per login page into `handleOAuthSignIn` /
`handleOAuthLogin` helpers; admin variant clears stale password error via
`setError('')` before OAuth attempt. Dedup 3 `signInWith{Google,GitHub,Apple}`
server actions into shared `signInWithOAuth(provider, next)`. Rename
stale "Aegis Fitness OS" template brand to "Five Star Gym" in DESIGN.md.
Fix plan doc P01 radius contradiction (spec standardizes `rounded-md`,
not `rounded-full`) and add `npm run e2e` to the gate command.

Tests: 1151 pass, 0 fail. Typecheck clean.

Co-Authored-By: Claude <noreply@anthropic.com>
sfU (account enumeration): the signUp fallback in `handleFormSubmit`
surfaces raw Supabase error messages. When an attacker enters a
registered email with a wrong password, signIn fails with "Invalid
login credentials" and the code falls back to signUp, which throws
"User already registered" — leaking account existence to the caller.
Detect that class of message via regex and surface the same generic
credential error as a failed login, so the error reveals nothing.

se1 (stale dashboard after save): `registrarHistoricoTreinoAction` calls
`revalidatePath('/aluno/dashboard')`, which invalidates the cache but
does not update this already-mounted client component's `aluno` props.
Add `router.refresh()` after a successful save so the RSC payload is
re-fetched and XP/level/streak/treinosNoMes reflect the new state
without a manual reload. Mock `next/navigation` in
`dashboard-client.test.tsx` to satisfy `useRouter`.

Tests: 1151 pass, 0 fail. Typecheck + lint clean.

Co-Authored-By: Claude <noreply@anthropic.com>
- auth-redirect.ts: wrap new URL() in try/catch; malformed next returns '/'
  instead of 500 (cubic PMD5t P1)
- callback/route.ts: strip query string from Sentry callbackUrl to avoid
  persisting OAuth code in third-party logs (cubic PMD50 P1)
- actions/auth.ts: replace silent localhost:3001 fallback with explicit
  throw when NEXT_PUBLIC_APP_URL unset, read lazily at call time (coderabbit sfi)
- .env.example: move NEXT_PUBLIC_APP_URL above NODE_ENV (coderabbit PLDGo)

Co-Authored-By: Claude <noreply@anthropic.com>
… + a11y

Address remaining cubic/coderabbit deferred review threads:

- card-treino: focus-visible→focus-within on exercise row divs (non-focusable
  container; focus-within fires when child button/link receives keyboard focus)
- .env.example: revert NEXT_PUBLIC_APP_URL to :3000 (matches `next dev` default;
  :3001 broke test env + dev parity)
- auth: split OAuth return contract — dedicated `url`/`error` fields replace the
  brittle `startsWith('http')` heuristic; client never disambiguates via error
  string. Both login pages + auth.test.ts updated to the new contract.
- auth: lazy NEXT_PUBLIC_APP_URL read inside callbackUrl() (not module load) so
  test harnesses injecting env after import still work.
- meus-treinos-client.test: assert exercise names render, sliced to 3, joined;
  omit name list when 0 exercises (O8Gaq).
- dashboard-client.test: mock @/lib/actions/treinos (unblocks Prisma DATABASE_URL
  import-time failure) + WorkoutSession gate; cover WorkoutSession state toggle
  (O9FaX), router.refresh() after registrarHistorico success (PMTVI), error path.
- aluno/login.test: account-enumeration guard — "User already registered" and
  "User has already been registered" both masked to generic credential error,
  no leak, no push (PMTVN).

All 112 test files / 1159 tests pass. Typecheck clean.

Co-Authored-By: Claude <noreply@anthropic.com>
Document final commit 641831d: P2/P3 test coverage (O8Gaq, O9FaX, PMTVI,
PMTVN) + a11y (O8GbK) + .env parity (O9Faj) + OAuth url/error contract
(O9Fap, O9Fa4). 8 cubic threads resolved, 0 unresolved. 112 files / 1159 tests.

Co-Authored-By: Claude <noreply@anthropic.com>
20/21 E2E failures stemmed from selectors resolving to multiple elements
under Playwright strict mode:

- helpers/auth.ts + auth.spec.ts: getByRole('button', {name: /entrar|login/i})
  matched 3 buttons (submit + Google + GitHub). Anchored to
  /^entrar( no sistema)?$/i to hit only the staff "Entrar no Sistema" and
  aluno "Entrar" submit buttons.
- helpers/auth.ts: getByLabel(/senha|password/i) on /aluno/login resolved to
  the visibility-toggle button aria-label "Mostrar senha" instead of the
  RHF-controlled input (FormLabel has no htmlFor binding). Switched to
  input[type="password"] locator, which works on both /login (classic
  label) and /aluno/login (RHF).
- student-portal.spec.ts: getByRole('heading', {name:'Meus Treinos'}) hit 2
  elements — PageHeader h1 plus a nav/sidebar mirror reusing i18n
  myWorkouts. Pinned level:1.

Result: 21/21 pass (1.8m).

Co-Authored-By: Claude <noreply@anthropic.com>
…w/prod)

Design for fixing silent magic-link + OAuth auth failures in Vercel
Preview/Production. Root causes: NEXT_PUBLIC_APP_URL set to empty string in
Vercel Production (empty-string defeats ?? fallback), missing VERCEL_URL
tier, supabase/config.toml allowlist mismatches (127.0.0.1 vs localhost,
https-on-local, no localhost/vercel.app glob/prod), stale cloud Supabase
allowlist. callbackUrl() rewritten to tiered resolution (explicit → VERCEL_URL
→ localhost), config.toml allowlist fixed, Vercel prod env set. Password login
unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Context7 audit found Fix B glob https://*-*.vercel.app/auth/callback wrong:

- Supabase * matches non-separator chars (separators . and /); - is not a

  separator so * technically spans it, but ambiguous for multi-hyphen

  preview hostnames (smartmanagementsystem-6rfj2hapr-emiyakiritsugu3s-projects

  .vercel.app).

- ** matches any sequence of chars — documented Supabase-recommended pattern

  for Netlify/Vercel preview URLs (supabase.com/docs/guides/auth/redirect-urls).

- Supabase docs recommend exact paths for production; explicit prod URL retained.

Fix E references 'mirror Fix B entries' so cloud allowlist propagates automatically.

Co-Authored-By: Claude <noreply@anthropic.com>
…fallback

Fix A — callbackUrl() tiered resolution (src/lib/actions/auth.ts):

- Tier 1: NEXT_PUBLIC_APP_URL?.trim() — empty string converted to null

  via explicit || null so ?? falls through to VERCEL_URL.

- Tier 2: VERCEL_URL — Vercel auto-injected deploy hostname, prefix https://.

- Tier 3: http://localhost:3000 — zero-config local dev default.

- Removed throw-on-unset; local dev works without any env vars.

Fix B — supabase/config.toml allowlist: site_url → localhost:3000,

additional_redirect_urls with https://**.vercel.app/auth/callback glob

(** matches any chars per Context7 Supabase docs) + explicit prod URL.

Fix F — .env.example: annotated NEXT_PUBLIC_APP_URL with preview/prod

guidance (leave unset for VERCEL_URL fallback in preview).

Fix G — auth.test.ts: 5 new tier-resolution tests (14 total, all pass)

through public signInWithMagicLink API; afterEach cleans VERCEL_URL only.

Quality: tsc strict clean, 1164/1164 tests green.

Co-Authored-By: Claude <noreply@anthropic.com>
Matched local [auth.email] and [auth.mfa.totp] to cloud production values

so supabase config push only changes redirect URLs:

- auth.email: enable_confirmations=true, max_frequency=1m0s, otp_length=8

- auth.mfa.totp: enroll_enabled=true, verify_enabled=true

Co-Authored-By: Claude <noreply@anthropic.com>
Context7 audit of Supabase official docs (supabase.com/docs/guides/auth/redirect-urls)

shows the recommended pattern:

  process?.env?.NEXT_PUBLIC_VERCEL_URL

VERCEL_URL is Vercel's system env var; NEXT_PUBLIC_VERCEL_URL is the Next.js

framework-specific equivalent that Supabase explicitly recommends.

Changes:

- auth.ts: process.env.VERCEL_URL → process.env.NEXT_PUBLIC_VERCEL_URL

- auth.test.ts: all VERCEL_URL refs → NEXT_PUBLIC_VERCEL_URL (tests + afterEach)

- .env.example: comment updated to NEXT_PUBLIC_VERCEL_URL

- spec: code snippet + tier descriptions + Fix D all updated

Quality: tsc strict clean, 14/14 auth tests green.

Co-Authored-By: Claude <noreply@anthropic.com>
Replaced https://**.vercel.app/auth/callback with

https://smartmanagementsystem-*-emiyakiritsugu3s-projects.vercel.app/auth/callback

** glob matched every Vercel deployment worldwide — latent risk if any

auth flow ever passes user-influenced redirectTo. Scoped to team namespace:

* matches non-sep chars (hash portion), -team.vercel.app is literal suffix.

Explicit production URL retained for safety.

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
smartmanagementsystem Building Building Preview, Comment Jul 8, 2026 12:08pm

@ecc-tools

ecc-tools Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 37ea5bbc-a54a-4d97-8ebf-a71d53d8b066

📥 Commits

Reviewing files that changed from the base of the PR and between 6f7dfc5 and 641d57b.

📒 Files selected for processing (32)
  • .env.example
  • DESIGN.md
  • docs/CURRENT-STATE.md
  • docs/superpowers/plans/2026-05-14-fix-npm-audit-vulnerabilities.md
  • docs/superpowers/plans/2026-07-07-aluno-ui-10-fixes.md
  • docs/superpowers/specs/2026-07-08-auth-redirect-fix-design.md
  • src/app/aluno/aluno-header.test.tsx
  • src/app/aluno/aluno-header.tsx
  • src/app/aluno/dashboard/dashboard-client.test.tsx
  • src/app/aluno/dashboard/dashboard-client.tsx
  • src/app/aluno/login/page.test.tsx
  • src/app/aluno/login/page.tsx
  • src/app/aluno/meus-treinos/meus-treinos-client.test.tsx
  • src/app/aluno/meus-treinos/meus-treinos-client.tsx
  • src/app/aluno/perfil/page.tsx
  • src/app/auth/callback/route.test.ts
  • src/app/auth/callback/route.ts
  • src/app/login/page.tsx
  • src/app/page.test.tsx
  • src/app/page.tsx
  • src/components/dashboard/aluno/card-treino.tsx
  • src/components/ui/card.tsx
  • src/components/ui/circular-progress.tsx
  • src/lib/actions/auth.test.ts
  • src/lib/actions/auth.ts
  • src/lib/auth-redirect.ts
  • src/lib/constants.ts
  • src/utils/supabase/middleware.ts
  • supabase/config.toml
  • tests/e2e/helpers/auth.ts
  • tests/e2e/specs/auth.spec.ts
  • tests/e2e/specs/student-portal.spec.ts

Walkthrough

This PR fixes OAuth/magic-link redirect resolution across environments (new callbackUrl/validateNext logic, auth callback route, Supabase config/env updates), adds a workout session flow to the aluno dashboard, applies aluno UI/accessibility fixes (profile navigation, card heading semantics, exercise list truncation), rebrands landing page text, and adds design/plan documentation.

Changes

Auth Redirect & OAuth/Magic-Link Fix

Layer / File(s) Summary
Redirect validation and constants
src/lib/auth-redirect.ts, src/lib/constants.ts
Adds validateNext() allowlist-based open-redirect guard and AUTH_CALLBACK_PATH constant.
Auth server actions
src/lib/actions/auth.ts, src/lib/actions/auth.test.ts
Adds callbackUrl() tiered resolution, signInWithMagicLink, signInWithOAuth and provider wrappers, with unit tests.
Auth callback route
src/app/auth/callback/route.ts, src/app/auth/callback/route.test.ts
Implements GET /auth/callback to validate next, exchange the code for a session, bridge cookies, and log failures to Sentry.
Middleware bypass and config/env
src/utils/supabase/middleware.ts, supabase/config.toml, .env.example
Bypasses auth middleware for /auth/callback, expands Supabase redirect allowlist and email/MFA settings, updates env comments.
Login pages OAuth/magic-link UI
src/app/login/page.tsx, src/app/aluno/login/page.tsx, src/app/aluno/login/page.test.tsx
Adds social OAuth buttons, magic-link form, password-visibility toggle, and anti-enumeration error normalization.
E2E login selector fixes
tests/e2e/helpers/auth.ts, tests/e2e/specs/auth.spec.ts
Uses anchored button-name regex and type-based password locators to avoid ambiguous matches.

Estimated code review effort: 4 (Complex) | ~60 minutes

Aluno Dashboard Workout Session Flow

Layer / File(s) Summary
Dashboard session state and handler
src/app/aluno/dashboard/dashboard-client.tsx
Adds treinoEmSessao state, handleFinishWorkout calling registrarHistoricoTreinoAction, notifications, and router.refresh().
CardTreino start button
src/components/dashboard/aluno/card-treino.tsx
Adds optional onStartWorkout prop and "Iniciar Treino" button; adds interaction styling to exercise rows.
Dashboard/session tests
src/app/aluno/dashboard/dashboard-client.test.tsx
Mocks WorkoutSession and registrarHistoricoTreinoAction, tests session toggling and refresh/notification behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Aluno UI Fixes, Accessibility, and Landing Rebrand

Layer / File(s) Summary
Profile navigation and perfil page
src/app/aluno/aluno-header.tsx, src/app/aluno/aluno-header.test.tsx, src/app/aluno/perfil/page.tsx
Wires the profile menu item to navigate to /aluno/perfil via a new stub page.
Card/SVG accessibility
src/components/ui/card.tsx, src/components/ui/circular-progress.tsx
Renders CardTitle as <h3> and marks the progress SVG aria-hidden.
Meus Treinos exercise names
src/app/aluno/meus-treinos/meus-treinos-client.tsx, .test.tsx
Displays a truncated list of up to three exercise names when present.
Landing brand rename and heading fix
src/app/page.tsx, src/app/page.test.tsx, tests/e2e/specs/student-portal.spec.ts
Renames hero text to "Five Star Gym" and disambiguates a duplicated heading in e2e assertions.

Estimated code review effort: 2 (Simple) | ~15 minutes

Documentation Updates

Layer / File(s) Summary
Design system reference
DESIGN.md
Documents dark-mode theme tokens, animation variants, and layout guidance.
Status and plan docs
docs/CURRENT-STATE.md, docs/superpowers/plans/2026-07-07-aluno-ui-10-fixes.md
Records PR status and a phased UI fixes plan with gates.
Auth redirect fix spec
docs/superpowers/specs/2026-07-08-auth-redirect-fix-design.md
Documents root causes, fix plan, verification, and rollback for the redirect fix.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant CallbackRoute as /auth/callback
  participant Supabase
  participant Sentry

  Browser->>CallbackRoute: GET ?code&next
  CallbackRoute->>CallbackRoute: validateNext(next)
  CallbackRoute->>Supabase: exchangeCodeForSession(code)
  alt success
    Supabase-->>CallbackRoute: session/cookies
    CallbackRoute-->>Browser: redirect(validatedNext)
  else failure
    CallbackRoute->>Sentry: captureException
    CallbackRoute-->>Browser: redirect(/login?error=auth_callback_failed)
  end
Loading
sequenceDiagram
  participant CardTreino
  participant DashboardClient
  participant TreinosAction as registrarHistoricoTreinoAction
  participant Router

  CardTreino->>DashboardClient: onStartWorkout()
  DashboardClient->>DashboardClient: setTreinoEmSessao(true)
  DashboardClient->>DashboardClient: render WorkoutSession
  WorkoutSession->>DashboardClient: onFinish(payload)
  DashboardClient->>TreinosAction: registrarHistoricoTreinoAction(payload)
  TreinosAction-->>DashboardClient: success/error
  DashboardClient->>Router: refresh() (on success)
Loading

Possibly related PRs

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/aluno-ui-10-fixes

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.

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