Migrate session storage short url handling (#55021) (#55393)

This commit is contained in:
Joe Reuter 2020-01-22 09:16:47 +01:00 committed by GitHub
parent 14cd8e21f8
commit f84ac08225
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 152 additions and 461 deletions

View file

@ -1,32 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export default function(kibana) {
return new kibana.Plugin({
uiExports: {
app: {
require: ['kibana'],
title: 'Redirecting',
id: 'stateSessionStorageRedirect',
main: 'plugins/state_session_storage_redirect',
hidden: true,
},
},
});
}

View file

@ -1,5 +0,0 @@
{
"name": "state_session_storage_redirect",
"version": "kibana",
"description": "When using the state:storeInSessionStorage setting with the short-urls, we need some way to get the full URL's hashed states into sessionStorage, this app will grab the URL from the injected state and and put the URL hashed states into sessionStorage before redirecting the user."
}

View file

@ -36,7 +36,6 @@ import * as Plugins from './plugins';
import { indexPatternsMixin } from './index_patterns';
import { savedObjectsMixin } from './saved_objects/saved_objects_mixin';
import { capabilitiesMixin } from './capabilities';
import { urlShorteningMixin } from './url_shortening';
import { serverExtensionsMixin } from './server_extensions';
import { uiMixin } from '../ui';
import { sassMixin } from './sass';
@ -123,9 +122,6 @@ export default class KbnServer {
// setup capabilities routes
capabilitiesMixin,
// setup routes for short urls
urlShorteningMixin,
// ensure that all bundles are built, or that the
// watch bundle server is running
optimizeMixin,

View file

@ -1,20 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { urlShorteningMixin } from './url_shortening_mixin';

View file

@ -1,41 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { parse } from 'url';
import { trim } from 'lodash';
import Boom from 'boom';
export function shortUrlAssertValid(url) {
const { protocol, hostname, pathname } = parse(url);
if (protocol) {
throw Boom.notAcceptable(`Short url targets cannot have a protocol, found "${protocol}"`);
}
if (hostname) {
throw Boom.notAcceptable(`Short url targets cannot have a hostname, found "${hostname}"`);
}
const pathnameParts = trim(pathname, '/').split('/');
if (pathnameParts.length !== 2) {
throw Boom.notAcceptable(
`Short url target path must be in the format "/app/{{appId}}", found "${pathname}"`
);
}
}

View file

@ -1,63 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { shortUrlAssertValid } from './short_url_assert_valid';
describe('shortUrlAssertValid()', () => {
const invalid = [
['protocol', 'http://localhost:5601/app/kibana'],
['protocol', 'https://localhost:5601/app/kibana'],
['protocol', 'mailto:foo@bar.net'],
['protocol', 'javascript:alert("hi")'], // eslint-disable-line no-script-url
['hostname', 'localhost/app/kibana'],
['hostname and port', 'local.host:5601/app/kibana'],
['hostname and auth', 'user:pass@localhost.net/app/kibana'],
['path traversal', '/app/../../not-kibana'],
['deep path', '/app/kibana/foo'],
['deep path', '/app/kibana/foo/bar'],
['base path', '/base/app/kibana'],
];
invalid.forEach(([desc, url]) => {
it(`fails when url has ${desc}`, () => {
try {
shortUrlAssertValid(url);
throw new Error(`expected assertion to throw`);
} catch (err) {
if (!err || !err.isBoom) {
throw err;
}
}
});
});
const valid = [
'/app/kibana',
'/app/monitoring#angular/route',
'/app/text#document-id',
'/app/some?with=query',
'/app/some?with=query#and-a-hash',
];
valid.forEach(url => {
it(`allows ${url}`, () => {
shortUrlAssertValid(url);
});
});
});

View file

@ -1,26 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import Boom from 'boom';
export function handleShortUrlError(error) {
return Boom.boomify(error, {
statusCode: error.statusCode || error.status || 500,
});
}

View file

@ -1,67 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import { handleShortUrlError } from './short_url_error';
function createErrorWithStatus(status) {
const error = new Error();
error.status = status;
return error;
}
function createErrorWithStatusCode(statusCode) {
const error = new Error();
error.statusCode = statusCode;
return error;
}
describe('handleShortUrlError()', () => {
const caughtErrorsWithStatus = [
createErrorWithStatus(401),
createErrorWithStatus(403),
createErrorWithStatus(404),
];
const caughtErrorsWithStatusCode = [
createErrorWithStatusCode(401),
createErrorWithStatusCode(403),
createErrorWithStatusCode(404),
];
const uncaughtErrors = [new Error(), createErrorWithStatus(500), createErrorWithStatusCode(500)];
caughtErrorsWithStatus.forEach(err => {
it(`should handle errors with status of ${err.status}`, function() {
expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(err.status);
});
});
caughtErrorsWithStatusCode.forEach(err => {
it(`should handle errors with statusCode of ${err.statusCode}`, function() {
expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(err.statusCode);
});
});
uncaughtErrors.forEach(err => {
it(`should not handle unknown errors`, function() {
expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(500);
});
});
});

View file

@ -1,43 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { get } from 'lodash';
export function shortUrlLookupProvider(server) {
async function updateMetadata(doc, req) {
try {
await req.getSavedObjectsClient().update('url', doc.id, {
accessDate: new Date(),
accessCount: get(doc, 'attributes.accessCount', 0) + 1,
});
} catch (err) {
server.log('Warning: Error updating url metadata', err);
//swallow errors. It isn't critical if there is no update.
}
}
return {
async getUrl(id, req) {
const doc = await req.getSavedObjectsClient().get('url', id);
updateMetadata(doc, req);
return doc.attributes.url;
},
};
}

View file

@ -1,84 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import sinon from 'sinon';
import { shortUrlLookupProvider } from './short_url_lookup';
import { SavedObjectsClient } from '../../../../../core/server';
describe('shortUrlLookupProvider', () => {
const ID = 'bf00ad16941fc51420f91a93428b27a0';
const TYPE = 'url';
const URL = 'http://elastic.co';
const server = { log: sinon.stub() };
const sandbox = sinon.createSandbox();
let savedObjectsClient;
let req;
let shortUrl;
beforeEach(() => {
savedObjectsClient = {
get: sandbox.stub(),
create: sandbox.stub().returns(Promise.resolve({ id: ID })),
update: sandbox.stub(),
errors: SavedObjectsClient.errors,
};
req = { getSavedObjectsClient: () => savedObjectsClient };
shortUrl = shortUrlLookupProvider(server);
});
afterEach(() => {
sandbox.restore();
});
describe('getUrl', () => {
beforeEach(() => {
const attributes = { accessCount: 2, url: URL };
savedObjectsClient.get.returns({ id: ID, attributes });
});
it('provides the ID to savedObjectsClient', async () => {
await shortUrl.getUrl(ID, req);
sinon.assert.calledOnce(savedObjectsClient.get);
const [type, id] = savedObjectsClient.get.getCall(0).args;
expect(type).toEqual(TYPE);
expect(id).toEqual(ID);
});
it('returns the url', async () => {
const response = await shortUrl.getUrl(ID, req);
expect(response).toEqual(URL);
});
it('increments accessCount', async () => {
await shortUrl.getUrl(ID, req);
sinon.assert.calledOnce(savedObjectsClient.update);
const [type, id, attributes] = savedObjectsClient.update.getCall(0).args;
expect(type).toEqual(TYPE);
expect(id).toEqual(ID);
expect(Object.keys(attributes).sort()).toEqual(['accessCount', 'accessDate']);
expect(attributes.accessCount).toEqual(3);
});
});
});

View file

@ -1,23 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { createRoutes } from './routes/create_routes';
export function urlShorteningMixin(kbnServer, server) {
createRoutes(server);
}

View file

@ -23,7 +23,7 @@ import ReactDOM from 'react-dom';
import React from 'react';
import { I18nProvider } from '@kbn/i18n/react';
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public';
import { NewsfeedPluginInjectedConfig } from '../types';
import { FetchResult, NewsfeedPluginInjectedConfig } from '../types';
import { NewsfeedNavButton, NewsfeedApiFetchResult } from './components/newsfeed_header_nav_button';
import { getApi } from './lib/api';
@ -54,10 +54,14 @@ export class NewsfeedPublicPlugin implements Plugin<Setup, Start> {
private fetchNewsfeed(core: CoreStart) {
const { http, injectedMetadata } = core;
const config = injectedMetadata.getInjectedVar(
'newsfeed'
) as NewsfeedPluginInjectedConfig['newsfeed'];
const config = injectedMetadata.getInjectedVar('newsfeed') as
| NewsfeedPluginInjectedConfig['newsfeed']
| undefined;
if (!config) {
// running in new platform, injected metadata not available
return new Rx.Observable<void | FetchResult | null>();
}
return getApi(http, config, this.kibanaVersion).pipe(
takeUntil(this.stop$), // stop the interval when stop method is called
catchError(() => Rx.of(null)) // do not throw error

View file

@ -17,11 +17,15 @@
* under the License.
*/
import { shortUrlLookupProvider } from './lib/short_url_lookup';
import { createGotoRoute } from './goto';
export const GOTO_PREFIX = '/goto';
export function createRoutes(server) {
const shortUrlLookup = shortUrlLookupProvider(server);
export const getUrlIdFromGotoRoute = (path: string) =>
path.match(new RegExp(`${GOTO_PREFIX}/(.*)$`))?.[1];
server.route(createGotoRoute({ server, shortUrlLookup }));
}
export const getGotoPath = (urlId: string) => `${GOTO_PREFIX}/${urlId}`;
export const GETTER_PREFIX = '/api/short_url';
export const getUrlPath = (urlId: string) => `${GETTER_PREFIX}/${urlId}`;
export const CREATE_PATH = '/api/shorten_url';

View file

@ -19,6 +19,7 @@
import url from 'url';
import { HttpStart } from 'kibana/public';
import { CREATE_PATH, getGotoPath } from '../../common/short_url_routes';
export async function shortenUrl(
absoluteUrl: string,
@ -34,10 +35,10 @@ export async function shortenUrl(
const body = JSON.stringify({ url: relativeUrl });
const resp = await post('/api/shorten_url', { body });
const resp = await post(CREATE_PATH, { body });
return url.format({
protocol: parsedUrl.protocol,
host: parsedUrl.host,
pathname: `${basePath}/goto/${resp.urlId}`,
pathname: `${basePath}${getGotoPath(resp.urlId)}`,
});
}

View file

@ -20,6 +20,7 @@
import { registryMock, managerMock } from './plugin.test.mocks';
import { SharePlugin } from './plugin';
import { CoreStart } from 'kibana/public';
import { coreMock } from '../../../core/public/mocks';
describe('SharePlugin', () => {
beforeEach(() => {
@ -30,16 +31,28 @@ describe('SharePlugin', () => {
describe('setup', () => {
test('wires up and returns registry', async () => {
const setup = await new SharePlugin().setup();
const coreSetup = coreMock.createSetup();
const setup = await new SharePlugin().setup(coreSetup);
expect(registryMock.setup).toHaveBeenCalledWith();
expect(setup.register).toBeDefined();
});
test('registers redirect app', async () => {
const coreSetup = coreMock.createSetup();
await new SharePlugin().setup(coreSetup);
expect(coreSetup.application.register).toHaveBeenCalledWith(
expect.objectContaining({
id: 'short_url_redirect',
})
);
});
});
describe('start', () => {
test('wires up and returns show function, but not registry', async () => {
const coreSetup = coreMock.createSetup();
const service = new SharePlugin();
await service.setup();
await service.setup(coreSetup);
const start = await service.start({} as CoreStart);
expect(registryMock.start).toHaveBeenCalled();
expect(managerMock.start).toHaveBeenCalledWith(

View file

@ -17,15 +17,17 @@
* under the License.
*/
import { CoreStart, Plugin } from 'src/core/public';
import { CoreSetup, CoreStart, Plugin } from 'src/core/public';
import { ShareMenuManager, ShareMenuManagerStart } from './services';
import { ShareMenuRegistry, ShareMenuRegistrySetup } from './services';
import { createShortUrlRedirectApp } from './services/short_url_redirect_app';
export class SharePlugin implements Plugin<SharePluginSetup, SharePluginStart> {
private readonly shareMenuRegistry = new ShareMenuRegistry();
private readonly shareContextMenu = new ShareMenuManager();
public async setup() {
public async setup(core: CoreSetup) {
core.application.register(createShortUrlRedirectApp(core, window.location));
return {
...this.shareMenuRegistry.setup(),
};

View file

@ -0,0 +1,46 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { createShortUrlRedirectApp } from './short_url_redirect_app';
import { coreMock } from '../../../../core/public/mocks';
import { hashUrl } from '../../../kibana_utils/public';
jest.mock('../../../kibana_utils/public', () => ({ hashUrl: jest.fn(x => `${x}/hashed`) }));
describe('short_url_redirect_app', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should fetch url and redirect to hashed version', async () => {
const coreSetup = coreMock.createSetup({ basePath: 'base' });
coreSetup.http.get.mockResolvedValueOnce({ url: '/app/abc' });
const locationMock = { pathname: '/base/goto/12345', href: '' } as Location;
const { mount } = createShortUrlRedirectApp(coreSetup, locationMock);
await mount();
// check for fetching the complete URL
expect(coreSetup.http.get).toHaveBeenCalledWith('/api/short_url/12345');
// check for hashing the URL returned from the server
expect(hashUrl).toHaveBeenCalledWith('/app/abc');
// check for redirecting to the prepended path
expect(locationMock.href).toEqual('base/app/abc/hashed');
});
});

View file

@ -17,24 +17,29 @@
* under the License.
*/
import chrome from 'ui/chrome';
import { hashUrl } from '../../../../plugins/kibana_utils/public';
import uiRoutes from 'ui/routes';
import { fatalError } from 'ui/notify';
import { CoreSetup } from 'kibana/public';
import { getUrlIdFromGotoRoute, getUrlPath, GOTO_PREFIX } from '../../common/short_url_routes';
import { hashUrl } from '../../../kibana_utils/public';
uiRoutes.enable();
uiRoutes.when('/', {
resolve: {
url: function(AppState, globalState, $window) {
const redirectUrl = chrome.getInjected('redirectUrl');
try {
const hashedUrl = hashUrl(redirectUrl);
const url = chrome.addBasePath(hashedUrl);
export const createShortUrlRedirectApp = (core: CoreSetup, location: Location) => ({
id: 'short_url_redirect',
appRoute: GOTO_PREFIX,
chromeless: true,
title: 'Short URL Redirect',
async mount() {
const urlId = getUrlIdFromGotoRoute(location.pathname);
$window.location = url;
} catch (e) {
fatalError(e);
}
},
if (!urlId) {
throw new Error('Url id not present in path');
}
const response = await core.http.get<{ url: string }>(getUrlPath(urlId));
const redirectUrl = response.url;
const hashedUrl = hashUrl(redirectUrl);
const url = core.http.basePath.prepend(hashedUrl);
location.href = url;
return () => {};
},
});

View file

@ -22,11 +22,13 @@ import { CoreSetup, Logger } from 'kibana/server';
import { shortUrlLookupProvider } from './lib/short_url_lookup';
import { createGotoRoute } from './goto';
import { createShortenUrlRoute } from './shorten_url';
import { createGetterRoute } from './get';
export function createRoutes({ http }: CoreSetup, logger: Logger) {
const shortUrlLookup = shortUrlLookupProvider({ logger });
const router = http.createRouter();
createGotoRoute({ router, shortUrlLookup, http });
createGetterRoute({ router, shortUrlLookup, http });
createShortenUrlRoute({ router, shortUrlLookup });
}

View file

@ -17,23 +17,40 @@
* under the License.
*/
import { handleShortUrlError } from './lib/short_url_error';
import { shortUrlAssertValid } from './lib/short_url_assert_valid';
import { CoreSetup, IRouter } from 'kibana/server';
import { schema } from '@kbn/config-schema';
export const createGotoRoute = ({ server, shortUrlLookup }) => ({
method: 'GET',
path: '/goto_LP/{urlId}',
handler: async function(request, h) {
try {
const url = await shortUrlLookup.getUrl(request.params.urlId, request);
import { shortUrlAssertValid } from './lib/short_url_assert_valid';
import { ShortUrlLookupService } from './lib/short_url_lookup';
import { getUrlPath } from '../../common/short_url_routes';
export const createGetterRoute = ({
router,
shortUrlLookup,
http,
}: {
router: IRouter;
shortUrlLookup: ShortUrlLookupService;
http: CoreSetup['http'];
}) => {
router.get(
{
path: getUrlPath('{urlId}'),
validate: {
params: schema.object({ urlId: schema.string() }),
},
},
router.handleLegacyErrors(async function(context, request, response) {
const url = await shortUrlLookup.getUrl(request.params.urlId, {
savedObjects: context.core.savedObjects.client,
});
shortUrlAssertValid(url);
const app = server.getHiddenUiAppById('stateSessionStorageRedirect');
return h.renderApp(app, {
redirectUrl: url,
return response.ok({
body: {
url,
},
});
} catch (err) {
throw handleShortUrlError(err);
}
},
});
})
);
};

View file

@ -22,6 +22,7 @@ import { schema } from '@kbn/config-schema';
import { shortUrlAssertValid } from './lib/short_url_assert_valid';
import { ShortUrlLookupService } from './lib/short_url_lookup';
import { getGotoPath } from '../../common/short_url_routes';
export const createGotoRoute = ({
router,
@ -34,7 +35,7 @@ export const createGotoRoute = ({
}) => {
router.get(
{
path: '/goto/{urlId}',
path: getGotoPath('{urlId}'),
validate: {
params: schema.object({ urlId: schema.string() }),
},
@ -54,10 +55,13 @@ export const createGotoRoute = ({
},
});
}
return response.redirected({
const body = await context.core.rendering.render();
return response.ok({
headers: {
location: http.basePath.prepend('/goto_LP/' + request.params.urlId),
'content-security-policy': http.csp.header,
},
body,
});
})
);

View file

@ -22,6 +22,7 @@ import { schema } from '@kbn/config-schema';
import { shortUrlAssertValid } from './lib/short_url_assert_valid';
import { ShortUrlLookupService } from './lib/short_url_lookup';
import { CREATE_PATH } from '../../common/short_url_routes';
export const createShortenUrlRoute = ({
shortUrlLookup,
@ -32,7 +33,7 @@ export const createShortenUrlRoute = ({
}) => {
router.post(
{
path: '/api/shorten_url',
path: CREATE_PATH,
validate: {
body: schema.object({ url: schema.string() }),
},