fix(admin): gate /admin/openapi.json behind a feature flag (#7693)

Address Qodo finding 1: new features must ship behind a flag, disabled
by default (CONTRIBUTING.md, AGENTS.MD, best_practices.md). Adds
settings.adminOpenAPI.enabled (default false). expressPreSession
returns early when the flag is off, so the route is dormant on a fresh
install.

The codegen pipeline imports generateAdminDefinition() in-process and
does not depend on the runtime route, so default-off has no effect on
the typed client. Operators who want third-party tooling (Postman,
swagger-ui, downstream clients) to consume the spec at runtime opt in
via settings.

Adds:
- SettingsType + defaults entry in src/node/utils/Settings.ts
- settings.json.template documentation
- A backend spec asserting expressPreSession is a no-op when the flag
  is off (live route test now sets the flag explicitly)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
John McLear 2026-05-08 16:32:35 +01:00
parent 2c91b75d18
commit dbcfb39027
4 changed files with 72 additions and 3 deletions

View file

@ -1,7 +1,7 @@
'use strict';
import {ArgsExpressType} from '../../types/ArgsExpressType';
import {getEpVersion} from '../../utils/Settings';
import settings, {getEpVersion} from '../../utils/Settings';
const OPENAPI_VERSION = '3.0.2';
@ -11,8 +11,8 @@ const OPENAPI_VERSION = '3.0.2';
* Distinct from the public versioned API document built by openapi.ts
* admin routes are plain Express handlers (not APIHandler-driven), so this
* spec is hand-authored. The shape is consumed by admin/scripts/dump-spec.ts
* for client-side codegen and exposed at GET /admin/openapi.json for
* downstream tooling.
* for client-side codegen, and (when settings.adminOpenAPI.enabled) exposed
* at GET /admin/openapi.json for downstream tooling.
*/
export const generateAdminDefinition = (): any => ({
openapi: OPENAPI_VERSION,
@ -159,6 +159,11 @@ export const expressPreSession = async (
_hookName: string,
{app}: ArgsExpressType,
): Promise<void> => {
// Behind a feature flag, default off. Etherpad policy
// (CONTRIBUTING.md, AGENTS.MD) requires new features to ship disabled by
// default. The route is only useful for third-party tooling — codegen
// imports generateAdminDefinition() in-process and does not depend on it.
if (!settings.adminOpenAPI?.enabled) return;
app.get('/admin/openapi.json', (_req: any, res: any) => {
res.header('Access-Control-Allow-Origin', '*');
res.json(generateAdminDefinition());

View file

@ -331,6 +331,9 @@ export type SettingsType = {
githubRepo: string,
requireAdminForStatus: boolean,
},
adminOpenAPI: {
enabled: boolean,
},
adminEmail: string | null,
getPublicSettings: () => Pick<SettingsType, "title" | "skinVariants"|"randomVersionString"|"skinName"|"toolbar"| "exposeVersion"| "gitVersion" | "enablePadWideSettings" | "privacyBanner">,
}
@ -510,6 +513,18 @@ const settings: SettingsType = {
// disabling the updater itself.
requireAdminForStatus: false,
},
/**
* Admin OpenAPI document endpoint at /admin/openapi.json.
*
* Disabled by default per Etherpad's "new features behind a flag, off by
* default" policy (see CONTRIBUTING.md). The codegen pipeline imports
* generateAdminDefinition() in-process and does not depend on the route;
* enable this only if you want third-party tooling (Postman, swagger-ui,
* downstream clients) to consume the spec at runtime.
*/
adminOpenAPI: {
enabled: false,
},
/**
* Contact address for admin notifications (updates, future security advisories).
* Null disables outbound mail from the updater.