[7.x] introduce pre-/post-auth request hooks for HttpServer (#36690) (#38265)

* introduce pre-/post-auth request hooks for HttpServer (#36690)

* introduce pre-,post-auth stages

* cleanup integration_tests. now contracts available in tests

* auth per route is configurable

* Unify lifecycle results structure

* expose api to store auth state and status via http service

* update tests

* update docs

* use full name, auth should not mutate request

* move basePath functionality under namespace

* regenerate docs

* Revert "move basePath functionality under namespace"

This reverts commit 9599d32801.

* Revert "regenerate docs"

This reverts commit 1799d3b088.

* regenerate docs

* updated yarn.lock no idea why

* extract AuthStateStorage to a separate entity

* get rid of nested ifs

* describe what is the difference between hooks

* re-wording

* fix typings
This commit is contained in:
Mikhail Shustov 2019-06-07 08:09:55 +02:00 committed by GitHub
parent fd125bc2db
commit 3278cd7126
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1041 additions and 379 deletions

View file

@ -8,5 +8,5 @@
<b>Signature:</b>
```typescript
export declare type AuthenticationHandler<T> = (request: Request, sessionStorage: SessionStorage<T>, t: AuthToolkit) => Promise<AuthResult>;
export declare type AuthenticationHandler<T> = (request: Readonly<Request>, sessionStorage: SessionStorage<T>, t: AuthToolkit) => AuthResult | Promise<AuthResult>;
```

View file

@ -9,5 +9,5 @@ Authentication is successful with given credentials, allow request to pass throu
<b>Signature:</b>
```typescript
authenticated: (credentials: any) => AuthResult;
authenticated: (state: object) => AuthResult;
```

View file

@ -16,7 +16,7 @@ export interface AuthToolkit
| Property | Type | Description |
| --- | --- | --- |
| [authenticated](./kibana-plugin-server.authtoolkit.authenticated.md) | <code>(credentials: any) =&gt; AuthResult</code> | Authentication is successful with given credentials, allow request to pass through |
| [authenticated](./kibana-plugin-server.authtoolkit.authenticated.md) | <code>(state: object) =&gt; AuthResult</code> | Authentication is successful with given credentials, allow request to pass through |
| [redirected](./kibana-plugin-server.authtoolkit.redirected.md) | <code>(url: string) =&gt; AuthResult</code> | Authentication requires to interrupt request handling and redirect to a configured url |
| [rejected](./kibana-plugin-server.authtoolkit.rejected.md) | <code>(error: Error, options?: {`<p/>` statusCode?: number;`<p/>` }) =&gt; AuthResult</code> | Authentication is unsuccessful, fail the request with specified error. |

View file

@ -8,8 +8,9 @@
```typescript
http: {
registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];
registerAuth: HttpServiceSetup['registerAuth'];
registerOnRequest: HttpServiceSetup['registerOnRequest'];
registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];
getBasePathFor: HttpServiceSetup['getBasePathFor'];
setBasePathFor: HttpServiceSetup['setBasePathFor'];
createNewServer: HttpServiceSetup['createNewServer'];

View file

@ -9,12 +9,13 @@ Context passed to the plugins `setup` method.
<b>Signature:</b>
```typescript
export interface CoreSetup
export interface CoreSetup
```
## Properties
| Property | Type | Description |
| ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | <code>{`<p/>` adminClient\$: Observable&lt;ClusterClient&gt;;`<p/>` dataClient\$: Observable&lt;ClusterClient&gt;;`<p/>` }</code> | |
| [http](./kibana-plugin-server.coresetup.http.md) | <code>{`<p/>` registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];`<p/>` registerAuth: HttpServiceSetup['registerAuth'];`<p/>` registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];`<p/>` getBasePathFor: HttpServiceSetup['getBasePathFor'];`<p/>` setBasePathFor: HttpServiceSetup['setBasePathFor'];`<p/>` createNewServer: HttpServiceSetup['createNewServer'];`<p/>` }</code> | |
| Property | Type | Description |
| --- | --- | --- |
| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | <code>{`<p/>` adminClient$: Observable&lt;ClusterClient&gt;;`<p/>` dataClient$: Observable&lt;ClusterClient&gt;;`<p/>` }</code> | |
| [http](./kibana-plugin-server.coresetup.http.md) | <code>{`<p/>` registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];`<p/>` registerAuth: HttpServiceSetup['registerAuth'];`<p/>` registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];`<p/>` getBasePathFor: HttpServiceSetup['getBasePathFor'];`<p/>` setBasePathFor: HttpServiceSetup['setBasePathFor'];`<p/>` }</code> | |

View file

@ -20,6 +20,7 @@ export declare class KibanaRequest<Params = unknown, Query = unknown, Body = unk
| [params](./kibana-plugin-server.kibanarequest.params.md) | | <code>Params</code> | |
| [path](./kibana-plugin-server.kibanarequest.path.md) | | <code>string</code> | |
| [query](./kibana-plugin-server.kibanarequest.query.md) | | <code>Query</code> | |
| [url](./kibana-plugin-server.kibanarequest.url.md) | | <code>Url</code> | |
## Methods

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [KibanaRequest](./kibana-plugin-server.kibanarequest.md) &gt; [url](./kibana-plugin-server.kibanarequest.url.md)
## KibanaRequest.url property
<b>Signature:</b>
```typescript
readonly url: Url;
```

View file

@ -12,44 +12,46 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
## Classes
| Class | Description |
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via <code>asScoped(...)</code>). |
| [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | |
| [Router](./kibana-plugin-server.router.md) | |
| [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" <code>ClusterClient</code> but exposes additional <code>callAsCurrentUser</code> method that doesn't use credentials of the Kibana internal user (as <code>callAsInternalUser</code> does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API |
| Class | Description |
| --- | --- |
| [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via <code>asScoped(...)</code>). |
| [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | |
| [Router](./kibana-plugin-server.router.md) | |
| [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" <code>ClusterClient</code> but exposes additional <code>callAsCurrentUser</code> method that doesn't use credentials of the Kibana internal user (as <code>callAsInternalUser</code> does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API |
## Interfaces
| Interface | Description |
| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| [AuthToolkit](./kibana-plugin-server.authtoolkit.md) | A tool set defining an outcome of Auth interceptor for incoming request. |
| [CallAPIOptions](./kibana-plugin-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. |
| [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins <code>setup</code> method. |
| [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins <code>start</code> method. |
| [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. |
| [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | |
| [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | |
| [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) | |
| [InternalCoreStart](./kibana-plugin-server.internalcorestart.md) | |
| [Logger](./kibana-plugin-server.logger.md) | Logger exposes all the necessary methods to log any type of information and this is the interface used by the logging consumers including plugins. |
| [LoggerFactory](./kibana-plugin-server.loggerfactory.md) | The single purpose of <code>LoggerFactory</code> interface is to define a way to retrieve a context-based logger instance. |
| [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata |
| [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) | A tool set defining an outcome of OnRequest interceptor for incoming request. |
| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
| [PluginInitializerContext](./kibana-plugin-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. |
| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | |
| [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | |
| Interface | Description |
| --- | --- |
| [AuthToolkit](./kibana-plugin-server.authtoolkit.md) | A tool set defining an outcome of Auth interceptor for incoming request. |
| [CallAPIOptions](./kibana-plugin-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. |
| [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins <code>setup</code> method. |
| [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins <code>start</code> method. |
| [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. |
| [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | |
| [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) | |
| [InternalCoreStart](./kibana-plugin-server.internalcorestart.md) | |
| [Logger](./kibana-plugin-server.logger.md) | Logger exposes all the necessary methods to log any type of information and this is the interface used by the logging consumers including plugins. |
| [LoggerFactory](./kibana-plugin-server.loggerfactory.md) | The single purpose of <code>LoggerFactory</code> interface is to define a way to retrieve a context-based logger instance. |
| [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata |
| [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. |
| [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. |
| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
| [PluginInitializerContext](./kibana-plugin-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. |
| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | |
| [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | |
## Type Aliases
| Type Alias | Description |
| -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| [APICaller](./kibana-plugin-server.apicaller.md) | |
| [AuthenticationHandler](./kibana-plugin-server.authenticationhandler.md) | |
| [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | |
| [Headers](./kibana-plugin-server.headers.md) | |
| [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | |
| [OnRequestHandler](./kibana-plugin-server.onrequesthandler.md) | |
| [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The <code>plugin</code> export at the root of a plugin's <code>server</code> directory should conform to this interface. |
| [PluginName](./kibana-plugin-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. |
| Type Alias | Description |
| --- | --- |
| [APICaller](./kibana-plugin-server.apicaller.md) | |
| [AuthenticationHandler](./kibana-plugin-server.authenticationhandler.md) | |
| [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | |
| [Headers](./kibana-plugin-server.headers.md) | |
| [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | |
| [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | |
| [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | |
| [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The <code>plugin</code> export at the root of a plugin's <code>server</code> directory should conform to this interface. |
| [PluginName](./kibana-plugin-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. |

View file

@ -0,0 +1,12 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md)
## OnPostAuthHandler type
<b>Signature:</b>
```typescript
export declare type OnPostAuthHandler<Params = any, Query = any, Body = any> = (request: KibanaRequest<Params, Query, Body>, t: OnPostAuthToolkit) => OnPostAuthResult | Promise<OnPostAuthResult>;
```

View file

@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md)
## OnPostAuthToolkit interface
A tool set defining an outcome of OnPostAuth interceptor for incoming request.
<b>Signature:</b>
```typescript
export interface OnPostAuthToolkit
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [next](./kibana-plugin-server.onpostauthtoolkit.next.md) | <code>() =&gt; OnPostAuthResult</code> | To pass request to the next handler |
| [redirected](./kibana-plugin-server.onpostauthtoolkit.redirected.md) | <code>(url: string) =&gt; OnPostAuthResult</code> | To interrupt request handling and redirect to a configured url |
| [rejected](./kibana-plugin-server.onpostauthtoolkit.rejected.md) | <code>(error: Error, options?: {`<p/>` statusCode?: number;`<p/>` }) =&gt; OnPostAuthResult</code> | Fail the request with specified error. |

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) &gt; [next](./kibana-plugin-server.onpostauthtoolkit.next.md)
## OnPostAuthToolkit.next property
To pass request to the next handler
<b>Signature:</b>
```typescript
next: () => OnPostAuthResult;
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) &gt; [redirected](./kibana-plugin-server.onpostauthtoolkit.redirected.md)
## OnPostAuthToolkit.redirected property
To interrupt request handling and redirect to a configured url
<b>Signature:</b>
```typescript
redirected: (url: string) => OnPostAuthResult;
```

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) &gt; [rejected](./kibana-plugin-server.onpostauthtoolkit.rejected.md)
## OnPostAuthToolkit.rejected property
Fail the request with specified error.
<b>Signature:</b>
```typescript
rejected: (error: Error, options?: {
statusCode?: number;
}) => OnPostAuthResult;
```

View file

@ -0,0 +1,12 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md)
## OnPreAuthHandler type
<b>Signature:</b>
```typescript
export declare type OnPreAuthHandler<Params = any, Query = any, Body = any> = (request: KibanaRequest<Params, Query, Body>, t: OnPreAuthToolkit) => OnPreAuthResult | Promise<OnPreAuthResult>;
```

View file

@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md)
## OnPreAuthToolkit interface
A tool set defining an outcome of OnPreAuth interceptor for incoming request.
<b>Signature:</b>
```typescript
export interface OnPreAuthToolkit
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [next](./kibana-plugin-server.onpreauthtoolkit.next.md) | <code>() =&gt; OnPreAuthResult</code> | To pass request to the next handler |
| [redirected](./kibana-plugin-server.onpreauthtoolkit.redirected.md) | <code>(url: string, options?: {`<p/>` forward: boolean;`<p/>` }) =&gt; OnPreAuthResult</code> | To interrupt request handling and redirect to a configured url. If "options.forwarded" = true, request will be forwarded to another url right on the server. |
| [rejected](./kibana-plugin-server.onpreauthtoolkit.rejected.md) | <code>(error: Error, options?: {`<p/>` statusCode?: number;`<p/>` }) =&gt; OnPreAuthResult</code> | Fail the request with specified error. |

View file

@ -1,13 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) &gt; [next](./kibana-plugin-server.onrequesttoolkit.next.md)
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) &gt; [next](./kibana-plugin-server.onpreauthtoolkit.next.md)
## OnRequestToolkit.next property
## OnPreAuthToolkit.next property
To pass request to the next handler
<b>Signature:</b>
```typescript
next: () => OnRequestResult;
next: () => OnPreAuthResult;
```

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) &gt; [redirected](./kibana-plugin-server.onpreauthtoolkit.redirected.md)
## OnPreAuthToolkit.redirected property
To interrupt request handling and redirect to a configured url. If "options.forwarded" = true, request will be forwarded to another url right on the server.
<b>Signature:</b>
```typescript
redirected: (url: string, options?: {
forward: boolean;
}) => OnPreAuthResult;
```

