Skip to content

fix: guard authenticated account routes - #204

Merged
damianlegawiec merged 2 commits into
spree:mainfrom
laaichiu:account-route-guard
Jul 27, 2026
Merged

fix: guard authenticated account routes#204
damianlegawiec merged 2 commits into
spree:mainfrom
laaichiu:account-route-guard

Conversation

@laaichiu

@laaichiu laaichiu commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #202

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added authenticated account pages for Orders, Order details, Addresses, Credit Cards, Gift Cards, and Profile.
    • Introduced an authenticated account shell with localized sidebar navigation, active highlighting, and logout.
    • Added localized empty states and security/help callouts across account sections.
    • Implemented safer sign-in return links, including redirect rebasing when switching markets.
  • Bug Fixes

    • Improved protected-route behavior: unauthenticated visitors are redirected to sign-in, while refresh sessions allow access.
    • Enhanced redirect safety validation to block unsafe return targets.

@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

@laaichiu is attempting to deploy a commit to the Spree Commerce Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The PR adds protected account routing, shared authenticated shells, validated and market-aware login redirects, refresh-token recovery, localized account pages, and editable profile management with supporting tests.

Changes

Account authentication and storefront

Layer / File(s) Summary
Validated account redirects
src/lib/utils/account-redirect.ts, src/app/.../account/page.tsx, src/lib/utils/__tests__/account-redirect.test.ts
Validates localized account and checkout return paths, constructs safe login URLs, rebases redirects across markets, and applies the helper to account login handling.
Session refresh and middleware protection
src/lib/spree/auth-helpers.ts, src/lib/spree/middleware.ts, src/lib/spree/__tests__/*
Refresh-only sessions can recover access tokens, and protected account routes redirect requests lacking configured access or refresh cookies.
Authenticated account shell
src/app/.../account/(authenticated)/layout.tsx, src/components/account/*, src/app/.../layout.test.tsx, src/components/account/__tests__/*
Adds server and client authentication gates, shared navigation, logout handling, loading fallback, and account-shell behavior tests.
Account data pages
src/app/.../account/(authenticated)/{orders,addresses,credit-cards,gift-cards}/*
Adds localized pages that fetch account data and render populated lists or empty states, including completed-order filtering and order details.
Profile editing
src/app/.../account/(authenticated)/profile/page.tsx
Adds editable customer fields, conditional current-password validation for email changes, update feedback, and read-only account information.
Market-switch redirect handling
src/hooks/useCountrySwitch.ts, src/hooks/__tests__/useCountrySwitch.test.ts
Rebases login return targets when country or locale navigation changes the market path.

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

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant SpreeMiddleware
  participant AccountLayout
  participant AuthShell
  participant AccountPage
  Browser->>SpreeMiddleware: Request protected account route
  SpreeMiddleware->>Browser: Redirect to login when credentials are absent
  SpreeMiddleware->>AccountLayout: Continue request with session cookie
  AccountLayout->>AuthShell: Provide login href and page children
  AuthShell->>AccountPage: Render authenticated page
  AccountPage->>Browser: Return localized account content
Loading

Possibly related PRs

Poem

I hopped through routes where safe redirects gleam,
Guarding account pages like a dream.
Tokens refresh, the shells align,
Orders and cards now render fine.
Across each market, paths stay true—
A bunny cheers this change for you!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.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 matches the core change: guarding authenticated account routes.
Linked Issues check ✅ Passed The new middleware and authenticated layout block unauthenticated /account/... access and redirect to login, satisfying #202.
Out of Scope Changes check ✅ Passed All changes support authenticated account routing, redirect handling, or related tests; no clearly unrelated code stands out.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@laaichiu laaichiu changed the title fix: guard authenticated account routes (#202) fix: guard authenticated account routes Jul 26, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/app/[country]/[locale]/(storefront)/account/(authenticated)/addresses/page.tsx (1)

14-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Add localized route metadata for the new account pages.

  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/addresses/page.tsx#L14-L14: add typed route metadata.
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/orders/page.tsx#L13-L13: add typed route metadata.
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/orders/[id]/page.tsx#L15-L17: add typed route metadata.
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/credit-cards/page.tsx#L11-L13: add typed route metadata.
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/gift-cards/page.tsx#L11-L11: add typed route metadata.
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/profile/page.tsx#L215-L229: split the client editor from a server route wrapper, then export typed metadata from the wrapper.

As per coding guidelines, “Use Next.js Metadata APIs, including typed Metadata and generateMetadata, for route metadata.”

🤖 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
`@src/app/`[country]/[locale]/(storefront)/account/(authenticated)/addresses/page.tsx
at line 14, Add typed Next.js route metadata using the Metadata API for
addresses/page.tsx (lines 14-14), orders/page.tsx (lines 13-13),
orders/[id]/page.tsx (lines 15-17), credit-cards/page.tsx (lines 11-13), and
gift-cards/page.tsx (line 11). In profile/page.tsx (lines 215-229), split the
client editor from the server route wrapper, then export typed metadata from the
wrapper; use existing localization conventions for the metadata values.

Source: Coding guidelines

src/lib/spree/__tests__/auth-helpers.test.ts (1)

102-115: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add stale/expired coverage for the new no-access-token branch.

Only the success path is tested here. The new branch in ensureFreshSession also needs to classify refresh failures as "stale" (refresh token survives) vs "expired" (refresh token cleared) — mirroring the already-tested expired-token branch — but neither failure case is covered for this new code path.

✅ Suggested additional test cases
it("reports stale when refresh fails but the refresh token survives", async () => {
  mockCookieState.access = undefined;
  mockCookieState.refresh = "rt-1";
  mockClient.auth.refresh.mockRejectedValueOnce(new Error("network error"));

  const state = await ensureFreshSession();

  expect(state).toBe("stale");
});

it("reports expired when refresh fails and the refresh token is cleared", async () => {
  mockCookieState.access = undefined;
  mockCookieState.refresh = "rt-1";
  mockClient.auth.refresh.mockImplementationOnce(async () => {
    mockCookieState.refresh = undefined;
    throw Object.assign(new Error("invalid_grant"), { status: 401 });
  });

  const state = await ensureFreshSession();

  expect(state).toBe("expired");
});
🤖 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 `@src/lib/spree/__tests__/auth-helpers.test.ts` around lines 102 - 115, Add
tests in auth-helpers.test.ts for the no-access-token path in
ensureFreshSession: verify a refresh failure that leaves the refresh token
intact returns "stale", and a failure that clears it returns "expired". Preserve
the existing successful refresh assertions and model the two failure behaviors
using rejected refresh calls.
🤖 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
`@src/app/`[country]/[locale]/(storefront)/account/(authenticated)/orders/page.tsx:
- Around line 22-23: Update the orders loading flow around getOrders and the
completed_at filter so it retrieves and combines all required pages before
filtering and rendering, rather than limiting results to the first 50 records.
Preserve the existing exclusion of incomplete orders and ensure older completed
orders remain visible.

---

Nitpick comments:
In
`@src/app/`[country]/[locale]/(storefront)/account/(authenticated)/addresses/page.tsx:
- Line 14: Add typed Next.js route metadata using the Metadata API for
addresses/page.tsx (lines 14-14), orders/page.tsx (lines 13-13),
orders/[id]/page.tsx (lines 15-17), credit-cards/page.tsx (lines 11-13), and
gift-cards/page.tsx (line 11). In profile/page.tsx (lines 215-229), split the
client editor from the server route wrapper, then export typed metadata from the
wrapper; use existing localization conventions for the metadata values.

In `@src/lib/spree/__tests__/auth-helpers.test.ts`:
- Around line 102-115: Add tests in auth-helpers.test.ts for the no-access-token
path in ensureFreshSession: verify a refresh failure that leaves the refresh
token intact returns "stale", and a failure that clears it returns "expired".
Preserve the existing successful refresh assertions and model the two failure
behaviors using rejected refresh calls.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 29fb60ad-4167-480e-84f7-d98900f06cac

📥 Commits

Reviewing files that changed from the base of the PR and between a3d8454 and 6349ca9.

📒 Files selected for processing (19)
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/addresses/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/credit-cards/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/gift-cards/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/layout.test.tsx
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/layout.tsx
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/orders/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/orders/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/profile/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/layout.tsx
  • src/app/[country]/[locale]/(storefront)/account/page.tsx
  • src/components/account/AccountShell.tsx
  • src/components/account/AuthenticatedAccountShell.tsx
  • src/components/account/__tests__/AuthenticatedAccountShell.test.tsx
  • src/lib/spree/__tests__/auth-helpers.test.ts
  • src/lib/spree/auth-helpers.ts
  • src/lib/spree/middleware.test.ts
  • src/lib/spree/middleware.ts
  • src/lib/utils/__tests__/account-redirect.test.ts
  • src/lib/utils/account-redirect.ts
💤 Files with no reviewable changes (1)
  • src/app/[country]/[locale]/(storefront)/account/layout.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/app/[country]/[locale]/(storefront)/account/(authenticated)/addresses/page.tsx (1)

14-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Add localized route metadata for the new account pages.

  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/addresses/page.tsx#L14-L14: add typed route metadata.
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/orders/page.tsx#L13-L13: add typed route metadata.
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/orders/[id]/page.tsx#L15-L17: add typed route metadata.
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/credit-cards/page.tsx#L11-L13: add typed route metadata.
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/gift-cards/page.tsx#L11-L11: add typed route metadata.
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/profile/page.tsx#L215-L229: split the client editor from a server route wrapper, then export typed metadata from the wrapper.

As per coding guidelines, “Use Next.js Metadata APIs, including typed Metadata and generateMetadata, for route metadata.”

🤖 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
`@src/app/`[country]/[locale]/(storefront)/account/(authenticated)/addresses/page.tsx
at line 14, Add typed Next.js route metadata using the Metadata API for
addresses/page.tsx (lines 14-14), orders/page.tsx (lines 13-13),
orders/[id]/page.tsx (lines 15-17), credit-cards/page.tsx (lines 11-13), and
gift-cards/page.tsx (line 11). In profile/page.tsx (lines 215-229), split the
client editor from the server route wrapper, then export typed metadata from the
wrapper; use existing localization conventions for the metadata values.

Source: Coding guidelines

src/lib/spree/__tests__/auth-helpers.test.ts (1)

102-115: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add stale/expired coverage for the new no-access-token branch.

Only the success path is tested here. The new branch in ensureFreshSession also needs to classify refresh failures as "stale" (refresh token survives) vs "expired" (refresh token cleared) — mirroring the already-tested expired-token branch — but neither failure case is covered for this new code path.

✅ Suggested additional test cases
it("reports stale when refresh fails but the refresh token survives", async () => {
  mockCookieState.access = undefined;
  mockCookieState.refresh = "rt-1";
  mockClient.auth.refresh.mockRejectedValueOnce(new Error("network error"));

  const state = await ensureFreshSession();

  expect(state).toBe("stale");
});

it("reports expired when refresh fails and the refresh token is cleared", async () => {
  mockCookieState.access = undefined;
  mockCookieState.refresh = "rt-1";
  mockClient.auth.refresh.mockImplementationOnce(async () => {
    mockCookieState.refresh = undefined;
    throw Object.assign(new Error("invalid_grant"), { status: 401 });
  });

  const state = await ensureFreshSession();

  expect(state).toBe("expired");
});
🤖 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 `@src/lib/spree/__tests__/auth-helpers.test.ts` around lines 102 - 115, Add
tests in auth-helpers.test.ts for the no-access-token path in
ensureFreshSession: verify a refresh failure that leaves the refresh token
intact returns "stale", and a failure that clears it returns "expired". Preserve
the existing successful refresh assertions and model the two failure behaviors
using rejected refresh calls.
🤖 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
`@src/app/`[country]/[locale]/(storefront)/account/(authenticated)/orders/page.tsx:
- Around line 22-23: Update the orders loading flow around getOrders and the
completed_at filter so it retrieves and combines all required pages before
filtering and rendering, rather than limiting results to the first 50 records.
Preserve the existing exclusion of incomplete orders and ensure older completed
orders remain visible.

---

Nitpick comments:
In
`@src/app/`[country]/[locale]/(storefront)/account/(authenticated)/addresses/page.tsx:
- Line 14: Add typed Next.js route metadata using the Metadata API for
addresses/page.tsx (lines 14-14), orders/page.tsx (lines 13-13),
orders/[id]/page.tsx (lines 15-17), credit-cards/page.tsx (lines 11-13), and
gift-cards/page.tsx (line 11). In profile/page.tsx (lines 215-229), split the
client editor from the server route wrapper, then export typed metadata from the
wrapper; use existing localization conventions for the metadata values.

In `@src/lib/spree/__tests__/auth-helpers.test.ts`:
- Around line 102-115: Add tests in auth-helpers.test.ts for the no-access-token
path in ensureFreshSession: verify a refresh failure that leaves the refresh
token intact returns "stale", and a failure that clears it returns "expired".
Preserve the existing successful refresh assertions and model the two failure
behaviors using rejected refresh calls.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 29fb60ad-4167-480e-84f7-d98900f06cac

📥 Commits

Reviewing files that changed from the base of the PR and between a3d8454 and 6349ca9.

📒 Files selected for processing (19)
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/addresses/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/credit-cards/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/gift-cards/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/layout.test.tsx
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/layout.tsx
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/orders/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/orders/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/(authenticated)/profile/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/layout.tsx
  • src/app/[country]/[locale]/(storefront)/account/page.tsx
  • src/components/account/AccountShell.tsx
  • src/components/account/AuthenticatedAccountShell.tsx
  • src/components/account/__tests__/AuthenticatedAccountShell.test.tsx
  • src/lib/spree/__tests__/auth-helpers.test.ts
  • src/lib/spree/auth-helpers.ts
  • src/lib/spree/middleware.test.ts
  • src/lib/spree/middleware.ts
  • src/lib/utils/__tests__/account-redirect.test.ts
  • src/lib/utils/account-redirect.ts
💤 Files with no reviewable changes (1)
  • src/app/[country]/[locale]/(storefront)/account/layout.tsx
🛑 Comments failed to post (1)
src/app/[country]/[locale]/(storefront)/account/(authenticated)/orders/page.tsx (1)

22-23: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Do not silently truncate order history.

Lines 22-23 fetch only the first 50 records, then discard incomplete ones. Customers with older orders beyond that page will never see them. Add pagination/load-more behavior or retrieve all required pages before rendering.

🤖 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
`@src/app/`[country]/[locale]/(storefront)/account/(authenticated)/orders/page.tsx
around lines 22 - 23, Update the orders loading flow around getOrders and the
completed_at filter so it retrieves and combines all required pages before
filtering and rendering, rather than limiting results to the first 50 records.
Preserve the existing exclusion of incomplete orders and ensure older completed
orders remain visible.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/lib/utils/__tests__/account-redirect.test.ts (1)

50-82: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add explicit return types to the new callbacks.

  • src/lib/utils/__tests__/account-redirect.test.ts#L50-L82: annotate new suite and test callbacks with : void.
  • src/hooks/__tests__/useCountrySwitch.test.ts#L25-L27: annotate the mock implementation with : string.
  • src/hooks/__tests__/useCountrySwitch.test.ts#L124-L159: annotate the async test callback with : Promise<void>.

As per coding guidelines, use strict TypeScript: define explicit function return types.

🤖 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 `@src/lib/utils/__tests__/account-redirect.test.ts` around lines 50 - 82,
Annotate the callbacks in the affected tests with explicit return types: add :
void to the rebaseAccountRedirect describe and test callbacks in
src/lib/utils/__tests__/account-redirect.test.ts (lines 50-82), add : string to
the mock implementation in src/hooks/__tests__/useCountrySwitch.test.ts (lines
25-27), and add : Promise<void> to the async test callback in
src/hooks/__tests__/useCountrySwitch.test.ts (lines 124-159).

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@src/lib/utils/__tests__/account-redirect.test.ts`:
- Around line 50-82: Annotate the callbacks in the affected tests with explicit
return types: add : void to the rebaseAccountRedirect describe and test
callbacks in src/lib/utils/__tests__/account-redirect.test.ts (lines 50-82), add
: string to the mock implementation in
src/hooks/__tests__/useCountrySwitch.test.ts (lines 25-27), and add :
Promise<void> to the async test callback in
src/hooks/__tests__/useCountrySwitch.test.ts (lines 124-159).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cfd896d6-9572-458b-ac34-950461c66f2a

📥 Commits

Reviewing files that changed from the base of the PR and between 6349ca9 and 2095c3a.

📒 Files selected for processing (4)
  • src/hooks/__tests__/useCountrySwitch.test.ts
  • src/hooks/useCountrySwitch.ts
  • src/lib/utils/__tests__/account-redirect.test.ts
  • src/lib/utils/account-redirect.ts

@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
storefront Ready Ready Preview, Comment Jul 27, 2026 8:10am

Request Review

@damianlegawiec
damianlegawiec merged commit fcfe5e8 into spree:main Jul 27, 2026
7 checks 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.

Unauthorized Access to Order History Page

2 participants