Files
skills/docs/api-stack/api/api-auth-provider/src/provider/provider.md
2026-05-29 08:20:56 +01:00

3.7 KiB

provider — AuthProviderAdapter contract + path-prefix validation

(no submodules)

Purpose

The provider module is the type-level + thin-runtime heart of @suaveplan/api-auth-provider. It declares the abstract AuthProviderAdapter interface every concrete account-lifecycle provider (Better-Auth, the rolled reference, future Cognito/Auth0 bridges) structurally implements, and ships the Zod-validated authPathPrefix constructor that asserts a deployment's configured prefix is a legal segment-aligned mount point. By splitting the contract from any concrete provider we keep this package optional: a hosted-IdP deployment installs the session resolver (api-auth) only and never pulls in a provider implementation.

Features

  • AuthProviderAdapter<Caps> interface with id, configurable pathPrefix, routes(router, ctx) registration callback, and the four-hook AuthProviderLifecycle quartet (install/start/stop/uninstall).
  • AuthPathPrefix opaque brand — protects against accidental string substitution at the install boundary.
  • AuthPathPrefixSchema — a Zod schema enforcing leading slash, no trailing slash (except /), no path parameters, no whitespace.
  • authPathPrefix(value) constructor — returns the branded value or throws AUTH_PROVIDER.PROVIDER_LIFECYCLE_FAILED with a structured reason.
  • isAuthPathPrefix(value) predicate — non-throwing variant for tests.
  • DEFAULT_AUTH_PATH_PREFIX constant ("/auth").

Usage: Basic

import {
    authPathPrefix,
    DEFAULT_AUTH_PATH_PREFIX,
    type AuthProviderAdapter,
} from "@suaveplan/api-auth-provider/provider";

const myProvider: AuthProviderAdapter = {
    id: "rolled",
    pathPrefix: authPathPrefix(DEFAULT_AUTH_PATH_PREFIX),
    routes(router) {
        router.post("/login", { auth: null }, async () => new Response());
    },
    lifecycle: {
        install: async () => {},
        start: async () => {},
        stop: async () => {},
        uninstall: async () => {},
    },
};

Usage: Advanced

A deployment that mounts its provider under a non-standard prefix MUST run the input through authPathPrefix so the value is validated before the host records the claim in the path-ownership registry.

import { authPathPrefix } from "@suaveplan/api-auth-provider/provider";

const prefix = authPathPrefix(process.env.AUTH_PREFIX ?? "/auth/v2");
// Throws AUTH_PROVIDER.PROVIDER_LIFECYCLE_FAILED on input like
// "/auth/v2/" or "/auth/:tenant".

API Reference

  • interface AuthProviderAdapter<Caps> — provider contract.
  • interface AuthProviderLifecycle<Caps> — install/start/stop/uninstall.
  • type AuthPathPrefix — branded prefix string.
  • const AuthPathPrefixSchema: ZodType — validation schema.
  • function authPathPrefix(value: string): AuthPathPrefix — throwing constructor.
  • function isAuthPathPrefix(value: string): boolean — predicate.
  • const DEFAULT_AUTH_PATH_PREFIX = "/auth" — host default.

Implementation Notes

The brand is purely a TypeScript construct (unique symbol) — at runtime authPathPrefix("/auth") returns the string unchanged. The schema's order of refinements is significant: the leading-slash check fires first so callers see the most actionable error message. Whitespace is rejected before the parameter check because shell-supplied configuration is the typical source of both classes of error. The constructor surfaces failures as PROVIDER_LIFECYCLE_FAILED (not a hypothetical INVALID_PREFIX) because the install lifecycle is the only legitimate call site — keeping the code small makes the error registry tighter and the metadata table denser.