Updating (#65)
* Fix ActivityFeed: Remove dynamic import that was causing it to disappear in production * Fix ActivityFeed hydration error: Move localStorage read to useEffect to prevent server/client mismatch * Update Node.js version to 25 in Gitea workflows - Fix EBADENGINE error for camera-controls@3.1.2 which requires Node.js >=22 - Update production-deploy.yml, dev-deploy.yml, and ci-cd-with-gitea-vars.yml.disabled - Node.js v25 matches local development environment * Update Dockerfile to use Node.js 25 - Update base image from node:20 to node:25 - Matches Gitea workflow configuration and camera-controls@3.1.2 requirements * Fix production deployment: Start database dependencies - Remove --no-deps flag which prevented postgres and redis from starting - Remove --build flag as image is already built in previous step - This fixes 'Can't reach database server at postgres:5432' error * Fix postgres health check in production - Remove init-db.sql volume mount (not available in CI/CD environment) - Init script not needed as Prisma handles schema migrations - Postgres will initialize empty database automatically * Fix cache permission error in Docker container - Create cache directories AFTER copying standalone files - Create both fetch-cache and images subdirectories - Set proper ownership for nextjs user - Fixes EACCES permission denied errors for prerender cache * Fix German jogging fallback text * Use Directus content in production * fix: Security vulnerability - block malicious file requests * fix: Switch projects to Directus, add security fixes and example projects
This commit is contained in:
@@ -4,6 +4,29 @@ import type { NextRequest } from "next/server";
|
||||
const SUPPORTED_LOCALES = ["en", "de"] as const;
|
||||
type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
|
||||
|
||||
// Security: Block common malicious file patterns
|
||||
const BLOCKED_PATTERNS = [
|
||||
/\.php$/i,
|
||||
/\.asp$/i,
|
||||
/\.aspx$/i,
|
||||
/\.jsp$/i,
|
||||
/\.cgi$/i,
|
||||
/\.env$/i,
|
||||
/\.sql$/i,
|
||||
/\.gz$/i,
|
||||
/\.tar$/i,
|
||||
/\.zip$/i,
|
||||
/\.rar$/i,
|
||||
/\.bash_history$/i,
|
||||
/ftpsync\.settings$/i,
|
||||
/__MACOSX/i,
|
||||
/\.well-known\.zip$/i,
|
||||
];
|
||||
|
||||
function isBlockedPath(pathname: string): boolean {
|
||||
return BLOCKED_PATTERNS.some((pattern) => pattern.test(pathname));
|
||||
}
|
||||
|
||||
function pickLocaleFromHeader(acceptLanguage: string | null): SupportedLocale {
|
||||
if (!acceptLanguage) return "en";
|
||||
const lower = acceptLanguage.toLowerCase();
|
||||
@@ -20,6 +43,11 @@ function hasLocalePrefix(pathname: string): boolean {
|
||||
export function middleware(request: NextRequest) {
|
||||
const { pathname, search } = request.nextUrl;
|
||||
|
||||
// Security: Block malicious/suspicious requests immediately
|
||||
if (isBlockedPath(pathname)) {
|
||||
return new NextResponse(null, { status: 404 });
|
||||
}
|
||||
|
||||
// If a locale-prefixed request hits a public asset path (e.g. /de/images/me.jpg),
|
||||
// redirect to the non-prefixed asset path.
|
||||
if (hasLocalePrefix(pathname)) {
|
||||
|
||||
Reference in New Issue
Block a user