Skip to content

feat(server): appPaths — WAC-exempt application mount points (#582)#585

Closed
melvincarvalho wants to merge 3 commits into
gh-pagesfrom
app-paths-582
Closed

feat(server): appPaths — WAC-exempt application mount points (#582)#585
melvincarvalho wants to merge 3 commits into
gh-pagesfrom
app-paths-582

Conversation

@melvincarvalho

Copy link
Copy Markdown
Contributor

Closes #582. The first (and only hard-blocking) seam from the plugin-zero work on #206.

What

createServer({ appPaths: ['/myapp'] }) — URL prefixes owned by registered applications. Requests at or below an app path skip the WAC preHandler; the app owns authentication and authorization under its prefix, exactly like the built-in /storage/ and /db/ escapes.

Why

The WAC hook authorizes every URL against pod ACLs and rejects before routes run. Every bundled pseudo-plugin needed an escape and got one by editing the hook's hardcoded list; third-party apps registering routes on the returned fastify instance can't. With this seam, the bitmark-explorer/Tideholm class of app-plugin works with zero further core changes — proven end-to-end by Tideholm mounted at /tideholm with pod WebIDs as player accounts (12-check demo + a browser playtest, see the #206 comments).

Safety notes

  • Entries validated (must start with /, longer than /); malformed entries dropped, never accidental holes
  • Matching is per path segment: /myapp does not exempt /myapplication
  • Exempt requests never carry request.webId — identity below an app prefix is the app's business (Plugin api: bless getWebIdFromRequestAsync as api.auth.getAgent (request → WebID) #584 tracks blessing getWebIdFromRequestAsync as the public accessor)
  • Default off; omitting the option changes nothing (pinned by test)

Tests

7 new tests in test/app-paths.test.js (reach-the-app, bare/query forms, no webId leakage, sibling LDP still WAC'd, segment matching, malformed entries, default-off). Full suite: 1002 pass.

Relationship to the loader (#206 / #564)

Standalone option now; when the loader lands, manifest-declared prefixes feed appPaths automatically and this becomes an implementation detail of api.fastify. Sibling seams #583 (raw-body mode) and #584 (getAgent) are deliberately not in this PR.

createServer({ appPaths: ['/myapp'] }) declares URL prefixes owned by
registered applications. Requests at or below an app path skip the
WAC preHandler — the app owns authentication and authorization under
its prefix, exactly the deal the bundled pseudo-plugins (/storage/,
/db, /mcp, ...) already get from the hook's hardcoded exempt list.

This is the one hard blocker for third-party app plugins (#206):
without it, an app registering routes on the returned fastify
instance has its writes swallowed by WAC with no way to opt out.
Proven by plugin zero — Tideholm mounted at /tideholm with pod
WebIDs as player accounts (see #206 discussion).

Entries are validated (leading '/', longer than '/'); malformed
entries are dropped rather than becoming accidental holes. Matching
is per path segment, so /myapp does not exempt /myapplication.
Exempt requests never carry request.webId — identity below an app
prefix is the app's business (#584 tracks the public accessor).

Docs in configuration.md; 7 new tests, full suite green (1002).

Copilot AI 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.

Pull request overview

Adds an appPaths option to createServer to let operators declare application-owned URL prefixes that bypass the global WAC preHandler, enabling “plugin-zero” style apps mounted on the returned Fastify instance to handle auth/authz under their own prefix.

Changes:

  • Parse and validate options.appPaths in createServer, and exempt matching requests from the WAC hook.
  • Add an end-to-end test suite covering exemption behavior, segment matching, invalid entries, and default-off behavior.
  • Document appPaths usage and its security/behavioral contract in the configuration docs.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/server.js Introduces appPaths parsing and plugs it into the WAC preHandler exemption list.
test/app-paths.test.js Adds end-to-end tests verifying app-path exemption and safety properties.
docs/configuration.md Documents how to mount an app prefix and what WAC exemption implies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/server.js
Comment on lines +114 to +116
const appPaths = Array.isArray(options.appPaths)
? options.appPaths.filter((p) => typeof p === 'string' && p.startsWith('/') && p.length > 1)
: [];

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — entries are now trimmed and trailing-slash-stripped before validation ('/myapp/' matches like '/myapp', '///' and whitespace-only entries drop out). Regression test added ('trailing-slash entries are normalized, children still exempt').

Comment thread docs/configuration.md
Comment on lines +362 to +364
const fastify = createServer({ appPaths: ['/myapp'] });
fastify.all('/myapp/*', myAppHandler); // the app owns auth below its prefix
await fastify.listen({ port: 4443 });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right — same split as /db and /db/*. The example now registers both the bare prefix and the wildcard, with a comment saying why.

Trailing-slash entries ('/myapp/') previously failed to exempt child
routes because the matcher built '/myapp//'. Entries are now trimmed
and stripped of trailing slashes before validation, which also drops
oddities like '///' and '  '. Regression test added (8 total).

Docs example registers both '/myapp' and '/myapp/*' — fastify
wildcards don't match the bare prefix, same reason /db and /db/*
are registered separately.

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread docs/configuration.md
Comment on lines +369 to +373
Requests at or below an app path skip the WAC authorization hook — the
application authenticates and authorizes its own traffic, exactly like the
built-in `/storage/` and `/db/` routes. Everything outside the declared
prefixes keeps full WAC enforcement. Entries must start with `/` and be
longer than `/`; anything else is ignored.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added — the section now states that request.webId is never set under an app path (the WAC hook is what populates it) and points app authors at getWebIdFromRequestAsync, with #584 tracking the stable public accessor. It was already pinned by the 'never sets request.webId' test; now the docs say it too.

The WAC hook is also what populates request.webId, so app handlers
under an appPaths prefix must resolve identity themselves — point at
getWebIdFromRequestAsync and the #584 public-accessor issue.

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@melvincarvalho
melvincarvalho deleted the app-paths-582 branch July 10, 2026 14:11
melvincarvalho added a commit that referenced this pull request Jul 10, 2026
First release with application mount points (#582, #585): pluggable
apps are now possible without editing server.js. createServer({
appPaths: ['/myapp'] }) declares URL prefixes owned by registered
applications — requests below an app path skip the WAC hook and the
app owns its own auth, like /storage/ and /db/ always have.

This is step one of the #206 plugin system, proven by plugin zero:
Tideholm (github.com/melvincarvalho/tideholm, see jss-plugin/)
mounts a full multiplayer game at /tideholm where pod WebIDs are the
player accounts. Remaining seams tracked in #583 (raw-body mode for
wrapped node handlers) and #584 (public api.auth.getAgent); the
loader itself (manifest, discovery, policy) is #206/#564.
charlesvardeman pushed a commit to LA3D/JavaScriptSolidServer that referenced this pull request Jul 22, 2026
…iptSolidServer#582) (JavaScriptSolidServer#585)

* feat(server): appPaths — WAC-exempt application mount points (JavaScriptSolidServer#582)

createServer({ appPaths: ['/myapp'] }) declares URL prefixes owned by
registered applications. Requests at or below an app path skip the
WAC preHandler — the app owns authentication and authorization under
its prefix, exactly the deal the bundled pseudo-plugins (/storage/,
/db, /mcp, ...) already get from the hook's hardcoded exempt list.

This is the one hard blocker for third-party app plugins (JavaScriptSolidServer#206):
without it, an app registering routes on the returned fastify
instance has its writes swallowed by WAC with no way to opt out.
Proven by plugin zero — Tideholm mounted at /tideholm with pod
WebIDs as player accounts (see JavaScriptSolidServer#206 discussion).

Entries are validated (leading '/', longer than '/'); malformed
entries are dropped rather than becoming accidental holes. Matching
is per path segment, so /myapp does not exempt /myapplication.
Exempt requests never carry request.webId — identity below an app
prefix is the app's business (JavaScriptSolidServer#584 tracks the public accessor).

Docs in configuration.md; 7 new tests, full suite green (1002).

* review: normalize appPaths entries; docs example mounts the bare prefix

Trailing-slash entries ('/myapp/') previously failed to exempt child
routes because the matcher built '/myapp//'. Entries are now trimmed
and stripped of trailing slashes before validation, which also drops
oddities like '///' and '  '. Regression test added (8 total).

Docs example registers both '/myapp' and '/myapp/*' — fastify
wildcards don't match the bare prefix, same reason /db and /db/*
are registered separately.

* review(docs): appPaths requests never carry request.webId

The WAC hook is also what populates request.webId, so app handlers
under an appPaths prefix must resolve identity themselves — point at
getWebIdFromRequestAsync and the JavaScriptSolidServer#584 public-accessor issue.
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.

appPaths: WAC-hook exemption for application mount points (plugin seam from #206)

2 participants