mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 09:19:51 +00:00
Merge pull request #186 from ArnasDon/fix/invite-url-origin
fix(invite): derive invite-URL origin from the request, not wacrm.tech
This commit is contained in:
commit
7aac97e3ef
@ -28,8 +28,12 @@ META_APP_SECRET=your-meta-app-secret
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
# Canonical public URL of this deployment (scheme + host, no trailing
|
# Canonical public URL of this deployment (scheme + host, no trailing
|
||||||
# slash). Used for the sitemap, OG images, and any email the app
|
# slash). Used for the sitemap and OG images. Routes that need a
|
||||||
# sends. Defaults to https://wacrm.tech if unset.
|
# self-referential URL (invite links from /api/account/invitations,
|
||||||
|
# any future email-bound link) derive the origin from the request
|
||||||
|
# itself, so this variable is *only* needed when the request-derived
|
||||||
|
# origin would be wrong — e.g. when generating links from a cron job
|
||||||
|
# or a background worker that has no incoming request.
|
||||||
NEXT_PUBLIC_SITE_URL=https://crm.example.com
|
NEXT_PUBLIC_SITE_URL=https://crm.example.com
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
@ -28,11 +28,57 @@ import {
|
|||||||
} from "@/lib/auth/invitations";
|
} from "@/lib/auth/invitations";
|
||||||
import { isAccountRole } from "@/lib/auth/roles";
|
import { isAccountRole } from "@/lib/auth/roles";
|
||||||
|
|
||||||
// Resolve the base URL we publish invite links under. Mirrors the
|
// Resolve the base URL we publish invite links under.
|
||||||
// .env.local.example default so dev / preview / forks all behave
|
//
|
||||||
// without explicit config.
|
// Resolution order, first match wins:
|
||||||
function getBaseUrl(): string {
|
//
|
||||||
return process.env.NEXT_PUBLIC_SITE_URL || "https://wacrm.tech";
|
// 1. `NEXT_PUBLIC_SITE_URL` — admin's explicit config. Trumps
|
||||||
|
// everything; if you set this, that's where links point.
|
||||||
|
// 2. `X-Forwarded-Host` (+ `X-Forwarded-Proto`) — set by every
|
||||||
|
// reverse proxy in front of the app: Hostinger Managed
|
||||||
|
// Node.js, Vercel, Cloudflare, nginx. This is what makes
|
||||||
|
// invite links Just Work in production without forcing the
|
||||||
|
// operator to set an env var.
|
||||||
|
// 3. `Host` header + the protocol the request arrived on —
|
||||||
|
// bare deployments without a proxy.
|
||||||
|
// 4. Last-resort marketing-site fallback. Only hit if the
|
||||||
|
// request has no Host header at all, which is essentially
|
||||||
|
// impossible from a real browser. Logs a warning so the
|
||||||
|
// operator can spot the misconfig.
|
||||||
|
//
|
||||||
|
// Previous implementation hard-defaulted to `https://wacrm.tech`
|
||||||
|
// (the docs/marketing site, a different repo). Forks that didn't
|
||||||
|
// set `NEXT_PUBLIC_SITE_URL` got invite links pointing at the
|
||||||
|
// marketing site, which 404s on `/join/<token>`. This resolution
|
||||||
|
// chain removes the foot-gun.
|
||||||
|
function getBaseUrl(request: Request): string {
|
||||||
|
const explicit = process.env.NEXT_PUBLIC_SITE_URL?.trim();
|
||||||
|
if (explicit) return explicit.replace(/\/+$/, "");
|
||||||
|
|
||||||
|
const forwardedHost = request.headers
|
||||||
|
.get("x-forwarded-host")
|
||||||
|
?.split(",")[0]
|
||||||
|
?.trim();
|
||||||
|
const forwardedProto = request.headers
|
||||||
|
.get("x-forwarded-proto")
|
||||||
|
?.split(",")[0]
|
||||||
|
?.trim();
|
||||||
|
if (forwardedHost) {
|
||||||
|
return `${forwardedProto || "https"}://${forwardedHost}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const host = request.headers.get("host")?.trim();
|
||||||
|
if (host) {
|
||||||
|
// The protocol on `request.url` is whatever the framework saw —
|
||||||
|
// reliable for bare deployments where no proxy is rewriting it.
|
||||||
|
const reqProto = new URL(request.url).protocol.replace(":", "");
|
||||||
|
return `${reqProto}://${host}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn(
|
||||||
|
"[POST /api/account/invitations] could not derive base URL from request; falling back to marketing domain",
|
||||||
|
);
|
||||||
|
return "https://wacrm.tech";
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_LABEL_LEN = 80;
|
const MAX_LABEL_LEN = 80;
|
||||||
@ -133,7 +179,7 @@ export async function POST(request: Request) {
|
|||||||
invitation: data,
|
invitation: data,
|
||||||
// Plaintext payload — visible to the admin exactly once.
|
// Plaintext payload — visible to the admin exactly once.
|
||||||
token,
|
token,
|
||||||
url: inviteUrl(token, getBaseUrl()),
|
url: inviteUrl(token, getBaseUrl(request)),
|
||||||
expiresInDays: expiryDays,
|
expiresInDays: expiryDays,
|
||||||
},
|
},
|
||||||
{ status: 201 },
|
{ status: 201 },
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user