diff --git a/cafe-management-sys/src/pages/admin/AdminInvoices.jsx b/cafe-management-sys/src/pages/admin/AdminInvoices.jsx index c1f5ccf..3916eb1 100644 --- a/cafe-management-sys/src/pages/admin/AdminInvoices.jsx +++ b/cafe-management-sys/src/pages/admin/AdminInvoices.jsx @@ -23,7 +23,7 @@ import Refresh from '@mui/icons-material/Refresh'; import ReceiptLong from '@mui/icons-material/ReceiptLong'; import Print from '@mui/icons-material/Print'; import Download from '@mui/icons-material/Download'; -import { invoicesAPI, activityAPI, API_BASE_URL, unwrap } from '../../services/api'; +import { invoicesAPI, activityAPI, API_BASE_URL, getAuthToken, unwrap } from '../../services/api'; import ErrorState from '../../components/common/ErrorState'; import useOpsEvents from '../../hooks/useOpsEvents'; import { formatMoney } from '../../utils/formatMoney'; @@ -95,12 +95,18 @@ const AdminInvoices = () => { const printInvoice = (inv, mode = paper) => { const id = inv._id || inv.id; - window.open(`${API_BASE_URL}/invoices/${id}/print?mode=${mode}`, '_blank'); + const qp = new URLSearchParams({ mode }); + const token = getAuthToken(); + if (token) qp.set('token', token); + window.open(`${API_BASE_URL}/invoices/${id}/print?${qp.toString()}`, '_blank'); }; const downloadPdf = (inv, format = 'a4') => { const id = inv._id || inv.id; - window.open(`${API_BASE_URL}/invoices/${id}/pdf?format=${format}&download=1`, '_blank'); + const qp = new URLSearchParams({ format, download: '1' }); + const token = getAuthToken(); + if (token) qp.set('token', token); + window.open(`${API_BASE_URL}/invoices/${id}/pdf?${qp.toString()}`, '_blank'); }; return ( diff --git a/cafe-management-sys/src/services/api.js b/cafe-management-sys/src/services/api.js index 938cf0d..0f64831 100644 --- a/cafe-management-sys/src/services/api.js +++ b/cafe-management-sys/src/services/api.js @@ -58,7 +58,7 @@ export const resolveApiBaseUrl = ( export const API_BASE_URL = resolveApiBaseUrl(RAW_API_URL); // Helper function to get auth token (sessionStorage preferred, localStorage fallback) -const getAuthToken = () => { +export const getAuthToken = () => { return sessionStorage.getItem('token') || localStorage.getItem('token'); }; diff --git a/server/middleware/auth.js b/server/middleware/auth.js index 765c151..aaeb463 100644 --- a/server/middleware/auth.js +++ b/server/middleware/auth.js @@ -215,12 +215,27 @@ const attachUserIfPresent = (req, _res, next) => { } }; +/** + * Print/PDF links are opened in a new tab (window.open), which cannot carry an + * Authorization header. Allow a `?token=` query param as a fallback so those + * endpoints still authenticate; it is stripped before the request proceeds (and + * thus never logged). + */ +const attachTokenFromQuery = (req, res, next) => { + if (!req.headers.authorization && req.query && typeof req.query.token === 'string') { + req.headers.authorization = `Bearer ${req.query.token}`; + delete req.query.token; + } + next(); +}; + module.exports = { ensureAuthenticated, ensureAdmin, ensureAdminOrStaff, ensurePlatformAdmin, attachUserIfPresent, + attachTokenFromQuery, authRateLimiter, generalRateLimiter, strictRateLimiter, diff --git a/server/routes/invoices.js b/server/routes/invoices.js index 3c3e4de..307a6d2 100644 --- a/server/routes/invoices.js +++ b/server/routes/invoices.js @@ -8,8 +8,9 @@ const { printEscPos, voidInvoice, } = require('../controllers/invoiceController'); -const { ensureAuthenticated, ensureAdmin, ensureAdminOrStaff } = require('../middleware/auth'); +const { ensureAuthenticated, ensureAdmin, ensureAdminOrStaff, attachTokenFromQuery } = require('../middleware/auth'); +router.use(attachTokenFromQuery); router.use(ensureAuthenticated); router.get('/', ensureAdminOrStaff, listInvoices); router.get('/:id', ensureAdminOrStaff, getInvoice);