diff --git a/docs/development/core/server/kibana-plugin-server.authstatus.md b/docs/development/core/server/kibana-plugin-server.authstatus.md new file mode 100644 index 00000000000000..e59ade4f73e38b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.authstatus.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [AuthStatus](./kibana-plugin-server.authstatus.md) + +## AuthStatus enum + +Status indicating an outcome of the authentication. + +Signature: + +```typescript +export declare enum AuthStatus +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| authenticated | "authenticated" | auth interceptor successfully authenticated a user | +| unauthenticated | "unauthenticated" | auth interceptor failed user authentication | +| unknown | "unknown" | auth interceptor has not been registered | + diff --git a/docs/development/core/server/kibana-plugin-server.customhttpresponseoptions.md b/docs/development/core/server/kibana-plugin-server.customhttpresponseoptions.md new file mode 100644 index 00000000000000..cabee8a47e5ca0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.customhttpresponseoptions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CustomHttpResponseOptions](./kibana-plugin-server.customhttpresponseoptions.md) + +## CustomHttpResponseOptions interface + +HTTP response parameters for a response with adjustable status code. + +Signature: + +```typescript +export interface CustomHttpResponseOptions extends HttpResponseOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [statusCode](./kibana-plugin-server.customhttpresponseoptions.statuscode.md) | number | | + diff --git a/docs/development/core/server/kibana-plugin-server.customhttpresponseoptions.statuscode.md b/docs/development/core/server/kibana-plugin-server.customhttpresponseoptions.statuscode.md new file mode 100644 index 00000000000000..5444ccd2ebb554 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.customhttpresponseoptions.statuscode.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CustomHttpResponseOptions](./kibana-plugin-server.customhttpresponseoptions.md) > [statusCode](./kibana-plugin-server.customhttpresponseoptions.statuscode.md) + +## CustomHttpResponseOptions.statusCode property + +Signature: + +```typescript +statusCode: number; +``` diff --git a/docs/development/core/server/kibana-plugin-server.getauthheaders.md b/docs/development/core/server/kibana-plugin-server.getauthheaders.md index ee7572615fe1a9..fba8b8ca8ee3a4 100644 --- a/docs/development/core/server/kibana-plugin-server.getauthheaders.md +++ b/docs/development/core/server/kibana-plugin-server.getauthheaders.md @@ -9,5 +9,5 @@ Get headers to authenticate a user against Elasticsearch. Signature: ```typescript -export declare type GetAuthHeaders = (request: KibanaRequest | Request) => AuthHeaders | undefined; +export declare type GetAuthHeaders = (request: KibanaRequest | LegacyRequest) => AuthHeaders | undefined; ``` diff --git a/docs/development/core/server/kibana-plugin-server.getauthstate.md b/docs/development/core/server/kibana-plugin-server.getauthstate.md new file mode 100644 index 00000000000000..47fc38c28f5e0a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.getauthstate.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [GetAuthState](./kibana-plugin-server.getauthstate.md) + +## GetAuthState type + +Get authentication state for a request. Returned by `auth` interceptor. + +Signature: + +```typescript +export declare type GetAuthState = (request: KibanaRequest | LegacyRequest) => { + status: AuthStatus; + state: unknown; +}; +``` diff --git a/docs/development/core/server/kibana-plugin-server.headers.md b/docs/development/core/server/kibana-plugin-server.headers.md index 83259efe8b79d5..cd73d4de43b9db 100644 --- a/docs/development/core/server/kibana-plugin-server.headers.md +++ b/docs/development/core/server/kibana-plugin-server.headers.md @@ -4,9 +4,14 @@ ## Headers type +Http request headers to read. Signature: ```typescript -export declare type Headers = Record; +export declare type Headers = { + [header in KnownHeaders]?: string | string[] | undefined; +} & { + [header: string]: string | string[] | undefined; +}; ``` diff --git a/docs/development/core/server/kibana-plugin-server.httpresponseoptions.headers.md b/docs/development/core/server/kibana-plugin-server.httpresponseoptions.headers.md new file mode 100644 index 00000000000000..ee347f99a41a46 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpresponseoptions.headers.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpResponseOptions](./kibana-plugin-server.httpresponseoptions.md) > [headers](./kibana-plugin-server.httpresponseoptions.headers.md) + +## HttpResponseOptions.headers property + +HTTP Headers with additional information about response + +Signature: + +```typescript +headers?: ResponseHeaders; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpresponseoptions.md b/docs/development/core/server/kibana-plugin-server.httpresponseoptions.md new file mode 100644 index 00000000000000..8f9ccf22c8c6dc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpresponseoptions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpResponseOptions](./kibana-plugin-server.httpresponseoptions.md) + +## HttpResponseOptions interface + +HTTP response parameters + +Signature: + +```typescript +export interface HttpResponseOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [headers](./kibana-plugin-server.httpresponseoptions.headers.md) | ResponseHeaders | HTTP Headers with additional information about response | + diff --git a/docs/development/core/server/kibana-plugin-server.httpresponsepayload.md b/docs/development/core/server/kibana-plugin-server.httpresponsepayload.md new file mode 100644 index 00000000000000..3dc4e2c7956f75 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpresponsepayload.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpResponsePayload](./kibana-plugin-server.httpresponsepayload.md) + +## HttpResponsePayload type + +Data send to the client as a response payload. + +Signature: + +```typescript +export declare type HttpResponsePayload = undefined | string | Record | Buffer | Stream; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpserversetup.auth.md b/docs/development/core/server/kibana-plugin-server.httpserversetup.auth.md new file mode 100644 index 00000000000000..e39c3c63167686 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserversetup.auth.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerSetup](./kibana-plugin-server.httpserversetup.md) > [auth](./kibana-plugin-server.httpserversetup.auth.md) + +## HttpServerSetup.auth property + +Signature: + +```typescript +auth: { + get: GetAuthState; + isAuthenticated: IsAuthenticated; + getAuthHeaders: GetAuthHeaders; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpserversetup.basepath.md b/docs/development/core/server/kibana-plugin-server.httpserversetup.basepath.md new file mode 100644 index 00000000000000..5cfb2f5c4e8b43 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserversetup.basepath.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerSetup](./kibana-plugin-server.httpserversetup.md) > [basePath](./kibana-plugin-server.httpserversetup.basepath.md) + +## HttpServerSetup.basePath property + +Signature: + +```typescript +basePath: { + get: (request: KibanaRequest | LegacyRequest) => string; + set: (request: KibanaRequest | LegacyRequest, basePath: string) => void; + prepend: (url: string) => string; + remove: (url: string) => string; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpserversetup.createcookiesessionstoragefactory.md b/docs/development/core/server/kibana-plugin-server.httpserversetup.createcookiesessionstoragefactory.md new file mode 100644 index 00000000000000..3dc01a52a2f586 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserversetup.createcookiesessionstoragefactory.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerSetup](./kibana-plugin-server.httpserversetup.md) > [createCookieSessionStorageFactory](./kibana-plugin-server.httpserversetup.createcookiesessionstoragefactory.md) + +## HttpServerSetup.createCookieSessionStorageFactory property + +Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) + +Signature: + +```typescript +createCookieSessionStorageFactory: (cookieOptions: SessionStorageCookieOptions) => Promise>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpserversetup.istlsenabled.md b/docs/development/core/server/kibana-plugin-server.httpserversetup.istlsenabled.md new file mode 100644 index 00000000000000..6961d4feeb7c7c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserversetup.istlsenabled.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerSetup](./kibana-plugin-server.httpserversetup.md) > [isTlsEnabled](./kibana-plugin-server.httpserversetup.istlsenabled.md) + +## HttpServerSetup.isTlsEnabled property + +Flag showing whether a server was configured to use TLS connection. + +Signature: + +```typescript +isTlsEnabled: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpserversetup.md b/docs/development/core/server/kibana-plugin-server.httpserversetup.md new file mode 100644 index 00000000000000..143ae66c0b32cc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserversetup.md @@ -0,0 +1,93 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerSetup](./kibana-plugin-server.httpserversetup.md) + +## HttpServerSetup interface + +Kibana HTTP Service provides own abstraction for work with HTTP stack. Plugins don't have direct access to `hapi` server and its primitives anymore. Moreover, plugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood. This gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins. If the HTTP Service lacks functionality you need, we are happy to discuss and support your needs. + +Signature: + +```typescript +export interface HttpServerSetup +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [auth](./kibana-plugin-server.httpserversetup.auth.md) | {
get: GetAuthState;
isAuthenticated: IsAuthenticated;
getAuthHeaders: GetAuthHeaders;
} | | +| [basePath](./kibana-plugin-server.httpserversetup.basepath.md) | {
get: (request: KibanaRequest | LegacyRequest) => string;
set: (request: KibanaRequest | LegacyRequest, basePath: string) => void;
prepend: (url: string) => string;
remove: (url: string) => string;
} | | +| [createCookieSessionStorageFactory](./kibana-plugin-server.httpserversetup.createcookiesessionstoragefactory.md) | <T>(cookieOptions: SessionStorageCookieOptions<T>) => Promise<SessionStorageFactory<T>> | Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | +| [isTlsEnabled](./kibana-plugin-server.httpserversetup.istlsenabled.md) | boolean | Flag showing whether a server was configured to use TLS connection. | +| [registerAuth](./kibana-plugin-server.httpserversetup.registerauth.md) | (handler: AuthenticationHandler) => void | 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. | +| [registerOnPostAuth](./kibana-plugin-server.httpserversetup.registeronpostauth.md) | (handler: OnPostAuthHandler) => void | To define custom logic to perform for incoming requests. Runs the handler after Auth interceptor 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). | +| [registerOnPreAuth](./kibana-plugin-server.httpserversetup.registeronpreauth.md) | (handler: OnPreAuthHandler) => void | To define custom logic to perform for incoming requests. Runs the handler before Auth interceptor 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). | +| [registerRouter](./kibana-plugin-server.httpserversetup.registerrouter.md) | (router: Router) => void | Add all the routes registered with router to HTTP server request listeners. | +| [server](./kibana-plugin-server.httpserversetup.server.md) | Server | | + +## Example + +To handle an incoming request in your plugin you should: - Create a `Router` instance. Use `plugin-id` as a prefix path segment for your routes. + +```ts +import { Router } from 'src/core/server'; +const router = new Router('my-app'); + +``` +- Use `@kbn/config-schema` package to create a schema to validate the request `params`, `query`, and `body`. Every incoming request will be validated against the created schema. If validation failed, the request is rejected with `400` status and `Bad request` error without calling the route's handler. To opt out of validating the request, specify `false`. + +```ts +import { schema, TypeOf } from '@kbn/config-schema'; +const validate = { + params: schema.object({ + id: schema.string(), + }), +}; + +``` +- Declare a function to respond to incoming request. The function will receive `request` object containing request details: url, headers, matched route, as well as validated `params`, `query`, `body`. And `response` object instructing HTTP server to create HTTP response with information sent back to the client as the response body, headers, and HTTP status. Unlike, `hapi` route handler in the Legacy platform, any exception raised during the handler call will generate `500 Server error` response and log error details for further investigation. See below for returning custom error responses. + +```ts +const handler = async (request: KibanaRequest, response: ResponseFactory) => { + const data = await findObject(request.params.id); + // creates a command to respond with 'not found' error + if (!data) return response.notFound(); + // creates a command to send found data to the client and set response headers + return response.ok(data, { + headers: { + 'content-type': 'application/json' + } + }); +} + +``` +- Register route handler for GET request to 'my-app/path/{id}' path + +```ts +import { schema, TypeOf } from '@kbn/config-schema'; +import { Router } from 'src/core/server'; +const router = new Router('my-app'); + +const validate = { + params: schema.object({ + id: schema.string(), + }), +}; + +router.get({ + path: 'path/{id}', + validate +}, +async (request, response) => { + const data = await findObject(request.params.id); + if (!data) return response.notFound(); + return response.ok(data, { + headers: { + 'content-type': 'application/json' + } + }); +}); + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.httpserversetup.registerauth.md b/docs/development/core/server/kibana-plugin-server.httpserversetup.registerauth.md new file mode 100644 index 00000000000000..6e63e0996a63a9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserversetup.registerauth.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerSetup](./kibana-plugin-server.httpserversetup.md) > [registerAuth](./kibana-plugin-server.httpserversetup.registerauth.md) + +## HttpServerSetup.registerAuth property + +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. + +Signature: + +```typescript +registerAuth: (handler: AuthenticationHandler) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpserversetup.registeronpostauth.md b/docs/development/core/server/kibana-plugin-server.httpserversetup.registeronpostauth.md new file mode 100644 index 00000000000000..c74a67da350ecc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserversetup.registeronpostauth.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerSetup](./kibana-plugin-server.httpserversetup.md) > [registerOnPostAuth](./kibana-plugin-server.httpserversetup.registeronpostauth.md) + +## HttpServerSetup.registerOnPostAuth property + +To define custom logic to perform for incoming requests. Runs the handler after Auth interceptor 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). + +Signature: + +```typescript +registerOnPostAuth: (handler: OnPostAuthHandler) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpserversetup.registeronpreauth.md b/docs/development/core/server/kibana-plugin-server.httpserversetup.registeronpreauth.md new file mode 100644 index 00000000000000..f6efa1c1dd73c4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserversetup.registeronpreauth.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerSetup](./kibana-plugin-server.httpserversetup.md) > [registerOnPreAuth](./kibana-plugin-server.httpserversetup.registeronpreauth.md) + +## HttpServerSetup.registerOnPreAuth property + +To define custom logic to perform for incoming requests. Runs the handler before Auth interceptor 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). + +Signature: + +```typescript +registerOnPreAuth: (handler: OnPreAuthHandler) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpserversetup.registerrouter.md b/docs/development/core/server/kibana-plugin-server.httpserversetup.registerrouter.md new file mode 100644 index 00000000000000..4c2a9ae3274068 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserversetup.registerrouter.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerSetup](./kibana-plugin-server.httpserversetup.md) > [registerRouter](./kibana-plugin-server.httpserversetup.registerrouter.md) + +## HttpServerSetup.registerRouter property + +Add all the routes registered with `router` to HTTP server request listeners. + +Signature: + +```typescript +registerRouter: (router: Router) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpserversetup.server.md b/docs/development/core/server/kibana-plugin-server.httpserversetup.server.md new file mode 100644 index 00000000000000..a137eba7c8a5a7 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserversetup.server.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerSetup](./kibana-plugin-server.httpserversetup.md) > [server](./kibana-plugin-server.httpserversetup.server.md) + +## HttpServerSetup.server property + +Signature: + +```typescript +server: Server; +``` diff --git a/docs/development/core/server/kibana-plugin-server.isauthenticated.md b/docs/development/core/server/kibana-plugin-server.isauthenticated.md new file mode 100644 index 00000000000000..15f412710412a2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.isauthenticated.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) + +## IsAuthenticated type + +Return authentication status for a request. + +Signature: + +```typescript +export declare type IsAuthenticated = (request: KibanaRequest | LegacyRequest) => boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.md index a9622e4319d573..19167f2f640418 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequest.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.md @@ -26,6 +26,6 @@ export declare class KibanaRequestHeaders | Readonly copy of incoming request headers. | | [params](./kibana-plugin-server.kibanarequest.params.md) | | Params | | | [query](./kibana-plugin-server.kibanarequest.query.md) | | Query | | -| [route](./kibana-plugin-server.kibanarequest.route.md) | | RecursiveReadonly<KibanaRequestRoute> | | -| [url](./kibana-plugin-server.kibanarequest.url.md) | | Url | | +| [route](./kibana-plugin-server.kibanarequest.route.md) | | RecursiveReadonly<KibanaRequestRoute> | matched route details | +| [url](./kibana-plugin-server.kibanarequest.url.md) | | Url | a WHATWG URL standard object. | diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md index 301eeef1b6bb58..88954eedf4cfb5 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md @@ -4,6 +4,8 @@ ## KibanaRequest.route property +matched route details + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.url.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.url.md index b8bd46199763ee..62d1f971594764 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequest.url.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.url.md @@ -4,6 +4,8 @@ ## KibanaRequest.url property +a WHATWG URL standard object. + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-server.kibanaresponsefactory.md b/docs/development/core/server/kibana-plugin-server.kibanaresponsefactory.md new file mode 100644 index 00000000000000..82832ee9334a53 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.kibanaresponsefactory.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) + +## KibanaResponseFactory type + +Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. + +Signature: + +```typescript +export declare type KibanaResponseFactory = typeof kibanaResponseFactory; +``` diff --git a/docs/development/core/server/kibana-plugin-server.knownheaders.md b/docs/development/core/server/kibana-plugin-server.knownheaders.md new file mode 100644 index 00000000000000..986794f3aaa61d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.knownheaders.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [KnownHeaders](./kibana-plugin-server.knownheaders.md) + +## KnownHeaders type + +Set of well-known HTTP headers. + +Signature: + +```typescript +export declare type KnownHeaders = KnownKeys; +``` diff --git a/docs/development/core/server/kibana-plugin-server.legacyrequest.md b/docs/development/core/server/kibana-plugin-server.legacyrequest.md index 6f67928faa52cf..a794b3bbe87c7d 100644 --- a/docs/development/core/server/kibana-plugin-server.legacyrequest.md +++ b/docs/development/core/server/kibana-plugin-server.legacyrequest.md @@ -2,12 +2,15 @@ [Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [LegacyRequest](./kibana-plugin-server.legacyrequest.md) -## LegacyRequest type +## LegacyRequest interface -Support Legacy platform request for the period of migration. +> Warning: This API is now obsolete. +> +> `hapi` request object, supported during migration process only for backward compatibility. +> Signature: ```typescript -export declare type LegacyRequest = Request; +export interface LegacyRequest extends Request ``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 72c2498f40f739..4cb19665a5dd61 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -17,12 +17,18 @@ The plugin integrates with the core system via lifecycle events: `setup` | [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 asScoped(...)). | | [ElasticsearchErrorHelpers](./kibana-plugin-server.elasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | | [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | -| [Router](./kibana-plugin-server.router.md) | | +| [Router](./kibana-plugin-server.router.md) | Provides ability to declare a handler function for a particular path and HTTP request method. Each route can have only one handler functions, which is executed when the route is matched. | | [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | | [SavedObjectsSchema](./kibana-plugin-server.savedobjectsschema.md) | | | [SavedObjectsSerializer](./kibana-plugin-server.savedobjectsserializer.md) | | | [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API | +## Enumerations + +| Enumeration | Description | +| --- | --- | +| [AuthStatus](./kibana-plugin-server.authstatus.md) | Status indicating an outcome of the authentication. | + ## Interfaces | Interface | Description | @@ -32,13 +38,17 @@ The plugin integrates with the core system via lifecycle events: `setup` | [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 setup method. | | [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins start method. | +| [CustomHttpResponseOptions](./kibana-plugin-server.customhttpresponseoptions.md) | HTTP response parameters for a response with adjustable status code. | | [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. | | [ElasticsearchError](./kibana-plugin-server.elasticsearcherror.md) | | | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | | | [FakeRequest](./kibana-plugin-server.fakerequest.md) | Fake request object created manually by Kibana plugins. | +| [HttpResponseOptions](./kibana-plugin-server.httpresponseoptions.md) | HTTP response parameters | +| [HttpServerSetup](./kibana-plugin-server.httpserversetup.md) | Kibana HTTP Service provides own abstraction for work with HTTP stack. Plugins don't have direct access to hapi server and its primitives anymore. Moreover, plugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood. This gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins. If the HTTP Service lacks functionality you need, we are happy to discuss and support your needs. | | [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) | | | [InternalCoreStart](./kibana-plugin-server.internalcorestart.md) | | | [KibanaRequestRoute](./kibana-plugin-server.kibanarequestroute.md) | Request specific route information exposed to a handler. | +| [LegacyRequest](./kibana-plugin-server.legacyrequest.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 LoggerFactory interface is to define a way to retrieve a context-based logger instance. | | [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata | @@ -49,7 +59,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | | | [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | | | [ResponseErrorMeta](./kibana-plugin-server.responseerrormeta.md) | Additional metadata to enhance error output or provide error details. | -| [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Route specific configuration. | +| [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. | +| [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. | | [SavedObject](./kibana-plugin-server.savedobject.md) | | | [SavedObjectAttributes](./kibana-plugin-server.savedobjectattributes.md) | | | [SavedObjectReference](./kibana-plugin-server.savedobjectreference.md) | A reference to another saved object. | @@ -78,8 +89,15 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsUpdateOptions](./kibana-plugin-server.savedobjectsupdateoptions.md) | | | [SavedObjectsUpdateResponse](./kibana-plugin-server.savedobjectsupdateresponse.md) | | | [SessionStorage](./kibana-plugin-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. | +| [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. | | [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | +## Variables + +| Variable | Description | +| --- | --- | +| [kibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Set of helpers used to create KibanaResponse to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution. | + ## Type Aliases | Type Alias | Description | @@ -89,14 +107,20 @@ The plugin integrates with the core system via lifecycle events: `setup` | [AuthHeaders](./kibana-plugin-server.authheaders.md) | Auth Headers map | | [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | | | [GetAuthHeaders](./kibana-plugin-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. | -| [Headers](./kibana-plugin-server.headers.md) | | +| [GetAuthState](./kibana-plugin-server.getauthstate.md) | Get authentication state for a request. Returned by auth interceptor. | +| [Headers](./kibana-plugin-server.headers.md) | Http request headers to read. | +| [HttpResponsePayload](./kibana-plugin-server.httpresponsepayload.md) | Data send to the client as a response payload. | | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | | -| [LegacyRequest](./kibana-plugin-server.legacyrequest.md) | Support Legacy platform request for the period of migration. | +| [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. | +| [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. | +| [KnownHeaders](./kibana-plugin-server.knownheaders.md) | Set of well-known HTTP headers. | | [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | | | [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | | | [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The plugin export at the root of a plugin's server 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. | | [RecursiveReadonly](./kibana-plugin-server.recursivereadonly.md) | | +| [RedirectResponseOptions](./kibana-plugin-server.redirectresponseoptions.md) | HTTP response parameters for redirection response | +| [RequestHandler](./kibana-plugin-server.requesthandler.md) | A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) functions. | | [ResponseError](./kibana-plugin-server.responseerror.md) | Error message and optional data send to the client in case of error. | | [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. | | [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | \#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | diff --git a/docs/development/core/server/kibana-plugin-server.redirectresponseoptions.md b/docs/development/core/server/kibana-plugin-server.redirectresponseoptions.md new file mode 100644 index 00000000000000..6fb0a5add2fb65 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.redirectresponseoptions.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RedirectResponseOptions](./kibana-plugin-server.redirectresponseoptions.md) + +## RedirectResponseOptions type + +HTTP response parameters for redirection response + +Signature: + +```typescript +export declare type RedirectResponseOptions = HttpResponseOptions & { + headers: { + location: string; + }; +}; +``` diff --git a/docs/development/core/server/kibana-plugin-server.requesthandler.md b/docs/development/core/server/kibana-plugin-server.requesthandler.md new file mode 100644 index 00000000000000..b7e593c30f2f3c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.requesthandler.md @@ -0,0 +1,42 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RequestHandler](./kibana-plugin-server.requesthandler.md) + +## RequestHandler type + +A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) functions. + +Signature: + +```typescript +export declare type RequestHandler

