diff --git a/Dockerfile b/Dockerfile index 1f39bc2..d5c65ec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,7 +49,6 @@ RUN node_modules/.bin/prisma generate ARG NEXT_PUBLIC_APP_NAME ARG NEXT_PUBLIC_APP_DESCRIPTION ARG NEXT_PUBLIC_DOMAIN -ARG NEXT_PUBLIC_TWITTER_HANDLE ARG NEXT_PUBLIC_PLAUSIBLE_DOMAIN ARG NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC ARG NEXT_PUBLIC_THEME_COLOR @@ -63,7 +62,6 @@ ARG NEXT_PUBLIC_CREDITS_LINK_URL ENV NEXT_PUBLIC_APP_NAME=$NEXT_PUBLIC_APP_NAME ENV NEXT_PUBLIC_APP_DESCRIPTION=$NEXT_PUBLIC_APP_DESCRIPTION ENV NEXT_PUBLIC_DOMAIN=$NEXT_PUBLIC_DOMAIN -ENV NEXT_PUBLIC_TWITTER_HANDLE=$NEXT_PUBLIC_TWITTER_HANDLE ENV NEXT_PUBLIC_PLAUSIBLE_DOMAIN=$NEXT_PUBLIC_PLAUSIBLE_DOMAIN ENV NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC=$NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC ENV NEXT_PUBLIC_THEME_COLOR=$NEXT_PUBLIC_THEME_COLOR diff --git a/docker-compose.example.yml b/docker-compose.example.yml index e69b7ef..e0a544b 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -8,7 +8,6 @@ services: NEXT_PUBLIC_APP_NAME: ${NEXT_PUBLIC_APP_NAME} NEXT_PUBLIC_APP_DESCRIPTION: ${NEXT_PUBLIC_APP_DESCRIPTION} NEXT_PUBLIC_DOMAIN: ${NEXT_PUBLIC_DOMAIN} - NEXT_PUBLIC_TWITTER_HANDLE: ${NEXT_PUBLIC_TWITTER_HANDLE} NEXT_PUBLIC_PLAUSIBLE_DOMAIN: ${NEXT_PUBLIC_PLAUSIBLE_DOMAIN} NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC: ${NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC} NEXT_PUBLIC_THEME_COLOR: ${NEXT_PUBLIC_THEME_COLOR} diff --git a/docs/WHITE_LABEL.md b/docs/WHITE_LABEL.md index f608cac..69ac3a9 100644 --- a/docs/WHITE_LABEL.md +++ b/docs/WHITE_LABEL.md @@ -12,15 +12,14 @@ The application is configured via environment variables. You can set these in a |----------|-------------|---------| | `NEXT_PUBLIC_APP_NAME` | The name of the application. | `Hördle` | | `NEXT_PUBLIC_APP_DESCRIPTION` | The description used in metadata. | `Daily music guessing game...` | -| `NEXT_PUBLIC_DOMAIN` | The domain name (used for sharing). | `hoerdle.elpatron.me` | -| `NEXT_PUBLIC_TWITTER_HANDLE` | Twitter handle for metadata. | `@elpatron` | +| `NEXT_PUBLIC_DOMAIN` | The domain name (used for sharing). | `hoerdle.de` | ### Analytics (Plausible) | Variable | Description | Default | |----------|-------------|---------| -| `NEXT_PUBLIC_PLAUSIBLE_DOMAIN` | The domain to track in Plausible. | `hoerdle.elpatron.me` | -| `NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC` | The URL of the Plausible script. | `https://plausible.elpatron.me/js/script.js` | +| `NEXT_PUBLIC_PLAUSIBLE_DOMAIN` | The domain to track in Plausible. | `hoerdle.de` | +| `NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC` | The URL of the Plausible script. | `https://plausible.example.com/js/script.js` | ### Credits diff --git a/lib/config.ts b/lib/config.ts index 688170e..37c947b 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -1,10 +1,9 @@ export const config = { appName: process.env.NEXT_PUBLIC_APP_NAME || 'Hördle', appDescription: process.env.NEXT_PUBLIC_APP_DESCRIPTION || 'Daily music guessing game - Guess the song from short audio clips', - domain: process.env.NEXT_PUBLIC_DOMAIN || 'hoerdle.elpatron.me', - twitterHandle: process.env.NEXT_PUBLIC_TWITTER_HANDLE || '@elpatron', - plausibleDomain: process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN || 'hoerdle.elpatron.me', - plausibleScriptSrc: process.env.NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC || 'https://plausible.elpatron.me/js/script.js', + domain: process.env.NEXT_PUBLIC_DOMAIN || 'hoerdle.de', + plausibleDomain: process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN || 'hoerdle.de', + plausibleScriptSrc: process.env.NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC || 'https://plausible.example.com/js/script.js', colors: { themeColor: process.env.NEXT_PUBLIC_THEME_COLOR || '#000000', backgroundColor: process.env.NEXT_PUBLIC_BACKGROUND_COLOR || '#ffffff', diff --git a/proxy.ts b/proxy.ts index 3152fed..866e9e7 100644 --- a/proxy.ts +++ b/proxy.ts @@ -21,16 +21,41 @@ export default function proxy(request: NextRequest) { headers.set('Referrer-Policy', 'strict-origin-when-cross-origin'); headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()'); + // Extract Plausible domain from script URL for CSP + const plausibleScriptSrc = process.env.NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC || 'https://plausible.example.com/js/script.js'; + let plausibleOrigin = 'https://plausible.example.com'; + try { + const url = new URL(plausibleScriptSrc); + plausibleOrigin = url.origin; + } catch { + // If URL parsing fails, try to extract domain manually + const match = plausibleScriptSrc.match(/https?:\/\/([^/]+)/); + if (match) { + plausibleOrigin = `https://${match[1]}`; + } + } + + // Get other service URLs from environment (only add to CSP if configured) + const gotifyUrl = process.env.GOTIFY_URL; + const openrouterUrl = process.env.NEXT_PUBLIC_OPENROUTER_URL || 'https://openrouter.ai'; + + // Build CSP dynamically based on environment variables + const connectSrcParts = ["'self'", openrouterUrl, plausibleOrigin]; + if (gotifyUrl && !gotifyUrl.includes('example.com')) { + connectSrcParts.push(gotifyUrl); + } + const csp = [ "default-src 'self'", - "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://plausible.elpatron.me", + `script-src 'self' 'unsafe-inline' 'unsafe-eval' ${plausibleOrigin}`, "style-src 'self' 'unsafe-inline'", "img-src 'self' data: blob:", "font-src 'self' data:", - "connect-src 'self' https://openrouter.ai https://gotify.example.com https://plausible.elpatron.me", + `connect-src ${connectSrcParts.join(' ')}`, "media-src 'self' blob:", "frame-ancestors 'self'", ].join('; '); + headers.set('Content-Security-Policy', csp); return response;