-
Notifications
You must be signed in to change notification settings - Fork 75
fix(google-maps): static maps proxy, color mode, and bug fixes #587
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
906ad46
fb3d15e
3de9c14
f713a1a
b8cac43
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,67 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import { createError, defineEventHandler, getHeader, getQuery, setHeader } from 'h3' | ||||||||||||||||||||||||||||||||||||||||||||||
| import { $fetch } from 'ofetch' | ||||||||||||||||||||||||||||||||||||||||||||||
| import { withQuery } from 'ufo' | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useRuntimeConfig } from '#imports' | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export default defineEventHandler(async (event) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const runtimeConfig = useRuntimeConfig() | ||||||||||||||||||||||||||||||||||||||||||||||
| const publicConfig = (runtimeConfig.public['nuxt-scripts'] as any)?.googleStaticMapsProxy | ||||||||||||||||||||||||||||||||||||||||||||||
| const privateConfig = (runtimeConfig['nuxt-scripts'] as any)?.googleStaticMapsProxy | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (!publicConfig?.enabled) { | ||||||||||||||||||||||||||||||||||||||||||||||
| throw createError({ | ||||||||||||||||||||||||||||||||||||||||||||||
| statusCode: 404, | ||||||||||||||||||||||||||||||||||||||||||||||
| statusMessage: 'Google Static Maps proxy is not enabled', | ||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Get API key from private config (server-side only, not exposed to client) | ||||||||||||||||||||||||||||||||||||||||||||||
| const apiKey = privateConfig?.apiKey | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!apiKey) { | ||||||||||||||||||||||||||||||||||||||||||||||
| throw createError({ | ||||||||||||||||||||||||||||||||||||||||||||||
| statusCode: 500, | ||||||||||||||||||||||||||||||||||||||||||||||
| statusMessage: 'Google Maps API key not configured for proxy', | ||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Validate referer to prevent external abuse | ||||||||||||||||||||||||||||||||||||||||||||||
| const referer = getHeader(event, 'referer') | ||||||||||||||||||||||||||||||||||||||||||||||
| const host = getHeader(event, 'host') | ||||||||||||||||||||||||||||||||||||||||||||||
| if (referer && host) { | ||||||||||||||||||||||||||||||||||||||||||||||
| const refererUrl = new URL(referer).host | ||||||||||||||||||||||||||||||||||||||||||||||
| if (refererUrl !== host) { | ||||||||||||||||||||||||||||||||||||||||||||||
| throw createError({ | ||||||||||||||||||||||||||||||||||||||||||||||
| statusCode: 403, | ||||||||||||||||||||||||||||||||||||||||||||||
| statusMessage: 'Invalid referer', | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+31
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
The referer header URL parsing can throw an uncaught error if the referer is malformed, causing an unhandled exception instead of a proper HTTP error response. View DetailsAnalysisUnhandled URL parsing error in Google Static Maps proxy referer validationWhat fails: The How to reproduce: # Send a request with a malformed referer header
curl -H "Referer: this-is-not-a-valid-url" http://localhost:3000/api/google-static-mapsResult: Returns 500 Internal Server Error due to unhandled exception from Expected: Should return 400 Bad Request with proper error message, consistent with other validation errors in the function that use Details: The referer header can contain any string value from the HTTP request. When |
||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const query = getQuery(event) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Remove any client-provided key and use server-side key | ||||||||||||||||||||||||||||||||||||||||||||||
| const { key: _clientKey, ...safeQuery } = query | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const googleMapsUrl = withQuery('https://maps.googleapis.com/maps/api/staticmap', { | ||||||||||||||||||||||||||||||||||||||||||||||
| ...safeQuery, | ||||||||||||||||||||||||||||||||||||||||||||||
| key: apiKey, | ||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const response = await $fetch.raw(googleMapsUrl, { | ||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||
| 'User-Agent': 'Nuxt Scripts Google Static Maps Proxy', | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
| }).catch((error: any) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| throw createError({ | ||||||||||||||||||||||||||||||||||||||||||||||
| statusCode: error.statusCode || 500, | ||||||||||||||||||||||||||||||||||||||||||||||
| statusMessage: error.statusMessage || 'Failed to fetch static map', | ||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const cacheMaxAge = publicConfig.cacheMaxAge || 3600 | ||||||||||||||||||||||||||||||||||||||||||||||
| setHeader(event, 'Content-Type', response.headers.get('content-type') || 'image/png') | ||||||||||||||||||||||||||||||||||||||||||||||
| setHeader(event, 'Cache-Control', `public, max-age=${cacheMaxAge}, s-maxage=${cacheMaxAge}`) | ||||||||||||||||||||||||||||||||||||||||||||||
| setHeader(event, 'Vary', 'Accept-Encoding') | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| return response._data | ||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.