Skip to content

Implement internal pages - #714

Open
Pefington wants to merge 9 commits into
rajnandan1:mainfrom
parkpow:feat/internal-pages
Open

Implement internal pages#714
Pefington wants to merge 9 commits into
rajnandan1:mainfrom
parkpow:feat/internal-pages

Conversation

@Pefington

@Pefington Pefington commented Apr 20, 2026

Copy link
Copy Markdown

Summary

Adds an "Internal" toggle to the page creation/edit UI. When enabled, unauthenticated users are redirected to sign in when they try to access the page. Internal pages are also hidden from the page switcher for signed out users.

Changes

8e288327 feat: implement internal pages

Adds an "Internal" toggle to pages. When enabled, unauthenticated users are redirected to sign-in when visiting the page, and the page is hidden from the page switcher/nav. Changes include:

  • Migration adding page_is_internal INTEGER NOT NULL DEFAULT 0 to the pages table
  • DB types, repository, and controller updates
  • Page edit UI — Internal toggle in the General Information card
image
  • Pages list — Internal/Public badge
image
  • Page switcher API — filters internal pages for logged-out users

2077bcdf ux: hide page switcher if single page available

The page switcher component was always rendered even when only one page was visible to the current user (e.g., all other pages are internal). It now hides itself when there's only one page to switch between.

How to test

  1. Create a page and enable the Internal toggle → save
  2. While logged out, navigate to that page → should redirect to sign-in
  3. Log in → page is accessible
  4. With one single public page + at least one internal page, the page switcher should be visible when signed in but hidden when signed out

Summary by CodeRabbit

  • New Features
    • Added an Internal Page toggle when creating or editing pages.
    • Management pages list now shows an Access column (Public vs Internal).
    • Sign-in now supports redirecting users back to their original destination after authentication.
  • Bug Fixes
    • Internal pages are no longer shown in public page lists/selectors unless signed in.
    • Visiting internal dashboard pages now redirects unsigned users to sign in and returns them afterward.
    • Page selector dropdown now appears only when there’s more than one available page.
  • Chores
    • Page access settings now persist consistently by default.

@Plattenspatz

Copy link
Copy Markdown

Great work on this PR — the internal pages feature is exactly the foundation we need.
We have a use case that builds on top of this: per-user access restrictions for internal pages (e.g. user A can only see pages matching /customer-a*, user B only /customer-b*). I've opened #725 to describe it in detail.
Two questions:

@rajnandan1 — Is this PR on track to be merged? We'd like to base our work on it.

Thanks to both of you!

@Plattenspatz

Copy link
Copy Markdown

Thanks again for your work on this, @Pefington!
In the meantime I've been exploring the idea further and came up with an alternative approach that could unify the internal/public distinction with per-user page access using Kener's existing RBAC system. It would work a bit differently from your implementation, but solves the same problem (and more).
I've described it in detail here: #725 (comment)
Would be great to get your perspective on it as well — especially since you've already been deep in this part of the codebase.

@Pefington

Pefington commented May 6, 2026

Copy link
Copy Markdown
Author

Ah, shame, I initially implemented a role-based visibility toggle, but simplified it as we do not need it.
It was fairly simple, page.allowed_roles foreign key (logic flagged page as public if null).
Your proposal looks sound.
Feel free to build upon #714, or write from scratch. We are already using this on our fork, among other changes :)

@Plattenspatz

Copy link
Copy Markdown

@Pefington I’ve implemented a role-based solution and uploaded it as PR #727—feel free to take a look. I’d appreciate any feedback on it, especially since you’ve likely implemented something similar yourselves.

@rajnandan1

Copy link
Copy Markdown
Owner

checking

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 103ff8c8-298f-42e2-86cd-cbe2b8f3a41a

📥 Commits

Reviewing files that changed from the base of the PR and between de557a2 and d8e1c5d.

📒 Files selected for processing (1)
  • src/routes/(account)/account/signin/+page.server.ts

📝 Walkthrough

Walkthrough

Adds a page_is_internal flag to pages via migration, TypeScript types, and repository default. Public page listing and dashboard access now respect that flag, with sign-in preserving return paths. Manage UI adds editing and display controls for internal pages.

Changes

Internal Pages Feature

