feat(app): Next.js 16 + Auth.js v5 portal skeleton
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.
This commit was merged in pull request #4.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
// 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" };
|
||||
}
|
||||
Reference in New Issue
Block a user