Secure image access and bootstrap admin
Some checks failed
Build & Push Docker Image / build-and-push (push) Failing after 1m50s
Some checks failed
Build & Push Docker Image / build-and-push (push) Failing after 1m50s
This commit is contained in:
@@ -7,10 +7,22 @@ const path = require('path');
|
||||
const db = require('./database');
|
||||
|
||||
const app = express();
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'mischkomposition-super-secret-key-change-me';
|
||||
const FULLS_DIR = path.join(__dirname, '../public/images/fulls');
|
||||
const JWT_SECRET = process.env.JWT_SECRET;
|
||||
if (!JWT_SECRET) {
|
||||
throw new Error('JWT_SECRET must be set before starting the server');
|
||||
}
|
||||
const PUBLIC_DIR = path.join(__dirname, '../public');
|
||||
const DEFAULT_FULLS_DIR = path.join(PUBLIC_DIR, 'images/fulls');
|
||||
const DEFAULT_THUMBS_DIR = path.join(PUBLIC_DIR, 'images/thumbs');
|
||||
const DEV_FULLS_DIR = path.join(__dirname, '../images/fulls');
|
||||
const DEV_THUMBS_DIR = path.join(__dirname, '../images/thumbs');
|
||||
const FULLS_DIR = process.env.FULLS_DIR || (fs.existsSync(DEFAULT_FULLS_DIR) ? DEFAULT_FULLS_DIR : DEV_FULLS_DIR);
|
||||
const THUMBS_DIR = process.env.THUMBS_DIR || (fs.existsSync(DEFAULT_THUMBS_DIR) ? DEFAULT_THUMBS_DIR : DEV_THUMBS_DIR);
|
||||
const IMAGE_RE = /\.(jpg|jpeg|png|gif|webp)$/i;
|
||||
|
||||
console.log('Using full-size images at', FULLS_DIR);
|
||||
console.log('Using thumbnails at', THUMBS_DIR);
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(cookieParser());
|
||||
@@ -73,6 +85,55 @@ function listSubfolders(dir) {
|
||||
}
|
||||
}
|
||||
|
||||
function isSafePath(baseDir, requestedPath) {
|
||||
const resolvedBase = path.resolve(baseDir);
|
||||
const resolvedPath = path.resolve(resolvedBase, requestedPath);
|
||||
return resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase + path.sep);
|
||||
}
|
||||
|
||||
function hasAssignedFolder(userId, folder, callback) {
|
||||
db.get(
|
||||
'SELECT 1 FROM user_folders WHERE user_id = ? AND folder_name = ?',
|
||||
[userId, folder],
|
||||
(err, row) => callback(!err && !!row)
|
||||
);
|
||||
}
|
||||
|
||||
function serveProtectedImage(req, res) {
|
||||
const kind = req.params[0];
|
||||
const relativePath = req.params[1] || '';
|
||||
const baseDir = kind === 'thumbs' ? THUMBS_DIR : FULLS_DIR;
|
||||
const segments = relativePath.split('/').filter(Boolean);
|
||||
const fileName = segments[segments.length - 1];
|
||||
|
||||
if (!fileName || !IMAGE_RE.test(fileName) || !isSafePath(baseDir, relativePath)) {
|
||||
return res.status(404).end();
|
||||
}
|
||||
|
||||
const absolutePath = path.resolve(baseDir, relativePath);
|
||||
if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isFile()) {
|
||||
return res.status(404).end();
|
||||
}
|
||||
|
||||
// Bilder direkt im Root-Verzeichnis sind oeffentlich. Kundenordner sind geschuetzt.
|
||||
if (segments.length === 1) {
|
||||
return res.sendFile(absolutePath);
|
||||
}
|
||||
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not logged in' });
|
||||
}
|
||||
|
||||
if (req.user.role === 'admin') {
|
||||
return res.sendFile(absolutePath);
|
||||
}
|
||||
|
||||
hasAssignedFolder(req.user.id, segments[0], (allowed) => {
|
||||
if (!allowed) return res.status(403).json({ error: 'Forbidden' });
|
||||
res.sendFile(absolutePath);
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== AUTH API ====================
|
||||
|
||||
app.post('/api/auth/login', (req, res) => {
|
||||
@@ -250,10 +311,12 @@ app.post('/api/admin/assign', requireAdmin, (req, res) => {
|
||||
|
||||
// ==================== STATIC FILES ====================
|
||||
|
||||
app.use(express.static(path.join(__dirname, '../public')));
|
||||
app.get(/^\/images\/(fulls|thumbs)\/(.+)$/, serveProtectedImage);
|
||||
|
||||
app.use(express.static(PUBLIC_DIR));
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, '../public/index.html'));
|
||||
res.sendFile(path.join(PUBLIC_DIR, 'index.html'));
|
||||
});
|
||||
|
||||
const PORT = 8090;
|
||||
|
||||
Reference in New Issue
Block a user