summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2025-10-24 19:10:29 -0400
committerOwen Jacobson <owen@grimoire.ca>2025-11-06 18:59:15 -0500
commit1f44cd930cdff94bb8cf04f645a5b035507438d9 (patch)
tree0f572a68498c156df4b3a8b0d669c3786c38888d /docs
parente2a851f68aacd74a248e925ab334c3cf9eabba18 (diff)
Add an endpoint for creating push subscriptions.
The semantics of this endpoint are somewhat complex, and are incompletely captured in the associated docs change. For posterity, the intended workflow is: 1. Obtain Pilcrow's current VAPID key by connecting (it's in the events, either from boot or from the event stream). 2. Use the browser push APIs to create a push subscription, using that VAPID key. 3. Send Pilcrow the push subscription endpoint and keys, plus the VAPID key the client used to create it so that the server can detect race conditions with key rotation. 4. Wait for messages to arrive. This commit does not introduce any actual messages, just subscription management endpoints. When the server's VAPID key is rotated, all existing subscriptions are discarded. Without the VAPID key, the server cannot service those subscriptions. We can't exactly notify the broker to stop processing messages on those subscriptions, so this is an incomplete solution to what to do if the key is being rotated due to a compromise, but it's better than nothing. The shape of the API endpoint is heavily informed by the [JSON payload][web-push-json] provided by browser Web Push implementations, to ease client development in a browser-based context. The idea is that a client can take that JSON and send it to the server verbatim, without needing to transform it in any way, to submit the subscription to the server for use. [web-push-json]: https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription/toJSON Push subscriptions are operationally associated with a specific _user agent_, and have no inherent relationship with a Pilcrow login or token (session). Taken as-is, a subscription created by user A could be reused by user B if they share a user agent, even if user A logs out before user B logs in. Pilcrow therefore _logically_ associates push subscriptions with specific tokens, and abandons those subscriptions when the token is invalidated by * logging out, * expiry, or * changing passwords. (There are no other token invalidation workflows at this time.) Stored subscriptions are also abandoned when the server's VAPID key changes.
Diffstat (limited to 'docs')
-rw-r--r--docs/api/SUMMARY.md1
-rw-r--r--docs/api/authentication.md4
-rw-r--r--docs/api/basics.md2
-rw-r--r--docs/api/push.md124
4 files changed, 131 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/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).