Compare commits
6 Commits
79a1a23002
...
enhancemen
| Author | SHA1 | Date | |
|---|---|---|---|
| 112eb8dac1 | |||
| 3b37646b6d | |||
| 241ff16bb4 | |||
| 0e89251bac | |||
| fa9f4229a6 | |||
| eea226a9d5 |
@@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -191,15 +191,27 @@ export default function KnowledgeBaseViewer({ article, onClose }) {
|
|||||||
|
|
||||||
{/* PDF */}
|
{/* PDF */}
|
||||||
{isPDF && (
|
{isPDF && (
|
||||||
<div className="text-center py-12">
|
<div className="w-full" style={{ height: '700px' }}>
|
||||||
<File className="w-16 h-16 mx-auto mb-4" style={{ color: '#EF4444' }} />
|
<iframe
|
||||||
<p className="mb-4" style={{ color: '#94A3B8' }}>
|
src={`${API_BASE}/knowledge-base/${article.id}/content`}
|
||||||
PDF Preview not available. Click the download button to view this file.
|
title={article.title}
|
||||||
</p>
|
className="w-full h-full rounded"
|
||||||
<button onClick={handleDownload} className="intel-button intel-button-success">
|
style={{
|
||||||
<Download className="w-4 h-4 mr-2" />
|
border: '1px solid rgba(14, 165, 233, 0.3)',
|
||||||
Download PDF
|
background: 'rgba(15, 23, 42, 0.8)'
|
||||||
</button>
|
}}
|
||||||
|
>
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<File className="w-16 h-16 mx-auto mb-4" style={{ color: '#EF4444' }} />
|
||||||
|
<p className="mb-4" style={{ color: '#94A3B8' }}>
|
||||||
|
Your browser doesn't support PDF preview. Click the download button to view this file.
|
||||||
|
</p>
|
||||||
|
<button onClick={handleDownload} className="intel-button intel-button-success">
|
||||||
|
<Download className="w-4 h-4 mr-2" />
|
||||||
|
Download PDF
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</iframe>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user