View file

@ -1,8 +1,8 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) &gt; [rejected](./kibana-plugin-server.onrequesttoolkit.rejected.md)
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) &gt; [rejected](./kibana-plugin-server.onpreauthtoolkit.rejected.md)
## OnRequestToolkit.rejected property
## OnPreAuthToolkit.rejected property
Fail the request with specified error.
@ -11,5 +11,5 @@ Fail the request with specified error.
```typescript
rejected: (error: Error, options?: {
statusCode?: number;
}) => OnRequestResult;
}) => OnPreAuthResult;
```

View file

@ -1,12 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnRequestHandler](./kibana-plugin-server.onrequesthandler.md)
## OnRequestHandler type
<b>Signature:</b>
```typescript
export declare type OnRequestHandler<Params = any, Query = any, Body = any> = (req: KibanaRequest<Params, Query, Body>, t: OnRequestToolkit) => OnRequestResult | Promise<OnRequestResult>;
```

View file

@ -1,23 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md)
## OnRequestToolkit interface
A tool set defining an outcome of OnRequest interceptor for incoming request.
<b>Signature:</b>
```typescript
export interface OnRequestToolkit
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [next](./kibana-plugin-server.onrequesttoolkit.next.md) | <code>() =&gt; OnRequestResult</code> | To pass request to the next handler |
| [redirected](./kibana-plugin-server.onrequesttoolkit.redirected.md) | <code>(url: string) =&gt; OnRequestResult</code> | To interrupt request handling and redirect to a configured url |
| [rejected](./kibana-plugin-server.onrequesttoolkit.rejected.md) | <code>(error: Error, options?: {`<p/>` statusCode?: number;`<p/>` }) =&gt; OnRequestResult</code> | Fail the request with specified error. |
| [setUrl](./kibana-plugin-server.onrequesttoolkit.seturl.md) | <code>(newUrl: string &#124; Url) =&gt; void</code> | Change url for an incoming request. |

View file

@ -1,13 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) &gt; [redirected](./kibana-plugin-server.onrequesttoolkit.redirected.md)
## OnRequestToolkit.redirected property
To interrupt request handling and redirect to a configured url
<b>Signature:</b>
```typescript
redirected: (url: string) => OnRequestResult;
```

View file

@ -1,13 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) &gt; [setUrl](./kibana-plugin-server.onrequesttoolkit.seturl.md)
## OnRequestToolkit.setUrl property
Change url for an incoming request.
<b>Signature:</b>
```typescript
setUrl: (newUrl: string | Url) => void;
```

View file

@ -0,0 +1,51 @@
/*
* 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 { Request } from 'hapi';
import { KibanaRequest } from './router';
export enum AuthStatus {
authenticated = 'authenticated',
unauthenticated = 'unauthenticated',
unknown = 'unknown',
}
const toKey = (request: KibanaRequest | Request) =>
request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req;
export class AuthStateStorage {
private readonly storage = new WeakMap<ReturnType<typeof toKey>, unknown>();
constructor(private readonly canBeAuthenticated: () => boolean) {}
public set = (request: KibanaRequest | Request, state: unknown) => {
this.storage.set(toKey(request), state);
};
public get = (request: KibanaRequest | Request) => {
const key = toKey(request);
const state = this.storage.get(key);
const status: AuthStatus = this.storage.has(key)
? AuthStatus.authenticated
: this.canBeAuthenticated()
? AuthStatus.unauthenticated
: AuthStatus.unknown;
return { status, state };
};
public isAuthenticated = (request: KibanaRequest | Request) => {
return this.get(request).status === AuthStatus.authenticated;
};
}

View file

@ -600,9 +600,9 @@ test('registers auth request interceptor only once', async () => {
expect(doRegister()).rejects.toThrowError('Auth interceptor was already registered');
});
test('registers onRequest interceptor several times', async () => {
const { registerOnRequest } = await server.setup(config);
const doRegister = () => registerOnRequest(() => null as any);
test('registers registerOnPostAuth interceptor several times', async () => {
const { registerOnPostAuth } = await server.setup(config);
const doRegister = () => registerOnPostAuth(() => null as any);
doRegister();
expect(doRegister).not.toThrowError();
@ -620,11 +620,11 @@ test('#getBasePathFor() returns base path associated with an incoming request',
setBasePathFor,
registerRouter,
server: innerServer,
registerOnRequest,
registerOnPostAuth,
} = await server.setup(config);
const path = '/base-path';
registerOnRequest((req, t) => {
registerOnPostAuth((req, t) => {
setBasePathFor(req, path);
return t.next();
});
@ -652,11 +652,11 @@ test('#getBasePathFor() is based on server base path', async () => {
setBasePathFor,
registerRouter,
server: innerServer,
registerOnRequest,
registerOnPostAuth,
} = await server.setup(configWithBasePath);
const path = '/base-path';
registerOnRequest((req, t) => {
registerOnPostAuth((req, t) => {
setBasePathFor(req, path);
return t.next();
});
@ -707,3 +707,149 @@ test('#setBasePathFor() cannot be set twice for one request', async () => {
`"Request basePath was previously set. Setting multiple times is not supported."`
);
});
const cookieOptions = {
name: 'sid',
encryptionKey: 'something_at_least_32_characters',
validate: () => true,
isSecure: false,
};
test('Should enable auth for a route by default if registerAuth has been called', async () => {
const { registerAuth, registerRouter, server: innerServer } = await server.setup(config);
const router = new Router('');
router.get({ path: '/', validate: false }, async (req, res) => res.ok({}));
registerRouter(router);
const authenticate = jest
.fn()
.mockImplementation((req, sessionStorage, t) => t.authenticated({}));
await registerAuth(authenticate, cookieOptions);
await server.start();
await supertest(innerServer.listener)
.get('/')
.expect(200);
expect(authenticate).toHaveBeenCalledTimes(1);
});
test('Should support disabling auth for a route', async () => {
const { registerAuth, registerRouter, server: innerServer } = await server.setup(config);
const router = new Router('');
router.get({ path: '/', validate: false, authRequired: false }, async (req, res) => res.ok({}));
registerRouter(router);
const authenticate = jest.fn();
await registerAuth(authenticate, cookieOptions);
await server.start();
await supertest(innerServer.listener)
.get('/')
.expect(200);
expect(authenticate).not.toHaveBeenCalled();
});
describe('#auth.isAuthenticated()', () => {
it('returns true if has been authorized', async () => {
const { registerAuth, registerRouter, server: innerServer, auth } = await server.setup(config);
const router = new Router('');
router.get({ path: '/', validate: false }, async (req, res) =>
res.ok({ isAuthenticated: auth.isAuthenticated(req) })
);
registerRouter(router);
await registerAuth((req, sessionStorage, t) => t.authenticated({}), cookieOptions);
await server.start();
await supertest(innerServer.listener)
.get('/')
.expect(200, { isAuthenticated: true });
});
it('returns false if has not been authorized', async () => {
const { registerAuth, registerRouter, server: innerServer, auth } = await server.setup(config);
const router = new Router('');
router.get({ path: '/', validate: false, authRequired: false }, async (req, res) =>
res.ok({ isAuthenticated: auth.isAuthenticated(req) })
);
registerRouter(router);
await registerAuth((req, sessionStorage, t) => t.authenticated({}), cookieOptions);
await server.start();
await supertest(innerServer.listener)
.get('/')
.expect(200, { isAuthenticated: false });
});
it('returns false if no authorization mechanism has been registered', async () => {
const { registerRouter, server: innerServer, auth } = await server.setup(config);
const router = new Router('');
router.get({ path: '/', validate: false, authRequired: false }, async (req, res) =>
res.ok({ isAuthenticated: auth.isAuthenticated(req) })
);
registerRouter(router);
await server.start();
await supertest(innerServer.listener)
.get('/')
.expect(200, { isAuthenticated: false });
});
});
describe('#auth.get()', () => {
it('Should return authenticated status and allow associate auth state with request', async () => {
const user = { id: '42' };
const { registerRouter, registerAuth, server: innerServer, auth } = await server.setup(config);
await registerAuth((req, sessionStorage, t) => {
sessionStorage.set({ value: user });
return t.authenticated(user);
}, cookieOptions);
const router = new Router('');
router.get({ path: '/', validate: false }, async (req, res) => res.ok(auth.get(req)));
registerRouter(router);
await server.start();
await supertest(innerServer.listener)
.get('/')
.expect(200, { state: user, status: 'authenticated' });
});
it('Should return correct authentication unknown status', async () => {
const { registerRouter, server: innerServer, auth } = await server.setup(config);
const router = new Router('');
router.get({ path: '/', validate: false }, async (req, res) => res.ok(auth.get(req)));
registerRouter(router);
await server.start();
await supertest(innerServer.listener)
.get('/')
.expect(200, { status: 'unknown' });
});
it('Should return correct unauthenticated status', async () => {
const authenticate = jest.fn();
const { registerRouter, registerAuth, server: innerServer, auth } = await server.setup(config);
await registerAuth(authenticate, cookieOptions);
const router = new Router('');
router.get({ path: '/', validate: false, authRequired: false }, async (req, res) =>
res.ok(auth.get(req))
);
registerRouter(router);
await server.start();
await supertest(innerServer.listener)
.get('/')
.expect(200, { status: 'unauthenticated' });
expect(authenticate).not.toHaveBeenCalled();
});
});

View file

@ -24,33 +24,51 @@ import { Logger } from '../logging';
import { HttpConfig } from './http_config';
import { createServer, getServerOptions } from './http_tools';
import { adoptToHapiAuthFormat, AuthenticationHandler } from './lifecycle/auth';
import { adoptToHapiOnRequestFormat, OnRequestHandler } from './lifecycle/on_request';
import { adoptToHapiOnPostAuthFormat, OnPostAuthHandler } from './lifecycle/on_post_auth';
import { adoptToHapiOnPreAuthFormat, OnPreAuthHandler } from './lifecycle/on_pre_auth';
import { Router, KibanaRequest } from './router';
import {
SessionStorageCookieOptions,
createCookieSessionStorageFactory,
} from './cookie_session_storage';
import { AuthStateStorage } from './auth_state_storage';
export interface HttpServerSetup {
server: Server;
options: ServerOptions;
registerRouter: (router: Router) => void;
/**
* Define custom authentication and/or authorization mechanism for incoming requests.
* Applied to all resources by default. Only one AuthenticationHandler can be registered.
* To define custom authentication and/or authorization mechanism for incoming requests.
* A handler should return a state to associate with the incoming request.
* The state can be retrieved later via http.auth.get(..)
* Only one AuthenticationHandler can be registered.
*/
registerAuth: <T>(
authenticationHandler: AuthenticationHandler<T>,
handler: AuthenticationHandler<T>,
cookieOptions: SessionStorageCookieOptions<T>
) => void;
) => Promise<void>;
/**
* Define custom logic to perform for incoming requests.
* Applied to all resources by default.
* Can register any number of OnRequestHandlers, which are called in sequence (from the first registered to the last)
* To define custom logic to perform for incoming requests. Runs the handler before Auth
* hook performs a check that user has access to requested resources, so it's the only
* place when you can forward a request to another URL right on the server.
* Can register any number of registerOnPostAuth, which are called in sequence
* (from the first registered to the last).
*/
registerOnRequest: (requestHandler: OnRequestHandler) => void;
registerOnPreAuth: (handler: OnPreAuthHandler) => void;
/**
* To define custom logic to perform for incoming requests. Runs the handler after Auth hook
* did make sure a user has access to the requested resource.
* The auth state is available at stage via http.auth.get(..)
* Can register any number of registerOnPreAuth, which are called in sequence
* (from the first registered to the last).
*/
registerOnPostAuth: (handler: OnPostAuthHandler) => void;
getBasePathFor: (request: KibanaRequest | Request) => string;
setBasePathFor: (request: KibanaRequest | Request, basePath: string) => void;
auth: {
get: AuthStateStorage['get'];
isAuthenticated: AuthStateStorage['isAuthenticated'];
};
}
export class HttpServer {
@ -63,7 +81,11 @@ export class HttpServer {
string
>();
constructor(private readonly log: Logger) {}
private readonly authState: AuthStateStorage;
constructor(private readonly log: Logger) {
this.authState = new AuthStateStorage(() => this.authRegistered);
}
public isListening() {
return this.server !== undefined && this.server.listener.listening;
@ -105,16 +127,23 @@ export class HttpServer {
this.server = createServer(serverOptions);
this.config = config;
this.setupBasePathRewrite(config);
return {
options: serverOptions,
registerRouter: this.registerRouter.bind(this),
registerOnRequest: this.registerOnRequest.bind(this),
registerOnPreAuth: this.registerOnPreAuth.bind(this),
registerOnPostAuth: this.registerOnPostAuth.bind(this),
registerAuth: <T>(
fn: AuthenticationHandler<T>,
cookieOptions: SessionStorageCookieOptions<T>
) => this.registerAuth(fn, cookieOptions, config.basePath),
getBasePathFor: this.getBasePathFor.bind(this, config),
setBasePathFor: this.setBasePathFor.bind(this),
auth: {
get: this.authState.get,
isAuthenticated: this.authState.isAuthenticated,
},
// Return server instance with the connection options so that we can properly
// bridge core and the "legacy" Kibana internally. Once this bridge isn't
// needed anymore we shouldn't return the instance from this method.
@ -128,14 +157,16 @@ export class HttpServer {
}
this.log.debug('starting http server');
this.setupBasePathRewrite(this.server);
for (const router of this.registeredRouters) {
for (const route of router.getRoutes()) {
const isAuthRequired = Boolean(this.authRegistered && route.authRequired);
this.server.route({
handler: route.handler,
method: route.method,
path: this.getRouteFullPath(router.path, route.path),
options: {
auth: isAuthRequired ? undefined : false,
},
});
}
}
@ -155,13 +186,13 @@ export class HttpServer {
this.server = undefined;
}
private setupBasePathRewrite(server: Server) {
if (this.config!.basePath === undefined || !this.config!.rewriteBasePath) {
private setupBasePathRewrite(config: HttpConfig) {
if (config.basePath === undefined || !config.rewriteBasePath) {
return;
}
const basePath = this.config!.basePath;
server.ext('onRequest', (request, responseToolkit) => {
const basePath = config.basePath;
this.registerOnPreAuth((request, toolkit) => {
const newURL = modifyUrl(request.url.href!, urlParts => {
if (urlParts.pathname != null && urlParts.pathname.startsWith(basePath)) {
urlParts.pathname = urlParts.pathname.replace(basePath, '') || '/';
@ -170,18 +201,10 @@ export class HttpServer {
}
});
if (!newURL) {
return responseToolkit
.response('Not Found')
.code(404)
.takeover();
return toolkit.rejected(new Error('not found'), { statusCode: 404 });
}
request.setUrl(newURL);
// We should update raw request as well since it can be proxied to the old platform
// where base path isn't expected.
request.raw.req.url = request.url.href;
return responseToolkit.continue;
return toolkit.redirected(newURL, { forward: true });
});
}
@ -192,12 +215,20 @@ export class HttpServer {
return `${routerPath}${routePath.slice(routePathStartIndex)}`;
}
private registerOnRequest(fn: OnRequestHandler) {
private registerOnPostAuth(fn: OnPostAuthHandler) {
if (this.server === undefined) {
throw new Error('Server is not created yet');
}
this.server.ext('onRequest', adoptToHapiOnRequestFormat(fn));
this.server.ext('onPostAuth', adoptToHapiOnPostAuthFormat(fn));
}
private registerOnPreAuth(fn: OnPreAuthHandler) {
if (this.server === undefined) {
throw new Error('Server is not created yet');
}
this.server.ext('onRequest', adoptToHapiOnPreAuthFormat(fn));
}
private async registerAuth<T>(
@ -220,7 +251,7 @@ export class HttpServer {
);
this.server.auth.scheme('login', () => ({
authenticate: adoptToHapiAuthFormat(fn, sessionStorage),
authenticate: adoptToHapiAuthFormat(fn, sessionStorage, this.authState.set),
}));
this.server.auth.strategy('session', 'login');

View file

@ -19,21 +19,23 @@
import { Server, ServerOptions } from 'hapi';
import { HttpService } from './http_service';
import { HttpConfig } from './http_config';
import { HttpServerSetup } from './http_server';
const createSetupContractMock = () => {
const setupContract = {
options: {} as ServerOptions,
registerOnPreAuth: jest.fn(),
registerAuth: jest.fn(),
registerOnRequest: jest.fn(),
registerOnPostAuth: jest.fn(),
registerRouter: jest.fn(),
getBasePathFor: jest.fn(),
setBasePathFor: jest.fn(),
// we can mock some hapi server method when we need it
server: {} as Server,
createNewServer: async (cfg: Partial<HttpConfig>): Promise<HttpServerSetup> =>
({} as HttpServerSetup),
auth: {
get: jest.fn(),
isAuthenticated: jest.fn(),
},
createNewServer: jest.fn().mockResolvedValue({}),
};
return setupContract;
};

View file

@ -21,5 +21,6 @@ export { config, HttpConfig, HttpConfigType } from './http_config';
export { HttpService, HttpServiceSetup, HttpServiceStart } from './http_service';
export { Router, KibanaRequest } from './router';
export { BasePathProxyServer } from './base_path_proxy_server';
export { OnPreAuthHandler, OnPreAuthToolkit } from './lifecycle/on_pre_auth';
export { AuthenticationHandler, AuthToolkit } from './lifecycle/auth';
export { OnRequestHandler, OnRequestToolkit } from './lifecycle/on_request';
export { OnPostAuthHandler, OnPostAuthToolkit } from './lifecycle/on_post_auth';

View file

@ -16,9 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
import { parse } from 'url';
import request from 'request';
import Boom from 'boom';
@ -64,14 +61,14 @@ describe('http service', () => {
if (req.headers.authorization) {
const user = { id: '42' };
sessionStorage.set({ value: user, expires: Date.now() + sessionDurationMs });
return t.authenticated({ credentials: user });
return t.authenticated(user);
} else {
return t.rejected(Boom.unauthorized());
}
};
const { http } = await root.setup();
http.registerAuth(authenticate, cookieOptions);
await http.registerAuth(authenticate, cookieOptions);
http.registerRouter(router);
await root.start();
@ -97,14 +94,14 @@ describe('http service', () => {
if (req.headers.authorization) {
const user = { id: '42' };
sessionStorage.set({ value: user, expires: Date.now() + sessionDurationMs });
return t.authenticated({ credentials: user });
return t.authenticated(user);
} else {
return t.rejected(Boom.unauthorized());
}
};
const { http } = await root.setup();
http.registerAuth(authenticate, cookieOptions);
await http.registerAuth(authenticate, cookieOptions);
await root.start();
await kbnTestServer.request
@ -120,7 +117,7 @@ describe('http service', () => {
};
const { http } = await root.setup();
http.registerAuth(authenticate, cookieOptions);
await http.registerAuth(authenticate, cookieOptions);
await root.start();
const response = await kbnTestServer.request.get(root, '/').expect(302);
@ -132,14 +129,14 @@ describe('http service', () => {
if (req.headers.authorization) {
const user = { id: '42' };
sessionStorage.set({ value: user, expires: Date.now() + sessionDurationMs });
return t.authenticated({ credentials: user });
return t.authenticated(user);
} else {
return t.rejected(Boom.unauthorized());
}
};
const { http } = await root.setup();
http.registerAuth(authenticate, cookieOptions);
await http.registerAuth(authenticate, cookieOptions);
await root.start();
const legacyUrl = '/legacy';
@ -157,13 +154,43 @@ describe('http service', () => {
expect(response.header['set-cookie']).toBe(undefined);
});
it('Should pass associated auth state to Legacy platform', async () => {
const user = { id: '42' };
const authenticate: AuthenticationHandler<Storage> = async (req, sessionStorage, t) => {
if (req.headers.authorization) {
sessionStorage.set({ value: user, expires: Date.now() + sessionDurationMs });
return t.authenticated(user);
} else {
return t.rejected(Boom.unauthorized());
}
};
const { http } = await root.setup();
await http.registerAuth(authenticate, cookieOptions);
await root.start();
const legacyUrl = '/legacy';
const kbnServer = kbnTestServer.getKbnServer(root);
kbnServer.server.route({
method: 'GET',
path: legacyUrl,
handler: kbnServer.newPlatform.setup.core.http.auth.get,
});
const response = await kbnTestServer.request.get(root, legacyUrl).expect(200);
expect(response.body.state).toEqual(user);
expect(response.body.status).toEqual('authenticated');
expect(response.header['set-cookie']).toBe(undefined);
});
it(`Shouldn't expose internal error details`, async () => {
const authenticate: AuthenticationHandler<Storage> = async (req, sessionStorage, t) => {
throw new Error('sensitive info');
};
const { http } = await root.setup();
http.registerAuth(authenticate, cookieOptions);
await http.registerAuth(authenticate, cookieOptions);
await root.start();
await kbnTestServer.request.get(root, '/').expect({
@ -174,7 +201,7 @@ describe('http service', () => {
});
});
describe('#registerOnRequest()', () => {
describe('#registerOnPostAuth()', () => {
let root: ReturnType<typeof kbnTestServer.createRoot>;
beforeEach(async () => {
root = kbnTestServer.createRoot();
@ -186,8 +213,8 @@ describe('http service', () => {
router.get({ path: '/', validate: false }, async (req, res) => res.ok({ content: 'ok' }));
const { http } = await root.setup();
http.registerOnRequest((req, t) => t.next());
http.registerOnRequest(async (req, t) => {
http.registerOnPostAuth((req, t) => t.next());
http.registerOnPostAuth(async (req, t) => {
await Promise.resolve();
return t.next();
});
@ -200,7 +227,7 @@ describe('http service', () => {
it('Should support redirecting to configured url', async () => {
const redirectTo = '/redirect-url';
const { http } = await root.setup();
http.registerOnRequest(async (req, t) => t.redirected(redirectTo));
http.registerOnPostAuth(async (req, t) => t.redirected(redirectTo));
await root.start();
const response = await kbnTestServer.request.get(root, '/').expect(302);
@ -209,7 +236,7 @@ describe('http service', () => {
it('Should failing a request with configured error and status code', async () => {
const { http } = await root.setup();
http.registerOnRequest(async (req, t) =>
http.registerOnPostAuth(async (req, t) =>
t.rejected(new Error('unexpected error'), { statusCode: 400 })
);
await root.start();
@ -221,7 +248,7 @@ describe('http service', () => {
it(`Shouldn't expose internal error details`, async () => {
const { http } = await root.setup();
http.registerOnRequest(async (req, t) => {
http.registerOnPostAuth(async (req, t) => {
throw new Error('sensitive info');
});
await root.start();
@ -235,12 +262,12 @@ describe('http service', () => {
it(`Shouldn't share request object between interceptors`, async () => {
const { http } = await root.setup();
http.registerOnRequest(async (req, t) => {
http.registerOnPostAuth(async (req, t) => {
// @ts-ignore. don't complain customField is not defined on Request type
req.customField = { value: 42 };
return t.next();
});
http.registerOnRequest((req, t) => {
http.registerOnPostAuth((req, t) => {
// @ts-ignore don't complain customField is not defined on Request type
if (typeof req.customField !== 'undefined') {
throw new Error('Request object was mutated');
@ -259,7 +286,7 @@ describe('http service', () => {
});
});
describe('#registerOnRequest() toolkit', () => {
describe('#registerOnPostAuth() toolkit', () => {
let root: ReturnType<typeof kbnTestServer.createRoot>;
beforeEach(async () => {
root = kbnTestServer.createRoot();
@ -268,9 +295,8 @@ describe('http service', () => {
afterEach(async () => await root.shutdown());
it('supports Url change on the flight', async () => {
const { http } = await root.setup();
http.registerOnRequest((req, t) => {
t.setUrl(parse('/new-url'));
return t.next();
http.registerOnPreAuth((req, t) => {
return t.redirected('/new-url', { forward: true });
});
const router = new Router('/');
@ -287,9 +313,8 @@ describe('http service', () => {
it('url re-write works for legacy server as well', async () => {
const { http } = await root.setup();
const newUrl = '/new-url';
http.registerOnRequest((req, t) => {
t.setUrl(newUrl);
return t.next();
http.registerOnPreAuth((req, t) => {
return t.redirected(newUrl, { forward: true });
});
await root.start();
@ -314,7 +339,7 @@ describe('http service', () => {
it('basePath information for an incoming request is available in legacy server', async () => {
const reqBasePath = '/requests-specific-base-path';
const { http } = await root.setup();
http.registerOnRequest((req, t) => {
http.registerOnPreAuth((req, t) => {
http.setBasePathFor(req, reqBasePath);
return t.next();
});

View file

@ -17,6 +17,7 @@
* under the License.
*/
import Boom from 'boom';
import { noop } from 'lodash';
import { Lifecycle, Request, ResponseToolkit } from 'hapi';
import { SessionStorage, SessionStorageFactory } from '../session_storage';
@ -26,39 +27,60 @@ enum ResultType {
rejected = 'rejected',
}
/** @internal */
class AuthResult {
public static authenticated(credentials: any) {
return new AuthResult(ResultType.authenticated, credentials);
}
public static redirected(url: string) {
return new AuthResult(ResultType.redirected, url);
}
public static rejected(error: Error, options: { statusCode?: number } = {}) {
return new AuthResult(ResultType.rejected, { error, statusCode: options.statusCode });
}
public static isValidResult(candidate: any) {
return candidate instanceof AuthResult;
}
constructor(private readonly type: ResultType, public readonly payload: any) {}
public isAuthenticated() {
return this.type === ResultType.authenticated;
}
public isRedirected() {
return this.type === ResultType.redirected;
}
public isRejected() {
return this.type === ResultType.rejected;
}
interface Authenticated {
type: ResultType.authenticated;
state: object;
}
interface Redirected {
type: ResultType.redirected;
url: string;
}
interface Rejected {
type: ResultType.rejected;
error: Error;
statusCode?: number;
}
type AuthResult = Authenticated | Rejected | Redirected;
const authResult = {
authenticated(state: object): AuthResult {
return { type: ResultType.authenticated, state };
},
redirected(url: string): AuthResult {
return { type: ResultType.redirected, url };
},
rejected(error: Error, options: { statusCode?: number } = {}): AuthResult {
return { type: ResultType.rejected, error, statusCode: options.statusCode };
},
isValid(candidate: any): candidate is AuthResult {
return (
candidate &&
(candidate.type === ResultType.authenticated ||
candidate.type === ResultType.rejected ||
candidate.type === ResultType.redirected)
);
},
isAuthenticated(result: AuthResult): result is Authenticated {
return result.type === ResultType.authenticated;
},
isRedirected(result: AuthResult): result is Redirected {
return result.type === ResultType.redirected;
},
isRejected(result: AuthResult): result is Rejected {
return result.type === ResultType.rejected;
},
};
/**
* @public
* A tool set defining an outcome of Auth interceptor for incoming request.
*/
export interface AuthToolkit {
/** Authentication is successful with given credentials, allow request to pass through */
authenticated: (credentials: any) => AuthResult;
authenticated: (state: object) => AuthResult;
/** Authentication requires to interrupt request handling and redirect to a configured url */
redirected: (url: string) => AuthResult;
/** Authentication is unsuccessful, fail the request with specified error. */
@ -66,22 +88,23 @@ export interface AuthToolkit {
}
const toolkit: AuthToolkit = {
authenticated: AuthResult.authenticated,
redirected: AuthResult.redirected,
rejected: AuthResult.rejected,
authenticated: authResult.authenticated,
redirected: authResult.redirected,
rejected: authResult.rejected,
};
/** @public */
export type AuthenticationHandler<T> = (
request: Request,
request: Readonly<Request>,
sessionStorage: SessionStorage<T>,
t: AuthToolkit
) => Promise<AuthResult>;
) => AuthResult | Promise<AuthResult>;
/** @public */
export function adoptToHapiAuthFormat<T = any>(
fn: AuthenticationHandler<T>,
sessionStorage: SessionStorageFactory<T>
sessionStorage: SessionStorageFactory<T>,
onSuccess: (req: Request, state: unknown) => void = noop
) {
return async function interceptAuth(
req: Request,
@ -89,22 +112,20 @@ export function adoptToHapiAuthFormat<T = any>(
): Promise<Lifecycle.ReturnValue> {
try {
const result = await fn(req, sessionStorage.asScoped(req), toolkit);
if (AuthResult.isValidResult(result)) {
if (result.isAuthenticated()) {
return h.authenticated({ credentials: result.payload });
}
if (result.isRedirected()) {
return h.redirect(result.payload).takeover();
}
if (result.isRejected()) {
const { error, statusCode } = result.payload;
return Boom.boomify(error, { statusCode });
}
if (!authResult.isValid(result)) {
throw new Error(
`Unexpected result from Authenticate. Expected AuthResult, but given: ${result}.`
);
}
throw new Error(
`Unexpected result from Authenticate. Expected AuthResult, but given: ${result}.`
);
if (authResult.isAuthenticated(result)) {
onSuccess(req, result.state);
return h.authenticated({ credentials: result.state });
}
if (authResult.isRedirected(result)) {
return h.redirect(result.url).takeover();
}
const { error, statusCode } = result;
return Boom.boomify(error, { statusCode });
} catch (error) {
return Boom.internal(error.message, { statusCode: 500 });
}

View file

@ -18,16 +18,16 @@
*/
import Boom from 'boom';
import { adoptToHapiOnRequestFormat } from './on_request';
import { adoptToHapiOnPostAuthFormat } from './on_post_auth';
const requestMock = {} as any;
const createResponseToolkit = (customization = {}): any => ({ ...customization });
describe('adoptToHapiOnRequestFormat', () => {
describe('adoptToHapiOnPostAuthFormat', () => {
it('Should allow passing request to the next handler', async () => {
const continueSymbol = {};
const onRequest = adoptToHapiOnRequestFormat((req, t) => t.next());
const result = await onRequest(
const onPostAuth = adoptToHapiOnPostAuthFormat((req, t) => t.next());
const result = await onPostAuth(
requestMock,
createResponseToolkit({
['continue']: continueSymbol,
@ -39,10 +39,10 @@ describe('adoptToHapiOnRequestFormat', () => {
it('Should support redirecting to specified url', async () => {
const redirectUrl = '/docs';
const onRequest = adoptToHapiOnRequestFormat((req, t) => t.redirected(redirectUrl));
const onPostAuth = adoptToHapiOnPostAuthFormat((req, t) => t.redirected(redirectUrl));
const takeoverSymbol = {};
const redirectMock = jest.fn(() => ({ takeover: () => takeoverSymbol }));
const result = await onRequest(
const result = await onPostAuth(
requestMock,
createResponseToolkit({
redirect: redirectMock,
@ -54,10 +54,10 @@ describe('adoptToHapiOnRequestFormat', () => {
});
it('Should support specifying statusCode and message for Boom error', async () => {
const onRequest = adoptToHapiOnRequestFormat((req, t) => {
const onPostAuth = adoptToHapiOnPostAuthFormat((req, t) => {
return t.rejected(new Error('unexpected result'), { statusCode: 501 });
});
const result = (await onRequest(requestMock, createResponseToolkit())) as Boom;
const result = (await onPostAuth(requestMock, createResponseToolkit())) as Boom;
expect(result).toBeInstanceOf(Boom);
expect(result.message).toBe('unexpected result');
@ -65,10 +65,10 @@ describe('adoptToHapiOnRequestFormat', () => {
});
it('Should return Boom.internal error if interceptor throws', async () => {
const onRequest = adoptToHapiOnRequestFormat((req, t) => {
const onPostAuth = adoptToHapiOnPostAuthFormat((req, t) => {
throw new Error('unknown error');
});
const result = (await onRequest(requestMock, createResponseToolkit())) as Boom;
const result = (await onPostAuth(requestMock, createResponseToolkit())) as Boom;
expect(result).toBeInstanceOf(Boom);
expect(result.message).toBe('unknown error');
@ -76,12 +76,12 @@ describe('adoptToHapiOnRequestFormat', () => {
});
it('Should return Boom.internal error if interceptor returns unexpected result', async () => {
const onRequest = adoptToHapiOnRequestFormat((req, toolkit) => undefined as any);
const result = (await onRequest(requestMock, createResponseToolkit())) as Boom;
const onPostAuth = adoptToHapiOnPostAuthFormat((req, toolkit) => undefined as any);
const result = (await onPostAuth(requestMock, createResponseToolkit())) as Boom;
expect(result).toBeInstanceOf(Boom);
expect(result.message).toBe(
'Unexpected result from OnRequest. Expected OnRequestResult, but given: undefined.'
expect(result.message).toMatchInlineSnapshot(
`"Unexpected result from OnPostAuth. Expected OnPostAuthResult, but given: undefined."`
);
expect(result.output.statusCode).toBe(500);
});

View file

@ -0,0 +1,130 @@
/*
* 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';
import { Lifecycle, Request, ResponseToolkit } from 'hapi';
import { KibanaRequest } from '../router';
enum ResultType {
next = 'next',
redirected = 'redirected',
rejected = 'rejected',
}
interface Next {
type: ResultType.next;
}
interface Redirected {
type: ResultType.redirected;
url: string;
}
interface Rejected {
type: ResultType.rejected;
error: Error;
statusCode?: number;
}
type OnPostAuthResult = Next | Rejected | Redirected;
const postAuthResult = {
next(): OnPostAuthResult {
return { type: ResultType.next };
},
redirected(url: string): OnPostAuthResult {
return { type: ResultType.redirected, url };
},
rejected(error: Error, options: { statusCode?: number } = {}): OnPostAuthResult {
return { type: ResultType.rejected, error, statusCode: options.statusCode };
},
isValid(candidate: any): candidate is OnPostAuthResult {
return (
candidate &&
(candidate.type === ResultType.next ||
candidate.type === ResultType.rejected ||
candidate.type === ResultType.redirected)
);
},
isNext(result: OnPostAuthResult): result is Next {
return result.type === ResultType.next;
},
isRedirected(result: OnPostAuthResult): result is Redirected {
return result.type === ResultType.redirected;
},
isRejected(result: OnPostAuthResult): result is Rejected {
return result.type === ResultType.rejected;
},
};
/**
* @public
* A tool set defining an outcome of OnPostAuth interceptor for incoming request.
*/
export interface OnPostAuthToolkit {
/** To pass request to the next handler */
next: () => OnPostAuthResult;
/** To interrupt request handling and redirect to a configured url */
redirected: (url: string) => OnPostAuthResult;
/** Fail the request with specified error. */
rejected: (error: Error, options?: { statusCode?: number }) => OnPostAuthResult;
}
/** @public */
export type OnPostAuthHandler<Params = any, Query = any, Body = any> = (
request: KibanaRequest<Params, Query, Body>,
t: OnPostAuthToolkit
) => OnPostAuthResult | Promise<OnPostAuthResult>;
const toolkit: OnPostAuthToolkit = {
next: postAuthResult.next,
redirected: postAuthResult.redirected,
rejected: postAuthResult.rejected,
};
/**
* @public
* Adopt custom request interceptor to Hapi lifecycle system.
* @param fn - an extension point allowing to perform custom logic for
* incoming HTTP requests.
*/
export function adoptToHapiOnPostAuthFormat(fn: OnPostAuthHandler) {
return async function interceptRequest(
request: Request,
h: ResponseToolkit
): Promise<Lifecycle.ReturnValue> {
try {
const result = await fn(KibanaRequest.from(request, undefined), toolkit);
if (!postAuthResult.isValid(result)) {
throw new Error(
`Unexpected result from OnPostAuth. Expected OnPostAuthResult, but given: ${result}.`
);
}
if (postAuthResult.isNext(result)) {
return h.continue;
}
if (postAuthResult.isRedirected(result)) {
return h.redirect(result.url).takeover();
}
const { error, statusCode } = result;
return Boom.boomify(error, { statusCode });
} catch (error) {
return Boom.internal(error.message, { statusCode: 500 });
}
};
}

View file

@ -0,0 +1,108 @@
/*
* 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';
import { adoptToHapiOnPreAuthFormat } from './on_pre_auth';
const requestMock = {} as any;
const createResponseToolkit = (customization = {}): any => ({ ...customization });
describe('adoptToHapiOnPreAuthFormat', () => {
it('Should allow passing request to the next handler', async () => {
const continueSymbol = {};
const onPreAuth = adoptToHapiOnPreAuthFormat((req, t) => t.next());
const result = await onPreAuth(
requestMock,
createResponseToolkit({
['continue']: continueSymbol,
})
);
expect(result).toBe(continueSymbol);
});
it('Should support redirecting to specified url', async () => {
const redirectUrl = '/docs';
const onPreAuth = adoptToHapiOnPreAuthFormat((req, t) => t.redirected(redirectUrl));
const takeoverSymbol = {};
const redirectMock = jest.fn(() => ({ takeover: () => takeoverSymbol }));
const result = await onPreAuth(
requestMock,
createResponseToolkit({
redirect: redirectMock,
})
);
expect(redirectMock).toBeCalledWith(redirectUrl);
expect(result).toBe(takeoverSymbol);
});
it('Should support request forwarding to specified url', async () => {
const redirectUrl = '/docs';
const onPreAuth = adoptToHapiOnPreAuthFormat((req, t) =>
t.redirected(redirectUrl, { forward: true })
);
const continueSymbol = {};
const setUrl = jest.fn();
const reqMock = { setUrl, raw: { req: {} } } as any;
const result = await onPreAuth(
reqMock as any,
createResponseToolkit({
['continue']: continueSymbol,
})
);
expect(setUrl).toBeCalledWith(redirectUrl);
expect(reqMock.raw.req.url).toBe(redirectUrl);
expect(result).toBe(continueSymbol);
});
it('Should support specifying statusCode and message for Boom error', async () => {
const onPreAuth = adoptToHapiOnPreAuthFormat((req, t) => {
return t.rejected(new Error('unexpected result'), { statusCode: 501 });
});
const result = (await onPreAuth(requestMock, createResponseToolkit())) as Boom;
expect(result).toBeInstanceOf(Boom);
expect(result.message).toBe('unexpected result');
expect(result.output.statusCode).toBe(501);
});
it('Should return Boom.internal error if interceptor throws', async () => {
const onPreAuth = adoptToHapiOnPreAuthFormat((req, t) => {
throw new Error('unknown error');
});
const result = (await onPreAuth(requestMock, createResponseToolkit())) as Boom;
expect(result).toBeInstanceOf(Boom);
expect(result.message).toBe('unknown error');
expect(result.output.statusCode).toBe(500);
});
it('Should return Boom.internal error if interceptor returns unexpected result', async () => {
const onPreAuth = adoptToHapiOnPreAuthFormat((req, toolkit) => undefined as any);
const result = (await onPreAuth(requestMock, createResponseToolkit())) as Boom;
expect(result).toBeInstanceOf(Boom);
expect(result.message).toMatchInlineSnapshot(
`"Unexpected result from OnPreAuth. Expected OnPreAuthResult, but given: undefined."`
);
expect(result.output.statusCode).toBe(500);
});
});

View file

@ -0,0 +1,145 @@
/*
* 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';
import { Lifecycle, Request, ResponseToolkit } from 'hapi';
import { KibanaRequest } from '../router';
enum ResultType {
next = 'next',
redirected = 'redirected',
rejected = 'rejected',
}
interface Next {
type: ResultType.next;
}
interface Redirected {
type: ResultType.redirected;
url: string;
forward?: boolean;
}
interface Rejected {
type: ResultType.rejected;
error: Error;
statusCode?: number;
}
type OnPreAuthResult = Next | Rejected | Redirected;
const preAuthResult = {
next(): OnPreAuthResult {
return { type: ResultType.next };
},
redirected(url: string, options: { forward?: boolean } = {}): OnPreAuthResult {
return { type: ResultType.redirected, url, forward: options.forward };
},
rejected(error: Error, options: { statusCode?: number } = {}): OnPreAuthResult {
return { type: ResultType.rejected, error, statusCode: options.statusCode };
},
isValid(candidate: any): candidate is OnPreAuthResult {
return (
candidate &&
(candidate.type === ResultType.next ||
candidate.type === ResultType.rejected ||
candidate.type === ResultType.redirected)
);
},
isNext(result: OnPreAuthResult): result is Next {
return result.type === ResultType.next;
},
isRedirected(result: OnPreAuthResult): result is Redirected {
return result.type === ResultType.redirected;
},
isRejected(result: OnPreAuthResult): result is Rejected {
return result.type === ResultType.rejected;
},
};
/**
* @public
* A tool set defining an outcome of OnPreAuth interceptor for incoming request.
*/
export interface OnPreAuthToolkit {
/** To pass request to the next handler */
next: () => OnPreAuthResult;
/**
* To interrupt request handling and redirect to a configured url.
* If "options.forwarded" = true, request will be forwarded to another url right on the server.
* */
redirected: (url: string, options?: { forward: boolean }) => OnPreAuthResult;
/** Fail the request with specified error. */
rejected: (error: Error, options?: { statusCode?: number }) => OnPreAuthResult;
}
const toolkit: OnPreAuthToolkit = {
next: preAuthResult.next,
redirected: preAuthResult.redirected,
rejected: preAuthResult.rejected,
};
/** @public */
export type OnPreAuthHandler<Params = any, Query = any, Body = any> = (
request: KibanaRequest<Params, Query, Body>,
t: OnPreAuthToolkit
) => OnPreAuthResult | Promise<OnPreAuthResult>;
/**
* @public
* Adopt custom request interceptor to Hapi lifecycle system.
* @param fn - an extension point allowing to perform custom logic for
* incoming HTTP requests.
*/
export function adoptToHapiOnPreAuthFormat(fn: OnPreAuthHandler) {
return async function interceptPreAuthRequest(
request: Request,
h: ResponseToolkit
): Promise<Lifecycle.ReturnValue> {
try {
const result = await fn(KibanaRequest.from(request, undefined), toolkit);
if (!preAuthResult.isValid(result)) {
throw new Error(
`Unexpected result from OnPreAuth. Expected OnPreAuthResult, but given: ${result}.`
);
}
if (preAuthResult.isNext(result)) {
return h.continue;
}
if (preAuthResult.isRedirected(result)) {
const { url, forward } = result;
if (forward) {
request.setUrl(url);
// We should update raw request as well since it can be proxied to the old platform
request.raw.req.url = url;
return h.continue;
}
return h.redirect(url).takeover();
}
const { error, statusCode } = result;
return Boom.boomify(error, { statusCode });
} catch (error) {
return Boom.internal(error.message, { statusCode: 500 });
}
};
}

View file

@ -1,120 +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 { Url } from 'url';
import Boom from 'boom';
import { Lifecycle, Request, ResponseToolkit } from 'hapi';
import { KibanaRequest } from '../router';
enum ResultType {
next = 'next',
redirected = 'redirected',
rejected = 'rejected',
}
/** @internal */
class OnRequestResult {
public static next() {
return new OnRequestResult(ResultType.next);
}
public static redirected(url: string) {
return new OnRequestResult(ResultType.redirected, url);
}
public static rejected(error: Error, options: { statusCode?: number } = {}) {
return new OnRequestResult(ResultType.rejected, { error, statusCode: options.statusCode });
}
public static isValidResult(candidate: any) {
return candidate instanceof OnRequestResult;
}
constructor(private readonly type: ResultType, public readonly payload?: any) {}
public isNext() {
return this.type === ResultType.next;
}
public isRedirected() {
return this.type === ResultType.redirected;
}
public isRejected() {
return this.type === ResultType.rejected;
}
}
/**
* @public
* A tool set defining an outcome of OnRequest interceptor for incoming request.
*/
export interface OnRequestToolkit {
/** To pass request to the next handler */
next: () => OnRequestResult;
/** To interrupt request handling and redirect to a configured url */
redirected: (url: string) => OnRequestResult;
/** Fail the request with specified error. */
rejected: (error: Error, options?: { statusCode?: number }) => OnRequestResult;
/** Change url for an incoming request. */
setUrl: (newUrl: string | Url) => void;
}
/** @public */
export type OnRequestHandler<Params = any, Query = any, Body = any> = (
req: KibanaRequest<Params, Query, Body>,
t: OnRequestToolkit
) => OnRequestResult | Promise<OnRequestResult>;
/**
* @public
* Adopt custom request interceptor to Hapi lifecycle system.
* @param fn - an extension point allowing to perform custom logic for
* incoming HTTP requests.
*/
export function adoptToHapiOnRequestFormat(fn: OnRequestHandler) {
return async function interceptRequest(
request: Request,
h: ResponseToolkit
): Promise<Lifecycle.ReturnValue> {
try {
const result = await fn(KibanaRequest.from(request, undefined), {
next: OnRequestResult.next,
redirected: OnRequestResult.redirected,
rejected: OnRequestResult.rejected,
setUrl: (newUrl: string | Url) => {
request.setUrl(newUrl);
// We should update raw request as well since it can be proxied to the old platform
request.raw.req.url = typeof newUrl === 'string' ? newUrl : newUrl.href;
},
});
if (OnRequestResult.isValidResult(result)) {
if (result.isNext()) {
return h.continue;
}
if (result.isRedirected()) {
return h.redirect(result.payload).takeover();
}
if (result.isRejected()) {
const { error, statusCode } = result.payload;
return Boom.boomify(error, { statusCode });
}
}
throw new Error(
`Unexpected result from OnRequest. Expected OnRequestResult, but given: ${result}.`
);
} catch (error) {
return Boom.internal(error.message, { statusCode: 500 });
}
};
}

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { Url } from 'url';
import { ObjectType, TypeOf } from '@kbn/config-schema';
import { Request } from 'hapi';
@ -70,6 +71,7 @@ export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown> {
public readonly headers: Headers;
public readonly path: string;
public readonly url: Url;
constructor(
private readonly request: Request,
@ -79,6 +81,7 @@ export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown> {
) {
this.headers = request.headers;
this.path = request.path;
this.url = request.url;
}
public getFilteredHeaders(headersToKeep: string[]) {

View file

@ -35,6 +35,15 @@ export interface RouteConfig<P extends ObjectType, Q extends ObjectType, B exten
* To opt out of validating the request, specify `false`.
*/
validate: RouteValidateFactory<P, Q, B> | false;
/**
* A flag shows that authentication for a route:
* enabled when true
* disabled when false
*
* Enabled by default.
*/
authRequired?: boolean;
}
export type RouteValidateFactory<

View file

@ -27,6 +27,7 @@ import { RouteConfig, RouteMethod, RouteSchemas } from './route';
export interface RouterRoute {
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
path: string;
authRequired: boolean;
handler: (req: Request, responseToolkit: ResponseToolkit) => Promise<ResponseObject>;
}
@ -43,12 +44,14 @@ export class Router {
route: RouteConfig<P, Q, B>,
handler: RequestHandler<P, Q, B>
) {
const { path, authRequired = true } = route;
const routeSchemas = this.routeSchemasFromRouteConfig(route, 'GET');
this.routes.push({
handler: async (req, responseToolkit) =>
await this.handle(routeSchemas, req, responseToolkit, handler),
method: 'GET',
path: route.path,
path,
authRequired,
});
}
@ -59,12 +62,14 @@ export class Router {
route: RouteConfig<P, Q, B>,
handler: RequestHandler<P, Q, B>
) {
const { path, authRequired = true } = route;
const routeSchemas = this.routeSchemasFromRouteConfig(route, 'POST');
this.routes.push({
handler: async (req, responseToolkit) =>
await this.handle(routeSchemas, req, responseToolkit, handler),
method: 'POST',
path: route.path,
path,
authRequired,
});
}
@ -75,12 +80,14 @@ export class Router {
route: RouteConfig<P, Q, B>,
handler: RequestHandler<P, Q, B>
) {
const { path, authRequired = true } = route;
const routeSchemas = this.routeSchemasFromRouteConfig(route, 'POST');
this.routes.push({
handler: async (req, responseToolkit) =>
await this.handle(routeSchemas, req, responseToolkit, handler),
method: 'PUT',
path: route.path,
path,
authRequired,
});
}
@ -91,12 +98,14 @@ export class Router {
route: RouteConfig<P, Q, B>,
handler: RequestHandler<P, Q, B>
) {
const { path, authRequired = true } = route;
const routeSchemas = this.routeSchemasFromRouteConfig(route, 'DELETE');
this.routes.push({
handler: async (req, responseToolkit) =>
await this.handle(routeSchemas, req, responseToolkit, handler),
method: 'DELETE',
path: route.path,
path,
authRequired,
});
}

View file

@ -54,8 +54,10 @@ export {
AuthenticationHandler,
AuthToolkit,
KibanaRequest,
OnRequestHandler,
OnRequestToolkit,
OnPreAuthHandler,
OnPreAuthToolkit,
OnPostAuthHandler,
OnPostAuthToolkit,
Router,
} from './http';
export { Logger, LoggerFactory, LogMeta, LogRecord, LogLevel } from './logging';
@ -79,8 +81,9 @@ export interface CoreSetup {
dataClient$: Observable<ClusterClient>;
};
http: {
registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];
registerAuth: HttpServiceSetup['registerAuth'];
registerOnRequest: HttpServiceSetup['registerOnRequest'];
registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];
getBasePathFor: HttpServiceSetup['getBasePathFor'];
setBasePathFor: HttpServiceSetup['setBasePathFor'];
createNewServer: HttpServiceSetup['createNewServer'];

View file

@ -117,8 +117,9 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
dataClient$: deps.elasticsearch.dataClient$,
},
http: {
registerOnPreAuth: deps.http.registerOnPreAuth,
registerAuth: deps.http.registerAuth,
registerOnRequest: deps.http.registerOnRequest,
registerOnPostAuth: deps.http.registerOnPostAuth,
getBasePathFor: deps.http.getBasePathFor,
setBasePathFor: deps.http.setBasePathFor,
createNewServer: deps.http.createNewServer,

View file

@ -26,11 +26,11 @@ export type APICaller = (endpoint: string, clientParams: Record<string, unknown>
// Warning: (ae-forgotten-export) The symbol "AuthResult" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export type AuthenticationHandler<T> = (request: Request, sessionStorage: SessionStorage<T>, t: AuthToolkit) => Promise<AuthResult>;
export type AuthenticationHandler<T> = (request: Readonly<Request>, sessionStorage: SessionStorage<T>, t: AuthToolkit) => AuthResult | Promise<AuthResult>;
// @public
export interface AuthToolkit {
authenticated: (credentials: any) => AuthResult;
authenticated: (state: object) => AuthResult;
redirected: (url: string) => AuthResult;
rejected: (error: Error, options?: {
statusCode?: number;
@ -84,8 +84,9 @@ export interface CoreSetup {
};
// (undocumented)
http: {
registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];
registerAuth: HttpServiceSetup['registerAuth'];
registerOnRequest: HttpServiceSetup['registerOnRequest'];
registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];
getBasePathFor: HttpServiceSetup['getBasePathFor'];
setBasePathFor: HttpServiceSetup['setBasePathFor'];
createNewServer: HttpServiceSetup['createNewServer'];
@ -183,6 +184,8 @@ export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown> {
readonly query: Query;
// (undocumented)
unstable_getIncomingMessage(): import("http").IncomingMessage;
// (undocumented)
readonly url: Url;
}
// @public
@ -254,19 +257,34 @@ export interface LogRecord {
timestamp: Date;
}
// Warning: (ae-forgotten-export) The symbol "OnRequestResult" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "OnPostAuthResult" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export type OnRequestHandler<Params = any, Query = any, Body = any> = (req: KibanaRequest<Params, Query, Body>, t: OnRequestToolkit) => OnRequestResult | Promise<OnRequestResult>;
export type OnPostAuthHandler<Params = any, Query = any, Body = any> = (request: KibanaRequest<Params, Query, Body>, t: OnPostAuthToolkit) => OnPostAuthResult | Promise<OnPostAuthResult>;
// @public
export interface OnRequestToolkit {
next: () => OnRequestResult;
redirected: (url: string) => OnRequestResult;
export interface OnPostAuthToolkit {
next: () => OnPostAuthResult;
redirected: (url: string) => OnPostAuthResult;
rejected: (error: Error, options?: {
statusCode?: number;
}) => OnRequestResult;
setUrl: (newUrl: string | Url) => void;
}) => OnPostAuthResult;
}
// Warning: (ae-forgotten-export) The symbol "OnPreAuthResult" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export type OnPreAuthHandler<Params = any, Query = any, Body = any> = (request: KibanaRequest<Params, Query, Body>, t: OnPreAuthToolkit) => OnPreAuthResult | Promise<OnPreAuthResult>;
// @public
export interface OnPreAuthToolkit {
next: () => OnPreAuthResult;
redirected: (url: string, options?: {
forward: boolean;
}) => OnPreAuthResult;
rejected: (error: Error, options?: {
statusCode?: number;
}) => OnPreAuthResult;
}
// @public