Skip to content

fix(web): gate the dashboard on the server, repair two nav links - #19

Open
hallelx2 wants to merge 1 commit into
mainfrom
halleluyaholudele/dashboard-server-auth-and-nav-links
Open

fix(web): gate the dashboard on the server, repair two nav links#19
hallelx2 wants to merge 1 commit into
mainfrom
halleluyaholudele/dashboard-server-auth-and-nav-links

Conversation

@hallelx2

@hallelx2 hallelx2 commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Three issues you reported, one of them structural.

1 · The dashboard had no server-side authentication

(dashboard)/layout.tsx was a client component calling useSession() and redirecting from a useEffect. That is not protection. The check runs only after the server has sent the HTML and the browser has downloaded, parsed and hydrated the whole dashboard bundle — so an unauthenticated visitor received the entire screen, saw a spinner, and was then bounced.

What leaked: route existence, layout, navigation, feature set. A user with an expired session also got a flash of the UI before the redirect.

Now a server component — resolve the session, redirect if absent. Proof it is live: every /dashboard route moved from static to ƒ (dynamic) in the build output, which only happens because the layout now reads request headers.

Customer data was never exposed

Worth stating plainly, because "no auth on the dashboard" sounds worse than it was. Every /api/dashboard/* route proxies through forwardToCP, which returns 401 when the vls_session cookie is missing and otherwise forwards it to the control plane to validate. There is no service-token fallback for user data — the apiKey path is only for the shared demo corpus.

So the leak was the shell, not the contents. A real defect, not an incident.

I checked this specifically before writing it up, because my first three greps all pointed the wrong way — lib/server-auth.ts exists, and the route handlers authenticate indirectly through the proxy rather than by calling a session helper directly, so a naive search for getSession returns nothing.

Scope

This is a coarse gate. Per-page checks remain the standard — a route's protection should be readable in its own file — but every page below is still "use client" and cannot do it yet. Converting them is follow-up work, not this PR.

2 · Docs pointed at /dashboard

Anyone looking for documentation was sent into the product. Once the gate above landed, they would have been bounced to /login instead.

Now points at docs.vectorless.store, overridable via NEXT_PUBLIC_DOCS_URL so a preview deployment can target a staging docs site without a code change.

3 · Pricing, FAQ and How-it-works were dead on /whitepaper

The sections do exist and carry the right ids — #pricing, #faq, #how — but only on the landing page, and Nav renders on both it and /whitepaper. A bare #pricing resolves against the current document, so on the whitepaper page it did nothing.

Changed to /#pricing so the links work from either page: scroll on the landing page, navigate-then-scroll from elsewhere.

Verification

tsc --noEmit clean, next build clean, all dashboard routes confirmed dynamic.

Not verified: the redirect behaviour against a live control plane — getServerSession calls /admin/v1/auth/me, which I have no session cookie to exercise here. Worth a manual check on preview: hit /dashboard logged out and confirm you get a redirect rather than a spinner.

Summary by Sourcery

Gate the dashboard layout on server-side authentication, and repair navigation links for docs and key marketing sections.

Bug Fixes:

  • Protect all /dashboard routes with a server-side session check and redirect unauthenticated users before rendering the dashboard shell.
  • Update the main navigation Docs link to point to the external docs site, configurable via NEXT_PUBLIC_DOCS_URL.
  • Fix How it works, Pricing, and FAQ links so they correctly navigate to the landing page sections from both the landing page and /whitepaper.

Enhancements:

  • Convert the dashboard layout to a server component so dashboard pages are treated as dynamic and can read request headers for authentication.

Summary by CodeRabbit

  • Bug Fixes

    • Improved dashboard access control by redirecting unauthenticated visitors to the login page before content loads.
    • Removed unnecessary loading states during authentication checks.
  • Improvements

    • Documentation links in desktop and mobile navigation can now be configured and consistently open the correct destination.
    • Updated section links for more reliable navigation.

Three issues, one of them structural.

1. The dashboard had no server-side authentication.

(dashboard)/layout.tsx was a client component calling useSession() and
redirecting from a useEffect. That is not protection. The check runs only
after the server has sent the HTML and the browser has downloaded, parsed
and hydrated the whole dashboard bundle, so an unauthenticated visitor
received the entire screen, saw a spinner, and was then bounced. The
route existence, layout, navigation and feature set all leaked, and a
user with an expired session got a flash of the UI first.

It is now a server component: resolve the session, redirect if absent.
An unauthenticated request never receives a dashboard response. Proof in
the build output -- every /dashboard route moved from static to dynamic,
which only happens because the layout now reads request headers.

Worth stating plainly: customer DATA was never exposed. Every
/api/dashboard route proxies through forwardToCP, which returns 401 when
the vls_session cookie is missing and otherwise hands it to the control
plane to validate. The leak was the shell, not the contents -- a real
defect, not an incident.

This is a coarse gate. Per-page checks remain the standard, but every
page below is still a client component and cannot do it yet; converting
them is tracked separately.

2. Docs pointed at /dashboard.

Anyone looking for documentation was sent into the product, and once the
gate above landed they would have been bounced to /login. Now points at
docs.vectorless.store, overridable via NEXT_PUBLIC_DOCS_URL so preview
deployments can target a staging docs site.

3. Pricing, FAQ and How-it-works were dead on /whitepaper.

The sections do exist and carry the right ids -- but only on the landing
page, and Nav renders on both. A bare "#pricing" resolves against the
current document, so on /whitepaper it did nothing. Changed to "/#pricing"
so the links work from either page.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @hallelx2, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
vectorless-web Ready Ready Preview Aug 5, 2026 12:52am

@sourcery-ai

sourcery-ai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Reviewer's Guide

Server-side authentication is added to gate all /dashboard routes before rendering, and the main navigation’s docs and section links are corrected to point to the external docs site and to work from both the landing page and /whitepaper.

Sequence diagram for server-side gating of dashboard layout

sequenceDiagram
  actor User
  participant Browser
  participant NextApp
  participant DashboardLayout
  participant ServerAuth as getServerSession
  participant LoginRoute as login_route
  participant DashboardShell

  User->>Browser: request /dashboard
  Browser->>NextApp: HTTP GET /dashboard
  NextApp->>DashboardLayout: render
  DashboardLayout->>ServerAuth: getServerSession
  alt [session exists]
    ServerAuth-->>DashboardLayout: session
    DashboardLayout->>DashboardShell: render children
    DashboardShell-->>Browser: dashboard HTML
  else [no session]
    ServerAuth-->>DashboardLayout: null
    DashboardLayout->>NextApp: redirect /login
    NextApp-->>Browser: 307 redirect /login
    Browser->>LoginRoute: follow /login
  end
Loading

File-Level Changes

Change Details Files
Gate all dashboard routes with server-side session checking and redirect unauthenticated users before any dashboard UI is rendered.
  • Convert the dashboard layout from a client component using useSession/useEffect into an async server component.
  • Use getServerSession() to resolve the user session during layout rendering.
  • Issue a server-side redirect to /login via next/navigation when no session is present.
  • Remove the client-side loading spinner and pending-state handling from the dashboard layout.
apps/web/app/(dashboard)/layout.tsx
Fix main navigation links so docs go to the external docs site and anchored sections work from both the landing page and the whitepaper page.
  • Introduce a DOCS_URL constant sourced from NEXT_PUBLIC_DOCS_URL with a default of https://docs.vectorless.store.
  • Change the Docs navigation items from Next.js to plain tags pointing at DOCS_URL in both desktop and mobile menus.
  • Update How it works, Pricing, and FAQ links to use absolute paths (e.g., /#how, /#pricing, /#faq) so they scroll correctly on the landing page and navigate-then-scroll from /whitepaper.
  • Ensure mobile navigation mirrors the desktop nav behaviour and URLs for these links.
apps/web/components/Nav.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The dashboard layout now performs server-side session checks and redirects unauthenticated requests. Navigation uses root-relative section anchors and a configurable external documentation URL.

Changes

Dashboard navigation

Layer / File(s) Summary
Server-side dashboard authentication
apps/web/app/(dashboard)/layout.tsx
DashboardLayout is now an async server component. It calls getServerSession() and redirects requests without a session to /login.
Configurable navigation links
apps/web/components/Nav.tsx
Desktop and mobile links use root-relative section hashes. The Docs link uses NEXT_PUBLIC_DOCS_URL with an external default URL.

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

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant DashboardLayout
  participant getServerSession
  participant Login
  Browser->>DashboardLayout: request dashboard route
  DashboardLayout->>getServerSession: read server session
  getServerSession-->>DashboardLayout: session or no session
  DashboardLayout->>Login: redirect unauthenticated request to /login
  DashboardLayout-->>Browser: render dashboard shell for authenticated request
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the dashboard server-side authentication change and navigation link fixes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 halleluyaholudele/dashboard-server-auth-and-nav-links

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.

@hallelx2

hallelx2 commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

Correcting the framing in the description above.

I wrote that per-page auth checks are "follow-up work, not this PR", which reads like a tidy-up. Measured against the architecture standard, it is not:

standard actual
module/<feature>/views/ required absent
server/services/ business logic here absent
page.tsx ≈ gate, ~40 lines no JSX beyond <View/> 26 of 27 exceed it
"use client" at leaves only never on a page.tsx 23 of 27 pages
Server caller by default no client fetching 17 pages fetch client-side

Median page 273 lines; largest 754 (documents/[docId]/graph).

The consequence that matters here: no page can protect itself, because a client component cannot resolve a session on the server. So the whole dashboard now rests on the single layout gate this PR adds — it closes the leak, but with nothing behind it.

The gate is still worth merging. It should not be read as making the rest optional. Tracked as HAL-636.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/app/`(dashboard)/layout.tsx:
- Around line 38-39: Update the session handling around getServerSession so
control-plane timeouts and 5xx failures produce a distinct unavailable result
that renders or throws a 5xx response instead of redirecting. Restrict
redirect("/login") to genuinely absent, invalid, or expired sessions, preserving
normal rendering for valid sessions.
- Line 38: Update the dashboard layout’s getServerSession call to use a bounded
timeout for the underlying /admin/v1/auth/me request, passing a cancellation
signal through the existing session-fetch configuration. Convert timeout
failures to a null session so the existing unauthenticated redirect path runs,
while preserving successful session handling and other error behavior.

In `@apps/web/components/Nav.tsx`:
- Around line 8-18: Update the DOCS_URL initialization to trim
NEXT_PUBLIC_DOCS_URL and use the documented default URL when the value is empty
or whitespace-only, while preserving nonblank override values for both Docs
links.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 39c5dca7-dd33-4676-a674-91e73a0fbd02

📥 Commits

Reviewing files that changed from the base of the PR and between 3b94989 and 601d5aa.

📒 Files selected for processing (2)
  • apps/web/app/(dashboard)/layout.tsx
  • apps/web/components/Nav.tsx

if (!session) {
return null;
}
const session = await getServerSession();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the authentication call and the declared runtime limits.
ast-grep outline apps/web/lib/server-auth.ts --items all
rg -n -C 4 'getServerSession|fetch\(|AbortSignal\.timeout|signal:|maxDuration' apps/web
fd -HI 'package.json|.nvmrc|.node-version|Dockerfile|next.config.*|vercel.json' . -x sed -n '1,180p' {}

Repository: hallelx2/vectorless

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg '(^|/)apps/web/lib/server-auth\.(ts|tsx|js|jsx)$|^apps/web/next\.config\.(ts|js|mjs|cjs)$|(^|/)vercel\.json$|package\.json$' || true

echo
echo "== server-auth outline =="
ast-grep outline apps/web/lib/server-auth.ts --items all || true

echo
echo "== server-auth source =="
cat -n apps/web/lib/server-auth.ts

echo
echo "== dashboard layout relevant =="
sed -n '1,140p' apps/web/app/'(dashboard)'/layout.tsx | cat -n

echo
echo "== focused auth/control-plane references =="
rg -n -C 3 'getServerSession|setServerSession|fetch\(|CLOVA|control|signal:|AbortSignal\.timeout|maxDuration' apps/web/lib apps/web/next.config.* 2>/dev/null || true

echo
echo "== focused config references =="
jq '{scripts, dependencies, devDependencies, next, vercel}' apps/web/package.json 2>/dev/null || sed -n '1,220p' apps/web/package.json
for f in apps/web/next.config.* vercel.json; do
  [ -f "$f" ] && { echo "--- $f"; sed -n '1,220p' "$f"; }
done

Repository: hallelx2/vectorless

Length of output: 33731


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Vercel runtime config =="
cat -n apps/web/vercel.json

echo
echo "== top package runtime context =="
for f in package.json apps/web/package.json; do
  echo "--- $f"
  jq '{engines, scripts, dependencies, devDependencies}' "$f" 2>/dev/null || sed -n '1,220p' "$f"
done

Repository: hallelx2/vectorless

Length of output: 3140


Bound the control-plane session request.

getServerSession() calls fetch() for /admin/v1/auth/me without a cancellation signal, and the /dashboard layout awaits it before rendering. Add a bounded timeout to this auth request; return null when it times out so unauthenticated/failed requests redirect instead of exhausting the function’s runtime.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/`(dashboard)/layout.tsx at line 38, Update the dashboard
layout’s getServerSession call to use a bounded timeout for the underlying
/admin/v1/auth/me request, passing a cancellation signal through the existing
session-fetch configuration. Convert timeout failures to a null session so the
existing unauthenticated redirect path runs, while preserving successful session
handling and other error behavior.

Comment on lines +38 to +39
const session = await getServerSession();
if (!session) redirect("/login");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Do not redirect valid users when the control plane is unavailable.

getServerSession() returns null for fetch failures and every non-401/403 response from the control plane. This branch redirects those failures to /login.

Return a distinct unavailable result for timeouts and 5xx responses. Render or throw a 5xx response for that result. Keep the redirect for only absent, invalid, or expired sessions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/`(dashboard)/layout.tsx around lines 38 - 39, Update the session
handling around getServerSession so control-plane timeouts and 5xx failures
produce a distinct unavailable result that renders or throws a 5xx response
instead of redirecting. Restrict redirect("/login") to genuinely absent,
invalid, or expired sessions, preserving normal rendering for valid sessions.

Comment on lines +8 to +18
/**
* Docs live on their own Fumadocs deployment, not in this app. The link
* used to point at /dashboard, which sent anyone looking for
* documentation into the product — and, before the layout was gated on
* the server, into a login redirect.
*
* Overridable so a preview deployment can point at a staging docs site
* without a code change.
*/
const DOCS_URL = process.env.NEXT_PUBLIC_DOCS_URL ?? 'https://docs.vectorless.store';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat blank documentation URLs as unset.

?? does not replace an empty string. If NEXT_PUBLIC_DOCS_URL is empty or whitespace-only, both Docs links receive an invalid URL instead of the documented fallback.

Proposed fix
-const DOCS_URL = process.env.NEXT_PUBLIC_DOCS_URL ?? 'https://docs.vectorless.store';
+const DOCS_URL =
+  process.env.NEXT_PUBLIC_DOCS_URL?.trim() || 'https://docs.vectorless.store';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Docs live on their own Fumadocs deployment, not in this app. The link
* used to point at /dashboard, which sent anyone looking for
* documentation into the product and, before the layout was gated on
* the server, into a login redirect.
*
* Overridable so a preview deployment can point at a staging docs site
* without a code change.
*/
const DOCS_URL = process.env.NEXT_PUBLIC_DOCS_URL ?? 'https://docs.vectorless.store';
/**
* Docs live on their own Fumadocs deployment, not in this app. The link
* used to point at /dashboard, which sent anyone looking for
* documentation into the product and, before the layout was gated on
* the server, into a login redirect.
*
* Overridable so a preview deployment can point at a staging docs site
* without a code change.
*/
const DOCS_URL =
process.env.NEXT_PUBLIC_DOCS_URL?.trim() || 'https://docs.vectorless.store';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/components/Nav.tsx` around lines 8 - 18, Update the DOCS_URL
initialization to trim NEXT_PUBLIC_DOCS_URL and use the documented default URL
when the value is empty or whitespace-only, while preserving nonblank override
values for both Docs links.

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