Fix magic link routes
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
"title": "Community Rule",
|
||||
"author": "MEDLab",
|
||||
"description": "Community governance and rule-building app",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"httpPort": 3000,
|
||||
"healthCheckPath": "/api/health",
|
||||
"memoryLimit": 805306368,
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
import { logRouteError } from "../../../../../lib/server/requestId";
|
||||
import { apiRoute } from "../../../../../lib/server/apiRoute";
|
||||
import { safeInternalPath } from "../../../../../lib/safeInternalPath";
|
||||
import { getPublicOrigin } from "../../../../../lib/server/publicOrigin";
|
||||
import { magicLinkRequestBodySchema } from "../../../../../lib/server/validation/createFlowSchemas";
|
||||
import { jsonFromZodError } from "../../../../../lib/server/validation/zodHttp";
|
||||
|
||||
@@ -99,7 +100,7 @@ export const POST = apiRoute(SCOPE, async (request: NextRequest, _ctx, { request
|
||||
},
|
||||
});
|
||||
|
||||
const origin = request.nextUrl.origin;
|
||||
const origin = getPublicOrigin(request);
|
||||
const verifyUrl = `${origin}/api/auth/magic-link/verify?token=${encodeURIComponent(token)}`;
|
||||
|
||||
try {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from "../../../../../../../lib/server/responses";
|
||||
import { getSessionUser } from "../../../../../../../lib/server/session";
|
||||
import { rateLimitKey } from "../../../../../../../lib/server/rateLimit";
|
||||
import { getPublicOrigin } from "../../../../../../../lib/server/publicOrigin";
|
||||
|
||||
type RouteContext = { params: Promise<{ id: string; stakeholderId: string }> };
|
||||
|
||||
@@ -92,7 +93,7 @@ export const POST = apiRoute<RouteContext>(
|
||||
},
|
||||
});
|
||||
|
||||
const verifyUrl = stakeholderInviteVerifyUrl(request.nextUrl.origin, token);
|
||||
const verifyUrl = stakeholderInviteVerifyUrl(getPublicOrigin(request), token);
|
||||
try {
|
||||
await sendRuleStakeholderInviteEmail(row.email, verifyUrl, row.rule.title);
|
||||
} catch (err) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
unauthorized,
|
||||
} from "../../../../../lib/server/responses";
|
||||
import { getSessionUser } from "../../../../../lib/server/session";
|
||||
import { getPublicOrigin } from "../../../../../lib/server/publicOrigin";
|
||||
import {
|
||||
MAX_STAKEHOLDER_EMAILS,
|
||||
postRuleStakeholderBodySchema,
|
||||
@@ -151,7 +152,7 @@ export const POST = apiRoute<RouteContext>(
|
||||
return serverMisconfigured();
|
||||
}
|
||||
|
||||
const origin = request.nextUrl.origin;
|
||||
const origin = getPublicOrigin(request);
|
||||
const sent = await createRuleStakeholderInviteAndSendMail({
|
||||
scope: "rules.stakeholders.add",
|
||||
requestId,
|
||||
|
||||
@@ -20,6 +20,7 @@ import { stakeholderInviteVerifyUrl } from "../../../lib/server/ruleStakeholderI
|
||||
import { STAKEHOLDER_INVITE_TTL_MS } from "../../../lib/server/ruleStakeholders";
|
||||
import { getSessionUser } from "../../../lib/server/session";
|
||||
import { apiRoute } from "../../../lib/server/apiRoute";
|
||||
import { getPublicOrigin } from "../../../lib/server/publicOrigin";
|
||||
import {
|
||||
publishRuleBodySchema,
|
||||
uniqueStakeholderEmailsForPublish,
|
||||
@@ -148,7 +149,7 @@ export const POST = apiRoute(
|
||||
return { rule: created, invites: toSend };
|
||||
});
|
||||
|
||||
const origin = request.nextUrl.origin;
|
||||
const origin = getPublicOrigin(request);
|
||||
try {
|
||||
for (const inv of invites) {
|
||||
const verifyUrl = stakeholderInviteVerifyUrl(origin, inv.token);
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
unauthorized,
|
||||
} from "../../../../../lib/server/responses";
|
||||
import { getSessionUser } from "../../../../../lib/server/session";
|
||||
import { getPublicOrigin } from "../../../../../lib/server/publicOrigin";
|
||||
import { readLimitedJson } from "../../../../../lib/server/validation/requestBody";
|
||||
import { emailChangeRequestBodySchema } from "../../../../../lib/server/validation/userEmailChangeSchemas";
|
||||
import { jsonFromZodError } from "../../../../../lib/server/validation/zodHttp";
|
||||
@@ -115,7 +116,7 @@ export const POST = apiRoute(SCOPE, async (request: NextRequest, _ctx, { request
|
||||
},
|
||||
});
|
||||
|
||||
const origin = request.nextUrl.origin;
|
||||
const origin = getPublicOrigin(request);
|
||||
const verifyUrl = `${origin}/api/user/email-change/verify?token=${encodeURIComponent(token)}`;
|
||||
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
/**
|
||||
* Resolve the public origin of the request, preferring proxy-forwarded headers.
|
||||
*
|
||||
* Next.js standalone behind Cloudron's reverse proxy sees `Host: 0.0.0.0:3000`
|
||||
* (the bind address from `ENV HOSTNAME` in the Dockerfile), so
|
||||
* `request.nextUrl.origin` returns an internal address unsuitable for
|
||||
* outbound URLs (magic-link emails, stakeholder invites, etc.).
|
||||
*
|
||||
* Trusts `X-Forwarded-Host` + `X-Forwarded-Proto` when present. Falls back
|
||||
* to `request.nextUrl.origin` for local dev where no proxy is in front.
|
||||
*/
|
||||
export function getPublicOrigin(request: NextRequest): string {
|
||||
const forwardedHost = request.headers.get("x-forwarded-host");
|
||||
const forwardedProto = request.headers.get("x-forwarded-proto");
|
||||
|
||||
if (forwardedHost) {
|
||||
const proto = forwardedProto?.split(",")[0]?.trim() || "https";
|
||||
const host = forwardedHost.split(",")[0]?.trim();
|
||||
if (host) return `${proto}://${host}`;
|
||||
}
|
||||
|
||||
return request.nextUrl.origin;
|
||||
}
|
||||
Reference in New Issue
Block a user