npm package: @interopio/gateway
Description: [![npm version](https://img.shields.io/npm/v/@interopio/gateway.svg)](https://www.npmjs.com/package/@interopio/gateway)
Author: interop.io
Latest version: 0.25.3

# io.Gateway

[![npm version](https://img.shields.io/npm/v/@interopio/gateway.svg)](https://www.npmjs.com/package/@interopio/gateway)

## Overview

The `@interopio/gateway` package is the gateway for io.Connect Desktop/Browser.

## Table of Contents

- [Installation](#installation)
- [Requirements](#requirements)
- [Usage Examples](#usage-examples)
    - [Change Token TTL](#change-token-ttl)
    - [Basic Authenticator](#basic-authenticator)
    - [OAuth 2.0 Authenticator](#oauth-20-authenticator)
    - [Custom Authenticator](#custom-authenticator)
    - [Client Authentication](#client-authentication)
    - [Visibility](#visibility)
- [Changelog](#changelog)
- [License](#license)

## Installation

```shell
npm install @interopio/gateway@latest
```

## Requirements

- WebCrypto - [WebCrypto](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) is a standard API for
  performing cryptographic operations in web applications. It is supported in most modern browsers and Node.js
  environments. If running Node.js 18, you need to enable the `--experimental-global-webcrypto` flag, or set
  `globalThis.crypto` your self. For example `const { webcrypto } = require('crypto'); globalThis.crypto = webcrypto;`

- WebSocket - [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) is a protocol for full-duplex
  communication channels over a single TCP connection. It is supported in most modern browsers and Node.js environments.
  If running Node.js 18, you need to install the `ws` package. For example `npm install ws`.

- fetch - [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is a modern interface for making HTTP
  requests in web applications. It is supported in most modern browsers and Node.js environments.
  If running Node.js 16, you need to enable the `--experimental-fetch` flag, or set `globalThis.fetch` your self.

- URL - [URL API](https://developer.mozilla.org/en-US/docs/Web/API/URL) is a standard API for parsing and manipulating
  URLs in web applications. It is supported in most modern browsers and Node.js environments.
  If running Node.js 10 or below, you need to install the `url` package. For example `npm install url` and set
  `globalThis.URL` your self. For example `const { URL } = require('url'); globalThis.URL = URL;`

## API docs

See [`gateway.md`](./gateway.md)

## Usage Examples

### Change Token TTL

```typescript
import Gateway from '@interopio/gateway'

const gateway = Gateway({
    token: {
        ttl: 1 * 24 * 60 * 60 // expiry of access tokens in seconds. 86400 is 1 day.
    }
});

await gateway.start();
```

### Basic Authenticator

By default, io.Gateway is secured with the built-in `basic` authenticator.
It handles authentication requests with method `secret` (i.e. client is providing `login` and/or `secret`).

It can be customized by providing custom `usernameResolver` and/or `secretVerifier` function(s).

By default, usernames are lowercased before comparison and before being passed to the `usernameResolver`.
Set `usernameCaseSensitive` to `true` to preserve the original case of usernames.

```typescript
import Gateway from '@interopio/gateway';

const gateway = Gateway({
    authentication: {
        basic: {
            // When true, the original case of usernames is preserved.
            // When false (default), usernames are converted to lowercase.
            usernameCaseSensitive: true,
            usernameResolver: (login?: string) => {
                // custom username resolution logic
                return login === 'guest' ? 'guest@interop.io' : login;
            },
            secretVerifier: async (username: string, password: string): Promise<boolean> => {
                // custom validation logic
                return username === 'admin' && password === 'adminpass';
            }
        }
    }
});
```

### OAuth 2.0 Authenticator

To secure io.Gateway with OAuth 2.0 authentication services like auth0 or okta you need to configure

```typescript
import Gateway from '@interopio/gateway'

const gateway = Gateway({
    authentication: {
        default: 'oauth2',
        available: ['basic', 'oauth2'], // remove basic if want oauth2 only
        oauth2: {issuerBaseURL: 'https://<your-issuer-domain>', audience: 'https://<myapi>'}
    }
});

const client = gateway.client((msg) => console.log(msg));
const token = '<authorizationToken>';
client.send({type: 'hello', identity: {application: 'test'}, authentication: {method: 'access-token', token}});
```


### Custom Authenticator

To secure io.Gateway with a custom authenticator, you can add a subsection with `authenticator` property in the `authentication` option.
Then list it in the `available` array. The custom authenticator is a function that takes an `authentication` object and returns a promise that resolves to an object with either a `type` of `success` or `continue`.

- If the authentication is successful, resolve the result with `type: 'success'` and optionally a `user` property.
- If the authentication requires further user interaction, return an object with `type: 'continue'`.
- If the authentication fails, you can throw an error or return a rejected promise.

```typescript
// no-user.ts
import {AuthenticationRequest, AuthenticationResponse} from '@interopio/gateway/auth/api';

export default async function none({authentication}: AuthenticationRequest): Promise<AuthenticationResponse> {
    if (authentication.method === 'none') {
        // No authentication required
        return {type: 'success'};
    }
    return {type: 'error', message: 'Authentication method not supported'};
}
// os-user.ts
import {AuthenticationRequest, AuthenticationResponse} from '@interopio/gateway/auth/api';

export default async function node({authentication}: AuthenticationRequest): Promise<AuthenticationResponse> {
    if (authentication.method === 'none') {
        return {type: 'success', user: process.env['USER'] ?? process.env['USERNAME']};
    }
    return {type: 'error', message: 'Authentication method not supported'};
}

// myauth.ts
import {AuthenticationRequest, AuthenticationResponse} from '@interopio/gateway/auth/api';

export default async function myauth({authentication}: AuthenticationRequest): Promise<AuthenticationResponse> {
    if (authentication.method === 'access-token') {
        // Custom authentication logic
        // For example, check a token or perform some validation
        if (authentication.token === 'my-secret-token') {
            return {type: 'success', user: 'myuser'};
        }
        return {type: 'error', message: 'Invalid token'};
    }
    return {type: 'error', message: 'Authentication method not supported'};
}

// index.ts
import Gateway from '@interopio/gateway';

const gateway = Gateway({
    authentication: {
        default: 'myauth',
        available: ['basic', 'myauth'],
        myauth: {
            authenticator: none
        }
    }
});
```

### Client Authentication

```typescript
import {IOGateway} from '@interopio/gateway'

const gateway = IOGateway.Factory({});

await gateway.start();

const opts: IOGateway.GatewayClientOptions = {
    // this callback is invoked when authentication method 'gateway-client' is requested on hanshake (hello)
    onAuthenticate: async (request: IOGateway.Auth.AuthenticationRequest): Promise<IOGateway.Auth.AuthenticationSuccess> => {
        // throw Error to reject the request
        return {type: 'success', user: 'client'};
    }
};
const client: IOGateway.GatewayClient = gateway.client((msg) => {
    if (msg.type === 'welcome') {
        console.log(`authenticated with user: ${msg.resolved_identity.user}`);
    }
}, opts);
// handshake with authentication method 'gateway-client'
client.send({type: 'hello', identity: {application: 'demo'}, authentication: {method:'gateway-client'}});

client.close();
await gateway.close();

```

### Metrics Publisher

```typescript
import Gateway from '@interopio/gateway'

const gateway = Gateway({
    metrics: {
        publishers: ['rest'],
        rest: {
            endpoint: 'http://localhost:8084/api/metrics',
            authentication: false
        }
    }
});
```

### Visibility

```typescript
import Gateway from '@interopio/gateway'

const gateway = Gateway({
    contexts: {
        visibility: [
            { context: /'___channel___.+'/, restrictions: 'cluster' }, // all context starting with '___channel___' are with 'cluster' visibility
            { context: /T42\..+/, restrictions: 'local', identity: { application: 'io.Connect Desktop' } }, // all context starting with T42. created by 'io.Connect Desktop' are with 'local' visibility
            { context: /.+/, restrictions: 'cluster' } // all other contexts are with cluster visibility
        ]
    },
    methods: {
        visibility: [
            { method: /.+/, restrictions: 'cluster', identity: { application: 'Outlook' } }, // all methods registered by 'Outlook' are with 'cluster' visibility (overrides the T42.* rule below)
            { method: /T42\..+/, restrictions: 'local' }, // all methods and streams starting with T42. are with 'local' visibility
            { method: /.+/, restrictions: 'cluster' } // all other methods and streams are with 'cluster' visibility
        ]
    },
    peers: {
        visibility: [
            // { domain: 'context', restrictions: 'cluster' }, does nothing
            { domain: 'interop', restrictions: 'local' }, // interop domain is with 'local' visibility
            { domain: 'interop', restrictions: 'cluster' },
            { domain: 'bus', restrictions: 'local' }
        ]
    }
});
```

### Context Lifetime

```typescript
import Gateway from '@interopio/gateway'

const gateway = Gateway({contexts: {lifetime: 'retained'}});
```

### Cluster

You can use the `cluster` option to configure the cluster settings.

For example, to configure the cluster to use an io.Bridge instance running on `localhost:8084`, you can do the following:

```typescript
import Gateway from '@interopio/gateway';

const gateway = await IOGateway.Gateway({
    cluster: {
        // io.Bridge url
       endpoint: 'http://localhost:8084',
    }
});
```
## Changelog

See [changelog](./changelog.md)

## License

[MIT](license.md)