# Nchan hooks

## Overview

Nchan channels can be secured and managed with _hooks_: subscribe, unsubscribe, request and authenticate. Note that the first two are triggered only for WebSocket and EventSource connections, but not for HTTP requests.

This package provides a simple set of such hooks to help implementing secured channels as well as providing client-related data. They are located in `/hooks`.

| File     | Hook        | Location type | Intention                                 |
|----------|-------------|---------------|-------------------------------------------|
| auth.php | request     | publisher only| Block messages posted by unauthenticated HTTP requests |
| sub.php  | subscribe   | subscriber    | Restrict access to authenticated clients, provide `clientId`, and add session-specific channels |
| unsub.php| unsubscribe | subscriber    | Notify the controller about disconnected clients |

All scripts includes `common.php` with common functions and constants:

* `openSession($readonly=false)` opens the PHP session where tokens are stored
* `loadTokenStorage($readonly=false)` returns an instance of `TokenStorageInterface` that provides a token storage
* `SESSION_COOKIE` is a constant with the name of the cookie that contains the session ID
* `SESSION_KEY` is the key of the `$_SESSION` array that contains tokens.

The provided setup is aimed at standard QFQ isntallations.


## Nchan hooks helper

The business logic of hook scripts is delegated to `NchanRequest` class, which handles communication with nchan. _Authorization_ is typically performed using an instance `TokenStorage` to load and verify tokens stored in the PHP session.

A user is encoranged to customize `NchanRequest` by overriding/reimplementing the following methods when needed:

* `send(array $payload, string $url): string` - must post the payload to the provided location and return the response
* `generateClientId(): string` - must return an identification string of the client


### Authenticating HTTP publishers

The `auth.php` hook script verifies the `Authorization` header against the allowed channels defined in the token. Wildcards in names of channels are respected.

### Subscriber initialization

The `sub.php` script is triggered when a WebSocket client connects. It performs the following steps:
1. Validation of the client's token.
2. Generating a unique `clientId`.
3. Prepending `user.{clientId}` and `ses.{sessionId}` channels to the list of subscription channels (in accordance to the nginx configuration).
4. Sending a `hello` message back to the client with the effective subscription data.
5. Notifying the controller channel about this event.

### Handling disconnections

The script `unsub.php` is triggered when a WebSocket client disconnects. The provided example notifies only the channel controller about this event.

### NchanRequest class

The class `NchanRequest` provides properties of the nchan locations on which the hook script runs:

| Property         | Type     | Public? | Description                                          |
|------------------|----------|---------|------------------------------------------------------|
|`$nchanVersion`   | `string` | yes     | Nchan version                                        |
|`$requestUri`     | `string` | yes     | The nchan location                                   |
|`$subscriberType` | `string` | yes     | The type of the subscriber: `websocket`, `http`, etc.|
|`$group`          | `string` | no      | The group of the channels                            |
|`$pubChannels`    | `array`  | no      | The list of publisher channels                       |
|`$subChannels`    | `array`  | no      | The list of subscriber channels                      |
|`$responseChannel`| `string` | no      | The main channel to which the client has subscribed  |

Non-public properties can be accessed or modified with dedicated accessors, such as `getChannelGroup()`, `setChannelGroup($newGroup)`, `getPubChannels()`, etc. There are also several methods intended to communicate with nchan:

 * `respond(array $payload): ?NchanResponse` - sends a response with the given payload back to the client.
 * `notifyController(string $event, array $payload=[]): ?NchanResponse` - sends a message for the given event and payload to the controller channel of the current group.
 * `forward(string $body): void` - forwards the request body to the publisher channel and returns the response headers and body to the client; indended to intercept and modify messages from the client.
 * `close(int $code, string $msg=''): void` - closes the connection with the provided HTTP code and an optional message. This method should be used in sub/auth hooks before sending a reply message.
