Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
# `collision-detector` — install-time auth-path collision check
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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
```ts
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:
```ts
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.