- netlify/functions/ai.mjs: POST /api/ai runs against Netlify's AI Gateway — Netlify injects OPENROUTER_API_KEY + OPENROUTER_BASE_URL into Functions, so no key is managed or shipped. Curated model allowlist, best-effort per-IP throttle, CORS for web + Capacitor, and SSE streamed straight through. - sasquatchBrain: call the proxy instead of OpenRouter directly; drop VITE_OPENROUTER_API_KEY entirely; PROXY_MODELS set to the slugs that actually work via the gateway (its ZDR policy blocks gemini/anthropic/gpt-4o-mini). - netlify.toml: [functions] directory. - Verified in production: POST https://skate-spots.org/api/ai -> 200.
28 lines
1.2 KiB
TOML
28 lines
1.2 KiB
TOML
[build]
|
|
command = "npm run build"
|
|
publish = "dist"
|
|
|
|
# Server-side AI proxy for Sasquatch (holds the OpenRouter key).
|
|
[functions]
|
|
directory = "netlify/functions"
|
|
|
|
[[headers]]
|
|
for = "/*"
|
|
[headers.values]
|
|
# Keep this Content-Security-Policy in sync with public/_headers.
|
|
# Web-only hardening; the native Capacitor build is unaffected (it has no CSP).
|
|
Content-Security-Policy = "default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline' https://unpkg.com; img-src 'self' data: blob: https:; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https: wss:; media-src 'self' blob: https:; frame-src 'self' https:; worker-src 'self' blob:; manifest-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests"
|
|
X-Frame-Options = "DENY"
|
|
X-Content-Type-Options = "nosniff"
|
|
Referrer-Policy = "strict-origin-when-cross-origin"
|
|
Permissions-Policy = "geolocation=(self), camera=(self), microphone=()"
|
|
|
|
[[headers]]
|
|
for = "/assets/*"
|
|
[headers.values]
|
|
Cache-Control = "public, max-age=31536000, immutable"
|
|
|
|
[[redirects]]
|
|
from = "/*"
|
|
to = "/index.html"
|
|
status = 200
|