diff --git a/docker-compose.yml b/docker-compose.yml index 7583390..1a0923e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,6 +37,7 @@ services: environment: MONGO_URI: mongodb://mongodb:27017/photogallery WATERMARK_URL: http://watermarker:8000/watermark + ADMIN_PASSWORD: ${MAIN_ADMIN_PASSWORD} volumes: - ./main-site/photo-gallery-app/backend/uploads:/usr/src/app/uploads depends_on: @@ -54,8 +55,6 @@ services: mongodb: image: mongo:latest container_name: bpb-mongodb - ports: - - "27017:27017" volumes: - ./mongodb_data:/data/db restart: always diff --git a/main-site/admin/admin.js b/main-site/admin/admin.js index b2f3de8..42933de 100644 --- a/main-site/admin/admin.js +++ b/main-site/admin/admin.js @@ -344,13 +344,15 @@ document.addEventListener('DOMContentLoaded', () => { method: 'POST', headers: { 'Content-Type': 'application/json', + 'Authorization': `Bearer ${getAdminPassword()}`, }, body: JSON.stringify(updatedPhoto) }); + if (response.status === 401) { handleUnauthorized(); return; } if (response.ok) { closeEditModal(); - fetchPhotos(); // Refresh the gallery + fetchPhotos(); fetchTagMeta(); } else { alert('Failed to save changes.'); @@ -364,7 +366,10 @@ document.addEventListener('DOMContentLoaded', () => { async function deletePhoto(id) { if (confirm('Are you sure you want to delete this photo?')) { try { - await fetch(`${backendUrl}/photos/${id}`, { method: 'DELETE' }); + await fetch(`${backendUrl}/photos/${id}`, { + method: 'DELETE', + headers: { 'Authorization': `Bearer ${getAdminPassword()}` } + }); fetchPhotos(); } catch (error) { console.error('Error deleting photo:', error); @@ -392,7 +397,10 @@ document.addEventListener('DOMContentLoaded', () => { confirmBulkDeleteBtn.classList.add('is-loading'); try { - await Promise.all(ids.map(id => fetch(`${backendUrl}/photos/${id}`, { method: 'DELETE' }))); + await Promise.all(ids.map(id => fetch(`${backendUrl}/photos/${id}`, { + method: 'DELETE', + headers: { 'Authorization': `Bearer ${getAdminPassword()}` } + }))); clearSelection(); fetchPhotos(); closeBulkDeleteModal(); @@ -449,7 +457,10 @@ document.addEventListener('DOMContentLoaded', () => { }; await fetch(`${backendUrl}/photos/update/${id}`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${getAdminPassword()}`, + }, body: JSON.stringify(payload) }); })); @@ -613,7 +624,8 @@ document.addEventListener('DOMContentLoaded', () => { }); xhr.open('POST', `${backendUrl}/photos/upload`); - + xhr.setRequestHeader('Authorization', `Bearer ${getAdminPassword()}`); + uploadButton.classList.add('is-loading'); uploadProgress.style.display = 'block'; xhr.send(formData); @@ -758,11 +770,17 @@ document.addEventListener('DOMContentLoaded', () => { const response = await fetch('/api/update-status', { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${getAdminPassword()}`, }, body: JSON.stringify({ data }) }); + if (response.status === 401) { + handleUnauthorized(); + return; + } + const result = await response.json(); if (result.success) { diff --git a/main-site/photo-gallery-app/backend/routes/photos.js b/main-site/photo-gallery-app/backend/routes/photos.js index c4c4536..399a05a 100644 --- a/main-site/photo-gallery-app/backend/routes/photos.js +++ b/main-site/photo-gallery-app/backend/routes/photos.js @@ -21,6 +21,16 @@ const { } = require('../lib/tagConfig'); const WATERMARK_URL = process.env.WATERMARK_URL || 'http://watermarker:8000/watermark'; +const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || ''; + +function requireAuth(req, res, next) { + const auth = req.headers['authorization'] || ''; + const token = auth.startsWith('Bearer ') ? auth.slice(7) : ''; + if (!token || !ADMIN_PASSWORD || token !== ADMIN_PASSWORD) { + return res.status(401).json({ success: false, error: 'Unauthorized' }); + } + next(); +} // We now use a visible diagonal watermark only. Invisible watermarking is disabled by default. const DISABLE_WM = true; @@ -110,7 +120,7 @@ const parseIncomingTags = (tagsInput) => { }; // POST new photo(s) with WebP conversion + duplicate hash checks -router.route('/upload').post(upload.array('photos'), async (req, res) => { +router.route('/upload').post(requireAuth, upload.array('photos'), async (req, res) => { const files = (req.files && req.files.length) ? req.files : (req.file ? [req.file] : []); if (!files.length) { return res.status(400).json({ success: false, error: 'No file uploaded. Please select at least one file.' }); @@ -343,7 +353,7 @@ router.route('/:id').get((req, res) => { }); // DELETE a photo by ID -router.route('/:id').delete((req, res) => { +router.route('/:id').delete(requireAuth, (req, res) => { console.log('DELETE request received for photo ID:', req.params.id); console.log('Request headers:', req.headers); console.log('Request IP:', req.ip); @@ -369,7 +379,7 @@ router.route('/:id').delete((req, res) => { }); // UPDATE a photo by ID -router.route('/update/:id').post((req, res) => { +router.route('/update/:id').post(requireAuth, (req, res) => { Photo.findById(req.params.id) .then(photo => { const incomingCaption = req.body.caption; diff --git a/main-site/server.js b/main-site/server.js index 202eb5d..25dfb06 100644 --- a/main-site/server.js +++ b/main-site/server.js @@ -35,7 +35,16 @@ app.use(bodyParser.json()); // --- API Routes --- const apiRouter = express.Router(); -apiRouter.post('/update-status', (req, res) => { +function requireAuth(req, res, next) { + const auth = req.headers['authorization'] || ''; + const token = auth.startsWith('Bearer ') ? auth.slice(7) : ''; + if (!token || token !== ADMIN_PASSWORD) { + return res.status(401).json({ success: false, message: 'Unauthorized' }); + } + next(); +} + +apiRouter.post('/update-status', requireAuth, (req, res) => { console.log(`[${new Date().toISOString()}] Received request for /api/update-status`); const { data } = req.body;