Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion frontend-web/app/api/verify-phone/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@
return NextResponse.json({ error: "Missing user_json_url." }, { status: 400 });
}

// Validate and restrict the URL to prevent SSRF
let verifiedUrl: URL;
try {
verifiedUrl = new URL(user_json_url);
} catch {
return NextResponse.json({ error: "Invalid user_json_url format." }, { status: 400 });
}

// Allow-list of trusted verification API hosts
const ALLOWED_HOSTS = ["trusted-verification.example.com"];

if (
(verifiedUrl.protocol !== "https:" && verifiedUrl.protocol !== "http:") ||
!ALLOWED_HOSTS.includes(verifiedUrl.hostname)
) {
return NextResponse.json({ error: "user_json_url is not allowed." }, { status: 400 });
}

// ❌ Do NOT use `NEXT_PUBLIC_` for private API keys (public keys)
const API_KEY = process.env.PHONE_EMAIL_API_KEY;

Expand All @@ -17,7 +35,7 @@
}

// Fetch user details from the verification API
const response = await fetch(user_json_url, {
const response = await fetch(verifiedUrl.toString(), {
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
Expand All @@ -29,12 +47,12 @@
return NextResponse.json({ error: "Failed to fetch user data from provider." }, { status: response.status });
}

// ✅ Handle potential empty response
const text = await response.text();
if (!text) {
console.error("Received an empty response from verification API.");
return NextResponse.json({ error: "Verification service returned no data." }, { status: 502 });
}

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

// ✅ Manually parse JSON to prevent `Unexpected end of JSON input`
let userData;
Expand Down
Loading