6 Commits

4 changed files with 48 additions and 14 deletions

View File

@@ -40,12 +40,27 @@ function createKnowledgeBaseRouter(db, upload) {
} }
// POST /api/knowledge-base/upload - Upload new document // POST /api/knowledge-base/upload - Upload new document
router.post('/upload', requireAuth(db), requireRole(db, 'editor', 'admin'), upload.single('file'), async (req, res) => { router.post('/upload', requireAuth(db), requireRole(db, 'editor', 'admin'), (req, res, next) => {
upload.single('file')(req, res, (err) => {
if (err) {
console.error('[KB Upload] Multer error:', err);
return res.status(400).json({ error: err.message || 'File upload failed' });
}
next();
});
}, async (req, res) => {
console.log('[KB Upload] Request received:', {
hasFile: !!req.file,
body: req.body,
contentType: req.headers['content-type']
});
const uploadedFile = req.file; const uploadedFile = req.file;
const { title, description, category } = req.body; const { title, description, category } = req.body;
// Validate required fields // Validate required fields
if (!title || !title.trim()) { if (!title || !title.trim()) {
console.error('[KB Upload] Error: Title is missing');
if (uploadedFile) fs.unlinkSync(uploadedFile.path); if (uploadedFile) fs.unlinkSync(uploadedFile.path);
return res.status(400).json({ error: 'Title is required' }); return res.status(400).json({ error: 'Title is required' });
} }
@@ -241,6 +256,9 @@ function createKnowledgeBaseRouter(db, upload) {
res.setHeader('Content-Type', contentType); res.setHeader('Content-Type', contentType);
// Use inline instead of attachment to allow browser to display // Use inline instead of attachment to allow browser to display
res.setHeader('Content-Disposition', `inline; filename="${row.file_name}"`); res.setHeader('Content-Disposition', `inline; filename="${row.file_name}"`);
// Allow iframe embedding from frontend origin
res.removeHeader('X-Frame-Options');
res.setHeader('Content-Security-Policy', "frame-ancestors 'self' http://71.85.90.9:3000 http://localhost:3000");
res.sendFile(row.file_path); res.sendFile(row.file_path);
}); });
}); });

View File

@@ -34,7 +34,7 @@ const CORS_ORIGINS = process.env.CORS_ORIGINS
// Allowed file extensions for document uploads (documents only, no executables) // Allowed file extensions for document uploads (documents only, no executables)
const ALLOWED_EXTENSIONS = new Set([ const ALLOWED_EXTENSIONS = new Set([
'.pdf', '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff', '.tif', '.pdf', '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff', '.tif',
'.txt', '.csv', '.log', '.msg', '.eml', '.txt', '.md', '.csv', '.log', '.msg', '.eml',
'.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
'.odt', '.ods', '.odp', '.odt', '.ods', '.odp',
'.rtf', '.html', '.htm', '.xml', '.json', '.yaml', '.yml', '.rtf', '.html', '.htm', '.xml', '.json', '.yaml', '.yml',
@@ -96,7 +96,7 @@ app.use((req, res, next) => {
// Security headers // Security headers
app.use((req, res, next) => { app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('X-Frame-Options', 'SAMEORIGIN'); // Allow iframes from same origin
res.setHeader('X-XSS-Protection', '1; mode=block'); res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()'); res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
@@ -108,7 +108,11 @@ app.use(cors({
origin: CORS_ORIGINS, origin: CORS_ORIGINS,
credentials: true credentials: true
})); }));
app.use(express.json({ limit: '1mb' })); // Only parse JSON for requests with application/json content type
app.use(express.json({
limit: '1mb',
type: 'application/json'
}));
app.use(cookieParser()); app.use(cookieParser());
app.use('/uploads', express.static('uploads', { app.use('/uploads', express.static('uploads', {
dotfiles: 'deny', dotfiles: 'deny',

View File

@@ -158,7 +158,7 @@ const API_HOST = process.env.REACT_APP_API_HOST || 'http://localhost:3001';
const severityLevels = ['All Severities', 'Critical', 'High', 'Medium', 'Low']; const severityLevels = ['All Severities', 'Critical', 'High', 'Medium', 'Low'];
export default function App() { export default function App() {
const { isAuthenticated, loading: authLoading, canWrite, isAdmin } = useAuth(); const { isAuthenticated, loading: authLoading, canWrite, isAdmin, user } = useAuth();
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [selectedVendor, setSelectedVendor] = useState('All Vendors'); const [selectedVendor, setSelectedVendor] = useState('All Vendors');
const [selectedSeverity, setSelectedSeverity] = useState('All Severities'); const [selectedSeverity, setSelectedSeverity] = useState('All Severities');

View File

@@ -191,16 +191,28 @@ export default function KnowledgeBaseViewer({ article, onClose }) {
{/* PDF */} {/* PDF */}
{isPDF && ( {isPDF && (
<div className="w-full" style={{ height: '700px' }}>
<iframe
src={`${API_BASE}/knowledge-base/${article.id}/content`}
title={article.title}
className="w-full h-full rounded"
style={{
border: '1px solid rgba(14, 165, 233, 0.3)',
background: 'rgba(15, 23, 42, 0.8)'
}}
>
<div className="text-center py-12"> <div className="text-center py-12">
<File className="w-16 h-16 mx-auto mb-4" style={{ color: '#EF4444' }} /> <File className="w-16 h-16 mx-auto mb-4" style={{ color: '#EF4444' }} />
<p className="mb-4" style={{ color: '#94A3B8' }}> <p className="mb-4" style={{ color: '#94A3B8' }}>
PDF Preview not available. Click the download button to view this file. Your browser doesn't support PDF preview. Click the download button to view this file.
</p> </p>
<button onClick={handleDownload} className="intel-button intel-button-success"> <button onClick={handleDownload} className="intel-button intel-button-success">
<Download className="w-4 h-4 mr-2" /> <Download className="w-4 h-4 mr-2" />
Download PDF Download PDF
</button> </button>
</div> </div>
</iframe>
</div>
)} )}
{/* Images */} {/* Images */}