mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* Kibana request keep a reference to raw request. used to bind hapi-cookie * CookieSessionStorage should work with KibanaRequest as soon as registerAuth refactored to restrict access to hapi Request, CookieSessionStorage won't work with hapi request directly * change registerAuth public api * adopt auth lifecycle tests * move lifecycle auth tests from integration to unit and adopt to new api. * mark toRawRequest as internal to prevent exposure * generate docs * reword test cases * mark Request internals in tsdoc
This commit is contained in:
parent
bc70842706
commit
e84bb5cf35
26 changed files with 379 additions and 251 deletions
|
@ -8,5 +8,5 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type AuthenticationHandler<T> = (request: Readonly<Request>, sessionStorage: SessionStorage<T>, t: AuthToolkit) => AuthResult | Promise<AuthResult>;
|
||||
export declare type AuthenticationHandler = (request: Readonly<Request>, t: AuthToolkit) => AuthResult | Promise<AuthResult>;
|
||||
```
|
||||
|
|
|
@ -9,5 +9,5 @@ Authentication is successful with given credentials, allow request to pass throu
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
authenticated: (state: object) => AuthResult;
|
||||
authenticated: (state?: object) => AuthResult;
|
||||
```
|
||||
|
|
|
@ -16,7 +16,7 @@ export interface AuthToolkit
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [authenticated](./kibana-plugin-server.authtoolkit.authenticated.md) | <code>(state: object) => AuthResult</code> | Authentication is successful with given credentials, allow request to pass through |
|
||||
| [authenticated](./kibana-plugin-server.authtoolkit.authenticated.md) | <code>(state?: object) => AuthResult</code> | Authentication is successful with given credentials, allow request to pass through |
|
||||
| [redirected](./kibana-plugin-server.authtoolkit.redirected.md) | <code>(url: string) => AuthResult</code> | Authentication requires to interrupt request handling and redirect to a configured url |
|
||||
| [rejected](./kibana-plugin-server.authtoolkit.rejected.md) | <code>(error: Error, options?: {</code><br/><code> statusCode?: number;</code><br/><code> }) => AuthResult</code> | Authentication is unsuccessful, fail the request with specified error. |
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [from](./kibana-plugin-server.kibanarequest.from.md)
|
||||
|
||||
## KibanaRequest.from() method
|
||||
|
||||
Factory for creating requests. Validates the request before creating an instance of a KibanaRequest.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
static from<P extends ObjectType, Q extends ObjectType, B extends ObjectType>(req: Request, routeSchemas?: RouteSchemas<P, Q, B>): KibanaRequest<P["type"], Q["type"], B["type"]>;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| req | <code>Request</code> | |
|
||||
| routeSchemas | <code>RouteSchemas<P, Q, B></code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`KibanaRequest<P["type"], Q["type"], B["type"]>`
|
||||
|
|
@ -33,7 +33,5 @@ export declare class KibanaRequest<Params = unknown, Query = unknown, Body = unk
|
|||
|
||||
| Method | Modifiers | Description |
|
||||
| --- | --- | --- |
|
||||
| [from(req, routeSchemas)](./kibana-plugin-server.kibanarequest.from.md) | <code>static</code> | Factory for creating requests. Validates the request before creating an instance of a KibanaRequest. |
|
||||
| [getFilteredHeaders(headersToKeep)](./kibana-plugin-server.kibanarequest.getfilteredheaders.md) | | |
|
||||
| [unstable\_getIncomingMessage()](./kibana-plugin-server.kibanarequest.unstable_getincomingmessage.md) | | |
|
||||
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [unstable\_getIncomingMessage](./kibana-plugin-server.kibanarequest.unstable_getincomingmessage.md)
|
||||
|
||||
## KibanaRequest.unstable\_getIncomingMessage() method
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
unstable_getIncomingMessage(): import("http").IncomingMessage;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
`import("http").IncomingMessage`
|
||||
|
|
@ -43,6 +43,8 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | |
|
||||
| [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | |
|
||||
| [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Route specific configuration. |
|
||||
| [SessionStorage](./kibana-plugin-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. |
|
||||
| [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request |
|
||||
|
||||
## Type Aliases
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionStorage](./kibana-plugin-server.sessionstorage.md) > [clear](./kibana-plugin-server.sessionstorage.clear.md)
|
||||
|
||||
## SessionStorage.clear() method
|
||||
|
||||
Clears current session.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
clear(): void;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
`void`
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionStorage](./kibana-plugin-server.sessionstorage.md) > [get](./kibana-plugin-server.sessionstorage.get.md)
|
||||
|
||||
## SessionStorage.get() method
|
||||
|
||||
Retrieves session value from the session storage.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
get(): Promise<T | null>;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
`Promise<T | null>`
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionStorage](./kibana-plugin-server.sessionstorage.md)
|
||||
|
||||
## SessionStorage interface
|
||||
|
||||
Provides an interface to store and retrieve data across requests.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface SessionStorage<T>
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| [clear()](./kibana-plugin-server.sessionstorage.clear.md) | Clears current session. |
|
||||
| [get()](./kibana-plugin-server.sessionstorage.get.md) | Retrieves session value from the session storage. |
|
||||
| [set(sessionValue)](./kibana-plugin-server.sessionstorage.set.md) | Puts current session value into the session storage. |
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionStorage](./kibana-plugin-server.sessionstorage.md) > [set](./kibana-plugin-server.sessionstorage.set.md)
|
||||
|
||||
## SessionStorage.set() method
|
||||
|
||||
Puts current session value into the session storage.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
set(sessionValue: T): void;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| sessionValue | <code>T</code> | value to put |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`void`
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) > [asScoped](./kibana-plugin-server.sessionstoragefactory.asscoped.md)
|
||||
|
||||
## SessionStorageFactory.asScoped property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
asScoped: (request: Readonly<Request> | KibanaRequest) => SessionStorage<T>;
|
||||
```
|
|
@ -0,0 +1,20 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md)
|
||||
|
||||
## SessionStorageFactory interface
|
||||
|
||||
SessionStorage factory to bind one to an incoming request
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface SessionStorageFactory<T>
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [asScoped](./kibana-plugin-server.sessionstoragefactory.asscoped.md) | <code>(request: Readonly<Request> | KibanaRequest) => SessionStorage<T></code> | |
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { Request } from 'hapi';
|
||||
import { KibanaRequest } from './router';
|
||||
import { KibanaRequest, toRawRequest } from './router';
|
||||
|
||||
export enum AuthStatus {
|
||||
authenticated = 'authenticated',
|
||||
|
@ -25,17 +25,17 @@ export enum AuthStatus {
|
|||
unknown = 'unknown',
|
||||
}
|
||||
|
||||
const toKey = (request: KibanaRequest | Request) =>
|
||||
request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req;
|
||||
const getIncomingMessage = (request: KibanaRequest | Request) =>
|
||||
request instanceof KibanaRequest ? toRawRequest(request).raw.req : request.raw.req;
|
||||
|
||||
export class AuthStateStorage {
|
||||
private readonly storage = new WeakMap<ReturnType<typeof toKey>, unknown>();
|
||||
private readonly storage = new WeakMap<ReturnType<typeof getIncomingMessage>, unknown>();
|
||||
constructor(private readonly canBeAuthenticated: () => boolean) {}
|
||||
public set = (request: KibanaRequest | Request, state: unknown) => {
|
||||
this.storage.set(toKey(request), state);
|
||||
this.storage.set(getIncomingMessage(request), state);
|
||||
};
|
||||
public get = (request: KibanaRequest | Request) => {
|
||||
const key = toKey(request);
|
||||
const key = getIncomingMessage(request);
|
||||
const state = this.storage.get(key);
|
||||
const status: AuthStatus = this.storage.has(key)
|
||||
? AuthStatus.authenticated
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
import { Request, Server } from 'hapi';
|
||||
import hapiAuthCookie from 'hapi-auth-cookie';
|
||||
|
||||
import { KibanaRequest, toRawRequest } from './router';
|
||||
import { SessionStorageFactory, SessionStorage } from './session_storage';
|
||||
|
||||
export interface SessionStorageCookieOptions<T> {
|
||||
|
@ -29,10 +31,10 @@ export interface SessionStorageCookieOptions<T> {
|
|||
}
|
||||
|
||||
class ScopedCookieSessionStorage<T extends Record<string, any>> implements SessionStorage<T> {
|
||||
constructor(private readonly server: Server, private readonly request: Request) {}
|
||||
constructor(private readonly server: Server, private readonly request: Readonly<Request>) {}
|
||||
public async get(): Promise<T | null> {
|
||||
try {
|
||||
return await this.server.auth.test('security-cookie', this.request);
|
||||
return await this.server.auth.test('security-cookie', this.request as Request);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
|
@ -71,8 +73,9 @@ export async function createCookieSessionStorageFactory<T>(
|
|||
});
|
||||
|
||||
return {
|
||||
asScoped(request: Request) {
|
||||
return new ScopedCookieSessionStorage<T>(server, request);
|
||||
asScoped(request: Readonly<Request> | KibanaRequest) {
|
||||
const req = request instanceof KibanaRequest ? toRawRequest(request) : request;
|
||||
return new ScopedCookieSessionStorage<T>(server, req);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
*/
|
||||
|
||||
import { Server } from 'http';
|
||||
import request from 'request';
|
||||
import Boom from 'boom';
|
||||
|
||||
jest.mock('fs', () => ({
|
||||
readFileSync: jest.fn(),
|
||||
|
@ -586,17 +588,6 @@ test('returns server and connection options on start', async () => {
|
|||
expect(options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('registers auth request interceptor only once', async () => {
|
||||
const { registerAuth } = await server.setup(config);
|
||||
const doRegister = () =>
|
||||
registerAuth(() => null as any, {
|
||||
encryptionKey: 'any_password',
|
||||
} as any);
|
||||
|
||||
await doRegister();
|
||||
expect(doRegister()).rejects.toThrowError('Auth interceptor was already registered');
|
||||
});
|
||||
|
||||
test('registers registerOnPostAuth interceptor several times', async () => {
|
||||
const { registerOnPostAuth } = await server.setup(config);
|
||||
const doRegister = () => registerOnPostAuth(() => null as any);
|
||||
|
@ -697,7 +688,150 @@ const cookieOptions = {
|
|||
isSecure: false,
|
||||
};
|
||||
|
||||
test('Should enable auth for a route by default if registerAuth has been called', async () => {
|
||||
interface User {
|
||||
id: string;
|
||||
roles?: string[];
|
||||
}
|
||||
|
||||
interface StorageData {
|
||||
value: User;
|
||||
expires: number;
|
||||
}
|
||||
|
||||
describe('#registerAuth', () => {
|
||||
it('registers auth request interceptor only once', async () => {
|
||||
const { registerAuth } = await server.setup(config);
|
||||
const doRegister = () =>
|
||||
registerAuth(() => null as any, {
|
||||
encryptionKey: 'any_password',
|
||||
} as any);
|
||||
|
||||
await doRegister();
|
||||
expect(doRegister()).rejects.toThrowError('Auth interceptor was already registered');
|
||||
});
|
||||
|
||||
it('supports implementing custom authentication logic', async () => {
|
||||
const router = new Router('');
|
||||
router.get({ path: '/', validate: false }, async (req, res) => res.ok({ content: 'ok' }));
|
||||
|
||||
const { registerAuth, registerRouter, server: innerServer } = await server.setup(config);
|
||||
const { sessionStorageFactory } = await registerAuth<StorageData>((req, t) => {
|
||||
const user = { id: '42' };
|
||||
const sessionStorage = sessionStorageFactory.asScoped(req);
|
||||
sessionStorage.set({ value: user, expires: Date.now() + 1000 });
|
||||
return t.authenticated(user);
|
||||
}, cookieOptions);
|
||||
registerRouter(router);
|
||||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener)
|
||||
.get('/')
|
||||
.expect(200, { content: 'ok' });
|
||||
|
||||
expect(response.header['set-cookie']).toBeDefined();
|
||||
const cookies = response.header['set-cookie'];
|
||||
expect(cookies).toHaveLength(1);
|
||||
|
||||
const sessionCookie = request.cookie(cookies[0]);
|
||||
if (!sessionCookie) {
|
||||
throw new Error('session cookie expected to be defined');
|
||||
}
|
||||
expect(sessionCookie).toBeDefined();
|
||||
expect(sessionCookie.key).toBe('sid');
|
||||
expect(sessionCookie.value).toBeDefined();
|
||||
expect(sessionCookie.path).toBe('/');
|
||||
expect(sessionCookie.httpOnly).toBe(true);
|
||||
});
|
||||
|
||||
it('supports rejecting a request from an unauthenticated user', async () => {
|
||||
const { registerAuth, registerRouter, server: innerServer } = await server.setup(config);
|
||||
const router = new Router('');
|
||||
router.get({ path: '/', validate: false }, async (req, res) => res.ok({ content: 'ok' }));
|
||||
registerRouter(router);
|
||||
|
||||
await registerAuth((req, t) => t.rejected(Boom.unauthorized()), cookieOptions);
|
||||
await server.start();
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.get('/')
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
it('supports redirecting', async () => {
|
||||
const redirectTo = '/redirect-url';
|
||||
const { registerAuth, registerRouter, server: innerServer } = await server.setup(config);
|
||||
const router = new Router('');
|
||||
router.get({ path: '/', validate: false }, async (req, res) => res.ok({ content: 'ok' }));
|
||||
registerRouter(router);
|
||||
|
||||
await registerAuth((req, t) => {
|
||||
return t.redirected(redirectTo);
|
||||
}, cookieOptions);
|
||||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener)
|
||||
.get('/')
|
||||
.expect(302);
|
||||
expect(response.header.location).toBe(redirectTo);
|
||||
});
|
||||
|
||||
it(`doesn't expose internal error details`, async () => {
|
||||
const { registerAuth, registerRouter, server: innerServer } = await server.setup(config);
|
||||
const router = new Router('');
|
||||
router.get({ path: '/', validate: false }, async (req, res) => res.ok({ content: 'ok' }));
|
||||
registerRouter(router);
|
||||
|
||||
await registerAuth((req, t) => {
|
||||
throw new Error('sensitive info');
|
||||
}, cookieOptions);
|
||||
await server.start();
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.get('/')
|
||||
.expect({
|
||||
statusCode: 500,
|
||||
error: 'Internal Server Error',
|
||||
message: 'An internal server error occurred',
|
||||
});
|
||||
});
|
||||
|
||||
it(`allows manipulating cookies from route handler`, async () => {
|
||||
const { registerAuth, registerRouter, server: innerServer } = await server.setup(config);
|
||||
const { sessionStorageFactory } = await registerAuth<StorageData>((req, t) => {
|
||||
const user = { id: '42' };
|
||||
const sessionStorage = sessionStorageFactory.asScoped(req);
|
||||
sessionStorage.set({ value: user, expires: Date.now() + 1000 });
|
||||
return t.authenticated();
|
||||
}, cookieOptions);
|
||||
|
||||
const router = new Router('');
|
||||
router.get({ path: '/', validate: false }, (req, res) => res.ok({ content: 'ok' }));
|
||||
router.get({ path: '/with-cookie', validate: false }, (req, res) => {
|
||||
const sessionStorage = sessionStorageFactory.asScoped(req);
|
||||
sessionStorage.clear();
|
||||
return res.ok({ content: 'ok' });
|
||||
});
|
||||
registerRouter(router);
|
||||
|
||||
await server.start();
|
||||
|
||||
const responseToSetCookie = await supertest(innerServer.listener)
|
||||
.get('/')
|
||||
.expect(200);
|
||||
|
||||
expect(responseToSetCookie.header['set-cookie']).toBeDefined();
|
||||
|
||||
const responseToResetCookie = await supertest(innerServer.listener)
|
||||
.get('/with-cookie')
|
||||
.expect(200);
|
||||
|
||||
expect(responseToResetCookie.header['set-cookie']).toEqual([
|
||||
'sid=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Path=/',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('enables auth for a route by default if registerAuth has been called', async () => {
|
||||
const { registerAuth, registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('');
|
||||
|
@ -706,9 +840,7 @@ test('Should enable auth for a route by default if registerAuth has been called'
|
|||
);
|
||||
registerRouter(router);
|
||||
|
||||
const authenticate = jest
|
||||
.fn()
|
||||
.mockImplementation((req, sessionStorage, t) => t.authenticated({}));
|
||||
const authenticate = jest.fn().mockImplementation((req, t) => t.authenticated());
|
||||
await registerAuth(authenticate, cookieOptions);
|
||||
|
||||
await server.start();
|
||||
|
@ -719,7 +851,7 @@ test('Should enable auth for a route by default if registerAuth has been called'
|
|||
expect(authenticate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('Should support disabling auth for a route explicitly', async () => {
|
||||
test('supports disabling auth for a route explicitly', async () => {
|
||||
const { registerAuth, registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('');
|
||||
|
@ -738,7 +870,7 @@ test('Should support disabling auth for a route explicitly', async () => {
|
|||
expect(authenticate).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('Should support enabling auth for a route explicitly', async () => {
|
||||
test('supports enabling auth for a route explicitly', async () => {
|
||||
const { registerAuth, registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('');
|
||||
|
@ -746,9 +878,7 @@ test('Should support enabling auth for a route explicitly', async () => {
|
|||
res.ok({ authRequired: req.route.options.authRequired })
|
||||
);
|
||||
registerRouter(router);
|
||||
const authenticate = jest
|
||||
.fn()
|
||||
.mockImplementation((req, sessionStorage, t) => t.authenticated({}));
|
||||
const authenticate = jest.fn().mockImplementation((req, t) => t.authenticated({}));
|
||||
await registerAuth(authenticate, cookieOptions);
|
||||
|
||||
await server.start();
|
||||
|
@ -759,7 +889,7 @@ test('Should support enabling auth for a route explicitly', async () => {
|
|||
expect(authenticate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('Should allow attaching metadata to attach meta-data tag strings to a route', async () => {
|
||||
test('allows attaching metadata to attach meta-data tag strings to a route', async () => {
|
||||
const tags = ['my:tag'];
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
|
@ -782,7 +912,7 @@ test('Should allow attaching metadata to attach meta-data tag strings to a route
|
|||
.expect(200, { tags: [] });
|
||||
});
|
||||
|
||||
test('Should expose route details of incoming request to a route handler', async () => {
|
||||
test('exposes route details of incoming request to a route handler', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('');
|
||||
|
@ -812,7 +942,7 @@ describe('#auth.isAuthenticated()', () => {
|
|||
);
|
||||
registerRouter(router);
|
||||
|
||||
await registerAuth((req, sessionStorage, t) => t.authenticated({}), cookieOptions);
|
||||
await registerAuth((req, t) => t.authenticated(), cookieOptions);
|
||||
|
||||
await server.start();
|
||||
await supertest(innerServer.listener)
|
||||
|
@ -829,7 +959,7 @@ describe('#auth.isAuthenticated()', () => {
|
|||
);
|
||||
registerRouter(router);
|
||||
|
||||
await registerAuth((req, sessionStorage, t) => t.authenticated({}), cookieOptions);
|
||||
await registerAuth((req, t) => t.authenticated(), cookieOptions);
|
||||
|
||||
await server.start();
|
||||
await supertest(innerServer.listener)
|
||||
|
@ -854,11 +984,11 @@ describe('#auth.isAuthenticated()', () => {
|
|||
});
|
||||
|
||||
describe('#auth.get()', () => {
|
||||
it('Should return authenticated status and allow associate auth state with request', async () => {
|
||||
it('returns authenticated status and allow associate auth state with request', async () => {
|
||||
const user = { id: '42' };
|
||||
const { registerRouter, registerAuth, server: innerServer, auth } = await server.setup(config);
|
||||
await registerAuth((req, sessionStorage, t) => {
|
||||
sessionStorage.set({ value: user });
|
||||
const { sessionStorageFactory } = await registerAuth<StorageData>((req, t) => {
|
||||
sessionStorageFactory.asScoped(req).set({ value: user, expires: Date.now() + 1000 });
|
||||
return t.authenticated(user);
|
||||
}, cookieOptions);
|
||||
|
||||
|
@ -872,7 +1002,7 @@ describe('#auth.get()', () => {
|
|||
.expect(200, { state: user, status: 'authenticated' });
|
||||
});
|
||||
|
||||
it('Should return correct authentication unknown status', async () => {
|
||||
it('returns correct authentication unknown status', async () => {
|
||||
const { registerRouter, server: innerServer, auth } = await server.setup(config);
|
||||
const router = new Router('');
|
||||
router.get({ path: '/', validate: false }, (req, res) => res.ok(auth.get(req)));
|
||||
|
@ -884,7 +1014,7 @@ describe('#auth.get()', () => {
|
|||
.expect(200, { status: 'unknown' });
|
||||
});
|
||||
|
||||
it('Should return correct unauthenticated status', async () => {
|
||||
it('returns correct unauthenticated status', async () => {
|
||||
const authenticate = jest.fn();
|
||||
|
||||
const { registerRouter, registerAuth, server: innerServer, auth } = await server.setup(config);
|
||||
|
|
|
@ -26,13 +26,17 @@ import { createServer, 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 } from './router';
|
||||
import { Router, KibanaRequest, toRawRequest } from './router';
|
||||
import {
|
||||
SessionStorageCookieOptions,
|
||||
createCookieSessionStorageFactory,
|
||||
} from './cookie_session_storage';
|
||||
import { SessionStorageFactory } from './session_storage';
|
||||
import { AuthStateStorage } from './auth_state_storage';
|
||||
|
||||
const getIncomingMessage = (request: KibanaRequest | Request) =>
|
||||
request instanceof KibanaRequest ? toRawRequest(request).raw.req : request.raw.req;
|
||||
|
||||
export interface HttpServerSetup {
|
||||
server: Server;
|
||||
options: ServerOptions;
|
||||
|
@ -44,9 +48,9 @@ export interface HttpServerSetup {
|
|||
* Only one AuthenticationHandler can be registered.
|
||||
*/
|
||||
registerAuth: <T>(
|
||||
handler: AuthenticationHandler<T>,
|
||||
handler: AuthenticationHandler,
|
||||
cookieOptions: SessionStorageCookieOptions<T>
|
||||
) => Promise<void>;
|
||||
) => Promise<{ sessionStorageFactory: SessionStorageFactory<T> }>;
|
||||
/**
|
||||
* 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
|
||||
|
@ -76,10 +80,7 @@ export class HttpServer {
|
|||
private config?: HttpConfig;
|
||||
private registeredRouters = new Set<Router>();
|
||||
private authRegistered = false;
|
||||
private basePathCache = new WeakMap<
|
||||
ReturnType<KibanaRequest['unstable_getIncomingMessage']>,
|
||||
string
|
||||
>();
|
||||
private basePathCache = new WeakMap<ReturnType<typeof getIncomingMessage>, string>();
|
||||
|
||||
private readonly authState: AuthStateStorage;
|
||||
|
||||
|
@ -102,8 +103,7 @@ export class HttpServer {
|
|||
|
||||
// passing hapi Request works for BWC. can be deleted once we remove legacy server.
|
||||
private getBasePathFor(config: HttpConfig, request: KibanaRequest | Request) {
|
||||
const incomingMessage =
|
||||
request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req;
|
||||
const incomingMessage = getIncomingMessage(request);
|
||||
|
||||
const requestScopePath = this.basePathCache.get(incomingMessage) || '';
|
||||
const serverBasePath = config.basePath || '';
|
||||
|
@ -112,8 +112,8 @@ export class HttpServer {
|
|||
|
||||
// should work only for KibanaRequest as soon as spaces migrate to NP
|
||||
private setBasePathFor(request: KibanaRequest | Request, basePath: string) {
|
||||
const incomingMessage =
|
||||
request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req;
|
||||
const incomingMessage = getIncomingMessage(request);
|
||||
|
||||
if (this.basePathCache.has(incomingMessage)) {
|
||||
throw new Error(
|
||||
'Request basePath was previously set. Setting multiple times is not supported.'
|
||||
|
@ -134,10 +134,8 @@ export class HttpServer {
|
|||
registerRouter: this.registerRouter.bind(this),
|
||||
registerOnPreAuth: this.registerOnPreAuth.bind(this),
|
||||
registerOnPostAuth: this.registerOnPostAuth.bind(this),
|
||||
registerAuth: <T>(
|
||||
fn: AuthenticationHandler<T>,
|
||||
cookieOptions: SessionStorageCookieOptions<T>
|
||||
) => this.registerAuth(fn, cookieOptions, config.basePath),
|
||||
registerAuth: <T>(fn: AuthenticationHandler, cookieOptions: SessionStorageCookieOptions<T>) =>
|
||||
this.registerAuth(fn, cookieOptions, config.basePath),
|
||||
getBasePathFor: this.getBasePathFor.bind(this, config),
|
||||
setBasePathFor: this.setBasePathFor.bind(this),
|
||||
auth: {
|
||||
|
@ -233,7 +231,7 @@ export class HttpServer {
|
|||
}
|
||||
|
||||
private async registerAuth<T>(
|
||||
fn: AuthenticationHandler<T>,
|
||||
fn: AuthenticationHandler,
|
||||
cookieOptions: SessionStorageCookieOptions<T>,
|
||||
basePath?: string
|
||||
) {
|
||||
|
@ -245,14 +243,14 @@ export class HttpServer {
|
|||
}
|
||||
this.authRegistered = true;
|
||||
|
||||
const sessionStorage = await createCookieSessionStorageFactory<T>(
|
||||
const sessionStorageFactory = await createCookieSessionStorageFactory<T>(
|
||||
this.server,
|
||||
cookieOptions,
|
||||
basePath
|
||||
);
|
||||
|
||||
this.server.auth.scheme('login', () => ({
|
||||
authenticate: adoptToHapiAuthFormat(fn, sessionStorage, this.authState.set),
|
||||
authenticate: adoptToHapiAuthFormat(fn, this.authState.set),
|
||||
}));
|
||||
this.server.auth.strategy('session', 'login');
|
||||
|
||||
|
@ -261,5 +259,7 @@ export class HttpServer {
|
|||
// should be applied for all routes if they don't specify auth strategy in route declaration
|
||||
// https://github.com/hapijs/hapi/blob/master/API.md#-serverauthdefaultoptions
|
||||
this.server.auth.default('session');
|
||||
|
||||
return { sessionStorageFactory };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,3 +30,4 @@ export { BasePathProxyServer } from './base_path_proxy_server';
|
|||
export { OnPreAuthHandler, OnPreAuthToolkit } from './lifecycle/on_pre_auth';
|
||||
export { AuthenticationHandler, AuthToolkit } from './lifecycle/auth';
|
||||
export { OnPostAuthHandler, OnPostAuthToolkit } from './lifecycle/on_post_auth';
|
||||
export { SessionStorageFactory, SessionStorage } from './session_storage';
|
||||
|
|
|
@ -16,12 +16,9 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import request from 'request';
|
||||
import Boom from 'boom';
|
||||
|
||||
import { AuthenticationHandler } from '../../../../core/server';
|
||||
import { Router } from '../router';
|
||||
|
||||
import * as kbnTestServer from '../../../../test_utils/kbn_server';
|
||||
|
||||
interface User {
|
||||
|
@ -29,7 +26,7 @@ interface User {
|
|||
roles?: string[];
|
||||
}
|
||||
|
||||
interface Storage {
|
||||
interface StorageData {
|
||||
value: User;
|
||||
expires: number;
|
||||
}
|
||||
|
@ -41,7 +38,7 @@ describe('http service', () => {
|
|||
const cookieOptions = {
|
||||
name: 'sid',
|
||||
encryptionKey: 'something_at_least_32_characters',
|
||||
validate: (session: Storage) => true,
|
||||
validate: (session: StorageData) => true,
|
||||
isSecure: false,
|
||||
path: '/',
|
||||
};
|
||||
|
@ -53,90 +50,18 @@ describe('http service', () => {
|
|||
|
||||
afterEach(async () => await root.shutdown());
|
||||
|
||||
it('Should support implementing custom authentication logic', async () => {
|
||||
const router = new Router('');
|
||||
router.get({ path: '/', validate: false }, async (req, res) => res.ok({ content: 'ok' }));
|
||||
|
||||
const authenticate: AuthenticationHandler<Storage> = async (req, sessionStorage, t) => {
|
||||
if (req.headers.authorization) {
|
||||
const user = { id: '42' };
|
||||
sessionStorage.set({ value: user, expires: Date.now() + sessionDurationMs });
|
||||
return t.authenticated(user);
|
||||
} else {
|
||||
return t.rejected(Boom.unauthorized());
|
||||
}
|
||||
};
|
||||
|
||||
const { http } = await root.setup();
|
||||
await http.registerAuth(authenticate, cookieOptions);
|
||||
http.registerRouter(router);
|
||||
await root.start();
|
||||
|
||||
const response = await kbnTestServer.request.get(root, '/').expect(200, { content: 'ok' });
|
||||
|
||||
expect(response.header['set-cookie']).toBeDefined();
|
||||
const cookies = response.header['set-cookie'];
|
||||
expect(cookies).toHaveLength(1);
|
||||
|
||||
const sessionCookie = request.cookie(cookies[0]);
|
||||
if (!sessionCookie) {
|
||||
throw new Error('session cookie expected to be defined');
|
||||
}
|
||||
expect(sessionCookie).toBeDefined();
|
||||
expect(sessionCookie.key).toBe('sid');
|
||||
expect(sessionCookie.value).toBeDefined();
|
||||
expect(sessionCookie.path).toBe('/');
|
||||
expect(sessionCookie.httpOnly).toBe(true);
|
||||
});
|
||||
|
||||
it('Should support rejecting a request from an unauthenticated user', async () => {
|
||||
const authenticate: AuthenticationHandler<Storage> = async (req, sessionStorage, t) => {
|
||||
if (req.headers.authorization) {
|
||||
const user = { id: '42' };
|
||||
sessionStorage.set({ value: user, expires: Date.now() + sessionDurationMs });
|
||||
return t.authenticated(user);
|
||||
} else {
|
||||
return t.rejected(Boom.unauthorized());
|
||||
}
|
||||
};
|
||||
|
||||
const { http } = await root.setup();
|
||||
await http.registerAuth(authenticate, cookieOptions);
|
||||
await root.start();
|
||||
|
||||
await kbnTestServer.request
|
||||
.get(root, '/')
|
||||
.unset('Authorization')
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
it('Should support redirecting', async () => {
|
||||
const redirectTo = '/redirect-url';
|
||||
const authenticate: AuthenticationHandler<Storage> = async (req, sessionStorage, t) => {
|
||||
return t.redirected(redirectTo);
|
||||
};
|
||||
|
||||
const { http } = await root.setup();
|
||||
await http.registerAuth(authenticate, cookieOptions);
|
||||
await root.start();
|
||||
|
||||
const response = await kbnTestServer.request.get(root, '/').expect(302);
|
||||
expect(response.header.location).toBe(redirectTo);
|
||||
});
|
||||
|
||||
it('Should run auth for legacy routes and proxy request to legacy server route handlers', async () => {
|
||||
const authenticate: AuthenticationHandler<Storage> = async (req, sessionStorage, t) => {
|
||||
const { http } = await root.setup();
|
||||
const { sessionStorageFactory } = await http.registerAuth<StorageData>((req, t) => {
|
||||
if (req.headers.authorization) {
|
||||
const user = { id: '42' };
|
||||
const sessionStorage = sessionStorageFactory.asScoped(req);
|
||||
sessionStorage.set({ value: user, expires: Date.now() + sessionDurationMs });
|
||||
return t.authenticated(user);
|
||||
} else {
|
||||
return t.rejected(Boom.unauthorized());
|
||||
}
|
||||
};
|
||||
|
||||
const { http } = await root.setup();
|
||||
await http.registerAuth(authenticate, cookieOptions);
|
||||
}, cookieOptions);
|
||||
await root.start();
|
||||
|
||||
const legacyUrl = '/legacy';
|
||||
|
@ -156,17 +81,17 @@ describe('http service', () => {
|
|||
|
||||
it('Should pass associated auth state to Legacy platform', async () => {
|
||||
const user = { id: '42' };
|
||||
const authenticate: AuthenticationHandler<Storage> = async (req, sessionStorage, t) => {
|
||||
|
||||
const { http } = await root.setup();
|
||||
const { sessionStorageFactory } = await http.registerAuth<StorageData>((req, t) => {
|
||||
if (req.headers.authorization) {
|
||||
const sessionStorage = sessionStorageFactory.asScoped(req);
|
||||
sessionStorage.set({ value: user, expires: Date.now() + sessionDurationMs });
|
||||
return t.authenticated(user);
|
||||
} else {
|
||||
return t.rejected(Boom.unauthorized());
|
||||
}
|
||||
};
|
||||
|
||||
const { http } = await root.setup();
|
||||
await http.registerAuth(authenticate, cookieOptions);
|
||||
}, cookieOptions);
|
||||
await root.start();
|
||||
|
||||
const legacyUrl = '/legacy';
|
||||
|
@ -183,22 +108,6 @@ describe('http service', () => {
|
|||
|
||||
expect(response.header['set-cookie']).toBe(undefined);
|
||||
});
|
||||
|
||||
it(`Shouldn't expose internal error details`, async () => {
|
||||
const authenticate: AuthenticationHandler<Storage> = async (req, sessionStorage, t) => {
|
||||
throw new Error('sensitive info');
|
||||
};
|
||||
|
||||
const { http } = await root.setup();
|
||||
await http.registerAuth(authenticate, cookieOptions);
|
||||
await root.start();
|
||||
|
||||
await kbnTestServer.request.get(root, '/').expect({
|
||||
statusCode: 500,
|
||||
error: 'Internal Server Error',
|
||||
message: 'An internal server error occurred',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#registerOnPostAuth()', () => {
|
||||
|
|
|
@ -21,18 +21,11 @@ import Boom from 'boom';
|
|||
import { adoptToHapiAuthFormat } from './auth';
|
||||
import { httpServerMock } from '../http_server.mocks';
|
||||
|
||||
const SessionStorageMock = {
|
||||
asScoped: () => null as any,
|
||||
};
|
||||
|
||||
describe('adoptToHapiAuthFormat', () => {
|
||||
it('Should allow authenticating a user identity with given credentials', async () => {
|
||||
const credentials = {};
|
||||
const authenticatedMock = jest.fn();
|
||||
const onAuth = adoptToHapiAuthFormat(
|
||||
async (req, sessionStorage, t) => t.authenticated(credentials),
|
||||
SessionStorageMock
|
||||
);
|
||||
const onAuth = adoptToHapiAuthFormat((req, t) => t.authenticated(credentials));
|
||||
await onAuth(
|
||||
httpServerMock.createRawRequest(),
|
||||
httpServerMock.createRawResponseToolkit({
|
||||
|
@ -46,10 +39,7 @@ describe('adoptToHapiAuthFormat', () => {
|
|||
|
||||
it('Should allow redirecting to specified url', async () => {
|
||||
const redirectUrl = '/docs';
|
||||
const onAuth = adoptToHapiAuthFormat(
|
||||
async (req, sessionStorage, t) => t.redirected(redirectUrl),
|
||||
SessionStorageMock
|
||||
);
|
||||
const onAuth = adoptToHapiAuthFormat((req, t) => t.redirected(redirectUrl));
|
||||
const takeoverSymbol = {};
|
||||
const redirectMock = jest.fn(() => ({ takeover: () => takeoverSymbol }));
|
||||
const result = await onAuth(
|
||||
|
@ -64,9 +54,8 @@ describe('adoptToHapiAuthFormat', () => {
|
|||
});
|
||||
|
||||
it('Should allow to specify statusCode and message for Boom error', async () => {
|
||||
const onAuth = adoptToHapiAuthFormat(
|
||||
async (req, sessionStorage, t) => t.rejected(new Error('not found'), { statusCode: 404 }),
|
||||
SessionStorageMock
|
||||
const onAuth = adoptToHapiAuthFormat((req, t) =>
|
||||
t.rejected(new Error('not found'), { statusCode: 404 })
|
||||
);
|
||||
const result = (await onAuth(
|
||||
httpServerMock.createRawRequest(),
|
||||
|
@ -79,9 +68,9 @@ describe('adoptToHapiAuthFormat', () => {
|
|||
});
|
||||
|
||||
it('Should return Boom.internal error error if interceptor throws', async () => {
|
||||
const onAuth = adoptToHapiAuthFormat(async (req, sessionStorage, t) => {
|
||||
const onAuth = adoptToHapiAuthFormat((req, t) => {
|
||||
throw new Error('unknown error');
|
||||
}, SessionStorageMock);
|
||||
});
|
||||
const result = (await onAuth(
|
||||
httpServerMock.createRawRequest(),
|
||||
httpServerMock.createRawResponseToolkit()
|
||||
|
@ -93,10 +82,7 @@ describe('adoptToHapiAuthFormat', () => {
|
|||
});
|
||||
|
||||
it('Should return Boom.internal error if interceptor returns unexpected result', async () => {
|
||||
const onAuth = adoptToHapiAuthFormat(
|
||||
async (req, sessionStorage, t) => undefined as any,
|
||||
SessionStorageMock
|
||||
);
|
||||
const onAuth = adoptToHapiAuthFormat(async (req, t) => undefined as any);
|
||||
const result = (await onAuth(
|
||||
httpServerMock.createRawRequest(),
|
||||
httpServerMock.createRawResponseToolkit()
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
import Boom from 'boom';
|
||||
import { noop } from 'lodash';
|
||||
import { Lifecycle, Request, ResponseToolkit } from 'hapi';
|
||||
import { SessionStorage, SessionStorageFactory } from '../session_storage';
|
||||
|
||||
enum ResultType {
|
||||
authenticated = 'authenticated',
|
||||
|
@ -46,7 +45,7 @@ interface Rejected {
|
|||
type AuthResult = Authenticated | Rejected | Redirected;
|
||||
|
||||
const authResult = {
|
||||
authenticated(state: object): AuthResult {
|
||||
authenticated(state: object = {}): AuthResult {
|
||||
return { type: ResultType.authenticated, state };
|
||||
},
|
||||
redirected(url: string): AuthResult {
|
||||
|
@ -80,7 +79,7 @@ const authResult = {
|
|||
*/
|
||||
export interface AuthToolkit {
|
||||
/** Authentication is successful with given credentials, allow request to pass through */
|
||||
authenticated: (state: object) => AuthResult;
|
||||
authenticated: (state?: object) => AuthResult;
|
||||
/** Authentication requires to interrupt request handling and redirect to a configured url */
|
||||
redirected: (url: string) => AuthResult;
|
||||
/** Authentication is unsuccessful, fail the request with specified error. */
|
||||
|
@ -94,16 +93,14 @@ const toolkit: AuthToolkit = {
|
|||
};
|
||||
|
||||
/** @public */
|
||||
export type AuthenticationHandler<T> = (
|
||||
export type AuthenticationHandler = (
|
||||
request: Readonly<Request>,
|
||||
sessionStorage: SessionStorage<T>,
|
||||
t: AuthToolkit
|
||||
) => AuthResult | Promise<AuthResult>;
|
||||
|
||||
/** @public */
|
||||
export function adoptToHapiAuthFormat<T = any>(
|
||||
fn: AuthenticationHandler<T>,
|
||||
sessionStorage: SessionStorageFactory<T>,
|
||||
export function adoptToHapiAuthFormat(
|
||||
fn: AuthenticationHandler,
|
||||
onSuccess: (req: Request, state: unknown) => void = noop
|
||||
) {
|
||||
return async function interceptAuth(
|
||||
|
@ -111,7 +108,7 @@ export function adoptToHapiAuthFormat<T = any>(
|
|||
h: ResponseToolkit
|
||||
): Promise<Lifecycle.ReturnValue> {
|
||||
try {
|
||||
const result = await fn(req, sessionStorage.asScoped(req), toolkit);
|
||||
const result = await fn(req, toolkit);
|
||||
if (!authResult.isValid(result)) {
|
||||
throw new Error(
|
||||
`Unexpected result from Authenticate. Expected AuthResult, but given: ${result}.`
|
||||
|
|
|
@ -19,5 +19,5 @@
|
|||
|
||||
export { Headers, filterHeaders } from './headers';
|
||||
export { Router } from './router';
|
||||
export { KibanaRequest, KibanaRequestRoute } from './request';
|
||||
export { KibanaRequest, KibanaRequestRoute, toRawRequest } from './request';
|
||||
export { RouteMethod, RouteConfigOptions } from './route';
|
||||
|
|
|
@ -25,6 +25,8 @@ import { deepFreeze, RecursiveReadonly } from '../../../utils';
|
|||
import { filterHeaders, Headers } from './headers';
|
||||
import { RouteMethod, RouteSchemas, RouteConfigOptions } from './route';
|
||||
|
||||
const requestSymbol = Symbol('request');
|
||||
|
||||
/**
|
||||
* Request specific route information exposed to a handler.
|
||||
* @public
|
||||
|
@ -43,6 +45,7 @@ export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown> {
|
|||
/**
|
||||
* Factory for creating requests. Validates the request before creating an
|
||||
* instance of a KibanaRequest.
|
||||
* @internal
|
||||
*/
|
||||
public static from<P extends ObjectType, Q extends ObjectType, B extends ObjectType>(
|
||||
req: Request,
|
||||
|
@ -87,14 +90,19 @@ export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown> {
|
|||
public readonly url: Url;
|
||||
public readonly route: RecursiveReadonly<KibanaRequestRoute>;
|
||||
|
||||
/** @internal */
|
||||
protected readonly [requestSymbol]: Request;
|
||||
|
||||
constructor(
|
||||
private readonly request: Request,
|
||||
request: Request,
|
||||
readonly params: Params,
|
||||
readonly query: Query,
|
||||
readonly body: Body
|
||||
) {
|
||||
this.headers = request.headers;
|
||||
this.url = request.url;
|
||||
|
||||
this[requestSymbol] = request;
|
||||
this.route = deepFreeze(this.getRouteInfo());
|
||||
}
|
||||
|
||||
|
@ -102,19 +110,21 @@ export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown> {
|
|||
return filterHeaders(this.headers, headersToKeep);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
public unstable_getIncomingMessage() {
|
||||
return this.request.raw.req;
|
||||
}
|
||||
|
||||
private getRouteInfo() {
|
||||
const request = this[requestSymbol];
|
||||
return {
|
||||
path: this.request.path,
|
||||
method: this.request.method,
|
||||
path: request.path,
|
||||
method: request.method,
|
||||
options: {
|
||||
authRequired: this.request.route.settings.auth !== false,
|
||||
tags: this.request.route.settings.tags || [],
|
||||
authRequired: request.route.settings.auth !== false,
|
||||
tags: request.route.settings.tags || [],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns underlying Hapi Request object for KibanaRequest
|
||||
* @internal
|
||||
*/
|
||||
export const toRawRequest = (request: KibanaRequest) => request[requestSymbol];
|
||||
|
|
|
@ -18,8 +18,10 @@
|
|||
*/
|
||||
|
||||
import { Request } from 'hapi';
|
||||
import { KibanaRequest } from './router';
|
||||
/**
|
||||
* Provides an interface to store and retrieve data across requests.
|
||||
* @public
|
||||
*/
|
||||
export interface SessionStorage<T> {
|
||||
/**
|
||||
|
@ -37,6 +39,9 @@ export interface SessionStorage<T> {
|
|||
clear(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* SessionStorage factory to bind one to an incoming request
|
||||
* @public */
|
||||
export interface SessionStorageFactory<T> {
|
||||
asScoped: (request: Request) => SessionStorage<T>;
|
||||
asScoped: (request: Readonly<Request> | KibanaRequest) => SessionStorage<T>;
|
||||
}
|
||||
|
|
|
@ -62,6 +62,8 @@ export {
|
|||
Router,
|
||||
RouteMethod,
|
||||
RouteConfigOptions,
|
||||
SessionStorageFactory,
|
||||
SessionStorage,
|
||||
} from './http';
|
||||
export { Logger, LoggerFactory, LogMeta, LogRecord, LogLevel } from './logging';
|
||||
|
||||
|
|
|
@ -22,15 +22,14 @@ import { Url } from 'url';
|
|||
// @public (undocumented)
|
||||
export type APICaller = (endpoint: string, clientParams: Record<string, unknown>, options?: CallAPIOptions) => Promise<unknown>;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "SessionStorage" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-forgotten-export) The symbol "AuthResult" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type AuthenticationHandler<T> = (request: Readonly<Request>, sessionStorage: SessionStorage<T>, t: AuthToolkit) => AuthResult | Promise<AuthResult>;
|
||||
export type AuthenticationHandler = (request: Readonly<Request>, t: AuthToolkit) => AuthResult | Promise<AuthResult>;
|
||||
|
||||
// @public
|
||||
export interface AuthToolkit {
|
||||
authenticated: (state: object) => AuthResult;
|
||||
authenticated: (state?: object) => AuthResult;
|
||||
redirected: (url: string) => AuthResult;
|
||||
rejected: (error: Error, options?: {
|
||||
statusCode?: number;
|
||||
|
@ -167,10 +166,14 @@ export interface InternalCoreStart {
|
|||
|
||||
// @public
|
||||
export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown> {
|
||||
// @internal (undocumented)
|
||||
protected readonly [requestSymbol]: Request;
|
||||
constructor(request: Request, params: Params, query: Query, body: Body);
|
||||
// (undocumented)
|
||||
readonly body: Body;
|
||||
// Warning: (ae-forgotten-export) The symbol "RouteSchemas" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @internal
|
||||
static from<P extends ObjectType, Q extends ObjectType, B extends ObjectType>(req: Request, routeSchemas?: RouteSchemas<P, Q, B>): KibanaRequest<P["type"], Q["type"], B["type"]>;
|
||||
// (undocumented)
|
||||
getFilteredHeaders(headersToKeep: string[]): Pick<Record<string, string | string[] | undefined>, string>;
|
||||
|
@ -183,8 +186,6 @@ export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown> {
|
|||
// (undocumented)
|
||||
readonly route: RecursiveReadonly<KibanaRequestRoute>;
|
||||
// (undocumented)
|
||||
unstable_getIncomingMessage(): import("http").IncomingMessage;
|
||||
// (undocumented)
|
||||
readonly url: Url;
|
||||
}
|
||||
|
||||
|
@ -386,6 +387,19 @@ export class ScopedClusterClient {
|
|||
callAsInternalUser(endpoint: string, clientParams?: Record<string, unknown>, options?: CallAPIOptions): Promise<unknown>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface SessionStorage<T> {
|
||||
clear(): void;
|
||||
get(): Promise<T | null>;
|
||||
set(sessionValue: T): void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface SessionStorageFactory<T> {
|
||||
// (undocumented)
|
||||
asScoped: (request: Readonly<Request> | KibanaRequest) => SessionStorage<T>;
|
||||
}
|
||||
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue