35971596b3
Implement strict CORS enforcement and per-IP rate limiting in the Next.js middleware. Add input validation helpers for coordinates and request body size limits. Introduce SSRF protection for calendar URL fetching. Update mobile settings to support new transport options and arrival buffers. Include a comprehensive manual testing checklist for integration verification.
57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
import { randomUUID } from "crypto";
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
import { GeocodingClient } from "@/lib/geocoding-client";
|
|
|
|
// Module-level singleton — cache persists across requests
|
|
const client = new GeocodingClient();
|
|
|
|
/**
|
|
* Maximum allowed query length. Station/place names are typically
|
|
* < 100 characters. This prevents abuse with extremely long inputs.
|
|
*/
|
|
const MAX_QUERY_LENGTH = 256;
|
|
|
|
/**
|
|
* Allowed country codes for narrowing the search.
|
|
* Two-letter ISO 3166-1 alpha-2 codes.
|
|
*/
|
|
const ALLOWED_COUNTRY_CODES = /^[a-z]{2}(,[a-z]{2}){0,4}$/;
|
|
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const { searchParams } = new URL(request.url);
|
|
const name = searchParams.get("name");
|
|
const countrycodes = searchParams.get("countrycodes");
|
|
|
|
if (!name) {
|
|
return NextResponse.json({ error: "Missing 'name' parameter" }, { status: 400 });
|
|
}
|
|
|
|
if (name.length > MAX_QUERY_LENGTH) {
|
|
return NextResponse.json(
|
|
{ error: "Query too long (max 256 characters)" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
if (countrycodes && !ALLOWED_COUNTRY_CODES.test(countrycodes.toLowerCase())) {
|
|
return NextResponse.json(
|
|
{ error: "Invalid countrycodes (use up to 5 two-letter codes, comma-separated)" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const results = await client.geocode(name, countrycodes || undefined);
|
|
|
|
if (results.length === 0) {
|
|
return NextResponse.json({ error: "No results found" }, { status: 404 });
|
|
}
|
|
|
|
return NextResponse.json(results[0]);
|
|
} catch (error) {
|
|
const corrId = randomUUID().slice(0, 8);
|
|
console.error(`[${corrId}] Geocode API error:`, error);
|
|
return NextResponse.json({ error: "Internal server error", correlationId: corrId }, { status: 500 });
|
|
}
|
|
}
|