From f46f4dd552d9497f60ca78a159cef70579dd929c Mon Sep 17 00:00:00 2001 From: WhiteX Date: Sat, 14 Jun 2025 10:18:11 +0300 Subject: [PATCH] feat: remove domain isolation scripts and related components for cleanup --- Dockerfile_coolify | 147 ++++++++++----------- apps/web/components/Avatar/SafeAvatar.tsx | 60 --------- apps/web/middleware.js | 72 ---------- apps/web/pages/_document.js | 22 --- apps/web/public/api-response-sanitizer.js | 94 ------------- apps/web/public/domain-isolation-loader.js | 82 ------------ apps/web/tsconfig.build.json | 19 --- apps/web/utils/apiClient.js | 59 --------- apps/web/utils/runtimeConfig.js | 52 -------- extra/api-interceptor.js | 97 -------------- extra/api-response-sanitizer.js | 93 ------------- extra/domain-isolation-loader.js | 82 ------------ extra/runtime-config-start.sh | 31 ----- 13 files changed, 73 insertions(+), 837 deletions(-) delete mode 100644 apps/web/components/Avatar/SafeAvatar.tsx delete mode 100644 apps/web/middleware.js delete mode 100644 apps/web/pages/_document.js delete mode 100644 apps/web/public/api-response-sanitizer.js delete mode 100644 apps/web/public/domain-isolation-loader.js delete mode 100644 apps/web/tsconfig.build.json delete mode 100644 apps/web/utils/apiClient.js delete mode 100644 apps/web/utils/runtimeConfig.js delete mode 100644 extra/api-interceptor.js delete mode 100644 extra/api-response-sanitizer.js delete mode 100644 extra/domain-isolation-loader.js delete mode 100755 extra/runtime-config-start.sh diff --git a/Dockerfile_coolify b/Dockerfile_coolify index 4c300d9a..5116dcf5 100644 --- a/Dockerfile_coolify +++ b/Dockerfile_coolify @@ -13,8 +13,41 @@ RUN curl -fsSL https://deb.nodesource.com/setup_21.x | bash - \ && npm install -g corepack pm2 # Frontend Build -FROM base AS deps +FROM base AS builder +WORKDIR /app/web + +# Install dependencies based on the preferred package manager +COPY ./apps/web/package.json ./apps/web/pnpm-lock.yaml* ./apps/web/.npmrc* ./ +# Omit --production flag for TypeScript devDependencies +RUN \ + if [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ + elif [ -f package-lock.json ]; then npm ci; \ + else echo "Warning: Lockfile not found. It is recommended to commit lockfiles to version control." && npm install; \ + fi + +COPY ./apps/web/app ./app +COPY ./apps/web/components ./components +COPY ./apps/web/hooks ./hooks +COPY ./apps/web/lib ./lib +COPY ./apps/web/pages ./pages +COPY ./apps/web/public ./public +COPY ./apps/web/services ./services +COPY ./apps/web/styles ./styles +COPY ./apps/web/types ./types +COPY ./apps/web/utils ./utils +COPY ./apps/web/next.config.js . +COPY ./apps/web/tsconfig.json . +COPY ./apps/web/tailwind.config.js . +COPY ./apps/web/postcss.config.js . +COPY ./apps/web/components.json . +COPY ./apps/web/middleware.ts . +COPY ./apps/web/instrumentation.ts . + +# Remove any environment files that might interfere +RUN rm -f .env* + +# Environment variables must be present at build time ARG NEXT_PUBLIC_LEARNHOUSE_API_URL ARG NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL ARG NEXT_PUBLIC_LEARNHOUSE_DOMAIN @@ -29,63 +62,16 @@ ENV NEXT_PUBLIC_LEARNHOUSE_DEFAULT_ORG=${NEXT_PUBLIC_LEARNHOUSE_DEFAULT_ORG} ENV NEXT_PUBLIC_LEARNHOUSE_MULTI_ORG=${NEXT_PUBLIC_LEARNHOUSE_MULTI_ORG} ENV NEXT_PUBLIC_LEARNHOUSE_TOP_DOMAIN=${NEXT_PUBLIC_LEARNHOUSE_TOP_DOMAIN} -WORKDIR /app/web -COPY ./apps/web/package.json ./apps/web/pnpm-lock.yaml* ./ -COPY ./extra/patch-typescript.sh /app/patch-typescript.sh -COPY ./apps/web /app/web -RUN rm -f .env* +# Next.js collects completely anonymous telemetry data about general usage +# Disable telemetry at build time +ENV NEXT_TELEMETRY_DISABLED 1 -# Patch TypeScript issues using our comprehensive script -RUN chmod +x /app/patch-typescript.sh && /app/patch-typescript.sh - -# Create a modified next.config.js that completely disables type checking -RUN if [ -f next.config.js ]; then \ - cat next.config.js > next.config.js.bak && \ - echo "console.log('Using custom Next.js config with TypeScript checking disabled');" > next.config.js && \ - echo "const originalConfig = require('./next.config.js.bak');" >> next.config.js && \ - echo "module.exports = {" >> next.config.js && \ - echo " ...originalConfig," >> next.config.js && \ - echo " typescript: { ignoreBuildErrors: true }," >> next.config.js && \ - echo " eslint: { ignoreDuringBuilds: true }," >> next.config.js && \ - echo " webpack: (config, options) => {" >> next.config.js && \ - echo " config.infrastructureLogging = { level: 'error' };" >> next.config.js && \ - echo " if (originalConfig.webpack) {" >> next.config.js && \ - echo " return originalConfig.webpack(config, options);" >> next.config.js && \ - echo " }" >> next.config.js && \ - echo " return config;" >> next.config.js && \ - echo " }" >> next.config.js && \ - echo "};" >> next.config.js; \ - else \ - echo "console.log('Creating new Next.js config with TypeScript checking disabled');" > next.config.js && \ - echo "module.exports = {" >> next.config.js && \ - echo " typescript: { ignoreBuildErrors: true }," >> next.config.js && \ - echo " eslint: { ignoreDuringBuilds: true }," >> next.config.js && \ - echo " webpack: (config) => {" >> next.config.js && \ - echo " config.infrastructureLogging = { level: 'error' };" >> next.config.js && \ - echo " return config;" >> next.config.js && \ - echo " }" >> next.config.js && \ - echo "};" >> next.config.js; \ - fi - -# Allow build to continue with type errors -RUN if [ -f pnpm-lock.yaml ]; then \ - corepack enable pnpm && \ - pnpm i --frozen-lockfile && \ - pnpm add @types/react@latest @types/node@latest next-navigation@latest typescript@latest --save-dev && \ - echo '{ "scripts": { "build:ignore-ts": "NEXT_TELEMETRY_DISABLED=1 SKIP_TYPE_CHECK=true TS_NODE_TRANSPILE_ONLY=true next build" } }' > .npmrc-scripts.json && \ - pnpm pkg set scripts.build:ignore-ts="NEXT_TELEMETRY_DISABLED=1 SKIP_TYPE_CHECK=true TS_NODE_TRANSPILE_ONLY=true next build" && \ - NODE_OPTIONS=--max-old-space-size=6144 NEXT_IGNORE_TYPE_ERROR=true SKIP_TYPE_CHECK=true TS_NODE_TRANSPILE_ONLY=true pnpm run build:ignore-ts; \ - else echo "Lockfile not found." && exit 1; \ - fi - -# Make sure the images directory exists in public folder with a placeholder avatar -RUN mkdir -p /app/web/public/images && \ - echo "iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABmJLR0QA/wD/AP+gvaeTAAADc0lEQVRoge2ZS0hUURjHf2fGcSaViDSJoKKSoiAzaGE9VrXIjRZBm2hVRG2iB0SPVUW7IM0ehLWIdlYQQY+FipWZWj0siJ60ygiziGye8870cWfuPJ1774yO9v/g4pz7+L7z/b/vnnvuOQMtWrT4lxH/OkHQWlpfqdedlo5T49wl1V9eclXkSLCtXeGDNvL5AFwoaa0CXhX0Qdpup8L7ddeiFNlL9gJLbOMCuOWkPXfSGvFvLWlLgAceUwFccdIGnbRG/HuB0x66KcBxJ21RmbsGOA/oveTx75YjwJMKthFgk5N2DICTNiJ8tmnGNdc7acNeOW3jw062J/Cu2HAKmOmkDQggpXxnGy4A9rhpRw5vZBJQWrEYs1qclECn/ekuYIHLbsSN+0CldLZhvQtnaQ7uEVXXgRNSytlCyhe28XrgdgMmcUWpdJr/5CRErbQe2GQbv6CUej0+Pl5XDD/UXQMKdVhpx4AO4KpS6pVdJ6X8KIR4YhveAOxvyGyOKDUBwKuXFxXwAdghhLhn1wkh7hGsEVv9DF4pTsmIMnz6M3mAZUqpm1LKjN1HSvm8XC5fsY0vBY7OH2p0pGJMtwExgA3AGSnlErvO5/M9EkI8tw3vALY3alSVT8mINhznfSNm3wFb/g+0ULIGNQ3a7wZ6hRDdNl1BKbVfCPHWNn4A2Duz2cwCLUMa9GnB7/evklKuUEq9K5VK72dmZsrVtKVSaSkwB3gqhPjmdW4t0DIkzuQdWnGMrvMZdFQxNniMrgDvMrDdQXcAOBTFtypp6SsZkDMg40DGOMZ0+ON0A1uBJNAP9ETxbwhaNgETZkUlwLqI/o1By1YgbdRfBvZE9G8MWnYAl4z664DvX/AJA1qeNupHgb1R/BuDlgfR923AL8w7HImwA2vpNRq/MO9wJMIOLKRH4x4GOqP4NwYt0/b5Y8DhiP6NQcvzRv0VYF8U/8agZR+Qwpw/tkfxbwxa7gLOovn9wN4o/g1By7TZWag+hzQctHwe7T2EudJrCUJDy3NgqGVIg/4RsMUFeYeWwE2rDoxhLvMqbAeOT/fkzYGJsNORT4tn+HzAHvSHAe0I4W7ZMA5cLBaLw+l0Op/L5Ubz+fw787ex6bpbNLuUT6Ct7Z4QYgnQabQV0BMuhxDilVKqh/8gYLO02q92i/8sPwESO6wpKl0UfgAAAABJRU5ErkJggg==" | base64 -d > /app/web/public/images/empty_avatar.png - -# Copy domain isolation scripts to public directory -COPY ./extra/api-interceptor.js /app/web/public/api-interceptor.js -COPY ./extra/api-response-sanitizer.js /app/web/public/api-response-sanitizer.js -COPY ./extra/domain-isolation-loader.js /app/web/public/domain-isolation-loader.js +# Build Next.js based on the preferred package manager +RUN \ + if [ -f pnpm-lock.yaml ]; then pnpm build; \ + elif [ -f package-lock.json ]; then npm run build; \ + else npm run build; \ + fi # Final image FROM base AS runner @@ -93,20 +79,39 @@ RUN addgroup --system --gid 1001 system \ && adduser --system --uid 1001 app \ && mkdir .next \ && chown app:system .next -COPY --from=deps /app/web/public ./app/web/public -COPY --from=deps --chown=app:system /app/web/.next/standalone ./app/web/ -COPY --from=deps --chown=app:system /app/web/.next/static ./app/web/.next/static + +COPY --from=builder /app/web/public ./app/web/public + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=app:system /app/web/.next/standalone ./app/web/ +COPY --from=builder --chown=app:system /app/web/.next/static ./app/web/.next/static + +# Environment variables must be redefined at run time +ARG NEXT_PUBLIC_LEARNHOUSE_API_URL +ARG NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL +ARG NEXT_PUBLIC_LEARNHOUSE_DOMAIN +ARG NEXT_PUBLIC_LEARNHOUSE_DEFAULT_ORG +ARG NEXT_PUBLIC_LEARNHOUSE_MULTI_ORG +ARG NEXT_PUBLIC_LEARNHOUSE_TOP_DOMAIN + +ENV NEXT_PUBLIC_LEARNHOUSE_API_URL=${NEXT_PUBLIC_LEARNHOUSE_API_URL} +ENV NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL=${NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL} +ENV NEXT_PUBLIC_LEARNHOUSE_DOMAIN=${NEXT_PUBLIC_LEARNHOUSE_DOMAIN} +ENV NEXT_PUBLIC_LEARNHOUSE_DEFAULT_ORG=${NEXT_PUBLIC_LEARNHOUSE_DEFAULT_ORG} +ENV NEXT_PUBLIC_LEARNHOUSE_MULTI_ORG=${NEXT_PUBLIC_LEARNHOUSE_MULTI_ORG} +ENV NEXT_PUBLIC_LEARNHOUSE_TOP_DOMAIN=${NEXT_PUBLIC_LEARNHOUSE_TOP_DOMAIN} # Backend Build WORKDIR /app/api -COPY ./apps/api/pyproject.toml ./ COPY ./apps/api/uv.lock ./ +COPY ./apps/api/pyproject.toml ./ -# Install dependencies with proper flags and fallback to pip if needed +# Install dependencies with uv (fallback to pip if needed) RUN pip install --upgrade pip && \ pip install uv && \ - (uv pip sync --system --python=python3.12 || pip install -e .) && \ - pip install uvicorn # Ensure at least the ASGI server is available + (uv sync --system || pip install -e .) && \ + pip install uvicorn COPY ./apps/api ./ @@ -115,15 +120,9 @@ WORKDIR /app COPY ./extra/nginx.conf /etc/nginx/conf.d/default.conf ENV PORT=80 LEARNHOUSE_PORT=9000 HOSTNAME=0.0.0.0 COPY ./extra/start.sh /app/start.sh -COPY ./extra/runtime-config-start.sh /app/runtime-config-start.sh -# Make the runtime script executable -RUN chmod +x /app/start.sh && chmod +x /app/runtime-config-start.sh +# Make the start script executable +RUN chmod +x /app/start.sh -# Ensure the isolation scripts are available in the runner stage -COPY ./extra/api-interceptor.js /app/extra/api-interceptor.js -COPY ./extra/api-response-sanitizer.js /app/extra/api-response-sanitizer.js -COPY ./extra/domain-isolation-loader.js /app/extra/domain-isolation-loader.js - -# Use the runtime config script -CMD ["/app/runtime-config-start.sh"] \ No newline at end of file +# Use the start script +CMD ["sh", "/app/start.sh"] \ No newline at end of file diff --git a/apps/web/components/Avatar/SafeAvatar.tsx b/apps/web/components/Avatar/SafeAvatar.tsx deleted file mode 100644 index 9f05769a..00000000 --- a/apps/web/components/Avatar/SafeAvatar.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react' -import Image from 'next/image' - -interface SafeAvatarProps { - src?: string - alt: string - size?: number - className?: string -} - -/** - * SafeAvatar component that ensures correct domain for avatar images - */ -const SafeAvatar: React.FC = ({ - src, - alt, - size = 40, - className -}) => { - // Default empty avatar path that uses relative URL (domain-safe) - const defaultAvatarSrc = '/images/empty_avatar.png' - - // Handle potentially cross-domain avatar URLs - const sanitizedSrc = React.useMemo(() => { - if (!src) return defaultAvatarSrc - - try { - // Check if the URL has a domain - const url = new URL(src, window.location.origin) - - // If the URL is from a different domain, use the default avatar - if (url.hostname !== window.location.hostname) { - console.warn(`[SafeAvatar] Detected cross-domain avatar: ${src}`) - return defaultAvatarSrc - } - - return src - } catch (e) { - // If parsing fails, just use the src as is (could be a relative path) - return src - } - }, [src]) - - return ( - {alt} { - // If image fails to load, fallback to default - const target = e.target as HTMLImageElement - target.src = defaultAvatarSrc - }} - /> - ) -} - -export default SafeAvatar \ No newline at end of file diff --git a/apps/web/middleware.js b/apps/web/middleware.js deleted file mode 100644 index 5c143ba7..00000000 --- a/apps/web/middleware.js +++ /dev/null @@ -1,72 +0,0 @@ -import { NextResponse } from 'next/server'; - -// This middleware runs on every request -export function middleware(request) { - // Get the current hostname from the request headers - const currentHostname = request.headers.get('host'); - - // Always inspect for cross-domain requests regardless of referrer - const url = request.nextUrl.clone(); - const path = url.pathname; - - // Check for common patterns that might indicate cross-domain content - // 1. Handle image files that might be requested from the wrong domain - if (path.endsWith('.png') || path.endsWith('.jpg') || path.endsWith('.jpeg') || - path.endsWith('.gif') || path.endsWith('.webp') || path.endsWith('.svg')) { - // Ensure image path is properly routed to current domain - if (path.includes('empty_avatar.png')) { - console.log(`Intercepting image request: ${path}`); - // Rewrite all empty_avatar.png requests to use the local domain - return NextResponse.rewrite(new URL(`/images/empty_avatar.png`, request.url)); - } - } - - // 2. Check if request is going to the wrong domain through API path - if (path.includes('/api/') && request.headers.has('referer')) { - const refererUrl = new URL(request.headers.get('referer')); - // If referer domain doesn't match the requested API domain, redirect - if (refererUrl.hostname !== currentHostname) { - console.log(`Redirecting cross-domain API request: ${path}`); - const newUrl = new URL(path, `https://${currentHostname}`); - return NextResponse.redirect(newUrl); - } - } - - // Get the referrer URL if it exists - const referer = request.headers.get('referer'); - - // If there is a referrer, check if it's from a different domain - if (referer) { - try { - const refererUrl = new URL(referer); - const refererHostname = refererUrl.hostname; - - // If the referrer hostname doesn't match the current hostname - if (refererHostname !== currentHostname) { - console.log(`Cross-domain request detected: ${refererHostname} -> ${currentHostname}`); - - // For path segments that might include another domain - if (path.includes('/next/static/') || path.includes('/api/')) { - // Ensure all paths use the current hostname - // This prevents asset URL problems when different hostnames appear in the path - const localPath = path.replace(/https?:\/\/[^\/]+/, ''); - url.pathname = localPath; - return NextResponse.rewrite(url); - } - } - } catch (e) { - console.error('Error processing referer in middleware:', e); - } - } - - // Continue with the request as normal - return NextResponse.next(); -} - -// Configure which paths this middleware will run on -export const config = { - matcher: [ - // Apply to all paths - '/:path*', - ], -}; \ No newline at end of file diff --git a/apps/web/pages/_document.js b/apps/web/pages/_document.js deleted file mode 100644 index efb49dbf..00000000 --- a/apps/web/pages/_document.js +++ /dev/null @@ -1,22 +0,0 @@ -import { Html, Head, Main, NextScript } from 'next/document'; - -export default function Document() { - return ( - - - {/* Load domain isolation loader first - immediate protection */} -