From a16af3c0d4fad0798e2229af64507c067632dfb1 Mon Sep 17 00:00:00 2001 From: m0nggh Date: Mon, 18 May 2026 09:54:50 +0800 Subject: [PATCH] add admin whitelist endpoint --- packages/backend/src/routes/api/admin.ts | 58 ++++++++++++++++++++++++ packages/backend/src/routes/api/index.ts | 2 + 2 files changed, 60 insertions(+) create mode 100644 packages/backend/src/routes/api/admin.ts diff --git a/packages/backend/src/routes/api/admin.ts b/packages/backend/src/routes/api/admin.ts new file mode 100644 index 000000000..16a86361a --- /dev/null +++ b/packages/backend/src/routes/api/admin.ts @@ -0,0 +1,58 @@ +import { Router } from 'express' + +import logger from '@/helpers/logger' +import EmailSuppressionEntry from '@/models/email-suppression-entry' + +const router = Router() + +/** + * Middleware to ensure only plumber admins can access admin routes. + */ +router.use((req, res, next) => { + if (!req.context?.isAdminOperation) { + res.status(403).json({ error: 'Admin access required' }) + return + } + next() +}) + +/** + * GET /api/admin/email-suppression/whitelist?emails=a@x.com,b+1@x.com + * + * Whitelists one or more emails from the suppression list. + * Requires x-plumber-admin-token header. + */ +router.get('/email-suppression/whitelist', async (req, res) => { + const emailsParam = req.query.emails as string + if (!emailsParam) { + res.status(400).json({ error: 'emails query parameter is required' }) + return + } + + // `+` in query strings is decoded to a space; restore it since spaces + // are not valid in email addresses. This lets callers paste URLs with + // unencoded `+` characters directly. + const emails = emailsParam + .split(',') + .map((e) => e.trim().replace(/ /g, '+')) + .filter(Boolean) + if (emails.length === 0) { + res.status(400).json({ error: 'emails must not be empty' }) + return + } + + const whitelisted = await EmailSuppressionEntry.whitelistEmails(emails) + + logger.info('Admin whitelisted emails from suppression list', { + event: 'admin-email-suppression-whitelist', + requested: emails, + whitelisted, + }) + + res.json({ + whitelisted, + count: whitelisted.length, + }) +}) + +export default router diff --git a/packages/backend/src/routes/api/index.ts b/packages/backend/src/routes/api/index.ts index 5dc8b903d..27b432247 100644 --- a/packages/backend/src/routes/api/index.ts +++ b/packages/backend/src/routes/api/index.ts @@ -5,6 +5,7 @@ import { requireAuthentication, setCurrentUserContext, } from './middleware/authentication' +import adminRouter from './admin' import appsRouter from './apps' import chatRouter from './chat' @@ -17,6 +18,7 @@ router.use(requireAuthentication) // Mount routes that admins can access before blockAdminOperations router.use('/apps', appsRouter) +router.use('/admin', adminRouter) // Block admin mutations for all subsequent routes router.use(blockAdminOperations)