Skip to content

[haxcms-nodejs] v1 API conformance normalization (canonical spec + Node fixes) - #20

Merged
btopro merged 2 commits into
mainfrom
conformance/v1-normalization
Aug 6, 2026
Merged

[haxcms-nodejs] v1 API conformance normalization (canonical spec + Node fixes)#20
btopro merged 2 commits into
mainfrom
conformance/v1-normalization

Conversation

@btopro

@btopro btopro commented Aug 6, 2026

Copy link
Copy Markdown
Member

Summary

Normalizes the haxcms-nodejs v1 API surface against the PHP backend and the shared OpenAPI specs, addressing all High and Medium findings from the conformance audit. This PR carries the canonical spec — the PHP PR syncs from it.

Changes

Spec (canonical — src/openapi/):

  • LoginRequest adds jwt (revalidate support); auth-failure statuses aligned to 401 per spec
  • POST settings aliases (configuration/api-keys, configuration/media, blocks, skeletons, themes) documented as read-only (no requestBody); token stance consistent between GET and POST
  • /system/api/v1/session security set to [] (anonymous login probe)
  • providers/{provider}/search now requires bearerAuth + siteTokenHeader
  • GET /x/api/v1/files documents the filename query param
  • Single-user deployment assumption notes on config-write operation descriptions

Node code:

  • A4: views results + blocks usage now enforce anonymous-visibility (tags/search/items sources, listBlocks/blockDetail/blockUsage)
  • E1: files.dateCreated in seconds (was milliseconds)
  • E3: export 400 supportedFormats moved inside data
  • E4: html export sent as file download (Content-Disposition)
  • E5: content concat HTML escapes record.id/record.title (output-injection fix)
  • E6: views tags source honors ?sort (default -count)
  • D5: archiveSite uniquifies collided archive names (name-1, name-2…)
  • D6: connectionSettings adds no-store cache headers (token-bearing JS)
  • D2: login/refresh failures return 401 with JSON envelopes; JWT-revalidate support kept
  • F1-d: removed POST skeletons/:skeletonName read alias
  • F2/Q14+F3: provider-search site token enforced in the router (consistent 403 envelope)
  • F6: downloadSite excludes node_modules; drops dead getUserTokenFromHeader

Decisions applied

  • No admin/superUser tier (single-user Node assumption — Q1)
  • No IAM route authorization (Q2)
  • validateHaxiamManagedUserIdentityForRequest left as-is (S5)

Validation

  • node --check on all 14 changed files → OK
  • YAML parse of both specs → OK
  • Route maps load → OK
  • 30 structural spec checks passed
  • Conformance validator: 26/28 PASS (the 2 caveats are PHP-side, addressed in the PHP PR)

Related

Co-Authored-By: Oz oz-agent@warp.dev

btopro and others added 2 commits August 6, 2026 13:36
Spec (canonical):
- LoginRequest adds jwt (revalidate); auth failures 401 per spec
- POST settings aliases documented as read-only (no requestBody)
- /session security [] (anon login probe)
- provider-search requires bearerAuth + siteTokenHeader
- GET /x/api/v1/files documents filename param
- single-user assumption notes on config-write ops

Node code:
- views/blocks: anonymous-visibility enforcement on results/usage
- files dateCreated in seconds (was milliseconds)
- content concat HTML escapes record.id/title
- views tags source honors ?sort (default -count)
- exports: supportedFormats inside data; html export as file download
- archiveSite uniquifies collided archive names
- connectionSettings adds no-store cache headers
- login failures 401 + JSON envelopes; keeps JWT-revalidate
- removes POST skeletons/:name read alias
- provider-search site token enforced in router
- downloadSite excludes node_modules; drops dead getUserData code

Refs: haxtheweb/issues conformance audit (site/system parity)

Co-Authored-By: Oz <oz-agent@warp.dev>
…c + Node fixes)

Co-Authored-By: Oz <oz-agent@warp.dev>
Copilot AI lite review requested due to automatic review settings August 6, 2026 18:01
@codesandbox

codesandbox Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@btopro
btopro merged commit b5d56de into main Aug 6, 2026
1 check failed

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

