Gas Station Feature
This commit is contained in:
@@ -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`
|
||||
Reference in New Issue
Block a user