Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion node_modules

This file was deleted.

4 changes: 4 additions & 0 deletions src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { requireAuth } from './middleware/auth.js';
import aiFeedbackRouter from './routes/ai-feedback.js';
import auditLogRouter from './routes/auditLog.js';
import authRouter from './routes/auth.js';
import backupRouter from './routes/backup.js';
import communityRouter from './routes/community.js';
import configRouter from './routes/config.js';
import conversationsRouter from './routes/conversations.js';
Expand Down Expand Up @@ -69,4 +70,7 @@ router.use('/guilds', requireAuth(), auditLogMiddleware(), notificationsRouter);
// Webhook routes — require API secret or OAuth2 JWT (endpoint further restricts to api-secret)
router.use('/webhooks', requireAuth(), webhooksRouter);

// Backup routes — require API secret or OAuth2 JWT
router.use('/backups', requireAuth(), auditLogMiddleware(), backupRouter);

export default router;
39 changes: 39 additions & 0 deletions src/api/middleware/requireGlobalAdmin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { warn } from '../../logger.js';
import { getConfig } from '../../modules/config.js';
import { getBotOwnerIds } from '../../utils/permissions.js';

/**
* Middleware: restrict to API-secret callers or bot-owner OAuth users.
*/
export function requireGlobalAdmin(forResource, req, res, next) {
// Support both requireGlobalAdmin(req, res, next) and requireGlobalAdmin('Resource', req, res, next)
if (arguments.length === 3) {
// Called as requireGlobalAdmin(req, res, next)
// Parameters are shifted: forResource=req, req=res, res=next, next=undefined
next = res; // res parameter is actually the next function
res = req; // req parameter is actually the res object
req = forResource; // forResource is the actual req object
forResource = 'Global admin access';
} else {
forResource = forResource || 'Global admin access';
Comment on lines +8 to +18
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “argument shifting” overload makes the middleware hard to reason about and easy to misuse (especially if future callers pass unexpected arity). A clearer pattern is to export a standard (req, res, next) middleware and optionally provide a small factory/wrapper (e.g., requireGlobalAdminFor('Backup access')) that returns (req,res,next)=>..., avoiding reliance on arguments.length and parameter reassignment.

Suggested change
export function requireGlobalAdmin(forResource, req, res, next) {
// Support both requireGlobalAdmin(req, res, next) and requireGlobalAdmin('Resource', req, res, next)
if (arguments.length === 3) {
// Called as requireGlobalAdmin(req, res, next)
// Parameters are shifted: forResource=req, req=res, res=next, next=undefined
next = res; // res parameter is actually the next function
res = req; // req parameter is actually the res object
req = forResource; // forResource is the actual req object
forResource = 'Global admin access';
} else {
forResource = forResource || 'Global admin access';
export function requireGlobalAdmin(forResourceOrReq, resOrReq, nextOrRes, maybeNext) {
// Support both requireGlobalAdmin(req, res, next) and requireGlobalAdmin('Resource', req, res, next)
let forResource = 'Global admin access';
let req;
let res;
let next;
if (typeof forResourceOrReq === 'string') {
// Called as requireGlobalAdmin('Resource', req, res, next)
forResource = forResourceOrReq || 'Global admin access';
req = resOrReq;
res = nextOrRes;
next = maybeNext;
} else {
// Called as requireGlobalAdmin(req, res, next)
req = forResourceOrReq;
res = resOrReq;
next = nextOrRes;

Copilot uses AI. Check for mistakes.
}

if (req.authMethod === 'api-secret') {
return next();
}

if (req.authMethod === 'oauth') {
const config = getConfig();
const botOwners = getBotOwnerIds(config);
if (botOwners.includes(req.user?.userId)) {
return next();
}
return res.status(403).json({ error: `${forResource} requires bot owner permissions` });
}

warn('Unknown authMethod in global admin check', {
authMethod: req.authMethod,
path: req.path,
});
return res.status(401).json({ error: 'Unauthorized' });
}
Loading
Loading