Gas Station Feature

This commit is contained in:
Eric Gullickson
2025-11-04 18:46:46 -06:00
parent d8d0ada83f
commit 5dc58d73b9
61 changed files with 12952 additions and 52 deletions

View File

@@ -0,0 +1,504 @@
# Gas Stations Feature - Secrets Verification Guide
## Overview
Complete guide for verifying Google Maps API key secrets are correctly configured in both backend and frontend containers. This document covers verification commands, troubleshooting steps, and security validation.
## Secrets Architecture
### Backend Secret Flow
```
Host filesystem: ./secrets/app/google-maps-api-key.txt
↓ (Docker volume mount)
Container: /run/secrets/google-maps-api-key
↓ (Config loader reads at startup)
Backend service: process.env or config object
↓ (Used in Google Maps client)
Google Places API calls
```
### Frontend Secret Flow
```
Host filesystem: ./secrets/app/google-maps-api-key.txt
↓ (Docker volume mount)
Container: /run/secrets/google-maps-api-key
↓ (Entrypoint script reads at startup)
Generated file: /usr/share/nginx/html/config.js
↓ (Loaded in index.html)
Browser: window.CONFIG.googleMapsApiKey
↓ (Used in maps-loader.ts)
Google Maps JavaScript API
```
## Verification Commands
### 1. Host Filesystem Verification
**Check secret file exists**:
```bash
ls -la ./secrets/app/google-maps-api-key.txt
```
**Expected Output**:
```
-rw-r--r-- 1 user user 39 Jan 15 10:00 ./secrets/app/google-maps-api-key.txt
```
**View secret content** (only in development):
```bash
cat ./secrets/app/google-maps-api-key.txt
```
**Expected**: API key starting with `AIzaSy...` (39 characters)
**Check file permissions**:
```bash
stat -c "%a %n" ./secrets/app/google-maps-api-key.txt
```
**Expected**: `644` (readable by owner and group)
### 2. Backend Container Verification
**Check secret mounted in container**:
```bash
docker compose exec mvp-backend cat /run/secrets/google-maps-api-key
```
**Expected**: Same API key as host file
**Check secret file permissions in container**:
```bash
docker compose exec mvp-backend ls -la /run/secrets/google-maps-api-key
```
**Expected**: Read-only file (`:ro` mount flag)
**Check backend can access secret**:
```bash
docker compose exec mvp-backend node -e "
const fs = require('fs');
try {
const key = fs.readFileSync('/run/secrets/google-maps-api-key', 'utf8').trim();
console.log('Secret loaded, length:', key.length);
console.log('Starts with AIzaSy:', key.startsWith('AIzaSy'));
} catch (error) {
console.error('Failed to read secret:', error.message);
}
"
```
**Expected Output**:
```
Secret loaded, length: 39
Starts with AIzaSy: true
```
**Check backend logs for secret loading**:
```bash
docker compose logs mvp-backend | grep -i "google.*api.*key"
```
**Expected** (log message should NOT show actual key, only status):
```
[Config] Google Maps API key loaded successfully
```
### 3. Frontend Container Verification
**Check secret mounted in container**:
```bash
docker compose exec mvp-frontend cat /run/secrets/google-maps-api-key
```
**Expected**: Same API key as host file
**Check config.js generated**:
```bash
docker compose exec mvp-frontend cat /usr/share/nginx/html/config.js
```
**Expected Output**:
```javascript
window.CONFIG = {
googleMapsApiKey: 'AIzaSyYourActualKeyHere'
};
```
**Check entrypoint script ran**:
```bash
docker compose logs mvp-frontend | grep -i config
```
**Expected Output**:
```
[Config] Loaded Google Maps API key from /run/secrets/google-maps-api-key
[Config] Generated /usr/share/nginx/html/config.js
```
**Check config.js is served**:
```bash
curl -s http://localhost:3000/config.js
```
**Expected**: JavaScript file with `window.CONFIG = {...}`
### 4. Browser Verification
**Check config loaded in browser console**:
1. Open https://motovaultpro.com in browser
2. Open Developer Tools (F12)
3. Go to Console tab
4. Run:
```javascript
console.log(window.CONFIG);
```
**Expected Output**:
```javascript
{
googleMapsApiKey: "AIzaSyYourActualKeyHere"
}
```
**Check Google Maps script loads**:
```javascript
console.log(typeof google !== 'undefined' ? 'Google Maps loaded' : 'Not loaded yet');
```
**Check for config errors**:
```javascript
// Should not show any errors related to config
console.log(document.querySelectorAll('script[src*="config.js"]').length);
// Should return: 1
```
### 5. API Integration Verification
**Test backend can call Google Maps API**:
```bash
docker compose exec mvp-backend node -e "
const https = require('https');
const fs = require('fs');
const apiKey = fs.readFileSync('/run/secrets/google-maps-api-key', 'utf8').trim();
const url = \`https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=37.7749,-122.4194&radius=5000&type=gas_station&key=\${apiKey}\`;
https.get(url, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => {
const json = JSON.parse(data);
console.log('API Status:', json.status);
console.log('Results:', json.results?.length || 0, 'stations');
});
}).on('error', (err) => {
console.error('API Error:', err.message);
});
"
```
**Expected Output**:
```
API Status: OK
Results: 20 stations
```
**Test full API workflow with JWT**:
```bash
# Get JWT token (from Auth0 or test token)
export TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
# Search for stations
curl -X POST http://localhost:3001/api/stations/search \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"latitude": 37.7749,
"longitude": -122.4194,
"radius": 5000
}' | jq '.stations | length'
```
**Expected**: Number greater than 0 (indicating stations found)
## Security Validation
### 1. Secret Not in Environment Variables
**Check backend environment**:
```bash
docker compose exec mvp-backend env | grep -i google
```
**Expected**: No GOOGLE_MAPS_API_KEY in environment (should use file mount)
**Check frontend environment**:
```bash
docker compose exec mvp-frontend env | grep -i google
```
**Expected**: No GOOGLE_MAPS_API_KEY in environment
### 2. Secret Not in Docker Images
**Check backend image**:
```bash
docker history motovaultpro-backend | grep -i google
```
**Expected**: No API key in image layers
**Check frontend image**:
```bash
docker history motovaultpro-frontend | grep -i google
```
**Expected**: No API key in image layers
### 3. Secret Not in Logs
**Check all logs for API key exposure**:
```bash
docker compose logs | grep -i "AIzaSy"
```
**Expected**: No matches (API key should never be logged)
**Check for "secret" or "key" logging**:
```bash
docker compose logs | grep -i "api.*key" | grep -v "loaded successfully"
```
**Expected**: Only status messages, no actual key values
### 4. File Permissions Secure
**Check host file not world-readable**:
```bash
ls -l ./secrets/app/google-maps-api-key.txt | awk '{print $1}'
```
**Expected**: `-rw-r--r--` or stricter (not `-rw-rw-rw-`)
**Check container mount is read-only**:
```bash
docker compose config | grep -A5 "google-maps-api-key"
```
**Expected**: Contains `:ro` flag
### 5. Secret Rotation Test
**Update secret and verify change propagates**:
```bash
# Backup current secret
cp ./secrets/app/google-maps-api-key.txt ./secrets/app/google-maps-api-key.txt.bak
# Update with new key (test key for rotation testing)
echo "AIzaSyTestKeyForRotation" > ./secrets/app/google-maps-api-key.txt
# Restart containers
docker compose restart mvp-backend mvp-frontend
# Wait for startup
sleep 5
# Verify new key loaded in backend
docker compose exec mvp-backend cat /run/secrets/google-maps-api-key
# Verify new key in frontend config.js
docker compose exec mvp-frontend cat /usr/share/nginx/html/config.js
# Restore original key
mv ./secrets/app/google-maps-api-key.txt.bak ./secrets/app/google-maps-api-key.txt
# Restart again
docker compose restart mvp-backend mvp-frontend
```
## Troubleshooting
### Backend Can't Read Secret
**Symptom**:
```bash
docker compose logs mvp-backend | grep -i "google"
# Shows: Failed to load Google Maps API key
```
**Solutions**:
1. **Check file exists on host**:
```bash
ls -la ./secrets/app/google-maps-api-key.txt
```
2. **Check docker-compose.yml mount**:
```bash
grep -A5 "mvp-backend:" docker-compose.yml | grep google-maps
```
Should show:
```yaml
- ./secrets/app/google-maps-api-key.txt:/run/secrets/google-maps-api-key:ro
```
3. **Check file in container**:
```bash
docker compose exec mvp-backend ls -la /run/secrets/
```
4. **Restart backend**:
```bash
docker compose restart mvp-backend
```
### Frontend config.js Not Generated
**Symptom**:
```bash
docker compose exec mvp-frontend cat /usr/share/nginx/html/config.js
# Shows: No such file or directory
```
**Solutions**:
1. **Check entrypoint script exists**:
```bash
docker compose exec mvp-frontend ls -la /app/load-config.sh
```
2. **Check entrypoint runs**:
```bash
docker compose logs mvp-frontend | head -20
```
Should show config generation messages.
3. **Run entrypoint manually**:
```bash
docker compose exec mvp-frontend sh /app/load-config.sh
```
4. **Restart frontend**:
```bash
docker compose restart mvp-frontend
```
### Browser Shows "API key undefined"
**Symptom**: Browser console shows `window.CONFIG.googleMapsApiKey` is undefined
**Solutions**:
1. **Check config.js loads**:
Open browser DevTools > Network tab > Refresh page > Look for `config.js`
2. **Check config.js content**:
```bash
curl http://localhost:3000/config.js
```
3. **Check index.html loads config.js**:
View page source, look for:
```html
<script src="/config.js"></script>
```
4. **Clear browser cache**:
Hard refresh: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac)
### Google API Returns "Invalid API Key"
**Symptom**: API calls return 400 error with "The provided API key is invalid"
**Solutions**:
1. **Verify key format**:
```bash
cat ./secrets/app/google-maps-api-key.txt | wc -c
```
Should be 39 characters (including newline) or 40.
2. **Check for extra whitespace**:
```bash
cat ./secrets/app/google-maps-api-key.txt | od -c
```
Should only show key and newline, no spaces/tabs.
3. **Verify key in Google Cloud Console**:
- Go to APIs & Services > Credentials
- Confirm key exists and is not deleted
4. **Check API restrictions**:
- Verify IP/domain restrictions don't block your server
- Verify API restrictions include Places API
### Secret File Permissions Wrong
**Symptom**: Container can't read secret file
**Solutions**:
```bash
# Fix file permissions
chmod 644 ./secrets/app/google-maps-api-key.txt
# Verify ownership
ls -l ./secrets/app/google-maps-api-key.txt
# Restart containers
docker compose restart mvp-backend mvp-frontend
```
## Automated Verification Script
Create a verification script for convenience:
```bash
#!/bin/bash
# verify-secrets.sh
echo "=== Gas Stations Secrets Verification ==="
echo ""
echo "1. Host file exists:"
[ -f ./secrets/app/google-maps-api-key.txt ] && echo "✓ PASS" || echo "✗ FAIL"
echo ""
echo "2. Backend can read secret:"
docker compose exec -T mvp-backend cat /run/secrets/google-maps-api-key >/dev/null 2>&1 && echo "✓ PASS" || echo "✗ FAIL"
echo ""
echo "3. Frontend can read secret:"
docker compose exec -T mvp-frontend cat /run/secrets/google-maps-api-key >/dev/null 2>&1 && echo "✓ PASS" || echo "✗ FAIL"
echo ""
echo "4. Frontend config.js generated:"
docker compose exec -T mvp-frontend cat /usr/share/nginx/html/config.js >/dev/null 2>&1 && echo "✓ PASS" || echo "✗ FAIL"
echo ""
echo "5. API key format valid:"
KEY=$(cat ./secrets/app/google-maps-api-key.txt)
[[ $KEY =~ ^AIzaSy ]] && echo "✓ PASS" || echo "✗ FAIL"
echo ""
echo "=== Verification Complete ==="
```
**Usage**:
```bash
chmod +x verify-secrets.sh
./verify-secrets.sh
```
## References
- Architecture: `/backend/src/features/stations/docs/ARCHITECTURE.md`
- Google Maps Setup: `/backend/src/features/stations/docs/GOOGLE-MAPS-SETUP.md`
- Deployment Checklist: `/backend/src/features/stations/docs/deployment/DEPLOYMENT-CHECKLIST.md`
- Runtime Config: `/frontend/docs/RUNTIME-CONFIG.md`