Layer / File(s) Summary
Schema, types, and repository default
migrations/20260417120000_add_page_is_internal.ts, src/lib/server/types/db.ts, src/lib/server/db/repositories/pages.ts
Adds page_is_internal to the database schema and server-side page types, and writes 0 when new pages omit the field.
Public pages API filtering
src/lib/server/api-server/pages/get.ts
Reads the logged-in session from cookies and excludes internal pages from the public pages response when no user is logged in.
Dashboard access redirect
src/lib/server/controllers/dashboardController.ts, src/routes/(account)/account/signin/+page.server.ts, src/routes/(account)/account/signin/+page.svelte
Redirects unauthenticated internal-page dashboard requests to sign-in, carries a next path through sign-in load and form submission, and redirects back after login when the path is safe.
Manage UI: internal toggle and access display
src/routes/(manage)/manage/app/pages/[page_id]/+page.svelte, src/routes/(manage)/manage/app/pages/+page.svelte, src/lib/components/PageSelector.svelte
Adds an internal-page toggle to the editor, shows Internal/Public badges in the pages table, and changes the PageSelector dropdown condition to require more than one page.

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

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant dashboardController
  participant signInPageServer
  participant signInPageSvelte

  Browser->>dashboardController: request dashboard data for internal page
  dashboardController->>Browser: redirect to /account/signin?next=...
  Browser->>signInPageServer: load sign-in page with next
  signInPageServer->>signInPageSvelte: return next in page data
  Browser->>signInPageSvelte: submit login form with next
  signInPageSvelte->>signInPageServer: POST login + next form data
  signInPageServer->>Browser: redirect to next path or default route
Loading

Poem

I nibble at pages, both public and shy,
With page_is_internal tucked safely awry.
If hidden, I hop to sign in,
Then back to the path I was in —
A bunny-approved detour, oh my! 🐰

🚥 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 matches the main change: adding internal-page support across the app.
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 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.

@greptile-apps

greptile-apps Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an Internal Page access control feature: a database column, migration, and UI toggle let admins mark pages as internal-only, causing unauthenticated visitors to be redirected to sign-in (with a ?next= return URL) and filtering those pages out of the public page switcher.

  • DB & types – a migration adds page_is_internal INTEGER NOT NULL DEFAULT 0, with corresponding PageRecord/PageRecordInsert type updates and repository wiring.
  • Access enforcementGetPageDashboardData throws a 302 redirect for unauthenticated visitors on internal pages; the sign-in flow reads the ?next= param and safely redirects back after successful login.
  • Page switcher APIGET /dashboard-apis/pages now calls GetLoggedInSession to filter out internal pages for logged-out users, and PageSelector.svelte hides itself when fewer than two pages are available.

Confidence Score: 5/5

This PR is safe to merge. Access enforcement, open-redirect protection, and database migration are all correctly implemented.

The access-control redirect in the dashboard controller correctly reads the authenticated user from layout data and encodes the return URL before throwing the redirect. The sign-in action validates the next parameter with a tight path-safety check (must start with /, no double-slash) that prevents open-redirect abuse. The migration is idempotent, the DB default is 0 (public), and the API filter runs server-side. No gaps in the implementation were found across the full change set.

No files require special attention.

Important Files Changed

