diff --git a/fluxer_api/src/api/rpc/tests/RpcSessionInitHarnessAccess.test.ts b/fluxer_api/src/api/rpc/tests/RpcSessionInitHarnessAccess.test.ts new file mode 100644 index 000000000..261769180 --- /dev/null +++ b/fluxer_api/src/api/rpc/tests/RpcSessionInitHarnessAccess.test.ts @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes'; +import {afterEach, beforeEach, describe, expect, test} from 'vitest'; +import {createTestAccount} from '../../auth/tests/AuthTestUtils'; +import {Config} from '../../Config'; +import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness'; +import {HTTP_STATUS} from '../../test/TestConstants'; +import {createBuilder} from '../../test/TestRequestBuilder'; + +const HARNESS_TOKEN = 'rpc-session-init-harness-token'; +const TEST_AUTH_HEADER = 'x-test-token'; + +interface RpcSessionResponse { + type: string; + data: { + user: { + id: string; + }; + }; +} + +describe('POST /test/rpc-session-init harness access', () => { + let harness: ApiTestHarness; + let previousTestHarnessToken: string | undefined; + beforeEach(async () => { + harness = await createApiTestHarness(); + previousTestHarnessToken = Config.dev.testHarnessToken; + }); + afterEach(async () => { + Config.dev.testHarnessToken = previousTestHarnessToken; + await harness?.shutdown(); + }); + + test('rejects a session init that omits the harness token', async () => { + const account = await createTestAccount(harness); + Config.dev.testHarnessToken = HARNESS_TOKEN; + await createBuilder(harness, '') + .post('/test/rpc-session-init') + .body({type: 'session', token: account.token, version: 1, ip: '127.0.0.1'}) + .expect(HTTP_STATUS.FORBIDDEN, APIErrorCodes.TEST_HARNESS_FORBIDDEN) + .execute(); + }); + + test('rejects before the rpc request body is parsed or dispatched', async () => { + Config.dev.testHarnessToken = HARNESS_TOKEN; + await createBuilder(harness, '') + .post('/test/rpc-session-init') + .body({type: 'definitely_not_an_rpc_request'}) + .expect(HTTP_STATUS.FORBIDDEN, APIErrorCodes.TEST_HARNESS_FORBIDDEN) + .execute(); + }); + + test('rejects a session init carrying the wrong harness token', async () => { + const account = await createTestAccount(harness); + Config.dev.testHarnessToken = HARNESS_TOKEN; + await createBuilder(harness, '') + .post('/test/rpc-session-init') + .header(TEST_AUTH_HEADER, `${HARNESS_TOKEN}-wrong`) + .body({type: 'session', token: account.token, version: 1, ip: '127.0.0.1'}) + .expect(HTTP_STATUS.FORBIDDEN, APIErrorCodes.TEST_HARNESS_FORBIDDEN) + .execute(); + }); + + test('accepts a session init carrying the harness token', async () => { + const account = await createTestAccount(harness); + Config.dev.testHarnessToken = HARNESS_TOKEN; + const response = await createBuilder(harness, '') + .post('/test/rpc-session-init') + .header(TEST_AUTH_HEADER, HARNESS_TOKEN) + .body({type: 'session', token: account.token, version: 1, ip: '127.0.0.1'}) + .expect(HTTP_STATUS.OK) + .execute(); + expect(response.type).toBe('session'); + expect(response.data.user.id).toBe(account.userId); + }); + + test('accepts a session init when no harness token is configured', async () => { + const account = await createTestAccount(harness); + Config.dev.testHarnessToken = undefined; + const response = await createBuilder(harness, '') + .post('/test/rpc-session-init') + .body({type: 'session', token: account.token, version: 1, ip: '127.0.0.1'}) + .expect(HTTP_STATUS.OK) + .execute(); + expect(response.data.user.id).toBe(account.userId); + }); +}); diff --git a/fluxer_api/src/api/test/TestHarnessController.ts b/fluxer_api/src/api/test/TestHarnessController.ts index e88e5799a..3c97b8dbf 100644 --- a/fluxer_api/src/api/test/TestHarnessController.ts +++ b/fluxer_api/src/api/test/TestHarnessController.ts @@ -2796,6 +2796,7 @@ export function TestHarnessController(app: HonoApp) { return ctx.json({cleared: true, deleted_count: totalDeleted}); }); app.post('/test/rpc-session-init', async (ctx) => { + ensureHarnessAccess(ctx); const request = RpcRequest.parse(await ctx.req.json()); const response = await ctx.get('rpcService').handleRpcRequest({request, requestCache: ctx.get('requestCache')}); return ctx.json(response);