[haxcms-php] v1 API conformance normalization (spec-driven auth + PHP fixes) - #616
Merged
Conversation
Structural (C1): - Site + system auth policy now spec-driven (reads OpenAPI at runtime) - Fail-closed to authenticated; startup mutation-security guard - Retires hand-maintained regex table (resolves files/reports site-token gap) Site fixes: - PATCH content: maps top-level body/content/schema/details to node.* + 400 - PATCH files: rejects delete-on-PATCH; ignores client body.path (UUID only) - items: rich haxElementSchema (tag+properties+content) - items: 400 on empty POST; overridePathauto in detail keys; drops nested node.details.* - revisions GET: findItemByIdOrSlug (slug resolution) - views: anonymous-visibility context to applyItemFilters - search/exports: escaped-quote bug fix - outline 400 message aligned to Node wording System fixes: - Adds GET session/refresh - Un-strips jwt (revalidate) in v1 login - Drops legacy u/p branch; login/refresh 401 + JSON envelopes - connectionSettings: single-site siteName resolution - Removes undocumented routes (GET session/login, GET session/logout, POST provider-search, GET v1/openapi* aliases) - provider-search site token enforced in router - cloneSite: configured basePath/sitesDirectory; listSites drops title filter Refs: haxtheweb/issues conformance audit (site/system parity) Co-Authored-By: Oz <oz-agent@warp.dev>
site-spec.yaml + system-spec.yaml copied byte-identical via scripts/sync-openapi-specs.js after node-backend merge. Co-Authored-By: Oz <oz-agent@warp.dev>
…-driven auth + PHP fixes) Co-Authored-By: Oz <oz-agent@warp.dev>
…/list/detail - views.php tags + search sources: inline anon-visibility filter (isAnonymousSiteApiRequest + isItemVisibleToAnonymous), mirroring Node - blocks.php blockUsage + listBlocks: pass to applyItemFilters - blocks.php blockDetail: wrap getOrderedItems in applyItemFilters with Refs: conformance audit A4/S6 (PHP side was incomplete) Co-Authored-By: Oz <oz-agent@warp.dev>
…tags/search + blocks) Co-Authored-By: Oz <oz-agent@warp.dev>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Aligns PHP system/site APIs with the Node/OpenAPI contract by making auth/security decisions spec-driven and tightening handler behavior to match documented request/response semantics.
Changes:
- Moves site + system route auth policy determination to runtime OpenAPI spec parsing (with added user-token enforcement and a startup “mutation routes must not be public” guard).
- Updates multiple PHP v1 handlers to mirror Node/spec behaviors (JWT revalidate flow, anonymous visibility filtering, slug→UUID revision resolution, stricter mutation validation, UUID-only file operations).
- Updates OpenAPI specs to clarify security, aliases, and required parameters (e.g., session endpoints public access, provider-search security, login request shape).
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| system/backend/php/lib/systemRoutes/v1/session.php | Keeps jwt in params to allow JWT revalidation branch to execute. |
| system/backend/php/lib/systemRoutes/openapi/system-spec.yaml | Documents updated security requirements and read-only POST aliases; expands login schema. |
| system/backend/php/lib/systemRoutes/SystemRoutesMap.php | Adjusts mapped routes (session refresh; removes provider-search mapping). |
| system/backend/php/lib/systemRoutes/SystemApiSecurity.php | Replaces static route lists with spec-driven auth; adds provider-search site-token enforcement. |
| system/backend/php/lib/systemRoutes/SystemApiRouter.php | Adds router-level enforcement for provider-search site token with consistent 403 envelope. |
| system/backend/php/lib/siteRoutes/v1/views.php | Applies anonymous-visibility filtering for tags/search and passes request context into item filters. |
| system/backend/php/lib/siteRoutes/v1/search.php | Normalizes error message quoting. |
| system/backend/php/lib/siteRoutes/v1/revisions.php | Resolves slug to UUID before delegating revision operations. |
| system/backend/php/lib/siteRoutes/v1/itemsMutation.php | Validates POST payload presence; normalizes node.details behavior; adds overridePathauto support. |
| system/backend/php/lib/siteRoutes/v1/items.php | Implements richer haxElementSchema extraction from HTML using DOMDocument. |
| system/backend/php/lib/siteRoutes/v1/filesMutation.php | Enforces UUID-only resolution and disallows client-supplied body.path; rejects delete-on-PATCH. |
| system/backend/php/lib/siteRoutes/v1/exports.php | Normalizes error message quoting. |
| system/backend/php/lib/siteRoutes/v1/contentMutation.php | Maps spec-conformant top-level content/schema/details into node.* and requires body content. |
| system/backend/php/lib/siteRoutes/v1/blocks.php | Ensures anon filtering by passing context into item filtering. |
| system/backend/php/lib/siteRoutes/openapi/site-spec.yaml | Adds filename query parameter component and references it in a files listing route. |
| system/backend/php/lib/siteRoutes/SiteRoutesMap.php | Removes v1 discovery/openapi aliases from the site route map. |
| system/backend/php/lib/siteRoutes/SiteRouteUtils.php | Adds OpenAPI security normalization + spec parsing + policy lookup + mutation security assertion. |
| system/backend/php/lib/siteRoutes/SiteApiSecurity.php | Switches to spec-driven route policy lookup; adds authenticated-user header enforcement. |
| system/backend/php/lib/siteRoutes/SiteApiRouter.php | Passes matched route pattern (not concrete path) into auth; adds startup mutation-security check. |
| system/backend/php/lib/routes/saveOutline.php | Updates 400 error message text. |
| system/backend/php/lib/routes/login.php | Returns 401 for login failures; supports JWT revalidation via body jwt. |
| system/backend/php/lib/routes/listSites.php | Includes sites even without title; guards metadata/pageCount assignment. |
| system/backend/php/lib/routes/cloneSite.php | Rewrites file URLs using configured basePath/sitesDirectory with legacy fallback. |
| system/backend/php/lib/HAXCMS.php | Fixes single-site mode sitename resolution for token minting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+218
to
+226
| public static function enforceProviderSearchSiteToken($routeName, $method, $context) | ||
| { | ||
| $normalizedMethod = strtoupper((string) $method); | ||
| if ($normalizedMethod !== 'GET') { | ||
| return null; | ||
| } | ||
| if ($routeName !== 'v1/integrations/app-store/providers/:provider/search') { | ||
| return null; | ||
| } |
Comment on lines
169
to
179
| if (isset($this->params['username']) && isset($this->params['password'])) { | ||
| return $this->processCredentialLogin($this->params['username'], $this->params['password'], false); | ||
| } | ||
| //old way | ||
| // if we don't have a user and the don't answer, bail | ||
| else if (isset($this->params['u']) && isset($this->params['p'])) { | ||
| return $this->processCredentialLogin($this->params['u'], $this->params['p'], true); | ||
| } | ||
| // login end point requested yet a jwt already exists | ||
| // this is something of a revalidate case | ||
| // D2/Q7: login end point requested yet a jwt already exists — this is a | ||
| // revalidate case. The body jwt was previously stripped in the session v1 | ||
| // handler; it now reaches this branch so JWT-revalidate works in v1. | ||
| // D2/Q8: use validateJWT(false) so an invalid jwt returns a 401 envelope | ||
| // instead of exiting with 403. Set sessionJwt from the body jwt first so | ||
| // validateJWT can decode it (sessionJwt is normally set from the bearer | ||
| // header in the HAXCMS constructor). | ||
| else if (isset($this->params['jwt'])) { |
Comment on lines
18
to
21
| 'openapi' => dirname(__FILE__) . '/discovery/openapi.php', | ||
| 'openapi.json' => dirname(__FILE__) . '/discovery/openapi.php', | ||
| 'openapi.yaml' => dirname(__FILE__) . '/discovery/openapi.php', | ||
| 'v1' => dirname(__FILE__) . '/discovery/api.php', | ||
| 'v1/openapi' => dirname(__FILE__) . '/discovery/openapi.php', | ||
| 'v1/openapi.json' => dirname(__FILE__) . '/discovery/openapi.php', | ||
| 'v1/openapi.yaml' => dirname(__FILE__) . '/discovery/openapi.php', | ||
| 'v1/site' => dirname(__FILE__) . '/v1/site.php', |
Comment on lines
+943
to
+954
| private static $siteApiAuthPolicies = null; | ||
| /** | ||
| * Read site-spec.yaml once per request and build a map of | ||
| * 'method:routeKey => policy' mirroring Node | ||
| * readSiteApiAuthPoliciesFromOpenApiSpec (app.js:1658). Cached in a | ||
| * static property so repeated lookups in the same request skip re-parsing. | ||
| */ | ||
| public static function readSiteApiAuthPoliciesFromOpenApiSpec() | ||
| { | ||
| if (self::$siteApiAuthPolicies !== null) { | ||
| return self::$siteApiAuthPolicies; | ||
| } |
Comment on lines
+240
to
+245
| // Resolve siteName from the query param for token validation. If | ||
| // siteName is missing, defer to the handler's siteName validation (F3). | ||
| $siteName = isset($_GET['siteName']) ? (string) $_GET['siteName'] : ''; | ||
| if ($siteName === '') { | ||
| return null; | ||
| } |
Comment on lines
+208
to
+213
| * F2/Q14+F3: enforce the X-HAXCMS-Site-Token header on the app-store | ||
| * provider-search GET route in the router (not the handler) for a | ||
| * consistent 403 envelope. The current spec declares security: [] for | ||
| * this route; Q14 requires a site token. The spec will be updated by the | ||
| * node-backend and synced at merge; until then this explicit router-level | ||
| * check enforces the decision. siteName validation stays in the handler. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Normalizes the haxcms-php v1 API surface against the NodeJS backend and the shared OpenAPI specs, addressing all High and Medium findings from the conformance audit. The spec files are synced from haxcms-nodejs (canonical) in a separate commit.
Changes
Structural (C1 — resolves A1):
authenticated)assertSiteApiMutationRoutesAreSecuredSiteApiSecurity/SystemApiSecurityGET /x/api/v1/files+GET /x/api/v1/reportsnow require site token (authenticated-site) — resolving the security-stance gapSite fixes:
body/content/schema/details→node.*+ 400 on missing body{operation:delete}(400) and ignores clientbody.path(UUID only)haxElementSchemareturns rich per-element schema (tag + properties + content)findItemByIdOrSlug(slug resolution)overridePathauto; drops nestednode.details.*System fixes:
GET session/refreshjwt(revalidate) in v1 loginu/pbranch; login/refresh failures return 401 with JSON envelopessiteNameresolution (so siteToken mints foruser:sitename)GET session/login,GET session/logout,POST provider-search)cloneSiteuses configuredbasePath/sitesDirectory;listSitesdrops title filterSpec sync:
site-spec.yaml+system-spec.yamlcopied byte-identical from haxcms-nodejs viascripts/sync-openapi-specs.jsDecisions applied
validateHaxiamManagedUserIdentityForRequeststays Node-only (S5) — no PHP equivalent addedValidation
php -lon all 23 changed files → OKRelated
Co-Authored-By: Oz oz-agent@warp.dev