Filename Overview
migrations/20260417120000_add_page_is_internal.ts Adds page_is_internal column with NOT NULL DEFAULT 0; idempotent hasColumn guards on both up and down paths.
src/lib/server/controllers/dashboardController.ts Adds internal-page access check with redirect to sign-in and properly encoded ?next= return URL; correctly uses layoutData.loggedInUser.
src/routes/(account)/account/signin/+page.server.ts Reads ?next= from URL on load and passes it as a hidden form field; post-login redirect uses isSafePath guard (startsWith('/') + no double-slash) preventing open-redirect.
src/lib/server/api-server/pages/get.ts Filters internal pages server-side based on GetLoggedInSession result before mapping to PageNavItem; internal flag is not exposed in the response.
src/lib/server/db/repositories/pages.ts Adds page_is_internal ?? 0 to createPage insert; updatePage uses Partial spread so the field propagates correctly on edit.
src/lib/server/types/db.ts Adds page_is_internal: number to PageRecord and page_is_internal?: number to PageRecordInsert; types are consistent with migration and repository usage.
src/lib/components/PageSelector.svelte Changes threshold from > 0 to > 1 so the dropdown is hidden when only one page is available to the current user.
src/routes/(manage)/manage/app/pages/[page_id]/+page.svelte Adds Internal Page toggle; correctly converts DB number to boolean for the Switch component and back to 0/1 on save.
src/routes/(manage)/manage/app/pages/+page.svelte Adds Access column showing Internal/Public badge based on page_is_internal value.
src/routes/(account)/account/signin/+page.svelte Renders a hidden next input inside the form only when a next value is present, correctly threading the return URL through the login flow.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant U as Unauthenticated User
    participant SC as SvelteKit Server
    participant DC as dashboardController
    participant DB as Database

    U->>SC: GET /internal-page
    SC->>DC: GetPageDashboardData(pagePath, layoutData)
    DC->>DB: GetPageByPathWithMonitors(pagePath)
    DB-->>DC: "{ page: { page_is_internal: 1 }, monitors }"
    DC->>DC: Check layoutData.loggedInUser → null
    DC-->>SC: "throw redirect(302, /account/signin?next=%2Finternal-page)"
    SC-->>U: "302 → /account/signin?next=%2Finternal-page"

    U->>SC: "POST /account/signin (email, password, next=/internal-page)"
    SC->>SC: Validate credentials
    SC->>SC: isSafePath check on next
    SC-->>U: 302 → /internal-page (authenticated)

    U->>SC: GET /dashboard-apis/pages
    SC->>SC: GetLoggedInSession(cookies) → null
    SC->>DB: GetAllPages()
    DB-->>SC: [public_page, internal_page]
    SC->>SC: "filter(p => !p.page_is_internal || !!loggedInUser)"
    SC-->>U: [public_page only]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant U as Unauthenticated User
    participant SC as SvelteKit Server
    participant DC as dashboardController
    participant DB as Database

    U->>SC: GET /internal-page
    SC->>DC: GetPageDashboardData(pagePath, layoutData)
    DC->>DB: GetPageByPathWithMonitors(pagePath)
    DB-->>DC: "{ page: { page_is_internal: 1 }, monitors }"
    DC->>DC: Check layoutData.loggedInUser → null
    DC-->>SC: "throw redirect(302, /account/signin?next=%2Finternal-page)"
    SC-->>U: "302 → /account/signin?next=%2Finternal-page"

    U->>SC: "POST /account/signin (email, password, next=/internal-page)"
    SC->>SC: Validate credentials
    SC->>SC: isSafePath check on next
    SC-->>U: 302 → /internal-page (authenticated)

    U->>SC: GET /dashboard-apis/pages
    SC->>SC: GetLoggedInSession(cookies) → null
    SC->>DB: GetAllPages()
    DB-->>SC: [public_page, internal_page]
    SC->>SC: "filter(p => !p.page_is_internal || !!loggedInUser)"
    SC-->>U: [public_page only]
Loading

Reviews (3): Last reviewed commit: "fix: reject backslash-prefixed paths in ..." | Re-trigger Greptile

Comment thread src/lib/server/controllers/dashboardController.ts

@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: 1

🤖 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/lib/server/controllers/dashboardController.ts`:
- Around line 314-321: The internal-page redirect in dashboardController’s
access check currently sends anonymous users to /account/signin without
preserving the requested page. Update the redirect logic around the
pageDetails.page_is_internal / layoutData.loggedInUser check to include a return
URL for the original page, and make the sign-in flow consume that parameter
after successful login so users are sent back to the page they requested instead
of the default dashboard.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: bf4755a7-3975-4eeb-8328-53081d8c391c

📥 Commits

Reviewing files that changed from the base of the PR and between 10fb002 and a77e597.

📒 Files selected for processing (8)
  • migrations/20260417120000_add_page_is_internal.ts
  • src/lib/components/PageSelector.svelte
  • src/lib/server/api-server/pages/get.ts
  • src/lib/server/controllers/dashboardController.ts
  • src/lib/server/db/repositories/pages.ts
  • src/lib/server/types/db.ts
  • src/routes/(manage)/manage/app/pages/+page.svelte
  • src/routes/(manage)/manage/app/pages/[page_id]/+page.svelte

Comment thread src/lib/server/controllers/dashboardController.ts
…gn-in

Internal page redirects now append ?next=<encoded path> to the sign-in
URL. The sign-in page threads next through load → hidden form field →
login action, and redirects there after successful authentication.
Open-redirect guard: only relative paths starting with / are honoured.

@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: 1

🤖 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/routes/`(account)/account/signin/+page.server.ts:
- Around line 83-86: The redirect handling in signin/+page.server.ts still
allows backslash-prefixed relative paths like "/\\evil.com" through the
next/redirectTo check. Update the validation around redirectTo so the signin
flow only accepts safe in-app paths by rejecting any value whose first two
characters could form a protocol-relative or backslash-based escape, or by
parsing and allowlisting the pathname before calling redirect() in the signin
page server handler.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 5c045602-3ccb-453f-b068-4ecf6d11e696

📥 Commits

Reviewing files that changed from the base of the PR and between a77e597 and de557a2.

📒 Files selected for processing (3)
  • src/lib/server/controllers/dashboardController.ts
  • src/routes/(account)/account/signin/+page.server.ts
  • src/routes/(account)/account/signin/+page.svelte

Comment thread src/routes/(account)/account/signin/+page.server.ts Outdated
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.

3 participants