e7a1290246
Next.js 16 + Auth.js v5 skeleton: host→slug middleware, tenant-context layout, OIDC sign-in flow against breakpilot-dev realm. 100% coverage on src/lib. Bumps next to 16.2.6 to clear trivy CVEs in 15.0.3.
42 lines
1.4 KiB
TypeScript
42 lines
1.4 KiB
TypeScript
// Host → tenant slug parser for the portal middleware.
|
|
//
|
|
// In dev we serve at <slug>.localhost:3000 (e.g. acme.localhost:3000). In
|
|
// prod we serve at <slug>.breakpilot.com. Backstage lives at the apex —
|
|
// no subdomain — and resolves to a fixed `__backstage__` slug.
|
|
|
|
export type HostMatch =
|
|
| { kind: "tenant"; slug: string }
|
|
| { kind: "backstage" }
|
|
| { kind: "apex" }
|
|
| { kind: "unknown" };
|
|
|
|
// Longest-first so `stage.breakpilot.com` is matched before `breakpilot.com`.
|
|
const APEX_HOSTS = ["stage.breakpilot.com", "breakpilot.com", "localhost"];
|
|
const APEX_SET = new Set(APEX_HOSTS);
|
|
|
|
export function parseHost(host: string | null | undefined): HostMatch {
|
|
if (!host) return { kind: "unknown" };
|
|
|
|
const hostNoPort = host.split(":")[0].toLowerCase();
|
|
|
|
if (APEX_SET.has(hostNoPort)) return { kind: "apex" };
|
|
|
|
// Strip the known apex suffix to extract the subdomain.
|
|
for (const apex of APEX_HOSTS) {
|
|
const suffix = `.${apex}`;
|
|
if (hostNoPort.endsWith(suffix)) {
|
|
const sub = hostNoPort.slice(0, -suffix.length);
|
|
if (!sub) return { kind: "apex" };
|
|
// Backstage is reserved.
|
|
if (sub === "backstage") return { kind: "backstage" };
|
|
// Slugs are [a-z0-9-]{2,40} per the tenant-registry schema check.
|
|
if (/^[a-z0-9-]{2,40}$/.test(sub)) {
|
|
return { kind: "tenant", slug: sub };
|
|
}
|
|
return { kind: "unknown" };
|
|
}
|
|
}
|
|
|
|
return { kind: "unknown" };
|
|
}
|