Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions doc/7/core-classes/kuzzle/authenticate/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
code: true
type: page
title: authenticate
description: Authenticate the SDK with the setted authenticator
---

# authenticate

Authenticate the SDK by using the function set in the [authenticator](/sdk/js/7/core-classes/kuzzle/properties#authenticator) property.

## Arguments

```js
authenticate();
```

## Usage

<<< ./snippets/authenticate.js
11 changes: 11 additions & 0 deletions doc/7/core-classes/kuzzle/authenticate/snippets/authenticate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
kuzzle.authenticator = async () => {
await kuzzle.auth.login('local', { username: 'foo', password: 'bar' });
};

try {
await kuzzle.authenticate();

console.log('Success');
} catch (error) {
console.error(error.message);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
name: kuzzle#authenticate
description: Authenticate the SDK
hooks:
before: curl -X POST kuzzle:7512/users/foo/_create -H "Content-Type:application/json" --data '{"content":{"profileIds":["default"]},"credentials":{"local":{"username":"foo","password":"bar"}}}'
after: curl -X DELETE kuzzle:7512/users/foo
template: default
expected: Success
63 changes: 40 additions & 23 deletions doc/7/core-classes/kuzzle/properties/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ order: 10

# Read-only properties

| Property name | Type | Description |
| -------------------- | -------- | ---------------------|
| `authenticated` | <pre>boolean</pre> | Returns `true` if the SDK holds a valid token |
| `connected` | <pre>boolean</pre> | Returns `true` if the SDK is currently connected to a Kuzzle server. |
| `offlineQueue` | <pre>object[]</pre> | Contains the queued requests during offline mode |
| `protocol` | <pre>Protocol</pre> | Protocol used by the SDK |
| Property name | Type | Description |
|-----------------|---------------------|----------------------------------------------------------------------|
| `authenticated` | <pre>boolean</pre> | Returns `true` if the SDK holds a valid token |
| `connected` | <pre>boolean</pre> | Returns `true` if the SDK is currently connected to a Kuzzle server. |
| `offlineQueue` | <pre>object[]</pre> | Contains the queued requests during offline mode |
| `protocol` | <pre>Protocol</pre> | Protocol used by the SDK |

### connected

Expand All @@ -24,18 +24,35 @@ See the associated documentation:

# Writable properties

| Property name | Type | Description |
| -------------------- | -------- | ---------------------|
| `autoQueue` | <pre>boolean</pre> | If `true`, automatically queues all requests during offline mode |
| `autoReplay` | <pre>boolean</pre> | If `true`, automatically replays queued requests on a `reconnected` event |
| `autoResubscribe` | <pre>boolean</pre> | If `true`, automatically renews all subscriptions on a `reconnected` event |
| `jwt` | <pre>string</pre> | Authentication token |
| `offlineQueueLoader` | <pre>function</pre> | Called before dequeuing requests after exiting offline mode, to add items at the beginning of the offline queue |
| `queueFilter` | <pre>function</pre> | Custom function called during offline mode to filter queued requests on-the-fly |
| `queueMaxSize` | <pre>number</pre> | Number of maximum requests kept during offline mode|
| `queueTTL` | <pre>number</pre> | Time a queued request is kept during offline mode, in milliseconds |
| `replayInterval` | <pre>number</pre> | Delay between each replayed requests |
| `volatile` | <pre>object</pre> | Common volatile data, will be sent to all future requests |
| Property name | Type | Description |
|----------------------|---------------------|-----------------------------------------------------------------------------------------------------------------|
| `authenticator` | <pre>function</pre> | Authenticator function to authenticate the SDK. (After called, the `jwt` property of the SDK has to be set.) |
| `autoQueue` | <pre>boolean</pre> | If `true`, automatically queues all requests during offline mode |
| `autoReplay` | <pre>boolean</pre> | If `true`, automatically replays queued requests on a `reconnected` event |
| `autoResubscribe` | <pre>boolean</pre> | If `true`, automatically renews all subscriptions on a `reconnected` event |
| `jwt` | <pre>string</pre> | Authentication token |
| `offlineQueueLoader` | <pre>function</pre> | Called before dequeuing requests after exiting offline mode, to add items at the beginning of the offline queue |
| `queueFilter` | <pre>function</pre> | Custom function called during offline mode to filter queued requests on-the-fly |
| `queueMaxSize` | <pre>number</pre> | Number of maximum requests kept during offline mode |
| `queueTTL` | <pre>number</pre> | Time a queued request is kept during offline mode, in milliseconds |
| `replayInterval` | <pre>number</pre> | Delay between each replayed requests |
| `volatile` | <pre>object</pre> | Common volatile data, will be sent to all future requests |

### authenticator

The `authenticator` property can be set to a function returning a promise.

This function will be called after a successful reconnection if the current authentication token is not valid anymore.

This function has to authenticate the SDK. It can be a call to [auth.login](/sdk/js/7/controllers/auth/login) for example.

```js
kuzzle.authenticator = async () => {
await kuzzle.auth.login('local', { username: 'user', password: 'pass' });
}
```

If the `authenticator` function fail to authenticate the SDK, then the `reconnected` event is never emitted and a `reconnectionError` event is emitted.

### offlineQueueLoader

Expand All @@ -49,11 +66,11 @@ Promise<Object[]> offlineQueueLoader()

The returned (or resolved) array must contain objects, each with the following properties:

| Property | Type | Description |
|---|---|---|
| `query` | <pre>object</pre> | Object representing the request that is about to be sent to Kuzzle, following the [Kuzzle API](/core/2/guides/main-concepts/querying) format |
| `reject` | <pre>function</pre> | A [Promise.reject](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject) function |
| `resolve` | <pre>function</pre> | A [Promise.resolve](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) function |
| Property | Type | Description |
|-----------|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
| `query` | <pre>object</pre> | Object representing the request that is about to be sent to Kuzzle, following the [Kuzzle API](/core/2/guides/main-concepts/querying) format |
| `reject` | <pre>function</pre> | A [Promise.reject](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject) function |
| `resolve` | <pre>function</pre> | A [Promise.resolve](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) function |

### queueFilter

Expand Down
31 changes: 31 additions & 0 deletions doc/7/essentials/offline-tools/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,33 @@ order: 400
The Kuzzle SDK provides a set of properties that helps your application to be resilient to the loss of network connection
during its lifespan.

## Authentication after reconnection

When the SDK reconnect, the authentication token may not be valid anymore.

It's possible to set the [authenticator](/sdk/js/7/core-classes/kuzzle/properties#authenticator) function to allows the SDK to re-authenticate after a successful reconnection.

<details><summary>Example to automatically re-authenticate on reconnection</summary>

```js
const { Kuzzle, WebSocket } = require('kuzzle');

const kuzzle = new Kuzzle(new WebSocket('localhost'), { autoResubscribe: true });

kuzzle.authenticator = async () => {
await kuzzle.auth.login('local', { username: 'test', password: 'test' });
};

await kuzzle.connect();
await kuzzle.authenticate();

await kuzzle.realtime.subscribe('test', 'test', {}, () => {
console.log('Received');
});
```

</details>

## Contructor options and properties

These properties can be set in the `options` object when [instantiating a new SDK](/sdk/js/7/core-classes/kuzzle/constructor#arguments).
Expand Down Expand Up @@ -77,6 +104,10 @@ A read-only `number` specifying the time in milliseconds between different recon

Default value: *Depends on the Protocol*

### reconnectionError

Emitted when the SDK reconnect to Kuzzle and does not have a valid authentication token or can't renew it with the [authenticator](/sdk/js/7/core-classes/kuzzle/properties#authenticator) function.

## Methods

### [flushQueue()](/sdk/js/7/core-classes/kuzzle/flush-queue)
Expand Down
118 changes: 91 additions & 27 deletions src/Kuzzle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const events = [
'offlineQueuePop',
'queryError',
'reconnected',
'reconnectionError',
'tokenExpired'
];

Expand All @@ -42,41 +43,46 @@ export class Kuzzle extends KuzzleEventEmitter {
/**
* Protocol used by the SDK to communicate with Kuzzle.
*/
public protocol: any;
protocol: any;
/**
* If true, automatically renews all subscriptions on a reconnected event.
*/
public autoResubscribe: boolean;
autoResubscribe: boolean;
/**
* Timeout before sending again a similar event.
*/
public eventTimeout: number;
eventTimeout: number;
/**
* SDK version.
*/
public sdkVersion: string;
sdkVersion: string;
/**
* SDK name (e.g: `js@7.4.2`).
*/
public sdkName: string;
sdkName: string;
/**
* Common volatile data that will be sent to all future requests.
*/
public volatile: JSONObject;
volatile: JSONObject;
/**
* Handle deprecation warning in development mode (hidden in production)
*/
public deprecationHandler: Deprecation;

public auth: AuthController;
public bulk: any;
public collection: CollectionController;
public document: DocumentController;
public index: IndexController;
public ms: any;
public realtime: RealtimeController;
public security: any;
public server: any;
deprecationHandler: Deprecation;
/**
* Authenticator function called after a reconnection if the SDK is no longer
* authenticated.
*/
authenticator: () => Promise<void> = null;

auth: AuthController;
bulk: any;
collection: CollectionController;
document: DocumentController;
index: IndexController;
ms: any;
realtime: RealtimeController;
security: any;
server: any;

private _protectedEvents: any;
private _offlineQueue: any;
Expand Down Expand Up @@ -236,38 +242,38 @@ export class Kuzzle extends KuzzleEventEmitter {
this._cookieAuthentication = typeof options.cookieAuth === 'boolean'
? options.cookieAuth
: false;

if (this._cookieAuthentication) {
this.protocol.enableCookieSupport();
let autoQueueState;
let autoReplayState;
let autoResbuscribeState;

this.protocol.addListener('websocketRenewalStart', () => {
autoQueueState = this.autoQueue;
autoReplayState = this.autoReplay;
autoResbuscribeState = this.autoResubscribe;

this.autoQueue = true;
this.autoReplay = true;
this.autoResubscribe = true;
});

this.protocol.addListener('websocketRenewalDone', () => {
this.autoQueue = autoQueueState;
this.autoReplay = autoReplayState;
this.autoResubscribe = autoResbuscribeState;
});
}

this.deprecationHandler = new Deprecation(
typeof options.deprecationWarning === 'boolean' ? options.deprecationWarning : true
);

if (this._cookieAuthentication && typeof XMLHttpRequest === 'undefined') {
throw new Error('Support for cookie authentication with cookieAuth option is not supported outside a browser');
}

// controllers
this.useController(AuthController, 'auth');
this.useController(BulkController, 'bulk');
Expand Down Expand Up @@ -525,11 +531,19 @@ export class Kuzzle extends KuzzleEventEmitter {
this.emit('disconnected', context);
});

this.protocol.addListener('reconnect', () => {
this.protocol.addListener('reconnect', async () => {
if (this.autoQueue) {
this.stopQueuing();
}

// If an authenticator was set, check if the token is still valid and try
// to re-authenticate if needed. Otherwise the SDK is in disconnected state.
if (this.authenticator && ! await this.tryReAuthenticate()) {
this.disconnect();

return;
}

if (this.autoReplay) {
this.playQueue();
}
Expand All @@ -542,6 +556,56 @@ export class Kuzzle extends KuzzleEventEmitter {
return this.protocol.connect();
}

/**
* Try to re-authenticate the SDK if the current token is invalid.
*
* If the token is invalid, this method will return false and emit a
* "reconnectionError" event when:
* - the SDK cannot re-authenticate using the authenticator function
* - the authenticator function is not set
*
* This method never returns a rejected promise.
*/
private async tryReAuthenticate (): Promise<boolean> {
try {
const { valid } = await this.auth.checkToken();

if (valid) {
return true;
}

await this.authenticate();

return true;
}
catch (err) {
this.emit('reconnectionError', {
error: new Error(`Failed to authenticate the SDK after reconnection: ${err}`)
});

return false;
}
}

/**
* Use the "authenticator" function to authenticate the SDK.
*
* @returns The authentication token
*/
async authenticate (): Promise<void> {
if (typeof this.authenticator !== 'function') {
throw new Error('The "authenticator" property must be a function.');
}

await this.authenticator();

const { valid } = await this.auth.checkToken();

if (! valid) {
throw new Error('The "authenticator" function failed to authenticate the SDK.');
}
}

/**
* Adds a listener to a Kuzzle global event. When an event is fired, listeners are called in the order of their
* insertion.
Expand Down Expand Up @@ -801,7 +865,7 @@ Discarded request: ${JSON.stringify(request)}`));
uniqueQueue = {},
dequeuingProcess = () => {
if (this.offlineQueue.length > 0) {

this._timeoutRequest(
this.offlineQueue[0].timeout,
this.offlineQueue[0].request,
Expand Down Expand Up @@ -853,7 +917,7 @@ Discarded request: ${JSON.stringify(request)}`));

/**
* Sends a request with a timeout
*
*
* @param delay Delay before the request is rejected if not resolved
* @param request Request object
* @param options Request options
Expand Down
Loading