diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md index 0a49ee6e63d6c..1134994faa9bd 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md @@ -30,6 +30,7 @@ export declare class KibanaRequestboolean | Whether or not the request is a "system request" rather than an application-level request. Can be set on the client using the HttpFetchOptions#asSystemRequest option. | | [params](./kibana-plugin-core-server.kibanarequest.params.md) | | Params | | | [query](./kibana-plugin-core-server.kibanarequest.query.md) | | Query | | +| [rewrittenUrl](./kibana-plugin-core-server.kibanarequest.rewrittenurl.md) | | Url | URL rewritten in onPreRouting request interceptor. | | [route](./kibana-plugin-core-server.kibanarequest.route.md) | | RecursiveReadonly<KibanaRequestRoute<Method>> | matched route details | | [socket](./kibana-plugin-core-server.kibanarequest.socket.md) | | IKibanaSocket | [IKibanaSocket](./kibana-plugin-core-server.ikibanasocket.md) | | [url](./kibana-plugin-core-server.kibanarequest.url.md) | | Url | a WHATWG URL standard object. | diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.rewrittenurl.md b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.rewrittenurl.md new file mode 100644 index 0000000000000..10628bafaf1d4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.rewrittenurl.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaRequest](./kibana-plugin-core-server.kibanarequest.md) > [rewrittenUrl](./kibana-plugin-core-server.kibanarequest.rewrittenurl.md) + +## KibanaRequest.rewrittenUrl property + +URL rewritten in onPreRouting request interceptor. + +Signature: + +```typescript +readonly rewrittenUrl?: Url; +``` diff --git a/src/core/server/http/integration_tests/lifecycle.test.ts b/src/core/server/http/integration_tests/lifecycle.test.ts index 59090d101acbc..01817b29de8ac 100644 --- a/src/core/server/http/integration_tests/lifecycle.test.ts +++ b/src/core/server/http/integration_tests/lifecycle.test.ts @@ -117,6 +117,62 @@ describe('OnPreRouting', () => { expect(urlAfterForwarding).toBe('/redirectUrl'); }); + it('provides original request url', async () => { + const { registerOnPreRouting, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + router.get({ path: '/login', validate: false }, (context, req, res) => { + return res.ok({ body: { rewrittenUrl: req.rewrittenUrl?.path } }); + }); + + registerOnPreRouting((req, res, t) => t.rewriteUrl('/login')); + + await server.start(); + + await supertest(innerServer.listener) + .get('/initial?name=foo') + .expect(200, { rewrittenUrl: '/initial?name=foo' }); + }); + + it('provides original request url if rewritten several times', async () => { + const { registerOnPreRouting, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + router.get({ path: '/reroute-2', validate: false }, (context, req, res) => { + return res.ok({ body: { rewrittenUrl: req.rewrittenUrl?.path } }); + }); + + registerOnPreRouting((req, res, t) => t.rewriteUrl('/reroute-1')); + registerOnPreRouting((req, res, t) => t.rewriteUrl('/reroute-2')); + + await server.start(); + + await supertest(innerServer.listener) + .get('/initial?name=foo') + .expect(200, { rewrittenUrl: '/initial?name=foo' }); + }); + + it('does not provide request url if interceptor does not rewrite url', async () => { + const { registerOnPreRouting, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + router.get({ path: '/login', validate: false }, (context, req, res) => { + return res.ok({ body: { rewrittenUrl: req.rewrittenUrl?.path } }); + }); + + registerOnPreRouting((req, res, t) => t.next()); + + await server.start(); + + await supertest(innerServer.listener).get('/login').expect(200, {}); + }); + it('supports redirection from the interceptor', async () => { const { registerOnPreRouting, server: innerServer, createRouter } = await server.setup( setupDeps diff --git a/src/core/server/http/lifecycle/on_pre_routing.ts b/src/core/server/http/lifecycle/on_pre_routing.ts index e62eb54f2398f..92ae1f0b7bbdf 100644 --- a/src/core/server/http/lifecycle/on_pre_routing.ts +++ b/src/core/server/http/lifecycle/on_pre_routing.ts @@ -25,6 +25,7 @@ import { KibanaResponse, lifecycleResponseFactory, LifecycleResponseFactory, + KibanaRequestState, } from '../router'; enum ResultType { @@ -108,6 +109,9 @@ export function adoptToHapiOnRequest(fn: OnPreRoutingHandler, log: Logger) { } if (preRoutingResult.isRewriteUrl(result)) { + const appState = request.app as KibanaRequestState; + appState.rewrittenUrl = appState.rewrittenUrl ?? request.url; + const { url } = result; request.setUrl(url); // We should update raw request as well since it can be proxied to the old platform diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index 903eb75022df3..2d0e8d6c1a6ad 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -45,6 +45,7 @@ export interface KibanaRouteOptions extends RouteOptionsApp { export interface KibanaRequestState extends ApplicationState { requestId: string; requestUuid: string; + rewrittenUrl?: Url; } /** @@ -186,6 +187,11 @@ export class KibanaRequest< isAuthenticated: boolean; }; + /** + * URL rewritten in onPreRouting request interceptor. + */ + public readonly rewrittenUrl?: Url; + /** @internal */ protected readonly [requestSymbol]: Request; @@ -199,10 +205,12 @@ export class KibanaRequest< private readonly withoutSecretHeaders: boolean ) { // The `requestId` and `requestUuid` properties will not be populated for requests that are 'faked' by internal systems that leverage - // KibanaRequest in conjunction with scoped Elaticcsearch and SavedObjectsClient in order to pass credentials. + // KibanaRequest in conjunction with scoped Elasticsearch and SavedObjectsClient in order to pass credentials. // In these cases, the ids default to a newly generated UUID. - this.id = (request.app as KibanaRequestState | undefined)?.requestId ?? uuid.v4(); - this.uuid = (request.app as KibanaRequestState | undefined)?.requestUuid ?? uuid.v4(); + const appState = request.app as KibanaRequestState | undefined; + this.id = appState?.requestId ?? uuid.v4(); + this.uuid = appState?.requestUuid ?? uuid.v4(); + this.rewrittenUrl = appState?.rewrittenUrl; this.url = request.url; this.headers = deepFreeze({ ...request.headers }); diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 20bd102e6f507..1b54cb6cf4c24 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1043,6 +1043,7 @@ export class KibanaRequest>; // (undocumented) readonly socket: IKibanaSocket;