feat: centralize app base URL — no localhost fallback in production

Introduce getAppBaseUrl() in packages/api/src/lib/app-base-url.ts:
- Reads NEXTAUTH_URL (trimmed, trailing slash stripped)
- production: throws if NEXTAUTH_URL is missing/empty so broken
  localhost links in emails are caught at runtime, not silently sent
- development/test: falls back to http://localhost:3100 with a
  one-time console.warn

Replace the duplicated inline fallback in:
- packages/api/src/router/invite.ts (invite email link)
- packages/api/src/router/auth.ts (password reset email link)

Extend GET /api/health to report:
  "baseUrl": { "configured": bool, "isLocalhost": bool }
so deployment checks can detect a misconfigured NEXTAUTH_URL.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-04-02 14:19:19 +02:00
parent 7c0110df91
commit dc5bbdc47d
4 changed files with 227 additions and 6 deletions
+38
View File
@@ -0,0 +1,38 @@
/**
* Returns the canonical public base URL of this app.
*
* Source: `NEXTAUTH_URL` environment variable (required in production).
*
* - Production (`NODE_ENV=production`): throws if the variable is missing or empty,
* because email links would silently point at localhost otherwise.
* - Development / test: falls back to `http://localhost:3100` and logs a one-time warning.
*
* Trailing slashes are stripped so callers can safely append paths with a leading `/`.
*/
let warned = false;
export function getAppBaseUrl(): string {
const raw = process.env["NEXTAUTH_URL"]?.trim();
if (raw) {
return raw.replace(/\/$/, "");
}
if (process.env["NODE_ENV"] === "production") {
throw new Error(
"NEXTAUTH_URL must be set in production — email links will contain localhost otherwise. " +
"Set it to the public URL of this app (e.g. https://capakraken.example.com).",
);
}
if (!warned) {
warned = true;
console.warn(
"[capakraken] NEXTAUTH_URL is not set — falling back to http://localhost:3100 for email links. " +
"Set NEXTAUTH_URL in your .env to suppress this warning.",
);
}
return "http://localhost:3100";
}