my own indieAuth provider! indiko.dunkirk.sh/docs
indieauth oauth2-server

chore: fix client id validation

dunkirk.sh ad135326 87c0d960

verified
+33 -12
+33 -12
src/routes/indieauth.ts
··· 110 110 return hash === challenge; 111 111 } 112 112 113 - // Canonicalize URL per IndieAuth spec 113 + // Canonicalize URL per IndieAuth spec (only for actual URLs, not internal IDs) 114 114 function canonicalizeURL(urlString: string): string { 115 + // Only canonicalize if it looks like a URL 116 + if (!urlString.startsWith("http://") && !urlString.startsWith("https://")) { 117 + return urlString; 118 + } 115 119 const url = new URL(urlString); 116 120 // Lowercase hostname per spec 117 121 url.hostname = url.hostname.toLowerCase(); ··· 584 588 return new Response("Missing required parameters", { status: 400 }); 585 589 } 586 590 587 - // Canonicalize URLs for consistent storage and comparison 588 - const clientId = canonicalizeURL(rawClientId); 589 - const redirectUri = canonicalizeURL(rawRedirectUri); 590 - 591 - // Validate redirect_uri is a valid URL 591 + // Validate and canonicalize URLs for consistent storage and comparison 592 + let clientId: string; 593 + let redirectUri: string; 592 594 try { 593 - new URL(redirectUri); 595 + clientId = canonicalizeURL(rawClientId); 596 + redirectUri = canonicalizeURL(rawRedirectUri); 594 597 } catch { 595 598 return new Response( 596 599 `<!DOCTYPE html> ··· 672 675 </p> 673 676 <div class="error-details"> 674 677 <strong>Provided redirect_uri:</strong> 675 - <code>${redirectUri}</code> 678 + <code>${rawRedirectUri}</code> 676 679 </div> 677 680 <p style="margin-top: 1.5rem; font-size: 0.875rem; color: var(--old-rose);"> 678 681 The redirect URI must be a valid, absolute URL (e.g., https://example.com/callback). ··· 1364 1367 } 1365 1368 1366 1369 // Canonicalize URLs for consistent storage and comparison 1367 - const clientId = canonicalizeURL(rawClientId); 1368 - const redirectUri = canonicalizeURL(rawRedirectUri); 1370 + let clientId: string; 1371 + let redirectUri: string; 1372 + try { 1373 + clientId = canonicalizeURL(rawClientId); 1374 + redirectUri = canonicalizeURL(rawRedirectUri); 1375 + } catch { 1376 + return new Response("Invalid client_id or redirect_uri URL format", { status: 400 }); 1377 + } 1369 1378 1370 1379 if (action === "deny") { 1371 1380 return Response.redirect( ··· 1474 1483 } = body; 1475 1484 1476 1485 // Canonicalize URLs for consistent comparison with stored values 1477 - const client_id = raw_client_id ? canonicalizeURL(raw_client_id) : undefined; 1478 - const redirect_uri = raw_redirect_uri ? canonicalizeURL(raw_redirect_uri) : undefined; 1486 + let client_id: string | undefined; 1487 + let redirect_uri: string | undefined; 1488 + try { 1489 + client_id = raw_client_id ? canonicalizeURL(raw_client_id) : undefined; 1490 + redirect_uri = raw_redirect_uri ? canonicalizeURL(raw_redirect_uri) : undefined; 1491 + } catch { 1492 + return Response.json( 1493 + { 1494 + error: "invalid_request", 1495 + error_description: "Invalid client_id or redirect_uri URL format", 1496 + }, 1497 + { status: 400 }, 1498 + ); 1499 + } 1479 1500 1480 1501 if (grant_type !== "authorization_code" && grant_type !== "refresh_token") { 1481 1502 return Response.json(