diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2025-11-08 16:28:10 -0500 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2025-11-08 16:28:10 -0500 |
| commit | fc6914831743f6d683c59adb367479defe6f8b3a (patch) | |
| tree | 5b997adac55f47b52f30022013b8ec3b2c10bcc5 /docs | |
| parent | 0ef69c7d256380e660edc45ace7f1d6151226340 (diff) | |
| parent | 6bab5b4405c9adafb2ce76540595a62eea80acc0 (diff) | |
Integrate the prototype push notification support.
We're going to move forwards with this for now, as low-utility as it is, so that we can more easily iterate on it in a real-world environment (hi.grimoire.ca).
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/api/SUMMARY.md | 1 | ||||
| -rw-r--r-- | docs/api/authentication.md | 4 | ||||
| -rw-r--r-- | docs/api/basics.md | 2 | ||||
| -rw-r--r-- | docs/api/events.md | 30 | ||||
| -rw-r--r-- | docs/api/push.md | 124 | ||||
| -rw-r--r-- | docs/ops.md | 14 |
6 files changed, 175 insertions, 0 deletions
diff --git a/docs/api/SUMMARY.md b/docs/api/SUMMARY.md index f51fbc7..5974ba7 100644 --- a/docs/api/SUMMARY.md +++ b/docs/api/SUMMARY.md @@ -7,3 +7,4 @@ - [Events](events.md) - [Invitations](invitations.md) - [Conversations and messages](conversations-messages.md) +- [Push notifications](push.md) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index 189103e..801f0e7 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -85,6 +85,8 @@ This endpoint will respond with a status of `401 Unauthorized` if the login name Invalidates the identity token used to make the request, logging the caller out. +This terminates any [push subscriptions](push.md#receiving-web-push-messages) associated with the token. + ### Request ```json @@ -103,6 +105,8 @@ The response will include a `Set-Cookie` header that clears the `identity` cooki Changes the current user's password, and invalidates all outstanding identity tokens. +This terminates any [push subscriptions](push.md#receiving-web-push-messages) associated with existing tokens. + ### Authentication failure This endpoint will respond with a status of `401 Unauthorized` if the provided identity token is not valid. diff --git a/docs/api/basics.md b/docs/api/basics.md index ab39570..180880e 100644 --- a/docs/api/basics.md +++ b/docs/api/basics.md @@ -49,3 +49,5 @@ In addition to the documented status codes for each endpoint, any endpoint may r ## Errors When the server returns an error (any response whose status code is 400 or greater), the response body is freeform text (specifically, `text/plain`), which may be shown to the user, logged, or otherwise handled. Programmatic action should rely on the documented status codes, and not on the response body. + +A small number of endpoints deliver errors in other formats. These exceptions are documented with the endpoints they're relevant to. diff --git a/docs/api/events.md b/docs/api/events.md index e692f82..9db4613 100644 --- a/docs/api/events.md +++ b/docs/api/events.md @@ -237,3 +237,33 @@ These events have the `event` field set to `"deleted"`. They include the followi | :---- | :-------- | :---------------------------------- | | `at` | timestamp | The moment the message was deleted. | | `id` | string | The deleted message's ID. | + +## VAPID key events + +The following events describe changes to Pilcrow's [VAPID key](https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#applicationserverkey). + +These events have the `type` field set to `"vapid"`. + +### VAPID key changed + +```json +{ + "type": "vapid", + "event": "changed", + "at": "2025-08-30T05:44:58.100206Z", + "key": "BKILjh0SzTiHOZ5P_sfduv-iqtOg_S18nR7ePcjnDivJaOY6nOG1L3OmvjXjNnthlRDdVnawl1_owfdPCvmDt5U=" +} +``` + +Sent whenever the server's VAPID key changes. + +These events have the `event` field set to `"changed"`. They include the following additional fields: + +| Field | Type | Description | +| :---- | :-------- | :----------------------------------------------------------------------------------------------- | +| `at` | timestamp | The moment the key was changed. | +| `key` | string | A URL-safe Base64 encoding of the VAPID public key, usable to create new Web Push subscriptions. | + +The server may change its VAPID key at any time, and will do so periodically to manage the risk of the private key being leaked. When the key is changed, old keys are immediately destroyed, and the corresponding change events are removed from the event stream. + +Clients must use the most recent VAPID key when creating Web Push subscriptions. If the key changes, clients must invalidate or recreate existing subscriptions - the previous key is no longer valid and will no longer be used, and push subscriptions using that key will not be fulfilled. diff --git a/docs/api/push.md b/docs/api/push.md new file mode 100644 index 0000000..363bff3 --- /dev/null +++ b/docs/api/push.md @@ -0,0 +1,124 @@ +# Push Notifications + +```mermaid +--- +Push lifecycle +--- +sequenceDiagram + actor Andrea + participant API + participant Broker + + Note over Andrea, Broker : Creating a push subscription + + Andrea ->>+ API : Client Boot + API ->>- Andrea : Boot message with VAPID keys + + Andrea ->>+ API : Subscribe to events + + Andrea ->>+ Broker : New Push Subscription + Broker ->>- Andrea : Subscription URL + + Andrea ->>+ API : Subscribe + API ->>- Andrea : Created + + API ->>- Andrea : Disconnect + + Note over Andrea, Broker : Asynchronous notification + + API ->> Broker : Push Publication + Broker ->> Andrea : Push Delivery + + Andrea ->>+ API : Resume event subscription + API ->>- Andrea : Disconnect + + Note over Andrea, Broker : VAPID key rotation + + Andrea ->>+ API : Subscribe to events + API -) Andrea : VAPID key changed + + Andrea ->>+ Broker : Unsubscribe + Broker ->>- Andrea : Unsubscribed + Andrea ->>+ Broker : New Push Subscription + Broker ->>- Andrea : Subscription URL + + Andrea ->>+ API : Subscribe + API ->>- Andrea : Created + + API ->>- Andrea : Disconnect +``` + +Pilcrow uses [Web Push] to notify clients asynchronously of interesting events, to allow clients to disconnect from [the event stream](events.md) while keeping their users informed of updates to conversations. Clients are _not required_ to implement push subscriptions. Pilcrow's primarily channel for communicating real-time communications data to clients is the [event stream](events.md). + +[Web Push]: https://developer.mozilla.org/en-US/docs/Web/API/Push_API + +## VAPID keys + +Pilcrow uses a generated Voluntary Application Server Identification (VAPID) key to authenticate to push brokers. Clients do not generally need to handle VAPID authentication themselves, but, due to the design of the Web Push protocol, clients are responsible for providing Pilcrow's VAPID key to push brokers when subscribing to notifications. To make this possible, Pilcrow delivers its VAPID key to clients via the event stream. See the [VAPID key events](events.md#vapid-key-events) section for details of these events. + +Pilcrow rotates the VAPID key periodically, and sends all clients an event when this occurs. This immediately invalidates all existing subscriptions, as the previous VAPID key is destroyed. Clients that wish to continue receiving messages must re-subscribe using the new VAPID key when this happens, and will miss any Web Push messages sent during this interval. + +## Receiving Web Push messages + +Pilcrow sends web push messages to subscriptions. A Pilcrow user may have many subscriptions - in principle, generally one per client, but Pilcrow does not enforce any such limit. Clients create subscriptions as needed, and inform the Pilcrow server of those subscriptions to begin the flow of push messages. + +The specific taxonomy and structure of push messages is still under development. This API is even more experimental than the rest of Pilcrow. + +Pilcrow keeps track of the token used to create a web push subscription, and abandons subscriptions when that token is invalidated. This prevents the server from inadvertently sending information to a client after the user for which that information is intended has logged out, or after another user has taken over the client. Clients are responsible for re-establishing subscriptions on login if they wish to resume push messaging. + +## `POST /api/push/subscribe` + +Inform the Pilcrow server of a push subscription. + +### Request + +```json +{ + "subscription": { + "endpoint": "https://example.push.com/P1234", + "keys": { + "p256dh": "base64-encoded key", + "auth": "base64-encoded authentication secret" + } + } + "vapid": "BKILjh0SzTiHOZ5P_sfduv-iqtOg_S18nR7ePcjnDivJaOY6nOG1L3OmvjXjNnthlRDdVnawl1_owfdPCvmDt5U=" +} +``` + +The request must have the following fields: + +| Field | Type | Description | +| :------------- | :----- | :------------------------------------------------------ | +| `subscription` | object | The push subscription object created by the user agent. | +| `vapid` | string | The VAPID key used to create the subscription. | + +The `subscription` field should be the result of calling `toJSON()` on a `PushSubscription` object in the DOM API. For details, see [the WebPush specification](https://w3c.github.io/push-api/#dom-pushsubscription-tojson) or [MDN](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription/toJSON). + +The `vapid` field should be set to the key used when creating the subscription, which should in turn be obtained from the event stream. + +[wp-example]: https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription#sending_coding_information_to_the_server + +This request can be retried on any non-400 response, including any network-related errors. A duplicate subscription with the same endpoint, the same public key and the same authentication secret will be accepted by the server even if the subscription has already been created. + +### Success + +This endpoint will respond with a status of `201 Created` when successful. The response body is empty. + +### Duplicate endpoint + +If the push subscription's endpoint URL is already associated with a subscription with different keys, then Pilcrow will return a `409 Conflict` response. + +### Stale VAPID key + +If the provided `vapid` key is not the server's current VAPID key, then the client has created a subscription which is not usable. This may happen if Pilcrow rotates its key while a client is negotiating with a Web Push broker to set up a subscription using the old key. Pilcrow will respond a `400 Bad Request` response. + +**This response includes a JSON body**, unlike other errors, and will have a `content-type: application/json` header. + +The response will include the following fields: + +| Field | Type | Description | +| :-------- | :----- | :------------------------------ | +| `message` | string | A human-readable error message. | +| `key` | string | Pilcrow's new VAPID key. | + +Clients should immediately destroy the push subscription they were attempting to create, and may try again using the new VAPID key (either immediately, using the key from the response, or asynchronously, using the key when it is delivered via the event stream). diff --git a/docs/ops.md b/docs/ops.md index a622c04..678d2a4 100644 --- a/docs/ops.md +++ b/docs/ops.md @@ -21,3 +21,17 @@ By default, the `pilcrow` command will set the process' umask to a value that pr - any octal value corresponding to a valid umask, such as `0027`. Pilcrow does not check or change the permissions of the database after creation. Changing the umask of the server after the database has been created has no effect on the database's filesystem permissions. + +## VAPID keys + +Pilcrow uses [VAPID] to identify itself to public Web Push brokers, which then deliver notifications to Pilcrow's users of interesting events, such as messages. VAPID uses cryptographic signatures to authenticate the server. + +[VAPID]: https://datatracker.ietf.org/doc/html/rfc8292 + +The key is stored in the `pilcrow` database. Pilcrow will create its key automatically, and will rotate the key every 30 days. + +If the `pilcrow` database is accessed inappropriately or leaked, then the key can be used to send push notifications to Pilcrow users as if from the Pilcrow server. If this happens, the key _must_ be rotated to prevent misuse. + +The key can be rotated at any time running `pilcrow […options…] rotate-vapid-key`, as the same user Pilcrow normally runs as. This does not require that the server be shut down or restarted. The `[…options…]` must be set to the same values as used by the running server. + +When the key is rotated, no further push messages will be sent from the Pilcrow server using that key. Unfortunately, the Web Push protocol doesn't allow Pilcrow to proactively invalidate clients' push subscriptions, but Pilcrow will inform clients when the key is rotated so that they can invalidate the affected subscriptions themselves. |
