Security fix: Implement Google Maps API photo proxy (Fix 3)

Completed HIGH severity security fix (CVSS 6.5) to prevent Google Maps
API key exposure to frontend clients.

Issue: API key was embedded in photo URLs sent to frontend, allowing
potential abuse and quota exhaustion.

Solution: Implemented backend proxy endpoint for photos.

Backend Changes:
- google-maps.client.ts: Changed photoUrl to photoReference, added fetchPhoto()
- stations.types.ts: Updated type definition (photoUrl → photoReference)
- stations.controller.ts: Added getStationPhoto() proxy method
- stations.routes.ts: Added GET /api/stations/photo/:reference route
- stations.service.ts: Updated to use photoReference
- stations.repository.ts: Updated database queries and mappings
- admin controllers/services: Updated for consistency
- Created migration 003 to rename photo_url column

Frontend Changes:
- stations.types.ts: Updated type definition (photoUrl → photoReference)
- photo-utils.ts: NEW - Helper to generate proxy URLs
- StationCard.tsx: Use photoReference with helper function

Tests & Docs:
- Updated mock data to use photoReference
- Updated test expectations for proxy URLs
- Updated API.md and TESTING.md documentation

Database Migration:
- 003_rename_photo_url_to_photo_reference.sql: Renames column in station_cache

Security Benefits:
- API key never sent to frontend
- All photo requests proxied through authenticated endpoint
- Photos cached for 24 hours (Cache-Control header)
- No client-side API key exposure

Files modified: 16 files
New files: 2 (photo-utils.ts, migration 003)

Status: All 3 P0 security fixes now complete
- Fix 1: crypto.randomBytes() ✓
- Fix 2: Magic byte validation ✓
- Fix 3: API key proxy ✓

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2025-12-14 09:56:33 -06:00
parent a35e1a3aea
commit bcb1cea311
16 changed files with 130 additions and 46 deletions

View File

@@ -62,7 +62,7 @@ Search for gas stations near a location using Google Maps Places API.
"latitude": 37.7750,
"longitude": -122.4195,
"rating": 4.2,
"photoUrl": "https://maps.googleapis.com/maps/api/place/photo?...",
"photoReference": "/api/stations/photo/{reference}",
"distance": 150
}
],
@@ -85,7 +85,7 @@ Search for gas stations near a location using Google Maps Places API.
| stations[].latitude | number | Station latitude |
| stations[].longitude | number | Station longitude |
| stations[].rating | number | Google rating (0-5) |
| stations[].photoUrl | string | Photo URL (nullable) |
| stations[].photoReference | string | Photo URL (nullable) |
| stations[].distance | number | Distance from search location (meters) |
| searchLocation | object | Original search coordinates |
| searchRadius | number | Actual search radius used |
@@ -187,7 +187,7 @@ Save a station to user's favorites with optional metadata.
"latitude": 37.7750,
"longitude": -122.4195,
"rating": 4.2,
"photoUrl": "https://maps.googleapis.com/maps/api/place/photo?..."
"photoReference": "/api/stations/photo/{reference}"
}
}
```
@@ -286,7 +286,7 @@ Retrieve all stations saved by the authenticated user.
"latitude": 37.7750,
"longitude": -122.4195,
"rating": 4.2,
"photoUrl": "https://maps.googleapis.com/maps/api/place/photo?..."
"photoReference": "/api/stations/photo/{reference}"
}
}
]
@@ -355,7 +355,7 @@ Retrieve a specific saved station by Google Place ID.
"latitude": 37.7750,
"longitude": -122.4195,
"rating": 4.2,
"photoUrl": "https://maps.googleapis.com/maps/api/place/photo?..."
"photoReference": "/api/stations/photo/{reference}"
}
}
```
@@ -437,7 +437,7 @@ Update metadata for a saved station.
"latitude": 37.7750,
"longitude": -122.4195,
"rating": 4.2,
"photoUrl": "https://maps.googleapis.com/maps/api/place/photo?..."
"photoReference": "/api/stations/photo/{reference}"
}
}
```

View File

@@ -471,7 +471,7 @@ export const mockStations = [
latitude: 37.7750,
longitude: -122.4195,
rating: 4.2,
photoUrl: 'https://example.com/photo1.jpg',
photoReference: 'mock-photo-reference-1',
distance: 150
},
{
@@ -481,7 +481,7 @@ export const mockStations = [
latitude: 37.7755,
longitude: -122.4190,
rating: 4.0,
photoUrl: null,
photoReference: null,
distance: 300
}
];