diff --git a/server.js b/server.js index 96451f0..2f1bccc 100644 --- a/server.js +++ b/server.js @@ -1639,6 +1639,15 @@ initializeDatabase().then(async () => { // to be present inside any protected route or background-per-user task. const uid = () => currentUserId(); + // Inside a multer upload callback the per-request AsyncLocalStorage context + // (which uid() reads) is not reliably propagated — busboy drives the multipart + // stream from outside the userContext.run(...) scope, so currentUserId() can + // come back null there. The request object, by contrast, always carries the + // resolved user (set by the auth middleware before the route). Use this for + // any INSERT done from within an uploadX(req, res, cb) callback, so the row is + // not orphaned with user_id = NULL. Mirrors userStorageDirForReq's reasoning. + const uidFromReq = (req) => (req && req.user && req.user.id) || currentUserId(); + // Resolve a per-user storage directory (data///), creating it // on first use. Used for every attachment / signature / photo path so users' // files are isolated on disk the same way their DB rows are. @@ -3206,7 +3215,7 @@ initializeDatabase().then(async () => { : sanitizeInput(original.replace(/\.[^.]+$/, '')); await dbRun( 'INSERT INTO basis_anhaenge (user_id, name, dateiname, mime, pfad) VALUES (?, ?, ?, ?, ?)', - [uid(), name, sanitizeInput(original), req.file.mimetype || 'application/octet-stream', req.file.filename] + [uidFromReq(req), name, sanitizeInput(original), req.file.mimetype || 'application/octet-stream', req.file.filename] ); } res.redirect('/vorlagen'); @@ -3262,8 +3271,10 @@ initializeDatabase().then(async () => { try { if (err) console.error('Signature upload error:', err.message); if (req.file) { - // keep only the newly uploaded file (in the user's subdir) - const dir = userStorageDir(signaturDir); + // keep only the newly uploaded file (in the user's subdir). Resolve the + // dir from req.user (not currentUserId()) — this runs in the multer + // callback where the AsyncLocalStorage user context can be absent. + const dir = userStorageDirForReq(signaturDir, req); fs.readdirSync(dir).forEach((f) => { if (f !== req.file.filename) fs.promises.unlink(path.join(dir, f)).catch(() => {}); }); @@ -3300,8 +3311,9 @@ initializeDatabase().then(async () => { try { if (err) console.error('Photo upload error:', err.message); if (req.file) { - // keep only the newly uploaded file (in the user's subdir) - const dir = userStorageDir(fotoDir); + // keep only the newly uploaded file (in the user's subdir). Resolve the + // dir from req.user — see /unterschrift handler for why (multer callback). + const dir = userStorageDirForReq(fotoDir, req); fs.readdirSync(dir).forEach((f) => { if (f !== req.file.filename) fs.promises.unlink(path.join(dir, f)).catch(() => {}); }); @@ -3374,7 +3386,7 @@ initializeDatabase().then(async () => { : sanitizeInput(original.replace(/\.[^.]+$/, '')); await dbRun( 'INSERT INTO interne_anhaenge (user_id, bewerbung_id, name, dateiname, mime, pfad) VALUES (?, ?, ?, ?, ?, ?)', - [uid(), id, name, sanitizeInput(original), req.file.mimetype || 'application/octet-stream', req.file.filename] + [uidFromReq(req), id, name, sanitizeInput(original), req.file.mimetype || 'application/octet-stream', req.file.filename] ); } res.redirect('/bewerbung/' + id);