Skip to content

Commit b9df176

Browse files
committed
feat: add POST /admin/flush-auth-settings endpoint with step-up auth
Admin-only endpoint to flush cached auth settings for the current tenant. Requires administrator role + password_or_mfa access level (step-up auth). Clears both svcCache and graphileCache entries so the next request re-queries auth settings from the tenant DB.
1 parent bb02c3e commit b9df176

2 files changed

Lines changed: 56 additions & 1 deletion

File tree

graphql/server/src/middleware/flush.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,58 @@ export const flush = async (
2323
return next();
2424
};
2525

26+
/**
27+
* Admin-only endpoint to flush auth settings from the svcCache.
28+
*
29+
* Requires:
30+
* - Admin role (role === 'administrator')
31+
* - Step-up authentication (access_level === 'password_or_mfa')
32+
*
33+
* Clears both svcCache and graphileCache entries for the current tenant
34+
* so the next request re-queries auth settings from the tenant DB.
35+
*/
36+
export const flushAuthSettingsCache = async (
37+
req: Request,
38+
res: Response,
39+
): Promise<void> => {
40+
const token = req.token;
41+
42+
// Require authentication
43+
if (!token?.role) {
44+
res.status(401).json({ error: 'Authentication required' });
45+
return;
46+
}
47+
48+
// Require admin role (database-level superuser, not org-level)
49+
if (token.role !== 'administrator') {
50+
log.warn(`[flush-auth-settings] Denied: role=${token.role}, expected administrator`);
51+
res.status(403).json({ error: 'Administrator role required' });
52+
return;
53+
}
54+
55+
// Require step-up authentication
56+
if (token.access_level !== 'password_or_mfa') {
57+
log.warn(`[flush-auth-settings] Denied: access_level=${token.access_level}, expected password_or_mfa`);
58+
res.status(403).json({
59+
error: 'Step-up authentication required (access_level must be password_or_mfa)',
60+
});
61+
return;
62+
}
63+
64+
const svcKey = req.svc_key;
65+
if (!svcKey) {
66+
log.warn('[flush-auth-settings] No svc_key on request, nothing to flush');
67+
res.status(400).json({ error: 'Could not determine tenant cache key' });
68+
return;
69+
}
70+
71+
graphileCache.delete(svcKey);
72+
svcCache.delete(svcKey);
73+
log.info(`[flush-auth-settings] Flushed cache for key=${svcKey} (requested by user_id=${token.user_id})`);
74+
75+
res.status(200).json({ flushed: true, cacheKey: svcKey });
76+
};
77+
2678
export const flushService = async (
2779
opts: ConstructiveOptions,
2880
databaseId: string

graphql/server/src/server.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { createAuthenticateMiddleware } from './middleware/auth';
2626
import { cors } from './middleware/cors';
2727
import { errorHandler, notFoundHandler } from './middleware/error-handler';
2828
import { favicon } from './middleware/favicon';
29-
import { flush, flushService } from './middleware/flush';
29+
import { flush, flushAuthSettingsCache, flushService } from './middleware/flush';
3030
import { graphile } from './middleware/graphile';
3131
import { multipartBridge } from './middleware/multipart-bridge';
3232
import { createDebugDatabaseMiddleware } from './middleware/observability/debug-db';
@@ -179,6 +179,9 @@ class Server {
179179

180180
app.use(createCaptchaMiddleware(effectiveOpts));
181181

182+
// Admin: flush auth settings cache (requires administrator + step-up auth)
183+
app.post('/admin/flush-auth-settings', flushAuthSettingsCache);
184+
182185
// Cookie lifecycle: set/clear session cookies on auth mutations (when enabled)
183186
app.use(createCookieMiddleware());
184187

0 commit comments

Comments
 (0)