Files
2026-05-27 23:29:53 +01:00

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 — /auth matches /auth and /auth/login but NOT /authoritative.
  • Configurable prefix — a deployment that mounts the provider under /myauth shifts the collision domain accordingly.
  • Per-route isPublic opt-out for plugin routes that augment the auth surface (e.g. a custom /auth/healthz).
  • Plugin-level publicRoutes allowlist — identical effect, declared once per plugin manifest.
  • Aggregating reporter — every offender is returned in one frozen report.
  • Trailing-slash normalisation — /auth and /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): CollisionReport
  • function isInsidePrefix(routePath: string, prefix: string): boolean
  • function normalisePrefix(prefix: string): string
  • interface 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.