Fix admin gallery auth: use X-Admin-Password header instead of Authorization Bearer
NPMplus proxy strips the Authorization header before it reaches the backend, causing all write operations to return 401. Switch to a custom X-Admin-Password header which passes through the proxy unmodified. - backend/routes/photos.js: requireAuth checks X-Admin-Password first, falls back to Authorization Bearer for compatibility - backend/server.js: add X-Admin-Password to CORS allowedHeaders - admin/admin.js: send X-Admin-Password instead of Authorization Bearer in all fetch calls and XHR upload requests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
95e88780ab
commit
1a1fe7044a
@ -406,7 +406,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${getAdminPassword()}`,
|
'X-Admin-Password': getAdminPassword(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify(updatedPhoto)
|
body: JSON.stringify(updatedPhoto)
|
||||||
});
|
});
|
||||||
@ -437,7 +437,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
try {
|
try {
|
||||||
await fetch(`${backendUrl}/photos/${id}`, {
|
await fetch(`${backendUrl}/photos/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Authorization': `Bearer ${getAdminPassword()}` }
|
headers: { 'X-Admin-Password': getAdminPassword() }
|
||||||
});
|
});
|
||||||
fetchPhotos();
|
fetchPhotos();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -468,7 +468,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
try {
|
try {
|
||||||
await Promise.all(ids.map(id => fetch(`${backendUrl}/photos/${id}`, {
|
await Promise.all(ids.map(id => fetch(`${backendUrl}/photos/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Authorization': `Bearer ${getAdminPassword()}` }
|
headers: { 'X-Admin-Password': getAdminPassword() }
|
||||||
})));
|
})));
|
||||||
clearSelection();
|
clearSelection();
|
||||||
fetchPhotos();
|
fetchPhotos();
|
||||||
@ -528,7 +528,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${getAdminPassword()}`,
|
'X-Admin-Password': getAdminPassword(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
@ -740,7 +740,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
xhr.open('POST', `${backendUrl}/photos/upload`);
|
xhr.open('POST', `${backendUrl}/photos/upload`);
|
||||||
xhr.setRequestHeader('Authorization', `Bearer ${getAdminPassword()}`);
|
xhr.setRequestHeader('X-Admin-Password', getAdminPassword());
|
||||||
|
|
||||||
uploadButton.classList.add('is-loading');
|
uploadButton.classList.add('is-loading');
|
||||||
uploadProgress.style.display = 'block';
|
uploadProgress.style.display = 'block';
|
||||||
@ -832,7 +832,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const url = idx !== '' ? `${backendUrl}/photos/presets/${idx}` : `${backendUrl}/photos/presets`;
|
const url = idx !== '' ? `${backendUrl}/photos/presets/${idx}` : `${backendUrl}/photos/presets`;
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method,
|
method,
|
||||||
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${getAdminPassword()}` },
|
headers: { 'Content-Type': 'application/json', 'X-Admin-Password': getAdminPassword() },
|
||||||
body: JSON.stringify({ name, tags })
|
body: JSON.stringify({ name, tags })
|
||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@ -853,7 +853,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (!confirm('Delete this preset?')) return;
|
if (!confirm('Delete this preset?')) return;
|
||||||
const res = await fetch(`${backendUrl}/photos/presets/${idx}`, {
|
const res = await fetch(`${backendUrl}/photos/presets/${idx}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Authorization': `Bearer ${getAdminPassword()}` }
|
headers: { 'X-Admin-Password': getAdminPassword() }
|
||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@ -1024,7 +1024,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${getAdminPassword()}`,
|
'X-Admin-Password': getAdminPassword(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ data })
|
body: JSON.stringify({ data })
|
||||||
});
|
});
|
||||||
|
|||||||
@ -24,8 +24,11 @@ const WATERMARK_URL = process.env.WATERMARK_URL || 'http://watermarker:8000/wate
|
|||||||
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || '';
|
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || '';
|
||||||
|
|
||||||
function requireAuth(req, res, next) {
|
function requireAuth(req, res, next) {
|
||||||
const auth = req.headers['authorization'] || '';
|
// Accept password via custom header (proxy-safe) or legacy Authorization bearer
|
||||||
const token = auth.startsWith('Bearer ') ? auth.slice(7) : '';
|
const custom = req.headers['x-admin-password'] || '';
|
||||||
|
const auth = req.headers['authorization'] || '';
|
||||||
|
const bearer = auth.startsWith('Bearer ') ? auth.slice(7) : '';
|
||||||
|
const token = custom || bearer;
|
||||||
if (!token || !ADMIN_PASSWORD || token !== ADMIN_PASSWORD) {
|
if (!token || !ADMIN_PASSWORD || token !== ADMIN_PASSWORD) {
|
||||||
return res.status(401).json({ success: false, error: 'Unauthorized' });
|
return res.status(401).json({ success: false, error: 'Unauthorized' });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ const mongoose = require('mongoose');
|
|||||||
const app = express();
|
const app = express();
|
||||||
const port = process.env.PORT || 5000;
|
const port = process.env.PORT || 5000;
|
||||||
|
|
||||||
app.use(cors({ origin: '*', methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'] }));
|
app.use(cors({ origin: '*', methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Admin-Password'] }));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use('/uploads', express.static('uploads'));
|
app.use('/uploads', express.static('uploads'));
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user