Fix magic link routes
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
"title": "Community Rule",
|
"title": "Community Rule",
|
||||||
"author": "MEDLab",
|
"author": "MEDLab",
|
||||||
"description": "Community governance and rule-building app",
|
"description": "Community governance and rule-building app",
|
||||||
"version": "0.1.5",
|
"version": "0.1.6",
|
||||||
"httpPort": 3000,
|
"httpPort": 3000,
|
||||||
"healthCheckPath": "/api/health",
|
"healthCheckPath": "/api/health",
|
||||||
"memoryLimit": 805306368,
|
"memoryLimit": 805306368,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import { logRouteError } from "../../../../../lib/server/requestId";
|
import { logRouteError } from "../../../../../lib/server/requestId";
|
||||||
import { apiRoute } from "../../../../../lib/server/apiRoute";
|
import { apiRoute } from "../../../../../lib/server/apiRoute";
|
||||||
import { safeInternalPath } from "../../../../../lib/safeInternalPath";
|
import { safeInternalPath } from "../../../../../lib/safeInternalPath";
|
||||||
|
import { getPublicOrigin } from "../../../../../lib/server/publicOrigin";
|
||||||
import { magicLinkRequestBodySchema } from "../../../../../lib/server/validation/createFlowSchemas";
|
import { magicLinkRequestBodySchema } from "../../../../../lib/server/validation/createFlowSchemas";
|
||||||
import { jsonFromZodError } from "../../../../../lib/server/validation/zodHttp";
|
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)}`;
|
const verifyUrl = `${origin}/api/auth/magic-link/verify?token=${encodeURIComponent(token)}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
} from "../../../../../../../lib/server/responses";
|
} from "../../../../../../../lib/server/responses";
|
||||||
import { getSessionUser } from "../../../../../../../lib/server/session";
|
import { getSessionUser } from "../../../../../../../lib/server/session";
|
||||||
import { rateLimitKey } from "../../../../../../../lib/server/rateLimit";
|
import { rateLimitKey } from "../../../../../../../lib/server/rateLimit";
|
||||||
|
import { getPublicOrigin } from "../../../../../../../lib/server/publicOrigin";
|
||||||
|
|
||||||
type RouteContext = { params: Promise<{ id: string; stakeholderId: string }> };
|
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 {
|
try {
|
||||||
await sendRuleStakeholderInviteEmail(row.email, verifyUrl, row.rule.title);
|
await sendRuleStakeholderInviteEmail(row.email, verifyUrl, row.rule.title);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
unauthorized,
|
unauthorized,
|
||||||
} from "../../../../../lib/server/responses";
|
} from "../../../../../lib/server/responses";
|
||||||
import { getSessionUser } from "../../../../../lib/server/session";
|
import { getSessionUser } from "../../../../../lib/server/session";
|
||||||
|
import { getPublicOrigin } from "../../../../../lib/server/publicOrigin";
|
||||||
import {
|
import {
|
||||||
MAX_STAKEHOLDER_EMAILS,
|
MAX_STAKEHOLDER_EMAILS,
|
||||||
postRuleStakeholderBodySchema,
|
postRuleStakeholderBodySchema,
|
||||||
@@ -151,7 +152,7 @@ export const POST = apiRoute<RouteContext>(
|
|||||||
return serverMisconfigured();
|
return serverMisconfigured();
|
||||||
}
|
}
|
||||||
|
|
||||||
const origin = request.nextUrl.origin;
|
const origin = getPublicOrigin(request);
|
||||||
const sent = await createRuleStakeholderInviteAndSendMail({
|
const sent = await createRuleStakeholderInviteAndSendMail({
|
||||||
scope: "rules.stakeholders.add",
|
scope: "rules.stakeholders.add",
|
||||||
requestId,
|
requestId,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { stakeholderInviteVerifyUrl } from "../../../lib/server/ruleStakeholderI
|
|||||||
import { STAKEHOLDER_INVITE_TTL_MS } from "../../../lib/server/ruleStakeholders";
|
import { STAKEHOLDER_INVITE_TTL_MS } from "../../../lib/server/ruleStakeholders";
|
||||||
import { getSessionUser } from "../../../lib/server/session";
|
import { getSessionUser } from "../../../lib/server/session";
|
||||||
import { apiRoute } from "../../../lib/server/apiRoute";
|
import { apiRoute } from "../../../lib/server/apiRoute";
|
||||||
|
import { getPublicOrigin } from "../../../lib/server/publicOrigin";
|
||||||
import {
|
import {
|
||||||
publishRuleBodySchema,
|
publishRuleBodySchema,
|
||||||
uniqueStakeholderEmailsForPublish,
|
uniqueStakeholderEmailsForPublish,
|
||||||
@@ -148,7 +149,7 @@ export const POST = apiRoute(
|
|||||||
return { rule: created, invites: toSend };
|
return { rule: created, invites: toSend };
|
||||||
});
|
});
|
||||||
|
|
||||||
const origin = request.nextUrl.origin;
|
const origin = getPublicOrigin(request);
|
||||||
try {
|
try {
|
||||||
for (const inv of invites) {
|
for (const inv of invites) {
|
||||||
const verifyUrl = stakeholderInviteVerifyUrl(origin, inv.token);
|
const verifyUrl = stakeholderInviteVerifyUrl(origin, inv.token);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
unauthorized,
|
unauthorized,
|
||||||
} from "../../../../../lib/server/responses";
|
} from "../../../../../lib/server/responses";
|
||||||
import { getSessionUser } from "../../../../../lib/server/session";
|
import { getSessionUser } from "../../../../../lib/server/session";
|
||||||
|
import { getPublicOrigin } from "../../../../../lib/server/publicOrigin";
|
||||||
import { readLimitedJson } from "../../../../../lib/server/validation/requestBody";
|
import { readLimitedJson } from "../../../../../lib/server/validation/requestBody";
|
||||||
import { emailChangeRequestBodySchema } from "../../../../../lib/server/validation/userEmailChangeSchemas";
|
import { emailChangeRequestBodySchema } from "../../../../../lib/server/validation/userEmailChangeSchemas";
|
||||||
import { jsonFromZodError } from "../../../../../lib/server/validation/zodHttp";
|
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)}`;
|
const verifyUrl = `${origin}/api/user/email-change/verify?token=${encodeURIComponent(token)}`;
|
||||||
|
|
||||||
try {
|
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