= (request: KibanaRequest, TypeOf, TypeOf>, response: KibanaResponseFactory) => KibanaResponse | Promise>; +``` + +## Example + + +```ts +const router = new Router('my-app'); +// creates a route handler for GET request on 'my-app/path/{id}' path +router.get( + { + path: 'path/{id}', + // defines a validation schema for a named segment of the route path + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + // function to execute to create a responses + async (request, response) => { + const data = await findObject(request.params.id); + // creates a command to respond with 'not found' error + if (!data) return response.notFound(); + // creates a command to send found data to the client + return response.ok(data); + } +); + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.routeconfig.md b/docs/development/core/server/kibana-plugin-server.routeconfig.md new file mode 100644 index 00000000000000..87ec365dc25108 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfig.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfig](./kibana-plugin-server.routeconfig.md) + +## RouteConfig interface + +Route specific configuration. + +Signature: + +```typescript +export interface RouteConfig

+``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [options](./kibana-plugin-server.routeconfig.options.md) | RouteConfigOptions | Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md). | +| [path](./kibana-plugin-server.routeconfig.path.md) | string | The endpoint \_within\_ the router path to register the route. E.g. if the router is registered at /elasticsearch and the route path is /search, the full path for the route is /elasticsearch/search. Supports: - named path segments path/{name}. - optional path segments path/{position?}. - multi-segments path/{coordinates*2}. Segments are accessible within a handler function as params property of [KibanaRequest](./kibana-plugin-server.kibanarequest.md) object. To have read access to params you \*must\* specify validation schema with [RouteConfig.validate](./kibana-plugin-server.routeconfig.validate.md). | +| [validate](./kibana-plugin-server.routeconfig.validate.md) | RouteSchemas<P, Q, B> | false | A schema created with @kbn/config-schema that every request will be validated against. You \*must\* specify a validation schema to be able to read: - url path segments - request query - request body To opt out of validating the request, specify false. | + diff --git a/docs/development/core/server/kibana-plugin-server.routeconfig.options.md b/docs/development/core/server/kibana-plugin-server.routeconfig.options.md new file mode 100644 index 00000000000000..12ca36da6de7cb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfig.options.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfig](./kibana-plugin-server.routeconfig.md) > [options](./kibana-plugin-server.routeconfig.options.md) + +## RouteConfig.options property + +Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md). + +Signature: + +```typescript +options?: RouteConfigOptions; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfig.path.md b/docs/development/core/server/kibana-plugin-server.routeconfig.path.md new file mode 100644 index 00000000000000..3437f0e0fe0640 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfig.path.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfig](./kibana-plugin-server.routeconfig.md) > [path](./kibana-plugin-server.routeconfig.path.md) + +## RouteConfig.path property + +The endpoint \_within\_ the router path to register the route. E.g. if the router is registered at `/elasticsearch` and the route path is `/search`, the full path for the route is `/elasticsearch/search`. Supports: - named path segments `path/{name}`. - optional path segments `path/{position?}`. - multi-segments `path/{coordinates*2}`. Segments are accessible within a handler function as `params` property of [KibanaRequest](./kibana-plugin-server.kibanarequest.md) object. To have read access to `params` you \*must\* specify validation schema with [RouteConfig.validate](./kibana-plugin-server.routeconfig.validate.md). + +Signature: + +```typescript +path: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfig.validate.md b/docs/development/core/server/kibana-plugin-server.routeconfig.validate.md new file mode 100644 index 00000000000000..f7177485f5fb60 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfig.validate.md @@ -0,0 +1,32 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfig](./kibana-plugin-server.routeconfig.md) > [validate](./kibana-plugin-server.routeconfig.validate.md) + +## RouteConfig.validate property + +A schema created with `@kbn/config-schema` that every request will be validated against. You \*must\* specify a validation schema to be able to read: - url path segments - request query - request body To opt out of validating the request, specify `false`. + +Signature: + +```typescript +validate: RouteSchemas | false; +``` + +## Example + + +```ts + import { schema } from '@kbn/config-schema'; + router.get({ + path: 'path/{id}' + validate: { + params: schema.object({ + id: schema.string(), + }), + query: schema.object({...}), + body: schema.object({...}), + }, + }) + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptions.authrequired.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.authrequired.md index 3fb4426c407cd2..2bb2491cae5dfc 100644 --- a/docs/development/core/server/kibana-plugin-server.routeconfigoptions.authrequired.md +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.authrequired.md @@ -4,7 +4,7 @@ ## RouteConfigOptions.authRequired property -A flag shows that authentication for a route: enabled when true disabled when false +A flag shows that authentication for a route: `enabled` when true `disabled` when false Enabled by default. diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md index 97e480c5490fc5..b4d210ac0b7110 100644 --- a/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md @@ -4,7 +4,7 @@ ## RouteConfigOptions interface -Route specific configuration. +Additional route options. Signature: @@ -16,6 +16,6 @@ export interface RouteConfigOptions | Property | Type | Description | | --- | --- | --- | -| [authRequired](./kibana-plugin-server.routeconfigoptions.authrequired.md) | boolean | A flag shows that authentication for a route: enabled when true disabled when falseEnabled by default. | +| [authRequired](./kibana-plugin-server.routeconfigoptions.authrequired.md) | boolean | A flag shows that authentication for a route: enabled when true disabled when falseEnabled by default. | | [tags](./kibana-plugin-server.routeconfigoptions.tags.md) | readonly string[] | Additional metadata tag strings to attach to the route. | diff --git a/docs/development/core/server/kibana-plugin-server.router.(constructor).md b/docs/development/core/server/kibana-plugin-server.router.(constructor).md index 5f8e1e5e293ab7..26048a603c9f6b 100644 --- a/docs/development/core/server/kibana-plugin-server.router.(constructor).md +++ b/docs/development/core/server/kibana-plugin-server.router.(constructor).md @@ -16,5 +16,5 @@ constructor(path: string); | Parameter | Type | Description | | --- | --- | --- | -| path | string | | +| path | string | a router path, set as the very first path segment for all registered routes. | diff --git a/docs/development/core/server/kibana-plugin-server.router.delete.md b/docs/development/core/server/kibana-plugin-server.router.delete.md index cd49f80baaf70d..565dc10ce76e83 100644 --- a/docs/development/core/server/kibana-plugin-server.router.delete.md +++ b/docs/development/core/server/kibana-plugin-server.router.delete.md @@ -4,7 +4,7 @@ ## Router.delete() method -Register a `DELETE` request with the router +Register a route handler for `DELETE` request. Signature: diff --git a/docs/development/core/server/kibana-plugin-server.router.get.md b/docs/development/core/server/kibana-plugin-server.router.get.md index ab8e7c8c5a65d2..a3899eaa678f7c 100644 --- a/docs/development/core/server/kibana-plugin-server.router.get.md +++ b/docs/development/core/server/kibana-plugin-server.router.get.md @@ -4,7 +4,7 @@ ## Router.get() method -Register a `GET` request with the router +Register a route handler for `GET` request. Signature: diff --git a/docs/development/core/server/kibana-plugin-server.router.getroutes.md b/docs/development/core/server/kibana-plugin-server.router.getroutes.md deleted file mode 100644 index 3e4785a3a7c6ca..00000000000000 --- a/docs/development/core/server/kibana-plugin-server.router.getroutes.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [getRoutes](./kibana-plugin-server.router.getroutes.md) - -## Router.getRoutes() method - -Returns all routes registered with the this router. - -Signature: - -```typescript -getRoutes(): Readonly[]; -``` -Returns: - -`Readonly[]` - -List of registered routes. - diff --git a/docs/development/core/server/kibana-plugin-server.router.md b/docs/development/core/server/kibana-plugin-server.router.md index 52193bbc553c71..a5eb1c66c4f0c9 100644 --- a/docs/development/core/server/kibana-plugin-server.router.md +++ b/docs/development/core/server/kibana-plugin-server.router.md @@ -4,6 +4,7 @@ ## Router class +Provides ability to declare a handler function for a particular path and HTTP request method. Each route can have only one handler functions, which is executed when the route is matched. Signature: @@ -28,9 +29,18 @@ export declare class Router | Method | Modifiers | Description | | --- | --- | --- | -| [delete(route, handler)](./kibana-plugin-server.router.delete.md) | | Register a DELETE request with the router | -| [get(route, handler)](./kibana-plugin-server.router.get.md) | | Register a GET request with the router | -| [getRoutes()](./kibana-plugin-server.router.getroutes.md) | | Returns all routes registered with the this router. | -| [post(route, handler)](./kibana-plugin-server.router.post.md) | | Register a POST request with the router | -| [put(route, handler)](./kibana-plugin-server.router.put.md) | | Register a PUT request with the router | +| [delete(route, handler)](./kibana-plugin-server.router.delete.md) | | Register a route handler for DELETE request. | +| [get(route, handler)](./kibana-plugin-server.router.get.md) | | Register a route handler for GET request. | +| [post(route, handler)](./kibana-plugin-server.router.post.md) | | Register a route handler for POST request. | +| [put(route, handler)](./kibana-plugin-server.router.put.md) | | Register a route handler for PUT request. | + +## Example + + +```ts +const router = new Router('my-app'); +// handler is called when 'my-app/path' resource is requested with `GET` method +router.get({ path: '/path', validate: false }, (req, res) => res.ok({ content: 'ok' })); + +``` diff --git a/docs/development/core/server/kibana-plugin-server.router.post.md b/docs/development/core/server/kibana-plugin-server.router.post.md index a499a46b1ee79a..7aca35466d643a 100644 --- a/docs/development/core/server/kibana-plugin-server.router.post.md +++ b/docs/development/core/server/kibana-plugin-server.router.post.md @@ -4,7 +4,7 @@ ## Router.post() method -Register a `POST` request with the router +Register a route handler for `POST` request. Signature: diff --git a/docs/development/core/server/kibana-plugin-server.router.put.md b/docs/development/core/server/kibana-plugin-server.router.put.md index 7b1337279cca9a..760ccf9ef88e8d 100644 --- a/docs/development/core/server/kibana-plugin-server.router.put.md +++ b/docs/development/core/server/kibana-plugin-server.router.put.md @@ -4,7 +4,7 @@ ## Router.put() method -Register a `PUT` request with the router +Register a route handler for `PUT` request. Signature: diff --git a/docs/development/core/server/kibana-plugin-server.scopedclusterclient.(constructor).md b/docs/development/core/server/kibana-plugin-server.scopedclusterclient.(constructor).md index 94b49e43a113c8..0fea07320b2f9f 100644 --- a/docs/development/core/server/kibana-plugin-server.scopedclusterclient.(constructor).md +++ b/docs/development/core/server/kibana-plugin-server.scopedclusterclient.(constructor).md @@ -9,7 +9,7 @@ Constructs a new instance of the `ScopedClusterClient` class Signature: ```typescript -constructor(internalAPICaller: APICaller, scopedAPICaller: APICaller, headers?: Record | undefined); +constructor(internalAPICaller: APICaller, scopedAPICaller: APICaller, headers?: Headers | undefined); ``` ## Parameters @@ -18,5 +18,5 @@ constructor(internalAPICaller: APICaller, scopedAPICaller: APICaller, headers?: | --- | --- | --- | | internalAPICaller | APICaller | | | scopedAPICaller | APICaller | | -| headers | Record<string, string | string[] | undefined> | undefined | | +| headers | Headers | undefined | | diff --git a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md new file mode 100644 index 00000000000000..167ab03d7567f5 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) > [encryptionKey](./kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md) + +## SessionStorageCookieOptions.encryptionKey property + +A key used to encrypt a cookie value. Should be at least 32 characters long. + +Signature: + +```typescript +encryptionKey: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.issecure.md b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.issecure.md new file mode 100644 index 00000000000000..824fc9d136a3ff --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.issecure.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) > [isSecure](./kibana-plugin-server.sessionstoragecookieoptions.issecure.md) + +## SessionStorageCookieOptions.isSecure property + +Flag indicating whether the cookie should be sent only via a secure connection. + +Signature: + +```typescript +isSecure: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.md b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.md new file mode 100644 index 00000000000000..de412818142f25 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) + +## SessionStorageCookieOptions interface + +Configuration used to create HTTP session storage based on top of cookie mechanism. + +Signature: + +```typescript +export interface SessionStorageCookieOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [encryptionKey](./kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md) | string | A key used to encrypt a cookie value. Should be at least 32 characters long. | +| [isSecure](./kibana-plugin-server.sessionstoragecookieoptions.issecure.md) | boolean | Flag indicating whether the cookie should be sent only via a secure connection. | +| [name](./kibana-plugin-server.sessionstoragecookieoptions.name.md) | string | Name of the session cookie. | +| [validate](./kibana-plugin-server.sessionstoragecookieoptions.validate.md) | (sessionValue: T) => boolean | Promise<boolean> | Function called to validate a cookie content. | + diff --git a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.name.md b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.name.md new file mode 100644 index 00000000000000..e6bc7ea3fe00f1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.name.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) > [name](./kibana-plugin-server.sessionstoragecookieoptions.name.md) + +## SessionStorageCookieOptions.name property + +Name of the session cookie. + +Signature: + +```typescript +name: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.validate.md b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.validate.md new file mode 100644 index 00000000000000..f3cbfc0d84e18e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.validate.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) > [validate](./kibana-plugin-server.sessionstoragecookieoptions.validate.md) + +## SessionStorageCookieOptions.validate property + +Function called to validate a cookie content. + +Signature: + +```typescript +validate: (sessionValue: T) => boolean | Promise; +``` diff --git a/src/core/server/elasticsearch/cluster_client.ts b/src/core/server/elasticsearch/cluster_client.ts index c8500499d97029..aa40010bf3b1ac 100644 --- a/src/core/server/elasticsearch/cluster_client.ts +++ b/src/core/server/elasticsearch/cluster_client.ts @@ -18,10 +18,9 @@ */ import { Client } from 'elasticsearch'; import { get } from 'lodash'; -import { Request } from 'hapi'; import { ElasticsearchErrorHelpers } from './errors'; -import { GetAuthHeaders, isRealRequest } from '../http'; +import { GetAuthHeaders, isRealRequest, LegacyRequest } from '../http'; import { filterHeaders, Headers, KibanaRequest, ensureRawRequest } from '../http/router'; import { Logger } from '../logging'; import { @@ -36,8 +35,6 @@ import { ScopedClusterClient } from './scoped_cluster_client'; * @public */ -export type LegacyRequest = Request; - const noop = () => undefined; /** * The set of options that defines how API call should be made and result be diff --git a/src/core/server/elasticsearch/index.ts b/src/core/server/elasticsearch/index.ts index 1d439dfba49e9e..f732f9e39b9e30 100644 --- a/src/core/server/elasticsearch/index.ts +++ b/src/core/server/elasticsearch/index.ts @@ -18,7 +18,7 @@ */ export { ElasticsearchServiceSetup, ElasticsearchService } from './elasticsearch_service'; -export { CallAPIOptions, ClusterClient, FakeRequest, LegacyRequest } from './cluster_client'; +export { CallAPIOptions, ClusterClient, FakeRequest } from './cluster_client'; export { ScopedClusterClient, Headers, APICaller } from './scoped_cluster_client'; export { ElasticsearchClientConfig } from './elasticsearch_client_config'; export { config } from './elasticsearch_config'; diff --git a/src/core/server/http/auth_headers_storage.ts b/src/core/server/http/auth_headers_storage.ts index bc3b55b3718c01..469e194a61fed2 100644 --- a/src/core/server/http/auth_headers_storage.ts +++ b/src/core/server/http/auth_headers_storage.ts @@ -16,19 +16,21 @@ * specific language governing permissions and limitations * under the License. */ -import { Request } from 'hapi'; -import { KibanaRequest, ensureRawRequest } from './router'; +import { KibanaRequest, ensureRawRequest, LegacyRequest } from './router'; import { AuthHeaders } from './lifecycle/auth'; /** * Get headers to authenticate a user against Elasticsearch. + * @param request {@link KibanaRequest} - an incoming request. + * @return authentication headers {@link AuthHeaders} for - an incoming request. * @public * */ -export type GetAuthHeaders = (request: KibanaRequest | Request) => AuthHeaders | undefined; +export type GetAuthHeaders = (request: KibanaRequest | LegacyRequest) => AuthHeaders | undefined; +/** @internal */ export class AuthHeadersStorage { - private authHeadersCache = new WeakMap(); - public set = (request: KibanaRequest | Request, headers: AuthHeaders) => { + private authHeadersCache = new WeakMap(); + public set = (request: KibanaRequest | LegacyRequest, headers: AuthHeaders) => { this.authHeadersCache.set(ensureRawRequest(request), headers); }; public get: GetAuthHeaders = request => { diff --git a/src/core/server/http/auth_state_storage.ts b/src/core/server/http/auth_state_storage.ts index 79fd9ed64f3b54..059dc7f3803514 100644 --- a/src/core/server/http/auth_state_storage.ts +++ b/src/core/server/http/auth_state_storage.ts @@ -16,22 +16,51 @@ * specific language governing permissions and limitations * under the License. */ -import { Request } from 'hapi'; -import { KibanaRequest, ensureRawRequest } from './router'; +import { ensureRawRequest, KibanaRequest, LegacyRequest } from './router'; +/** + * Status indicating an outcome of the authentication. + * @public + */ export enum AuthStatus { + /** + * `auth` interceptor successfully authenticated a user + */ authenticated = 'authenticated', + /** + * `auth` interceptor failed user authentication + */ unauthenticated = 'unauthenticated', + /** + * `auth` interceptor has not been registered + */ unknown = 'unknown', } +/** + * Get authentication state for a request. Returned by `auth` interceptor. + * @param request {@link KibanaRequest} - an incoming request. + * @public + */ +export type GetAuthState = ( + request: KibanaRequest | LegacyRequest +) => { status: AuthStatus; state: unknown }; + +/** + * Return authentication status for a request. + * @param request {@link KibanaRequest} - an incoming request. + * @public + */ +export type IsAuthenticated = (request: KibanaRequest | LegacyRequest) => boolean; + +/** @internal */ export class AuthStateStorage { - private readonly storage = new WeakMap(); + private readonly storage = new WeakMap(); constructor(private readonly canBeAuthenticated: () => boolean) {} - public set = (request: KibanaRequest | Request, state: unknown) => { + public set = (request: KibanaRequest | LegacyRequest, state: unknown) => { this.storage.set(ensureRawRequest(request), state); }; - public get = (request: KibanaRequest | Request) => { + public get: GetAuthState = request => { const key = ensureRawRequest(request); const state = this.storage.get(key); const status: AuthStatus = this.storage.has(key) @@ -42,7 +71,7 @@ export class AuthStateStorage { return { status, state }; }; - public isAuthenticated = (request: KibanaRequest | Request) => { + public isAuthenticated: IsAuthenticated = request => { return this.get(request).status === AuthStatus.authenticated; }; } diff --git a/src/core/server/http/base_path_service.ts b/src/core/server/http/base_path_service.ts index df189d29f2f594..951463a2c9919f 100644 --- a/src/core/server/http/base_path_service.ts +++ b/src/core/server/http/base_path_service.ts @@ -16,24 +16,23 @@ * specific language governing permissions and limitations * under the License. */ -import { Request } from 'hapi'; -import { KibanaRequest, ensureRawRequest } from './router'; +import { ensureRawRequest, KibanaRequest, LegacyRequest } from './router'; import { modifyUrl } from '../../utils'; export class BasePath { - private readonly basePathCache = new WeakMap(); + private readonly basePathCache = new WeakMap(); constructor(private readonly serverBasePath?: string) {} - public get = (request: KibanaRequest | Request) => { + public get = (request: KibanaRequest | LegacyRequest) => { const requestScopePath = this.basePathCache.get(ensureRawRequest(request)) || ''; const serverBasePath = this.serverBasePath || ''; return `${serverBasePath}${requestScopePath}`; }; // should work only for KibanaRequest as soon as spaces migrate to NP - public set = (request: KibanaRequest | Request, requestSpecificBasePath: string) => { + public set = (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => { const rawRequest = ensureRawRequest(request); if (this.basePathCache.has(rawRequest)) { diff --git a/src/core/server/http/cookie_session_storage.ts b/src/core/server/http/cookie_session_storage.ts index 7b2569a1c6dd33..8a1b56d87fb4c9 100644 --- a/src/core/server/http/cookie_session_storage.ts +++ b/src/core/server/http/cookie_session_storage.ts @@ -24,10 +24,26 @@ import { KibanaRequest, ensureRawRequest } from './router'; import { SessionStorageFactory, SessionStorage } from './session_storage'; import { Logger } from '..'; +/** + * Configuration used to create HTTP session storage based on top of cookie mechanism. + * @public + */ export interface SessionStorageCookieOptions { + /** + * Name of the session cookie. + */ name: string; + /** + * A key used to encrypt a cookie value. Should be at least 32 characters long. + */ encryptionKey: string; + /** + * Function called to validate a cookie content. + */ validate: (sessionValue: T) => boolean | Promise; + /** + * Flag indicating whether the cookie should be sent only via a secure connection. + */ isSecure: boolean; } diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index c8b4c5c17f05e1..d90fb880a581c2 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -25,21 +25,98 @@ import { createServer, getListenerOptions, getServerOptions } from './http_tools import { adoptToHapiAuthFormat, AuthenticationHandler } from './lifecycle/auth'; import { adoptToHapiOnPostAuthFormat, OnPostAuthHandler } from './lifecycle/on_post_auth'; import { adoptToHapiOnPreAuthFormat, OnPreAuthHandler } from './lifecycle/on_pre_auth'; -import { Router, KibanaRequest, ResponseHeaders } from './router'; +import { KibanaRequest, LegacyRequest, ResponseHeaders, Router } from './router'; import { SessionStorageCookieOptions, createCookieSessionStorageFactory, } from './cookie_session_storage'; import { SessionStorageFactory } from './session_storage'; -import { AuthStateStorage } from './auth_state_storage'; -import { AuthHeadersStorage } from './auth_headers_storage'; +import { AuthStateStorage, GetAuthState, IsAuthenticated } from './auth_state_storage'; +import { AuthHeadersStorage, GetAuthHeaders } from './auth_headers_storage'; import { BasePath } from './base_path_service'; +/** + * Kibana HTTP Service provides own abstraction for work with HTTP stack. + * Plugins don't have direct access to `hapi` server and its primitives anymore. Moreover, + * plugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood. + * This gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins. + * If the HTTP Service lacks functionality you need, we are happy to discuss and support your needs. + * + * @example + * To handle an incoming request in your plugin you should: + * - Create a `Router` instance. Use `plugin-id` as a prefix path segment for your routes. + * ```ts + * import { Router } from 'src/core/server'; + * const router = new Router('my-app'); + * ``` + * + * - Use `@kbn/config-schema` package to create a schema to validate the request `params`, `query`, and `body`. Every incoming request will be validated against the created schema. If validation failed, the request is rejected with `400` status and `Bad request` error without calling the route's handler. + * To opt out of validating the request, specify `false`. + * ```ts + * import { schema, TypeOf } from '@kbn/config-schema'; + * const validate = { + * params: schema.object({ + * id: schema.string(), + * }), + * }; + * ``` + * + * - Declare a function to respond to incoming request. + * The function will receive `request` object containing request details: url, headers, matched route, as well as validated `params`, `query`, `body`. + * And `response` object instructing HTTP server to create HTTP response with information sent back to the client as the response body, headers, and HTTP status. + * Unlike, `hapi` route handler in the Legacy platform, any exception raised during the handler call will generate `500 Server error` response and log error details for further investigation. See below for returning custom error responses. + * ```ts + * const handler = async (request: KibanaRequest, response: ResponseFactory) => { + * const data = await findObject(request.params.id); + * // creates a command to respond with 'not found' error + * if (!data) return response.notFound(); + * // creates a command to send found data to the client and set response headers + * return response.ok(data, { + * headers: { + * 'content-type': 'application/json' + * } + * }); + * } + * ``` + * + * - Register route handler for GET request to 'my-app/path/{id}' path + * ```ts + * import { schema, TypeOf } from '@kbn/config-schema'; + * import { Router } from 'src/core/server'; + * const router = new Router('my-app'); + * + * const validate = { + * params: schema.object({ + * id: schema.string(), + * }), + * }; + * + * router.get({ + * path: 'path/{id}', + * validate + * }, + * async (request, response) => { + * const data = await findObject(request.params.id); + * if (!data) return response.notFound(); + * return response.ok(data, { + * headers: { + * 'content-type': 'application/json' + * } + * }); + * }); + * ``` + * @public + */ export interface HttpServerSetup { server: Server; + /** + * Add all the routes registered with `router` to HTTP server request listeners. + * @param router {@link Router} - a router with registered route handlers. + */ registerRouter: (router: Router) => void; /** * Creates cookie based session storage factory {@link SessionStorageFactory} + * @param cookieOptions {@link SessionStorageCookieOptions} - options to configure created cookie session storage. */ createCookieSessionStorageFactory: ( cookieOptions: SessionStorageCookieOptions @@ -49,35 +126,53 @@ export interface HttpServerSetup { * 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. + * @param handler {@link AuthenticationHandler} - function to perform authentication. */ registerAuth: (handler: AuthenticationHandler) => void; /** * 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 + * interceptor 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). + * @param handler {@link OnPreAuthHandler} - function to call. */ registerOnPreAuth: (handler: OnPreAuthHandler) => void; /** - * To define custom logic to perform for incoming requests. Runs the handler after Auth hook + * To define custom logic to perform for incoming requests. Runs the handler after Auth interceptor * 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). + * @param handler {@link OnPostAuthHandler} - function to call. */ registerOnPostAuth: (handler: OnPostAuthHandler) => void; basePath: { - get: (request: KibanaRequest | Request) => string; - set: (request: KibanaRequest | Request, basePath: string) => void; + /** + * returns `basePath` value, specific for an incoming request. + */ + get: (request: KibanaRequest | LegacyRequest) => string; + /** + * sets `basePath` value, specific for an incoming request. + */ + set: (request: KibanaRequest | LegacyRequest, basePath: string) => void; + /** + * returns a new `basePath` value, prefixed with passed `url`. + */ prepend: (url: string) => string; + /** + * returns a new `basePath` value, cleaned up from passed `url`. + */ remove: (url: string) => string; }; auth: { - get: AuthStateStorage['get']; - isAuthenticated: AuthStateStorage['isAuthenticated']; - getAuthHeaders: AuthHeadersStorage['get']; + get: GetAuthState; + isAuthenticated: IsAuthenticated; + getAuthHeaders: GetAuthHeaders; }; + /** + * Flag showing whether a server was configured to use TLS connection. + */ isTlsEnabled: boolean; } diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index cdfd2b09d75b12..e69906d512bacc 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -135,7 +135,7 @@ export class HttpService implements CoreService; -export type ResponseHeaders = Record; +/** + * Creates a Union type of all known keys of a given interface. + * @example + * ```ts + * interface Person { + * name: string; + * age: number; + * [attributes: string]: string | number; + * } + * type PersonKnownKeys = KnownKeys; // "age" | "name" + * ``` + */ +type KnownKeys = { + [K in keyof T]: string extends K ? never : number extends K ? never : K; +} extends { [_ in keyof T]: infer U } + ? U + : never; + +/** + * Set of well-known HTTP headers. + * @public + */ +export type KnownHeaders = KnownKeys; + +/** + * Http request headers to read. + * @public + */ +export type Headers = { [header in KnownHeaders]?: string | string[] | undefined } & { + [header: string]: string | string[] | undefined; +}; + +/** + * Http response headers to set. + * @public + */ +export type ResponseHeaders = { [header in KnownHeaders]?: string | string[] } & { + [header: string]: string | string[]; +}; const normalizeHeaderField = (field: string) => field.trim().toLowerCase(); diff --git a/src/core/server/http/router/index.ts b/src/core/server/http/router/index.ts index eefa74cee0802e..f9009949825ba3 100644 --- a/src/core/server/http/router/index.ts +++ b/src/core/server/http/router/index.ts @@ -17,8 +17,23 @@ * under the License. */ -export { Headers, filterHeaders, ResponseHeaders } from './headers'; -export { Router } from './router'; -export { KibanaRequest, KibanaRequestRoute, ensureRawRequest, isRealRequest } from './request'; -export { RouteMethod, RouteConfigOptions } from './route'; -export { ResponseError, ResponseErrorMeta } from './response'; +export { Headers, filterHeaders, ResponseHeaders, KnownHeaders } from './headers'; +export { Router, RequestHandler } from './router'; +export { + KibanaRequest, + KibanaRequestRoute, + isRealRequest, + LegacyRequest, + ensureRawRequest, +} from './request'; +export { RouteMethod, RouteConfig, RouteConfigOptions } from './route'; +export { + CustomHttpResponseOptions, + HttpResponseOptions, + HttpResponsePayload, + RedirectResponseOptions, + ResponseError, + ResponseErrorMeta, + kibanaResponseFactory, + KibanaResponseFactory, +} from './response'; diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index da10a6500ccc86..4eac2e98317fcc 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -38,16 +38,22 @@ export interface KibanaRequestRoute { options: Required; } +/** + * @deprecated + * `hapi` request object, supported during migration process only for backward compatibility. + * @public + */ +export interface LegacyRequest extends Request {} // eslint-disable-line @typescript-eslint/no-empty-interface + /** * Kibana specific abstraction for an incoming request. * @public - * */ + */ export class KibanaRequest { /** * Factory for creating requests. Validates the request before creating an * instance of a KibanaRequest. * @internal - * */ public static from

