Skip to content

Inherited and site-default page gating, gated media (#157) - #158

Merged
zumbrunn merged 3 commits into
mainfrom
feat/gating-inheritance-157
Nov 12, 2026
Merged

zumbrunn merged 3 commits into
mainfrom
feat/gating-inheritance-157

Conversation

@zumbrunn

Copy link
Copy Markdown
Member

Fixes #157

What

  • Inheritance: a page's roles: applies to everything beneath it, in every language.
  • Site default: site.auth.default_roles (same values as roles:, e.g. any) gates every page that neither declares nor inherits a rule.
  • Opt-out: roles: public opens a page or a whole section.
  • Precedence: own roles:, then nearest ancestor, then site default. /auth/* and theme assets are never gated.
  • Media: /content-media/* now applies the owning page's rule (404, private, no-store). It checked nothing before; the route-based media path already did.

How

One pass over the page index (applyGatingInheritance) resolves each page's effective rule into PageIndex.gated / roles; PageIndex.ownRoles keeps the declaration so the pass re-runs cleanly. loadPage exposes the effective rule as frontmatter.roles, so the existing gates (routes, API handlers, nav, sitemap, media) pick it up unchanged.

Behaviour worth reviewing

  • Translations take a section's rule from the same language, else from another language. Where languages disagree, a gate beats public (fails closed).
  • A malformed roles: value now inherits instead of opening the page.
  • roles: any now means any logged-in user, as documented. It was read as a role literally named any.
  • A file with no owning page stays public, as on the route-based path.

Not in this PR

Docs live in the dune-docs submodule; the "no directory-level config" paragraph in the content-gating page needs updating at the docs step.

Testing

  • 16 new inheritance tests, 4 new gating tests; related suites pass (917).
  • deno lint src/ and deno check clean.
  • End to end on a scratch site (default any, password login, one roles: public section): anonymous requests redirect to login or 404 on media, public pages and /auth/login stay open, a logged-in user reaches everything, gated media is private, no-store.

🤖 Generated with Claude Code

zumbrunn and others added 3 commits November 12, 2026 23:13
A page's roles: now applies to everything beneath it, and
site.auth.default_roles gates every page that neither declares nor
inherits a rule. Precedence: own roles, nearest ancestor, site default.
roles: public opts a page or section out.

- Resolve the effective rule once over the page index
  (applyGatingInheritance); PageIndex.ownRoles keeps the declaration so
  the pass re-runs cleanly. loadPage exposes the effective rule as
  frontmatter.roles, so every existing gate picks it up.
- Translations take the section rule from the same language, else
  another; a gate beats public when languages disagree.
- A malformed roles value inherits instead of opening the page.
- roles: any now means any logged-in user (was read as a role "any").
- /content-media/* applies the owning page's rule (404, private
  no-store); it checked nothing before.

Fixes #157

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… site default

findPagesForContentMediaPath rebuilt a directory map over every page on each
/content-media request; it is now built once per page array. A file with no
owning page falls under site.auth.default_roles when set. Changelog notes the
roles: any breaking edge.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…o login

Subresources and every other denial stay 404.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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.

Page gating: inherit roles: from ancestors, add a site-wide default, and apply it to co-located media

1 participant