diff --git a/src/node/hooks/express/openapi-admin.ts b/src/node/hooks/express/openapi-admin.ts index 91be2fc48..67e88da8f 100644 --- a/src/node/hooks/express/openapi-admin.ts +++ b/src/node/hooks/express/openapi-admin.ts @@ -22,7 +22,29 @@ export const generateAdminDefinition = (): any => ({ 'Distinct from the public /api/{version}/* surface served by /api/openapi.json.', version: getEpVersion(), }, - paths: {}, + paths: { + '/admin-auth/': { + post: { + operationId: 'verifyAdminAccess', + summary: 'Verify or establish an admin session', + description: + 'POST with `Authorization: Basic ` to log in as an admin ' + + '(server sets a session cookie on success). POST with no auth header ' + + 'to verify an existing admin session cookie. The response body is ' + + 'always empty; the status code conveys the outcome.', + security: [ + {basicAuth: []}, + {sessionCookie: []}, + {}, + ], + responses: { + '200': {description: 'Caller is an authenticated admin.'}, + '401': {description: 'No authentication presented and no admin session exists.'}, + '403': {description: 'Authenticated, but the user is not an admin.'}, + }, + }, + }, + }, components: { schemas: {}, securitySchemes: { diff --git a/src/tests/backend/specs/openapi-admin.ts b/src/tests/backend/specs/openapi-admin.ts index 894ae4373..ea8bf7da1 100644 --- a/src/tests/backend/specs/openapi-admin.ts +++ b/src/tests/backend/specs/openapi-admin.ts @@ -33,4 +33,26 @@ describe('admin OpenAPI document', function () { assert.equal(doc.components.securitySchemes.sessionCookie.type, 'apiKey'); assert.equal(doc.components.securitySchemes.sessionCookie.in, 'cookie'); }); + + describe('/admin-auth/', function () { + it('declares POST with operationId verifyAdminAccess', function () { + const op = doc.paths['/admin-auth/']?.post; + assert.ok(op, 'POST /admin-auth/ is missing'); + assert.equal(op.operationId, 'verifyAdminAccess'); + }); + + it('documents responses 200, 401, 403', function () { + const responses = doc.paths['/admin-auth/'].post.responses; + assert.ok(responses['200'], 'missing 200 response'); + assert.ok(responses['401'], 'missing 401 response'); + assert.ok(responses['403'], 'missing 403 response'); + }); + + it('declares security: basicAuth, sessionCookie, anonymous', function () { + const security = doc.paths['/admin-auth/'].post.security; + assert.ok(Array.isArray(security)); + const keys = security.map((s: any) => Object.keys(s)[0] ?? '__anon__'); + assert.deepEqual(keys.sort(), ['__anon__', 'basicAuth', 'sessionCookie'].sort()); + }); + }); });