Stale Detection: - Detect skills removed/moved from GitHub (3 consecutive 404s threshold) - Hide stale skills from browse/search, show warning banner on detail page - Serve cached files with isStale flag when GitHub returns 404 - Add stale-check crawler command for batch verification - CLI shows warning when installing stale skills from cache Sentry & Error Handling: - Filter browser extension errors and add denyUrls - Anti-inflation measures and sentinel recalibration for curation Claim & Removal: - Enhanced ClaimForm with repo-level removal support - Add repo-removal-request API endpoint with tests - Improved owner page with bilingual content Review Pipeline: - Review version and reviewer tracking in submit API - Source format filter for pending reviews - Updated review tests Other: - Updated i18n strings (en/fa) - BrowseFilters improvements - Dockerfile updates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
93 lines
2.9 KiB
TypeScript
93 lines
2.9 KiB
TypeScript
import { NextResponse, type NextRequest } from 'next/server';
|
|
import createIntlMiddleware from 'next-intl/middleware';
|
|
import { locales, defaultLocale } from './i18n';
|
|
import { csrfProtect, CsrfError, shouldProtectPath, requiresCsrfProtection, createCsrfErrorResponse } from './lib/csrf';
|
|
|
|
// Create the internationalization middleware
|
|
const intlMiddleware = createIntlMiddleware({
|
|
locales,
|
|
defaultLocale,
|
|
localePrefix: 'as-needed',
|
|
});
|
|
|
|
|
|
|
|
export async function middleware(request: NextRequest): Promise<NextResponse> {
|
|
const { pathname, method } = {
|
|
pathname: request.nextUrl.pathname,
|
|
method: request.method,
|
|
};
|
|
|
|
// Let next-auth handle its own routes without any middleware interference.
|
|
// This is critical: creating a NextResponse.next() and setting cookies on it
|
|
// can break next-auth's PKCE cookie flow (code_verifier gets lost).
|
|
if (pathname.startsWith('/api/auth')) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
|
|
// Handle CSRF protection for API routes
|
|
if (shouldProtectPath(pathname) && requiresCsrfProtection(method)) {
|
|
// Create a response to pass to csrfProtect (needed for cookie setting)
|
|
const response = NextResponse.next();
|
|
|
|
try {
|
|
// Validate CSRF token
|
|
await csrfProtect(request, response);
|
|
} catch (err) {
|
|
if (err instanceof CsrfError) {
|
|
console.warn(`CSRF validation failed for ${method} ${pathname}`);
|
|
return createCsrfErrorResponse();
|
|
}
|
|
// Re-throw unexpected errors
|
|
throw err;
|
|
}
|
|
|
|
// CSRF validation passed, continue with the request
|
|
// Return the response with any CSRF-related cookies set
|
|
return response;
|
|
}
|
|
|
|
// For API routes that don't need CSRF protection, pass through
|
|
if (pathname.startsWith('/api/')) {
|
|
// Still set CSRF token cookie for GET requests so clients can get the token
|
|
const response = NextResponse.next();
|
|
try {
|
|
await csrfProtect(request, response);
|
|
} catch {
|
|
// Ignore CSRF errors for safe methods - we just want to set the cookie
|
|
}
|
|
return response;
|
|
}
|
|
|
|
// For non-API routes, apply i18n middleware
|
|
// Also set CSRF token cookie on page loads
|
|
const intlResponse = intlMiddleware(request);
|
|
|
|
try {
|
|
// Apply CSRF to set the token cookie (won't validate since it's a safe method)
|
|
await csrfProtect(request, intlResponse);
|
|
} catch {
|
|
// Ignore CSRF errors for page loads - we just want to set the cookie
|
|
}
|
|
|
|
return intlResponse;
|
|
}
|
|
|
|
export const config = {
|
|
// Match all paths except static files and internal Next.js paths
|
|
matcher: [
|
|
// Match root
|
|
'/',
|
|
// Match locale paths
|
|
'/(en|fa)/:path*',
|
|
// Match API routes (for CSRF protection)
|
|
'/api/:path*',
|
|
// Match skill paths explicitly (repo names like "next.js" contain dots
|
|
// which would be excluded by the catch-all pattern below)
|
|
'/skill/:path*',
|
|
// Match other paths (exclude static files and Next.js internals)
|
|
'/((?!_next|_vercel|.*\\..*).*)',
|
|
],
|
|
};
|