Note

Copilot was unable to run its full agentic suite in this review.

This PR aligns NodeJS v1 system/site APIs more closely with the documented envelope/security behavior, tightening anonymous visibility, improving export/download responses, and enforcing site-token authorization for certain system routes.

Changes:

  • Standardize several auth-related responses (login, system routing) to JSON envelopes and updated status codes.
  • Enforce anonymous visibility rules across views/blocks/tags/search results.
  • Improve export/download behaviors (HTML export download, zip excludes node_modules/.git) and update OpenAPI specs accordingly.

Reviewed changes

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

Show a summary per file
File Description
src/systemRoutes/v1/routes/login.js Switches auth failure responses to JSON envelopes and adjusts status codes.
src/systemRoutes/v1/routes/getUserData.js Removes a bespoke header-token helper (likely consolidating header parsing elsewhere).
src/systemRoutes/v1/routes/downloadSite.js Changes zip behavior to glob files while excluding node_modules/.git.
src/systemRoutes/v1/routes/connectionSettings.js Adds no-cache headers for token/JWT-bearing JS response.
src/systemRoutes/v1/routes/archiveSite.js Prevents archive name collisions by uniquifying destination folder names.
src/siteRoutes/v1/views.js Enforces anonymous visibility in view sources and adds sort support for tag records.
src/siteRoutes/v1/files.js Normalizes dateCreated to seconds instead of milliseconds.
src/siteRoutes/v1/exports.js Fixes envelope shape for supportedFormats and sends HTML export as a download.
src/siteRoutes/v1/content.js Escapes authored fields when concatenating HTML to prevent injection.
src/siteRoutes/v1/blocks.js Enforces anonymous visibility when computing block usage/usage details.
src/openapi/system-spec.yaml Updates system API security/semantics (session probe, provider search auth, POST-as-read aliases, schema docs).
src/openapi/site-spec.yaml Documents new filename query filter parameter for file listing.
src/lib/SystemRoutesMap.js Removes open route and redundant POST handler mapping.
src/app.js Adds site-token policy enforcement for authenticated-site system routes; makes route handlers async.
Suppressed comments (1)

src/app.js:1

  • These handlers are now async, but there is no try/catch around awaited logic. In Express 4-style middleware, rejected promises from an async handler (or from awaited calls) may not be routed to error middleware reliably. Wrap the handler body in try { ... } catch (e) { next(e) } (or use a shared async-middleware wrapper) to ensure errors propagate via next().
#!/usr/bin/env node

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

Comment on lines +74 to +77
res.status(401).json({
status: 401,
data: { message: 'Authentication required' },
});
Comment on lines +126 to +134
.glob('**/*', {
cwd: sourceDir,
ignore: [
'node_modules/**',
'node_modules',
'.git/**',
'.git',
],
})
Comment thread src/app.js
Comment on lines +1847 to +1887
async function enforceSystemApiSiteTokenPolicy(req, res, op, method, basicAuth) {
const policy = getSystemApiRouteAuthPolicy(op, method);
if (policy !== 'authenticated-site') {
return true;
}
const siteToken = getRequestHeaderValue(req, 'x-haxcms-site-token');
if (siteToken === '') {
res.status(403).json({
status: 403,
data: { message: 'X-HAXCMS-Site-Token header is required for this endpoint' },
});
return false;
}
const userName = resolveSystemApiAuthenticatedUserName(req, basicAuth);
if (userName === '') {
res.status(403).json({
status: 403,
data: { message: 'Unable to resolve authenticated user context' },
});
return false;
}
const siteName = await resolveSiteApiRequestSiteName(req, {
userName: userName,
siteToken: siteToken,
});
if (!siteName) {
res.status(403).json({
status: 403,
data: { message: 'Unable to resolve site token context' },
});
return false;
}
if (!HAXCMS.validateRequestToken(siteToken, `${userName}:${siteName}`)) {
res.status(403).json({
status: 403,
data: { message: 'Invalid X-HAXCMS-Site-Token header' },
});
return false;
}
return true;
}
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.

2 participants