Files
skillhub/apps/web/middleware.ts
airano 4212b5ba47 sync: stale skill detection, sentry filtering, claim removal, review improvements
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>
2026-03-12 05:59:26 +03:30

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|.*\\..*).*)',
],
};