mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* 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 commit9599d32801
. * Revert "regenerate docs" This reverts commit1799d3b088
. * 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:
parent
fd125bc2db
commit
3278cd7126
40 changed files with 1041 additions and 379 deletions
|
@ -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>;
|
||||
```
|
||||
|
|
|
@ -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;
|
||||
```
|
||||
|
|
|
@ -16,7 +16,7 @@ export interface AuthToolkit
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [authenticated](./kibana-plugin-server.authtoolkit.authenticated.md) | <code>(credentials: any) => AuthResult</code> | Authentication is successful with given credentials, allow request to pass through |
|
||||
| [authenticated](./kibana-plugin-server.authtoolkit.authenticated.md) | <code>(state: object) => AuthResult</code> | Authentication is successful with given credentials, allow request to pass through |
|
||||
| [redirected](./kibana-plugin-server.authtoolkit.redirected.md) | <code>(url: string) => 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/>` }) => AuthResult</code> | Authentication is unsuccessful, fail the request with specified error. |
|
||||
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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<ClusterClient>;`<p/>` dataClient\$: Observable<ClusterClient>;`<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<ClusterClient>;`<p/>` dataClient$: Observable<ClusterClient>;`<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> | |
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [url](./kibana-plugin-server.kibanarequest.url.md)
|
||||
|
||||
## KibanaRequest.url property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly url: Url;
|
||||
```
|
|
@ -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. |
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [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>;
|
||||
```
|
|
@ -0,0 +1,22 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [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>() => OnPostAuthResult</code> | To pass request to the next handler |
|
||||
| [redirected](./kibana-plugin-server.onpostauthtoolkit.redirected.md) | <code>(url: string) => 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/>` }) => OnPostAuthResult</code> | Fail the request with specified error. |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) > [next](./kibana-plugin-server.onpostauthtoolkit.next.md)
|
||||
|
||||
## OnPostAuthToolkit.next property
|
||||
|
||||
To pass request to the next handler
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
next: () => OnPostAuthResult;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) > [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;
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) > [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;
|
||||
```
|
|
@ -0,0 +1,12 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [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>;
|
||||
```
|
|
@ -0,0 +1,22 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [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>() => OnPreAuthResult</code> | To pass request to the next handler |
|
||||
| [redirected](./kibana-plugin-server.onpreauthtoolkit.redirected.md) | <code>(url: string, options?: {`<p/>` forward: boolean;`<p/>` }) => 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/>` }) => OnPreAuthResult</code> | Fail the request with specified error. |
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) > [next](./kibana-plugin-server.onrequesttoolkit.next.md)
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) > [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;
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) > [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;
|
||||
```
|
|
@ -1,8 +1,8 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) > [rejected](./kibana-plugin-server.onrequesttoolkit.rejected.md)
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) > [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;
|
||||
```
|
|
@ -1,12 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [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>;
|
||||
```
|
|
@ -1,23 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [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>() => OnRequestResult</code> | To pass request to the next handler |
|
||||
| [redirected](./kibana-plugin-server.onrequesttoolkit.redirected.md) | <code>(url: string) => 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/>` }) => OnRequestResult</code> | Fail the request with specified error. |
|
||||
| [setUrl](./kibana-plugin-server.onrequesttoolkit.seturl.md) | <code>(newUrl: string | Url) => void</code> | Change url for an incoming request. |
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) > [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;
|
||||
```
|
|
@ -1,13 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) > [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;
|
||||
```
|
51
src/core/server/http/auth_state_storage.ts
Normal file
51
src/core/server/http/auth_state_storage.ts
Normal 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;
|
||||
};
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
130
src/core/server/http/lifecycle/on_post_auth.ts
Normal file
130
src/core/server/http/lifecycle/on_post_auth.ts
Normal 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 });
|
||||
}
|
||||
};
|
||||
}
|
108
src/core/server/http/lifecycle/on_pre_auth.test.ts
Normal file
108
src/core/server/http/lifecycle/on_pre_auth.test.ts
Normal 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);
|
||||
});
|
||||
});
|
145
src/core/server/http/lifecycle/on_pre_auth.ts
Normal file
145
src/core/server/http/lifecycle/on_pre_auth.ts
Normal 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 });
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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 });
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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[]) {
|
||||
|
|
|
@ -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<
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue