fix: remaining code issues — TLS, CORS, disconnect safety, cleanup
1. Trips TLS: Removed all ssl CERT_NONE / check_hostname=False from 5 external HTTPS call sites (OpenAI, Gemini, Google Places, Geocode). All external calls now use default TLS verification. 2. Internal CORS: Removed permissive cors() from inventory and budget. Both are internal services accessed only via gateway. 3. App visibility: Documented as cosmetic-only in layout.server.ts. Nav hiding is intentional UX, not access control. 4. Disconnect safety: Added confirm() dialog before service disconnect in Settings. Prevents accidental disconnects. 5. Inventory cleanup: Removed stale /test startup log message. Replaced with API key status indicator. 6. Frontend deps: 4 low-severity cookie vulnerabilities in @sveltejs/kit. Fix requires breaking downgrade to kit@0.0.30 — not safe. Documented.
This commit is contained in:
@@ -8,7 +8,7 @@ const cors = require('cors');
|
||||
const api = require('@actual-app/api');
|
||||
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
// CORS disabled — internal service accessed only via gateway
|
||||
app.use(express.json());
|
||||
|
||||
// Health check (before auth middleware)
|
||||
|
||||
@@ -22,7 +22,7 @@ const config = {
|
||||
workspaceId: '' // fetched at startup
|
||||
};
|
||||
|
||||
app.use(cors());
|
||||
// CORS disabled — internal service accessed only via gateway
|
||||
app.use(express.json());
|
||||
// Allow form-encoded payloads from NocoDB webhook buttons
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
@@ -2053,7 +2053,7 @@ app.listen(port, () => {
|
||||
console.log(' API Token: ' + (config.apiToken ? '✅ Set' : '❌ Missing'));
|
||||
console.log('');
|
||||
console.log('🌐 Open http://localhost:' + externalPort + ' to use the uploader');
|
||||
console.log('🔧 Test endpoint: http://localhost:' + externalPort + '/test');
|
||||
console.log('🔒 API Key: ' + (process.env.SERVICE_API_KEY ? '✅ Required' : '⚠️ Not set'));
|
||||
console.log('🎨 Modern theme preview: http://localhost:' + externalPort + '/preview');
|
||||
});
|
||||
|
||||
|
||||
@@ -394,9 +394,6 @@ def call_openai(messages, max_completion_tokens=2000):
|
||||
if not OPENAI_API_KEY:
|
||||
return {"error": "OpenAI API key not configured"}
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
data = json.dumps({
|
||||
"model": OPENAI_MODEL,
|
||||
@@ -416,7 +413,7 @@ def call_openai(messages, max_completion_tokens=2000):
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=120) as response:
|
||||
with urllib.request.urlopen(req, timeout=120) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
return result["choices"][0]["message"]["content"]
|
||||
except Exception as e:
|
||||
@@ -428,9 +425,6 @@ def call_gemini(prompt, use_search_grounding=True):
|
||||
if not GEMINI_API_KEY:
|
||||
return {"error": "Gemini API key not configured"}
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
# Build request body
|
||||
request_body = {
|
||||
@@ -462,7 +456,7 @@ def call_gemini(prompt, use_search_grounding=True):
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=120) as response:
|
||||
with urllib.request.urlopen(req, timeout=120) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
# Extract text from response
|
||||
if "candidates" in result and result["candidates"]:
|
||||
@@ -507,15 +501,12 @@ def get_place_details(place_id):
|
||||
|
||||
try:
|
||||
url = f"https://places.googleapis.com/v1/places/{place_id}"
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header('X-Goog-Api-Key', GOOGLE_API_KEY)
|
||||
req.add_header('X-Goog-FieldMask', 'displayName,formattedAddress,location,types,primaryType')
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=10) as response:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
place = json.loads(response.read().decode())
|
||||
location = place.get("location", {})
|
||||
return {
|
||||
@@ -539,14 +530,11 @@ def auto_resolve_place(name, address=""):
|
||||
try:
|
||||
query = f"{name} {address}".strip() if address else name
|
||||
url = "https://places.googleapis.com/v1/places:autocomplete"
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
payload = json.dumps({"input": query}).encode()
|
||||
req = urllib.request.Request(url, data=payload, method="POST")
|
||||
req.add_header("Content-Type", "application/json")
|
||||
req.add_header("X-Goog-Api-Key", GOOGLE_API_KEY)
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=10) as response:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
if data.get("suggestions"):
|
||||
first = data["suggestions"][0].get("placePrediction", {})
|
||||
@@ -571,12 +559,9 @@ def geocode_address(address):
|
||||
encoded_address = urllib.parse.quote(address)
|
||||
url = f"https://maps.googleapis.com/maps/api/geocode/json?address={encoded_address}&key={GOOGLE_API_KEY}"
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
req = urllib.request.Request(url)
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=10) as response:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
|
||||
if data.get("status") == "OK" and data.get("results"):
|
||||
@@ -641,9 +626,6 @@ Extract booking information from this PDF. Return structured JSON only, no markd
|
||||
]
|
||||
}).encode("utf-8")
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
req = urllib.request.Request(
|
||||
"https://api.openai.com/v1/responses",
|
||||
@@ -656,7 +638,7 @@ Extract booking information from this PDF. Return structured JSON only, no markd
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=120) as response:
|
||||
with urllib.request.urlopen(req, timeout=120) as response:
|
||||
result_data = json.loads(response.read().decode())
|
||||
# Responses API returns: output[0].content[0].text
|
||||
result = result_data["output"][0]["content"][0]["text"]
|
||||
@@ -907,11 +889,8 @@ def get_flight_status(airline_code, flight_number, date_str=None):
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
})
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=15) as response:
|
||||
with urllib.request.urlopen(req, timeout=15) as response:
|
||||
html_content = response.read().decode('utf-8')
|
||||
|
||||
# Extract trackpollBootstrap JSON
|
||||
@@ -3804,10 +3783,7 @@ class TripHandler(BaseHTTPRequestHandler):
|
||||
}
|
||||
payload = json.dumps({"input": query, "languageCode": "en"}).encode()
|
||||
req = urllib.request.Request(url, data=payload, headers=headers, method="POST")
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=10) as response:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
if data.get("suggestions"):
|
||||
first = data["suggestions"][0].get("placePrediction", {})
|
||||
@@ -4344,16 +4320,13 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
url = "https://places.googleapis.com/v1/places:autocomplete"
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
req_data = json.dumps(request_body).encode('utf-8')
|
||||
req = urllib.request.Request(url, data=req_data, method='POST')
|
||||
req.add_header('Content-Type', 'application/json')
|
||||
req.add_header('X-Goog-Api-Key', GOOGLE_API_KEY)
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=10) as response:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
|
||||
predictions = []
|
||||
@@ -4395,9 +4368,6 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
per_page = data.get("per_page", 50)
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
# Use search/metadata endpoint for date filtering
|
||||
search_url = f"{IMMICH_URL}/api/search/metadata"
|
||||
@@ -4419,7 +4389,7 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
req.add_header('Content-Type', 'application/json')
|
||||
req.add_header('x-api-key', IMMICH_API_KEY)
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=30) as response:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
|
||||
photos = []
|
||||
@@ -4459,16 +4429,13 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
# Download original image from Immich
|
||||
download_url = f"{IMMICH_URL}/api/assets/{asset_id}/original"
|
||||
req = urllib.request.Request(download_url)
|
||||
req.add_header('x-api-key', IMMICH_API_KEY)
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=60) as response:
|
||||
with urllib.request.urlopen(req, timeout=60) as response:
|
||||
image_data = response.read()
|
||||
content_type = response.headers.get('Content-Type', 'image/jpeg')
|
||||
|
||||
@@ -4521,15 +4488,12 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
thumb_url = f"{IMMICH_URL}/api/assets/{asset_id}/thumbnail"
|
||||
req = urllib.request.Request(thumb_url)
|
||||
req.add_header('x-api-key', IMMICH_API_KEY)
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=10) as response:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
image_data = response.read()
|
||||
content_type = response.headers.get('Content-Type', 'image/jpeg')
|
||||
|
||||
@@ -4551,15 +4515,12 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
url = f"{IMMICH_URL}/api/albums"
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header('x-api-key', IMMICH_API_KEY)
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=30) as response:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
albums = json.loads(response.read().decode())
|
||||
|
||||
# Simplify album data
|
||||
@@ -4597,15 +4558,12 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
url = f"{IMMICH_URL}/api/albums/{album_id}"
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header('x-api-key', IMMICH_API_KEY)
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=30) as response:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
album = json.loads(response.read().decode())
|
||||
|
||||
photos = []
|
||||
@@ -4653,16 +4611,13 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
# Download the photo
|
||||
req = urllib.request.Request(photo_url)
|
||||
if access_token:
|
||||
req.add_header('Authorization', f'Bearer {access_token}')
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=60) as response:
|
||||
with urllib.request.urlopen(req, timeout=60) as response:
|
||||
image_data = response.read()
|
||||
content_type = response.headers.get('Content-Type', 'image/jpeg')
|
||||
|
||||
@@ -4700,9 +4655,6 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
# Create session via Photos Picker API
|
||||
url = "https://photospicker.googleapis.com/v1/sessions"
|
||||
@@ -4711,7 +4663,7 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
req.add_header('Content-Type', 'application/json')
|
||||
req.data = b'{}'
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=30) as response:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
|
||||
self.send_json(result)
|
||||
@@ -4735,15 +4687,12 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
url = f"https://photospicker.googleapis.com/v1/sessions/{session_id}"
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header('Authorization', f'Bearer {access_token}')
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=30) as response:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
|
||||
self.send_json(result)
|
||||
@@ -4763,15 +4712,12 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
url = f"https://photospicker.googleapis.com/v1/mediaItems?sessionId={session_id}&pageSize=50"
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header('Authorization', f'Bearer {access_token}')
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=30) as response:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
|
||||
self.send_json(result)
|
||||
|
||||
Reference in New Issue
Block a user