Open-source marketplace for AI Agent skills. Features: - Next.js 15 web app with i18n (en/fa) - CLI tool for skill installation (npx skillhub) - GitHub crawler/indexer with multi-strategy discovery - Security scanning for all indexed skills - Self-hostable with Docker Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
64 lines
2.0 KiB
TypeScript
64 lines
2.0 KiB
TypeScript
import { createCsrfProtect, CsrfError } from '@edge-csrf/nextjs';
|
|
import { NextResponse } from 'next/server';
|
|
|
|
// CSRF token configuration
|
|
// Using double submit cookie pattern:
|
|
// - Secret is stored in httpOnly cookie (secure from XSS)
|
|
// - Token is stored in a readable cookie for JavaScript access
|
|
export const csrfProtect = createCsrfProtect({
|
|
cookie: {
|
|
// Secret cookie settings (httpOnly - not readable by JS)
|
|
name: '__csrf_secret',
|
|
path: '/',
|
|
sameSite: 'strict',
|
|
secure: process.env.NODE_ENV === 'production',
|
|
httpOnly: true,
|
|
},
|
|
// Token settings
|
|
token: {
|
|
// Response header where token is sent back to client
|
|
responseHeader: 'x-csrf-token',
|
|
},
|
|
});
|
|
|
|
// Error response for CSRF validation failure
|
|
export function createCsrfErrorResponse(): NextResponse {
|
|
return NextResponse.json(
|
|
{
|
|
error: 'CSRF token validation failed',
|
|
code: 'CSRF_ERROR',
|
|
message: 'Invalid or missing CSRF token. Please refresh the page and try again.',
|
|
},
|
|
{ status: 403 }
|
|
);
|
|
}
|
|
|
|
// Check if request method requires CSRF protection
|
|
export function requiresCsrfProtection(method: string): boolean {
|
|
const safeMethods = ['GET', 'HEAD', 'OPTIONS'];
|
|
return !safeMethods.includes(method.toUpperCase());
|
|
}
|
|
|
|
// Check if path should be protected
|
|
export function shouldProtectPath(pathname: string): boolean {
|
|
// Only protect API routes
|
|
if (!pathname.startsWith('/api/')) {
|
|
return false;
|
|
}
|
|
|
|
// List of API routes that need CSRF protection (state-changing)
|
|
const protectedRoutes = [
|
|
'/api/favorites', // POST (add), DELETE (remove)
|
|
'/api/favorites/check', // POST (batch check - read-like but uses POST)
|
|
'/api/ratings', // POST (submit rating)
|
|
'/api/skills/removal-request', // POST (request removal)
|
|
'/api/skills/add-request', // POST (request addition)
|
|
];
|
|
|
|
// Check if the pathname matches any protected route
|
|
return protectedRoutes.some((route) => pathname.startsWith(route));
|
|
}
|
|
|
|
// Export the CsrfError type for error handling
|
|
export { CsrfError };
|