Fix CORS on Planner→Journal callback endpoint
The Save to Journal button makes a cross-origin POST from planner.trails.cool to trails.cool. The browser's preflight OPTIONS request was returning HTML (no CORS headers), blocking the save flow. Add CORS headers to all responses and handle OPTIONS preflight in a loader. Origin is restricted to PLANNER_URL. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
77d785ccde
commit
a5d682d671
1 changed files with 26 additions and 8 deletions
|
|
@ -3,9 +3,27 @@ import type { Route } from "./+types/api.routes.$id.callback";
|
|||
import { verifyRouteToken } from "~/lib/jwt.server";
|
||||
import { updateRoute, getRoute } from "~/lib/routes.server";
|
||||
|
||||
const PLANNER_ORIGIN = process.env.PLANNER_URL ?? "http://localhost:3001";
|
||||
|
||||
function corsHeaders() {
|
||||
return {
|
||||
"Access-Control-Allow-Origin": PLANNER_ORIGIN,
|
||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
||||
};
|
||||
}
|
||||
|
||||
export async function loader({ request }: Route.LoaderArgs) {
|
||||
// Handle CORS preflight
|
||||
if (request.method === "OPTIONS") {
|
||||
return new Response(null, { status: 204, headers: corsHeaders() });
|
||||
}
|
||||
return data({ error: "Method not allowed" }, { status: 405, headers: corsHeaders() });
|
||||
}
|
||||
|
||||
export async function action({ params, request }: Route.ActionArgs) {
|
||||
if (request.method !== "POST") {
|
||||
return data({ error: "Method not allowed" }, { status: 405 });
|
||||
return data({ error: "Method not allowed" }, { status: 405, headers: corsHeaders() });
|
||||
}
|
||||
|
||||
// Verify JWT token from Authorization header or body
|
||||
|
|
@ -13,7 +31,7 @@ export async function action({ params, request }: Route.ActionArgs) {
|
|||
const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
||||
|
||||
if (!token) {
|
||||
return data({ error: "Missing authorization token" }, { status: 401 });
|
||||
return data({ error: "Missing authorization token" }, { status: 401, headers: corsHeaders() });
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -21,17 +39,17 @@ export async function action({ params, request }: Route.ActionArgs) {
|
|||
|
||||
// Verify token is for this route
|
||||
if (routeId !== params.id) {
|
||||
return data({ error: "Token not valid for this route" }, { status: 403 });
|
||||
return data({ error: "Token not valid for this route" }, { status: 403, headers: corsHeaders() });
|
||||
}
|
||||
|
||||
if (!permissions.includes("write")) {
|
||||
return data({ error: "Token does not have write permission" }, { status: 403 });
|
||||
return data({ error: "Token does not have write permission" }, { status: 403, headers: corsHeaders() });
|
||||
}
|
||||
|
||||
// Get route to verify it exists
|
||||
const route = await getRoute(params.id);
|
||||
if (!route) {
|
||||
return data({ error: "Route not found" }, { status: 404 });
|
||||
return data({ error: "Route not found" }, { status: 404, headers: corsHeaders() });
|
||||
}
|
||||
|
||||
// Parse GPX from request body
|
||||
|
|
@ -39,14 +57,14 @@ export async function action({ params, request }: Route.ActionArgs) {
|
|||
const { gpx } = body as { gpx: string };
|
||||
|
||||
if (!gpx) {
|
||||
return data({ error: "Missing GPX data" }, { status: 400 });
|
||||
return data({ error: "Missing GPX data" }, { status: 400, headers: corsHeaders() });
|
||||
}
|
||||
|
||||
// Update route with new GPX (creates new version)
|
||||
await updateRoute(params.id, route.ownerId, { gpx });
|
||||
|
||||
return data({ success: true, routeId: params.id });
|
||||
return data({ success: true, routeId: params.id }, { headers: corsHeaders() });
|
||||
} catch (e) {
|
||||
return data({ error: (e as Error).message }, { status: 401 });
|
||||
return data({ error: (e as Error).message }, { status: 401, headers: corsHeaders() });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue