diff --git a/backend/src/lib/authenticated.ts b/backend/src/lib/authenticated.ts index 7d3bf1f1b0b..f9e1dd2caee 100644 --- a/backend/src/lib/authenticated.ts +++ b/backend/src/lib/authenticated.ts @@ -7,9 +7,8 @@ export function authenticated(req: Http2ServerRequest, res: Http2ServerResponse) const token = getToken(req) if (!token) return unauthorized(req, res) isAuthenticated(token) - .then((response) => { - res.writeHead(response.status).end() - void response.blob() + .then((status) => { + res.writeHead(status).end() }) .catch(catchInternalServerError(res)) } diff --git a/backend/src/lib/token.ts b/backend/src/lib/token.ts index 9b339f2a7a1..263cc0d923f 100644 --- a/backend/src/lib/token.ts +++ b/backend/src/lib/token.ts @@ -28,10 +28,15 @@ export function getToken(req: Http2ServerRequest): string | undefined { return token } -export async function isAuthenticated(token: string) { - return fetchRetry(process.env.CLUSTER_API_URL + '/apis', { +// GET /api returns the core API group (~200 bytes) — unlike /apis which grows +// with every installed CRD. The response body is drained so the socket returns +// to the keepAlive pool immediately and native memory does not accumulate. +export async function isAuthenticated(token: string): Promise { + const response = await fetchRetry(process.env.CLUSTER_API_URL + '/api', { headers: { [HTTP2_HEADER_AUTHORIZATION]: `Bearer ${token}` }, }) + response.body?.on('error', () => undefined).resume() + return response.status } export const isHttp2ServerResponse = ( @@ -51,22 +56,19 @@ export async function getAuthenticatedToken( const token = getToken(req) if (token) { - const authResponse = await isAuthenticated(token) + const status = await isAuthenticated(token) /* istanbul ignore if */ - if (authResponse.status === constants.HTTP_STATUS_OK) { + if (status === constants.HTTP_STATUS_OK) { if (process.env.NODE_ENV === 'development') { const localStorage = new LocalStorage(LOCAL_STORAGE) localStorage.setItem(ADMIN_TOKEN, token) } return token + } + if (isHttp2ServerResponse(resOrSocket)) { + resOrSocket.writeHead(status).end() } else { - if (isHttp2ServerResponse(resOrSocket)) { - resOrSocket.writeHead(authResponse.status).end() - } else { - resOrSocket.destroy() - } - - void authResponse.blob() + resOrSocket.destroy() } } else if (isHttp2ServerResponse(resOrSocket)) { unauthorized(req, resOrSocket) diff --git a/backend/test/routes/aggregator.test.ts b/backend/test/routes/aggregator.test.ts index 909584bb57c..7b1ed38c07e 100644 --- a/backend/test/routes/aggregator.test.ts +++ b/backend/test/routes/aggregator.test.ts @@ -51,7 +51,7 @@ describe(`aggregator Route`, function () { }) it(`should page Unfiltered Applications`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) // initialize events - cache sequentially to ensure deterministic order for (const resource of resources) { @@ -98,7 +98,7 @@ describe(`aggregator Route`, function () { expect(await parseResponseJsonBody(res)).toEqual(responseNoFilter) }) it(`should page Filtered Applications`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) // initialize events - cache sequentially to ensure deterministic order for (const resource of resources) { @@ -129,7 +129,7 @@ describe(`aggregator Route`, function () { expect(await parseResponseJsonBody(res)).toEqual(responseFiltered) }) it(`should return application counts`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) // initialize events - cache sequentially to ensure deterministic order for (const resource of resources) { @@ -153,7 +153,7 @@ describe(`aggregator Route`, function () { expect(await parseResponseJsonBody(res)).toEqual(responseCount) }) it(`should return appset data`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) // initialize events - cache sequentially to ensure deterministic order for (const resource of resources) { diff --git a/backend/test/routes/ansibletower.test.ts b/backend/test/routes/ansibletower.test.ts index c76345cd2b1..daa7409adab 100644 --- a/backend/test/routes/ansibletower.test.ts +++ b/backend/test/routes/ansibletower.test.ts @@ -24,7 +24,7 @@ function nockCredentialSecret(host: string) { describe(`ansibletower Route`, function () { it(`should list Ansible Automation controller Jobs`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nockCredentialSecret(TOWER_HOST) nock(TOWER_HOST).get(ansiblePaths[0]).reply(200, response) const res = await request('POST', '/ansibletower', { @@ -37,7 +37,7 @@ describe(`ansibletower Route`, function () { }) it(`should reject body-supplied tower hostname`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) const res = await request('POST', '/ansibletower', { towerHost: TOWER_HOST + ansiblePaths[0], token: '12345', @@ -46,7 +46,7 @@ describe(`ansibletower Route`, function () { }) it(`should preserve the query string for paginated requests`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nockCredentialSecret(TOWER_HOST) nock(TOWER_HOST).get(ansiblePaths[0]).query({ page: '2', page_size: '20' }).reply(200, response) const res = await request('POST', '/ansibletower', { @@ -59,7 +59,7 @@ describe(`ansibletower Route`, function () { }) it(`should reject an external absolute URL in ansiblePath`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nockCredentialSecret(TOWER_HOST) const res = await request('POST', '/ansibletower', { secretNamespace: SECRET_NS, @@ -70,7 +70,7 @@ describe(`ansibletower Route`, function () { }) it(`should reject a network-path reference in ansiblePath`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nockCredentialSecret(TOWER_HOST) const res = await request('POST', '/ansibletower', { secretNamespace: SECRET_NS, @@ -81,7 +81,7 @@ describe(`ansibletower Route`, function () { }) it(`should fail closed when caller cannot read the credential secret`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .get(`/api/v1/namespaces/${SECRET_NS}/secrets/${SECRET_NAME}`) .reply(403, { kind: 'Status', apiVersion: 'v1', status: 'Failure', reason: 'Forbidden', code: 403 }) @@ -94,7 +94,7 @@ describe(`ansibletower Route`, function () { }) it(`when bad things happen to Ansible Automation controller Jobs 1`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nockCredentialSecret(TOWER_HOST) nock(TOWER_HOST).get(ansiblePaths[0]).reply(200, response) const res = await request('POST', '/ansibletower', { @@ -107,7 +107,7 @@ describe(`ansibletower Route`, function () { }) it(`when bad things happen to Ansible Automation controller Jobs 2`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nockCredentialSecret(TOWER_HOST) nock(TOWER_HOST).get(ansiblePaths[0]).reply(200, response) const res = await request('POST', '/ansibletower', { @@ -119,8 +119,9 @@ describe(`ansibletower Route`, function () { }) it(`when bad things happen to Ansible Automation controller Jobs 3`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(400) + nock(process.env.CLUSTER_API_URL).get('/api').reply(401) const res = await request('POST', '/ansibletower') + expect(res.statusCode).toEqual(401) expect(JSON.stringify(await parsePipedJsonBody(res))).toEqual(JSON.stringify({})) }) }) diff --git a/backend/test/routes/apiPath.test.ts b/backend/test/routes/apiPath.test.ts index 75732fcfc28..2d24f1f7d36 100644 --- a/backend/test/routes/apiPath.test.ts +++ b/backend/test/routes/apiPath.test.ts @@ -15,7 +15,7 @@ describe(`apiPath Route`, function () { nock(process.env.CLUSTER_API_URL).get(paths[0]).reply(200, response) - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200, { + nock(process.env.CLUSTER_API_URL).get('/api').reply(200, { status: 200, paths: response, }) diff --git a/backend/test/routes/hub.test.ts b/backend/test/routes/hub.test.ts index d2d47b3c4fc..5319fd2cc65 100644 --- a/backend/test/routes/hub.test.ts +++ b/backend/test/routes/hub.test.ts @@ -5,9 +5,7 @@ import { request } from '../mock-request' describe('global hub', function () { it('should return the boolean', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200, { - status: 200, - }) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .get('/apis/apiextensions.k8s.io/v1/customresourcedefinitions') // .reply(200, { isGlobalHub: true }) .reply(200, { diff --git a/backend/test/routes/hypershift-status.test.ts b/backend/test/routes/hypershift-status.test.ts index 24aa6478438..10467df915f 100644 --- a/backend/test/routes/hypershift-status.test.ts +++ b/backend/test/routes/hypershift-status.test.ts @@ -4,7 +4,7 @@ import { parseResponseJsonBody } from '../../src/lib/body-parser' import nock from 'nock' describe('hypershift-status Route', function () { - const mockAuth = () => nock(process.env.CLUSTER_API_URL).get('/apis').reply(200, { status: 200 }) + const mockAuth = () => nock(process.env.CLUSTER_API_URL).get('/api').reply(200, { status: 200 }) const mockMCE = (hypershiftEnabled = true, localHostingEnabled = true) => nock(process.env.CLUSTER_API_URL) diff --git a/backend/test/routes/metricsProxy.test.ts b/backend/test/routes/metricsProxy.test.ts index c6a55a6d0fc..6539faee920 100644 --- a/backend/test/routes/metricsProxy.test.ts +++ b/backend/test/routes/metricsProxy.test.ts @@ -4,14 +4,14 @@ import { request } from '../mock-request' describe('metrics proxy route', function () { it('Successfully calls prometheus endpoint', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200, { + nock(process.env.CLUSTER_API_URL).get('/api').reply(200, { status: 200, }) const res = await request('GET', '/prometheus/query') expect(res.statusCode).toEqual(200) }) it(`Successfully calls observability endpoint`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200, { + nock(process.env.CLUSTER_API_URL).get('/api').reply(200, { status: 200, }) const res = await request('GET', '/observability/query') diff --git a/backend/test/routes/operatorCheck.test.ts b/backend/test/routes/operatorCheck.test.ts index 2af878d2412..c94327efee4 100644 --- a/backend/test/routes/operatorCheck.test.ts +++ b/backend/test/routes/operatorCheck.test.ts @@ -23,7 +23,7 @@ const subscriptionOperators = { describe(`operatorCheck Route`, function () { it(`returns valid response with version for installed operator`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200, { + nock(process.env.CLUSTER_API_URL).get('/api').reply(200, { status: 200, }) nock(process.env.CLUSTER_API_URL) @@ -38,7 +38,7 @@ describe(`operatorCheck Route`, function () { }) }) it(`returns valid response for not-installed operator`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200, { + nock(process.env.CLUSTER_API_URL).get('/api').reply(200, { status: 200, }) nock(process.env.CLUSTER_API_URL) @@ -52,7 +52,7 @@ describe(`operatorCheck Route`, function () { }) }) it(`returns bad request for arbitrary operator`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200, { + nock(process.env.CLUSTER_API_URL).get('/api').reply(200, { status: 200, }) nock(process.env.CLUSTER_API_URL) diff --git a/backend/test/routes/search.test.ts b/backend/test/routes/search.test.ts index 0a50d6c469b..f4fc879f29f 100644 --- a/backend/test/routes/search.test.ts +++ b/backend/test/routes/search.test.ts @@ -4,7 +4,7 @@ import nock from 'nock' describe(`search Route`, function () { it(`uses search-api in the namespace of the MultiClusterHub`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200, { + nock(process.env.CLUSTER_API_URL).get('/api').reply(200, { status: 200, }) nock(process.env.CLUSTER_API_URL) @@ -27,7 +27,7 @@ describe(`search Route`, function () { //expect(res.statusCode).toEqual(200) }) it(`uses search-api in namespace of pod if no MultiClusterHub`, async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200, { + nock(process.env.CLUSTER_API_URL).get('/api').reply(200, { status: 200, }) nock(process.env.CLUSTER_API_URL).get('/apis/operator.open-cluster-management.io/v1/multiclusterhubs').reply(200, { diff --git a/backend/test/routes/upgrade-risks-prediction.test.ts b/backend/test/routes/upgrade-risks-prediction.test.ts index 6792470b2ad..c1966bfbdb1 100644 --- a/backend/test/routes/upgrade-risks-prediction.test.ts +++ b/backend/test/routes/upgrade-risks-prediction.test.ts @@ -5,7 +5,7 @@ import { request } from '../mock-request' describe('Upgrade risks prediction Route', function () { it('should return the upgrade risks', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .get('/api/v1/namespaces/openshift-config/secrets') .reply(200, { diff --git a/backend/test/routes/username.test.ts b/backend/test/routes/username.test.ts index dbb12649116..2f9c24eec5f 100644 --- a/backend/test/routes/username.test.ts +++ b/backend/test/routes/username.test.ts @@ -5,7 +5,7 @@ import nock from 'nock' describe('username Route', function () { it('should return the username', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200, { + nock(process.env.CLUSTER_API_URL).get('/api').reply(200, { status: 200, }) nock(process.env.CLUSTER_API_URL) @@ -23,7 +23,7 @@ describe('username Route', function () { expect(body).toEqual({ username: 'testuser' }) }) it('should return empty string if no username provided', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200, { + nock(process.env.CLUSTER_API_URL).get('/api').reply(200, { status: 200, }) nock(process.env.CLUSTER_API_URL) @@ -39,6 +39,7 @@ describe('username Route', function () { expect(body).toEqual({ username: '' }) }) it('should handle errors', async function () { + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL).post('/apis/authentication.k8s.io/v1/tokenreviews').replyWithError('failed') const res = await request('GET', '/username') expect(res.statusCode).toEqual(500) diff --git a/backend/test/routes/userpreference.test.ts b/backend/test/routes/userpreference.test.ts index b6b2b0456ea..8f53479cf89 100644 --- a/backend/test/routes/userpreference.test.ts +++ b/backend/test/routes/userpreference.test.ts @@ -5,7 +5,7 @@ import { request } from '../mock-request' describe('userpreference Route', function () { it('should return the userpreference', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .post('/apis/authentication.k8s.io/v1/tokenreviews') .reply(200, { @@ -53,7 +53,7 @@ describe('userpreference Route', function () { savedSearches: [{ description: '', id: '1678205878189', name: 'testing', searchText: 'kind:Pod' }], }, } - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .post('/apis/authentication.k8s.io/v1/tokenreviews') .reply(200, { diff --git a/backend/test/routes/virtualMachineProxy.test.ts b/backend/test/routes/virtualMachineProxy.test.ts index c7786318c80..98f96d3b254 100644 --- a/backend/test/routes/virtualMachineProxy.test.ts +++ b/backend/test/routes/virtualMachineProxy.test.ts @@ -10,7 +10,7 @@ describe('Virtual Machine actions', function () { }) it('should successfully call start action', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .post( '/apis/authorization.k8s.io/v1/selfsubjectaccessreviews', @@ -57,7 +57,7 @@ describe('Virtual Machine actions', function () { }) it('should successfully call pause action', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .post( '/apis/authorization.k8s.io/v1/selfsubjectaccessreviews', @@ -104,7 +104,7 @@ describe('Virtual Machine actions', function () { }) it('should successfully take snapshot action', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .post( '/apis/authorization.k8s.io/v1/selfsubjectaccessreviews', @@ -175,7 +175,7 @@ describe('Virtual Machine actions', function () { }) it('should error on start action request', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .post( '/apis/authorization.k8s.io/v1/selfsubjectaccessreviews', @@ -220,7 +220,7 @@ describe('Virtual Machine actions', function () { }) it('should fail with invalid route and secret', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .post( '/apis/authorization.k8s.io/v1/selfsubjectaccessreviews', @@ -252,7 +252,7 @@ describe('Virtual Machine actions', function () { }) it('should successfully restore a snapshot', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .post( '/apis/authorization.k8s.io/v1/selfsubjectaccessreviews', @@ -323,7 +323,7 @@ describe('Virtual Machine actions', function () { }) it('should successfully get VM', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .post( '/apis/authorization.k8s.io/v1/selfsubjectaccessreviews', @@ -366,7 +366,7 @@ describe('Virtual Machine actions', function () { }) it('should successfully get VM snapshot', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .post( '/apis/authorization.k8s.io/v1/selfsubjectaccessreviews', @@ -409,7 +409,7 @@ describe('Virtual Machine actions', function () { }) it('should successfully delete VM', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .post( '/apis/authorization.k8s.io/v1/selfsubjectaccessreviews', @@ -457,7 +457,7 @@ describe('Virtual Machine actions', function () { }) it('should successfully delete VM Snapshot', async function () { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) nock(process.env.CLUSTER_API_URL) .post( '/apis/authorization.k8s.io/v1/selfsubjectaccessreviews', @@ -507,7 +507,7 @@ describe('Virtual Machine actions', function () { describe('vmResourceUsageProxy', () => { beforeEach(() => { - nock(process.env.CLUSTER_API_URL).get('/apis').reply(200) + nock(process.env.CLUSTER_API_URL).get('/api').reply(200) }) afterEach(() => { nock.cleanAll()