( req: Request, @@ -68,6 +74,7 @@ export class KibanaRequest { * Validates the different parts of a request based on the schemas defined for * the route. Builds up the actual params, query and body object that will be * received in the route handler. + * @internal */ private static validate

( req: Request, @@ -102,8 +109,9 @@ export class KibanaRequest { return { query, params, body }; } - + /** a WHATWG URL standard object. */ public readonly url: Url; + /** matched route details */ public readonly route: RecursiveReadonly; /** * Readonly copy of incoming request headers. @@ -153,14 +161,14 @@ export class KibanaRequest { * Returns underlying Hapi Request * @internal */ -export const ensureRawRequest = (request: KibanaRequest | Request) => +export const ensureRawRequest = (request: KibanaRequest | LegacyRequest) => isKibanaRequest(request) ? request[requestSymbol] : request; function isKibanaRequest(request: unknown): request is KibanaRequest { return request instanceof KibanaRequest; } -function isRequest(request: any): request is Request { +function isRequest(request: any): request is LegacyRequest { try { return request.raw.req && typeof request.raw.req === 'object'; } catch { @@ -172,6 +180,6 @@ function isRequest(request: any): request is Request { * Checks if an incoming request either KibanaRequest or Legacy.Request * @internal */ -export function isRealRequest(request: unknown): request is KibanaRequest | Request { +export function isRealRequest(request: unknown): request is KibanaRequest | LegacyRequest { return isKibanaRequest(request) || isRequest(request); } diff --git a/src/core/server/http/router/response.ts b/src/core/server/http/router/response.ts index 65db87f8ae8f8e..100c4b0d660cd8 100644 --- a/src/core/server/http/router/response.ts +++ b/src/core/server/http/router/response.ts @@ -16,8 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import { IncomingHttpHeaders } from 'http'; import { Stream } from 'stream'; +import { ResponseHeaders } from './headers'; /** * Additional metadata to enhance error output or provide error details. @@ -40,6 +40,10 @@ export type ResponseError = meta?: ResponseErrorMeta; }; +/** + * A response data object, expected to returned as a result of {@link RequestHandler} execution + * @internal + */ export class KibanaResponse { constructor( readonly status: number, @@ -48,51 +52,31 @@ export class KibanaResponse { ) {} } -/** - * Creates a Union type of all known keys of a given interface. - * @example - * ```ts - * interface Person { - * name: string; - * age: number; - * [attributes: string]: string | number; - * } - * type PersonKnownKeys = KnownKeys; // "age" | "name" - * ``` - */ -type KnownKeys = { - [K in keyof T]: string extends K ? never : number extends K ? never : K; -} extends { [_ in keyof T]: infer U } - ? U - : never; - -type KnownHeaders = KnownKeys; /** * HTTP response parameters * @public */ export interface HttpResponseOptions { /** HTTP Headers with additional information about response */ - headers?: { [header in KnownHeaders]?: string | string[] } & { - [header: string]: string | string[]; - }; + headers?: ResponseHeaders; } /** + * Data send to the client as a response payload. * @public */ export type HttpResponsePayload = undefined | string | Record | Buffer | Stream; /** - * HTTP response parameters + * HTTP response parameters for a response with adjustable status code. * @public */ -export interface CustomResponseOptions extends HttpResponseOptions { +export interface CustomHttpResponseOptions extends HttpResponseOptions { statusCode: number; } /** - * HTTP response parameters + * HTTP response parameters for redirection response * @public */ export type RedirectResponseOptions = HttpResponseOptions & { @@ -101,7 +85,99 @@ export type RedirectResponseOptions = HttpResponseOptions & { }; }; -export const responseFactory = { +/** + * Set of helpers used to create `KibanaResponse` to form HTTP response on an incoming request. + * Should be returned as a result of {@link RequestHandler} execution. + * + * @example + * 1. Successful response. Supported types of response body are: + * - `undefined`, no content to send. + * - `string`, send text + * - `JSON`, send JSON object, HTTP server will throw if given object is not valid (has circular references, for example) + * - `Stream` send data stream + * - `Buffer` send binary stream + * ```js + * return response.ok(undefined); + * return response.ok('ack'); + * return response.ok({ id: '1' }); + * return response.ok(Buffer.from(...);); + * + * const stream = new Stream.PassThrough(); + * fs.createReadStream('./file').pipe(stream); + * return res.ok(stream); + * ``` + * HTTP headers are configurable via response factory parameter `options` {@link HttpResponseOptions}. + * + * ```js + * return response.ok({ id: '1' }, { + * headers: { + * 'content-type': 'application/json' + * } + * }); + * ``` + * 2. Redirection response. Redirection URL is configures via 'Location' header. + * ```js + * return response.redirected('The document has moved', { + * headers: { + * location: '/new-url', + * }, + * }); + * ``` + * 3. Error response. You may pass an error message to the client, where error message can be: + * - `string` send message text + * - `Error` send the message text of given Error object. + * - `{ message: string | Error, meta: {data: Record, ...} }` - send message text and attach additional error metadata. + * ```js + * return response.unauthorized('User has no access to the requested resource.', { + * headers: { + * 'WWW-Authenticate': 'challenge', + * } + * }) + * return response.badRequest(); + * return response.badRequest('validation error'); + * + * try { + * // ... + * } catch(error){ + * return response.badRequest(error); + * } + * + * return response.badRequest({ + * message: 'validation error', + * meta: { + * data: { + * requestBody: request.body, + * failedFields: validationResult + * }, + * } + * }); + * + * try { + * // ... + * } catch(error) { + * return response.badRequest({ + * message: error, + * meta: { + * data: { + * requestBody: request.body, + * }, + * } + * }); + * } + * + * ``` + * 4. Custom response. `ResponseFactory` may not cover your use case, so you can use the `custom` function to customize the response. + * ```js + * return response.custom('ok', { + * statusCode: 201, + * headers: { + * location: '/created-url' + * } + * }) + * ``` + * @public + */ +export const kibanaResponseFactory = { // Success /** * The request has succeeded. @@ -131,9 +207,9 @@ export const responseFactory = { /** * Creates a response with defined status code and payload. * @param payload - {@link HttpResponsePayload} payload to send to the client - * @param options - {@link CustomResponseOptions} configures HTTP response parameters. + * @param options - {@link CustomHttpResponseOptions} configures HTTP response parameters. */ - custom: (payload: HttpResponsePayload | ResponseError, options: CustomResponseOptions) => { + custom: (payload: HttpResponsePayload | ResponseError, options: CustomHttpResponseOptions) => { if (!options || !options.statusCode) { throw new Error(`options.statusCode is expected to be set. given options: ${options}`); } @@ -213,4 +289,4 @@ export const responseFactory = { * Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. * @public */ -export type ResponseFactory = typeof responseFactory; +export type KibanaResponseFactory = typeof kibanaResponseFactory; diff --git a/src/core/server/http/router/route.ts b/src/core/server/http/router/route.ts index caf13343ec96ca..e8053560148293 100644 --- a/src/core/server/http/router/route.ts +++ b/src/core/server/http/router/route.ts @@ -21,18 +21,18 @@ import { ObjectType } from '@kbn/config-schema'; /** * The set of common HTTP methods supported by Kibana routing. * @public - * */ + */ export type RouteMethod = 'get' | 'post' | 'put' | 'delete'; /** - * Route specific configuration. + * Additional route options. * @public - * */ + */ export interface RouteConfigOptions { /** * A flag shows that authentication for a route: - * enabled when true - * disabled when false + * `enabled` when true + * `disabled` when false * * Enabled by default. */ @@ -44,27 +44,58 @@ export interface RouteConfigOptions { tags?: readonly string[]; } +/** + * Route specific configuration. + * @public + */ export interface RouteConfig

{ /** * The endpoint _within_ the router path to register the route. E.g. if the * router is registered at `/elasticsearch` and the route path is `/search`, * the full path for the route is `/elasticsearch/search`. + * Supports: + * - named path segments `path/{name}`. + * - optional path segments `path/{position?}`. + * - multi-segments `path/{coordinates*2}`. + * Segments are accessible within a handler function as `params` property of {@link KibanaRequest} object. + * To have read access to `params` you *must* specify validation schema with {@link RouteConfig.validate}. */ path: string; /** * A schema created with `@kbn/config-schema` that every request will be validated against. - * + * You *must* specify a validation schema to be able to read: + * - url path segments + * - request query + * - request body * To opt out of validating the request, specify `false`. + * @example + * ```ts + * import { schema } from '@kbn/config-schema'; + * router.get({ + * path: 'path/{id}' + * validate: { + * params: schema.object({ + * id: schema.string(), + * }), + * query: schema.object({...}), + * body: schema.object({...}), + * }, + * }) + * ``` */ validate: RouteSchemas | false; + /** + * Additional route options {@link RouteConfigOptions}. + */ options?: RouteConfigOptions; } /** * RouteSchemas contains the schemas for validating the different parts of a * request. + * @public */ export interface RouteSchemas

{ params?: P; diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index b801aa41754faa..a4d4b62e40203b 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -22,25 +22,41 @@ import { Request, ResponseObject, ResponseToolkit } from 'hapi'; import { Logger } from '../../logging'; import { KibanaRequest } from './request'; -import { KibanaResponse, ResponseFactory, responseFactory } from './response'; +import { KibanaResponse, KibanaResponseFactory, kibanaResponseFactory } from './response'; import { RouteConfig, RouteConfigOptions, RouteMethod, RouteSchemas } from './route'; import { HapiResponseAdapter } from './response_adapter'; -export interface RouterRoute { +interface RouterRoute { method: RouteMethod; path: string; options: RouteConfigOptions; handler: (req: Request, responseToolkit: ResponseToolkit, log: Logger) => Promise; } -/** @public */ +/** + * Provides ability to declare a handler function for a particular path and HTTP request method. + * Each route can have only one handler functions, which is executed when the route is matched. + * + * @example + * ```ts + * const router = new Router('my-app'); + * // handler is called when 'my-app/path' resource is requested with `GET` method + * router.get({ path: '/path', validate: false }, (req, res) => res.ok({ content: 'ok' })); + * ``` + * + * @public + * */ export class Router { public routes: Array> = []; - + /** + * @param path - a router path, set as the very first path segment for all registered routes. + */ constructor(readonly path: string) {} /** - * Register a `GET` request with the router + * Register a route handler for `GET` request. + * @param route {@link RouteConfig} - a route configuration. + * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ public get

( route: RouteConfig, @@ -58,7 +74,9 @@ export class Router { } /** - * Register a `POST` request with the router + * Register a route handler for `POST` request. + * @param route {@link RouteConfig} - a route configuration. + * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ public post

( route: RouteConfig, @@ -76,7 +94,9 @@ export class Router { } /** - * Register a `PUT` request with the router + * Register a route handler for `PUT` request. + * @param route {@link RouteConfig} - a route configuration. + * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ public put

( route: RouteConfig, @@ -94,7 +114,9 @@ export class Router { } /** - * Register a `DELETE` request with the router + * Register a route handler for `DELETE` request. + * @param route {@link RouteConfig} - a route configuration. + * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ public delete

( route: RouteConfig, @@ -114,13 +136,14 @@ export class Router { /** * Returns all routes registered with the this router. * @returns List of registered routes. + * @internal */ public getRoutes() { return [...this.routes]; } /** - * Create the schemas for a route + * Create the validation schemas for a route * * @returns Route schemas if `validate` is specified on the route, otherwise * undefined. @@ -167,7 +190,7 @@ export class Router { } try { - const kibanaResponse = await handler(kibanaRequest, responseFactory); + const kibanaResponse = await handler(kibanaRequest, kibanaResponseFactory); return hapiResponseAdapter.handle(kibanaResponse); } catch (e) { log.error(e); @@ -176,7 +199,40 @@ export class Router { } } +/** + * A function executed when route path matched requested resource path. + * Request handler is expected to return a result of one of {@link KibanaResponseFactory} functions. + * @param request {@link KibanaRequest} - object containing information about requested resource, + * such as path, method, headers, parameters, query, body, etc. + * @param response {@link KibanaResponseFactory} - a set of helper functions used to respond to a request. + * + * @example + * ```ts + * const router = new Router('my-app'); + * // creates a route handler for GET request on 'my-app/path/{id}' path + * router.get( + * { + * path: 'path/{id}', + * // defines a validation schema for a named segment of the route path + * validate: { + * params: schema.object({ + * id: schema.string(), + * }), + * }, + * }, + * // function to execute to create a responses + * async (request, response) => { + * const data = await findObject(request.params.id); + * // creates a command to respond with 'not found' error + * if (!data) return response.notFound(); + * // creates a command to send found data to the client + * return response.ok(data); + * } + * ); + * ``` + * @public + */ export type RequestHandler

= ( request: KibanaRequest, TypeOf, TypeOf>, - createResponse: ResponseFactory + response: KibanaResponseFactory ) => KibanaResponse | Promise>; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 55943e0ecf491c..72a183f2dd1db8 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -56,27 +56,41 @@ export { ElasticsearchErrorHelpers, APICaller, FakeRequest, - LegacyRequest, } from './elasticsearch'; export { AuthenticationHandler, AuthHeaders, AuthResultParams, + AuthStatus, AuthToolkit, + CustomHttpResponseOptions, GetAuthHeaders, + GetAuthState, + HttpResponseOptions, + HttpResponsePayload, + HttpServerSetup, + IsAuthenticated, KibanaRequest, KibanaRequestRoute, + KnownHeaders, + LegacyRequest, OnPreAuthHandler, OnPreAuthToolkit, OnPostAuthHandler, OnPostAuthToolkit, + RedirectResponseOptions, + RequestHandler, ResponseError, ResponseErrorMeta, + kibanaResponseFactory, + KibanaResponseFactory, + RouteConfig, Router, RouteMethod, RouteConfigOptions, - SessionStorageFactory, SessionStorage, + SessionStorageCookieOptions, + SessionStorageFactory, } from './http'; export { Logger, LoggerFactory, LogMeta, LogRecord, LogLevel } from './logging'; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index d149b8fdec373e..94bed2ad01e640 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -39,6 +39,13 @@ export interface AuthResultParams { state?: Record; } +// @public +export enum AuthStatus { + authenticated = "authenticated", + unauthenticated = "unauthenticated", + unknown = "unknown" +} + // @public export interface AuthToolkit { authenticated: (data?: AuthResultParams) => AuthResult; @@ -108,6 +115,12 @@ export interface CoreSetup { export interface CoreStart { } +// @public +export interface CustomHttpResponseOptions extends HttpResponseOptions { + // (undocumented) + statusCode: number; +} + // @public export interface DiscoveredPlugin { readonly configPath: ConfigPath; @@ -161,13 +174,55 @@ export interface FakeRequest { } // @public -export type GetAuthHeaders = (request: KibanaRequest | Request) => AuthHeaders | undefined; +export type GetAuthHeaders = (request: KibanaRequest | LegacyRequest) => AuthHeaders | undefined; -// @public (undocumented) -export type Headers = Record; +// @public +export type GetAuthState = (request: KibanaRequest | LegacyRequest) => { + status: AuthStatus; + state: unknown; +}; + +// @public +export type Headers = { + [header in KnownHeaders]?: string | string[] | undefined; +} & { + [header: string]: string | string[] | undefined; +}; + +// @public +export interface HttpResponseOptions { + // Warning: (ae-forgotten-export) The symbol "ResponseHeaders" needs to be exported by the entry point index.d.ts + headers?: ResponseHeaders; +} + +// @public +export type HttpResponsePayload = undefined | string | Record | Buffer | Stream; + +// @public +export interface HttpServerSetup { + // (undocumented) + auth: { + get: GetAuthState; + isAuthenticated: IsAuthenticated; + getAuthHeaders: GetAuthHeaders; + }; + // (undocumented) + basePath: { + get: (request: KibanaRequest | LegacyRequest) => string; + set: (request: KibanaRequest | LegacyRequest, basePath: string) => void; + prepend: (url: string) => string; + remove: (url: string) => string; + }; + createCookieSessionStorageFactory: (cookieOptions: SessionStorageCookieOptions) => Promise>; + isTlsEnabled: boolean; + registerAuth: (handler: AuthenticationHandler) => void; + registerOnPostAuth: (handler: OnPostAuthHandler) => void; + registerOnPreAuth: (handler: OnPreAuthHandler) => void; + registerRouter: (router: Router) => void; + // (undocumented) + server: Server; +} -// Warning: (ae-forgotten-export) The symbol "HttpServerSetup" needs to be exported by the entry point index.d.ts -// // @public (undocumented) export type HttpServiceSetup = HttpServerSetup; @@ -192,6 +247,9 @@ export interface InternalCoreStart { plugins: PluginsServiceStart; } +// @public +export type IsAuthenticated = (request: KibanaRequest | LegacyRequest) => boolean; + // @public export class KibanaRequest { // @internal (undocumented) @@ -208,9 +266,7 @@ export class KibanaRequest { readonly params: Params; // (undocumented) readonly query: Query; - // (undocumented) readonly route: RecursiveReadonly; - // (undocumented) readonly url: Url; } @@ -225,7 +281,37 @@ export interface KibanaRequestRoute { } // @public -export type LegacyRequest = Request; +export type KibanaResponseFactory = typeof kibanaResponseFactory; + +// @public +export const kibanaResponseFactory: { + ok: (payload: HttpResponsePayload, options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; + accepted: (payload?: HttpResponsePayload, options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; + noContent: (options?: HttpResponseOptions) => KibanaResponse; + custom: (payload: string | Error | Record | Buffer | Stream | { + message: string | Error; + meta?: ResponseErrorMeta | undefined; + } | undefined, options: CustomHttpResponseOptions) => KibanaResponse | Buffer | Stream | { + message: string | Error; + meta?: ResponseErrorMeta | undefined; + }>; + redirected: (payload: HttpResponsePayload, options: RedirectResponseOptions) => KibanaResponse | Buffer | Stream>; + badRequest: (error?: ResponseError, options?: HttpResponseOptions) => KibanaResponse; + unauthorized: (error?: ResponseError, options?: HttpResponseOptions) => KibanaResponse; + forbidden: (error?: ResponseError, options?: HttpResponseOptions) => KibanaResponse; + notFound: (error?: ResponseError, options?: HttpResponseOptions) => KibanaResponse; + conflict: (error?: ResponseError, options?: HttpResponseOptions) => KibanaResponse; + internal: (error?: ResponseError, options?: HttpResponseOptions) => KibanaResponse; +}; + +// Warning: (ae-forgotten-export) The symbol "KnownKeys" needs to be exported by the entry point index.d.ts +// +// @public +export type KnownHeaders = KnownKeys; + +// @public @deprecated (undocumented) +export interface LegacyRequest extends Request { +} // @public export interface Logger { @@ -381,6 +467,16 @@ export type RecursiveReadonly = T extends (...args: any[]) => any ? T : T ext [K in keyof T]: RecursiveReadonly; }> : T; +// @public +export type RedirectResponseOptions = HttpResponseOptions & { + headers: { + location: string; + }; +}; + +// @public +export type RequestHandler

= (request: KibanaRequest, TypeOf, TypeOf>, response: KibanaResponseFactory) => KibanaResponse | Promise>; + // @public export type ResponseError = string | Error | { message: string | Error; @@ -397,6 +493,13 @@ export interface ResponseErrorMeta { errorCode?: string; } +// @public +export interface RouteConfig

{ + options?: RouteConfigOptions; + path: string; + validate: RouteSchemas | false; +} + // @public export interface RouteConfigOptions { authRequired?: boolean; @@ -406,13 +509,12 @@ export interface RouteConfigOptions { // @public export type RouteMethod = 'get' | 'post' | 'put' | 'delete'; -// @public (undocumented) +// @public export class Router { constructor(path: string); delete

(route: RouteConfig, handler: RequestHandler): void; - // Warning: (ae-forgotten-export) The symbol "RouteConfig" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "RequestHandler" needs to be exported by the entry point index.d.ts get

(route: RouteConfig, handler: RequestHandler): void; + // @internal getRoutes(): Readonly[]; // (undocumented) readonly path: string; @@ -861,7 +963,7 @@ export interface SavedObjectsUpdateResponse | undefined); + constructor(internalAPICaller: APICaller, scopedAPICaller: APICaller, headers?: Headers | undefined); callAsCurrentUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; callAsInternalUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; } @@ -873,6 +975,14 @@ export interface SessionStorage { set(sessionValue: T): void; } +// @public +export interface SessionStorageCookieOptions { + encryptionKey: string; + isSecure: boolean; + name: string; + validate: (sessionValue: T) => boolean | Promise; +} + // @public export interface SessionStorageFactory { // (undocumented) @@ -882,6 +992,7 @@ export interface SessionStorageFactory { // Warnings were encountered during analysis: // +// src/core/server/http/router/response.ts:188:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts // src/core/server/plugins/plugin_context.ts:34:10 - (ae-forgotten-export) The symbol "EnvironmentMode" needs to be exported by the entry point index.d.ts // src/core/server/plugins/plugins_service.ts:37:5 - (ae-forgotten-export) The symbol "DiscoveredPluginInternal" needs to be exported by the entry point index.d.ts