3.5 KiB
collision-detector — install-time auth-path collision check
(no submodules)
Purpose
collision-detector is the install-time guard that prevents a plugin from
accidentally squatting on the auth-provider's owned path prefix. It walks
every plugin route, segment-aligns its path against the provider's
pathPrefix, and emits AUTH_PROVIDER.PATH_COLLISION entries for every
overlap that is not on the plugin's publicRoutes allowlist or marked
isPublic on the per-route descriptor. Returning a report instead of
throwing keeps callers in control of how to surface aggregated failures —
typically the host turns a non-empty report into a single error listing
every offender.
Features
- Segment-aware containment —
/authmatches/authand/auth/loginbut NOT/authoritative. - Configurable prefix — a deployment that mounts the provider under
/myauthshifts the collision domain accordingly. - Per-route
isPublicopt-out for plugin routes that augment the auth surface (e.g. a custom/auth/healthz). - Plugin-level
publicRoutesallowlist — identical effect, declared once per plugin manifest. - Aggregating reporter — every offender is returned in one frozen report.
- Trailing-slash normalisation —
/authand/auth/are treated as equal.
Usage: Basic
import { detectAuthPathCollisions } from "@suaveplan/api-auth-provider/collision-detector";
const report = detectAuthPathCollisions({ provider, plugins });
if (report.hasCollisions) {
throw new Error(`Auth-path collisions: ${report.entries.length}`);
}
Usage: Advanced
Aggregating a problem-detail response across every offender:
import {
detectAuthPathCollisions,
type CollisionReportEntry,
} from "@suaveplan/api-auth-provider/collision-detector";
const report = detectAuthPathCollisions({ provider, plugins });
if (report.hasCollisions) {
const offenders = report.entries.map(
(e: CollisionReportEntry) => ({ plugin: e.details.plugin, path: e.details.path })
);
return new Response(
JSON.stringify({ type: "auth-collision", offenders }),
{ status: 409 }
);
}
API Reference
function detectAuthPathCollisions(input: DetectCollisionsInput): CollisionReportfunction isInsidePrefix(routePath: string, prefix: string): booleanfunction normalisePrefix(prefix: string): stringinterface DetectCollisionsInput { provider; plugins }interface PluginDescriptor { name; routes; publicRoutes? }interface PluginRouteDescriptor { path; method?; isPublic? }interface CollisionReport { entries; hasCollisions }interface CollisionReportEntry { code; message; details }
Implementation Notes
The detector returns a non-throwing report so the host can decide whether a
collision is fatal or merely loggable — empirically a deployment running an
older plugin against a newer provider needs the choice. Segment-aware
matching is essential: a naïve startsWith would falsely flag
/authoritative (or worse, /auth-cache) when the provider owns /auth.
The root prefix "/" is short-circuited to "owns nothing" so a deployment
that mistakenly configures the auth provider at the root does not blow up
the whole route table; the path-prefix validator catches that mis-configuration
at construction time. Two opt-out paths (per-route isPublic and plugin
publicRoutes) coexist because manifests historically use one or the other
and supporting both keeps migration tractable.