-
Notifications
You must be signed in to change notification settings - Fork 80
Add Web Push Extension #471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Sorry if this is obvious, but if the server needs to wait for the client to send the |
It's used to signal the client that the commands are supported. Could use an |
capability doesn't prevent server from changing the behavior after cap was enabled. There's even |
Nah, I mean that a cap would inform the server that the client supports the ext. For instance the chat history spec uses this to make servers not send playback on connect when the |
Okay, and why this information is needed upon connect? I understand why it's needed for chathistory |
As said above, I'm not necessarily opposed to making this an |
extensions/webpush.md
Outdated
## Implementation | ||
|
||
The `webpush` capability allows clients to subscribe to Web Push and receive | ||
notifications for messages of interest. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does a user/client indicate which messages would be of interest? There doesn't appear to be mechanisms in-place in the currently offered commands to indicate which messages would be of interest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, as discussed in #ircv3, this part isn't addressed yet. For now, it's spec'ed as a "server-defined subset of IRC messages".
I think there are multiple ways to go about this:
- Keep as-is, let servers decide completely. Leave it to a future extension to let clients indicate messages of interest. This can be useful for chathistory as well, to avoid fetching all unread history when connecting.
- Indicate a minimal set of messages to forward (e.g. PRIVMSG/NOTICE/INVITE with user as target, plus PRIVMSG/NOTICE highlights). Let servers send more if they want to. Clients can always ignore some push notifications.
- Build a webpush-specific command to indicate the messages of interest. This is a slippery slope, because the rules can be pretty complicated. If we go down this road I'd rather keep it simple and have just simple word matching.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think (1) is the superior option here.
Two issues that came up in discussion with @kylef:
|
Yeah, the size limit is going to be an issue. FCM alone also has a 4096 bytes limit for the payload.
I don't think JSON would help, the limit still applies regardless of the format of the payload. Also, servers can extend messages already with tags. Am I missing something? |
Sorry, that was unclear. I meant something like: abandon RFC1459-and-derivatives entirely in the format of the push message, parse prefix/tags/command/params directly into JSON. |
How is this going to help with the size limit of the push notification payload? |
extensions/webpush.md
Outdated
|
||
The `<endpoint>` is an URL pointing to a push server, which can be used to send push messages for this particular subscription. | ||
|
||
`<keys>` is a string encoded in the message-tag format. The values are [URL-safe base64-encoded][RFC 4648 section 5]. It MUST contain at least: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This uses the URL-safe base64 encoding because that's what the W3C API uses. So this allows Web clients to just use the result of subscription.toJSON().keys
as-is.
But maybe that isn't such a good idea. The W3C API also doesn't provide any function to encode an arbitrary byte buffer to URL-safe base64, so users need to roll out their own if we use it elsewhere.
Instead we could just use regular base64, just like SASL does. Users can call subscription.getKey()
.
Do you intent to add a command to view the list of subscriptions? I could see how as a user this could be useful to debug problems and be able to manually clean up or unsubscribe old clients for which you do not know the subscription URL for. This could very well be something that a NickServ or similar service could facilitate for a user and not be included in the spec though. |
Yeah, if it's just a debugging tool, I think I'd rather let servers include it as a special service or custom commands. Also need to investigate if/how subscription expiration can be integrated. |
UnifiedPush is basically Web Push without any kind of encryption. I've been discussing with them to add encryption, they seem open to the idea (they considered Web Push in the past but didn't understand fully how encryption works). |
My plan is to go ahead and ship this as a soju vendored extension, then gather field testing feedback and report back what I've learnt here. |
The vendored extension has been merged in soju and goguma. |
Goguma and pushgarden now support Apple Push Notification service, as more evidence that this approach works on all platforms. |
The vendored extension is now supported by Igloo and Ergo. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me say, thanks very much for your work on this spec; it's well-designed and solves an important problem.
More discussion of implementation considerations and security considerations would be helpful. For example, the implications of sending a POST to a user-chosen address should be discussed in more detail. The Soju restrictions (which I copied in Ergo) are to only allow https endpoints, and to disallow contacting loopback or internal IP addresses.
|
||
If the server supports [Voluntary Application Server Identification (VAPID)][RFC 8292] and the client has enabled the `draft/webpush` capability, the server MUST advertise its public key in the `VAPID` ISUPPORT token. This key can be used to verify notifications upon reception by the Web Push server. | ||
|
||
The value MUST be the [URL-safe base64-encoded][RFC 4648 section 5] public key usable with the Elliptic Curve Digital Signature Algorithm (ECDSA) over the P-256 curve. The value MUST NOT change over the lifetime of the connection to avoid race conditions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the connection referenced here? Is it the transient IRC connection established by the client in order to send WEBPUSH REGISTER
?
It's not clear on the basis of this spec how to do key rotation, or if it's even possible (I'm not sure what the right answer is, but the MUST NOT here seems excessive).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the IRC TCP connection.
The spec has been designed to allow key rotation. The server can advertise a different key to new connections. However, a server needs to keep the old key working or risk breaking previous subscriptions. For instance, maybe a client hasn't connected via TCP for a month but still receives push notifications every week or so.
The race condition is the following:
- Client connects and receives VAPID 1. Client sends a
WEBPUSH REGISTER
command. - Server sends a new VAPID 2.
- Server receives
WEBPUSH REGISTER
, but can't tell whether it must use VAPID 1 or VAPID 2 when sending push notifications.
It's important to use the correct VAPID, because the push endpoint should validate the VAPID.
We could add ways to fix the race (e.g. by adding some kind of ACK mechanism, simplest would be for the client to send the VAPID in WEBPUSH REGISTER
perhaps), but I figured it wasn't worth it since a server needs to keep the old key working anyways and the typical TCP connection lifetime is very small compared to the typical push registration lifetime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, that makes sense. Here's the disconnect I was having:
the typical TCP connection lifetime is very small compared to the typical push registration lifetime
This is not true of traditional or hybrid IRC setups where connections can live for weeks. The current spec language seems to imply that the VAPID key cannot be rotated as long as there is any open TCP connection (that hasn't already sent WEBPUSH REGISTER?). This would make rotation impossible in practice.
It seems to me that the race window from rotation is very small as long as the server notifies active clients about the rotation, by sending them a new 005 line with the new value.
What is the expected behavior if the push endpoint fails to validate the VAPID signature? 404?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not true of traditional or hybrid IRC setups where connections can live for weeks
Web Push is useful in settings where the TCP connection is short-lived.
The current spec language seems to imply that the VAPID key cannot be rotated as long as there is any open TCP connection (that hasn't already sent WEBPUSH REGISTER?). This would make rotation impossible in practice.
The way it's designed is that the server would keep old keys. When a new TCP connection is established, the new keys are sent. When a Web Push registration is renewed, the new keys can be used. While an old Web Push subscription is active, the server cannot throw away the old keys.
A server cannot assume that a client will use the new VAPID keys as soon as the server advertises them:
- The client might be disconnected while the key rotation is performed. The client needs to tell the Web Push server about the new VAPID keys. The client might not reconnect for a while (multiple days).
- Web Push is already quite tricky to implement without dynamic key rotation. I haven't implemented dynamic key rotation in my clients because it would add more complexity for little benefit. I assume many other client authors would feel the same.
What is the expected behavior if the push endpoint fails to validate the VAPID signature? 404?
RFC 8292 section 4.2 says it should be a 403.
|
||
### Errors | ||
|
||
Errors are returned using the standard replies syntax. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a FAIL WEBPUSH FORBIDDEN
code to address situations where the command is administratively disallowed for any reason (e.g. too many existing subscriptions, or the user is not logged into an account).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably the server could use cap-notify to add/remove the cap dynamically depending on user auth status? That would better reflect the server policy: clients don't display an error when unauthenticated, and clients can enable web push on CAP NEW
.
It's true that right now we only have INTERNAL_ERROR
which might not be appropriate for cases such as limits (per-user limit, per-endpoint-host limit, rate limit, etc).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would we advertise the CAP to unauthenticated users pre-registration, and then remove it after the 001? That seems awkward.
And yeah, FORBIDDEN
seems justified for other cases like limits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the server would hide the cap pre-conn-reg, and send CAP NEW
after authentication.
A FORBIDDEN
error will be bubbled up to the user, which is unlikely to be what you want here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a MAX_REGISTRATIONS
code for this case.
|
||
The `draft/webpush` capability allows clients to subscribe to Web Push and receive notifications for messages of interest. | ||
|
||
Once a client has subscribed, the server will send push notifications for a server-defined subset of IRC messages. Each push notification MUST contain exactly one IRC message as the payload, without the final CRLF. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it intentional that this disallows sending a draft/multiline?
I'm not sure what the right approach is (the practical upper limit on push message sizes appears to be 4096 bytes, and multilines could exceed that), but it's worth thinking about. In my implementation, you'll get the first line of the multiline in the push message (even if it doesn't actually contain the highlight), and it will be tagged with the overall msgid of the multiline (which is the normal fallback behavior).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't considered draft/multiline at all when writing the spec.
I think sending the first line is fair: UX-wise notifications are not designed to display big blobs of text, and the user can always tap to see the whole thing.
Yes, agreed! Also some recommendations around expiration on the server side, how a client should renew registrations, etc. |
I've submitted a draft RFC for an IMAP WebPush extension. This is, as expected, very close to this extension for IRC: https://datatracker.ietf.org/doc/draft-gougeon-imap-webpush/ I have notably added another command to silent a registration, this can be useful when the client sync for some time after a push message and if the client define a "do not disturb" period. This may interest you to add it as well. Since you are already using your IRC extension, I wonder if there are things I forgot to take in considerations ?
This is actually possible to send Web Push message to FCM servers directly, without a gateway, I've described it here: https://unifiedpush.org/news/20250131_push_for_decentralized/ |
Is anyone aware of a proposed post-quantum replacement for RFC8291? |
Allows clients to update their keys.
Fixes some race conditions.
This is necessary to let clients know when the registration has completed.
What is the difference with unregistering (and potentially re-registering later)?
Great to see you've found a way to do this? A few years back, I've tried reading the Chromium source code and send Web Push payloads directly to FCM servers, but it seemed like that endpoint was restricted to Chrome API keys. |
Added sections for security and implementation considerations. |
The most advantageous is When |
I'm not entirely sure I undertsand the use-case here. I don't recommend switching notifications on and off because this can easily result in race conditions and missed notifications. I recommend either always relying on push notifications and not showing any notification from the TCP connection (preferred), or ignoring push messages when the TCP connection is opened (but this is racy). |
A primary goal of this spec is to not tie the extension to any proprietary push service. This is achieved by using the standard Web Push protocol. Web Push also provides encryption of the payload.
IRC clients are responsible for providing a push endpoint to the IRC server. This is usually provided by the platform (e.g. by the browser for the Web). IRC servers don't need to register or anything like that, all they need is send an HTTP request when they wish to deliver a push notification.
See the spec for more details.
Not mentioned in the spec is the integration with proprietary push services. For instance, Android apps can't (unfortunately) use Web Push: they must use Firebase Cloud Messaging (FCM). Since FCM is also used on Chrome for their Web Push implementation, I've tried to see if there was a way to figure out some Web Push parameters from the FCM ones, but it sounds like the Web Push functionality is limited to the Chrome API keys.
My approach is to introduce a relay which listens for Web Push messages and forwards them to FCM. This does require an additional moving part that IRC clients need to rely on, however this sounds like a reasonable trade-off to me. Since the relay doesn't see any clear-text privacy-sensitive information (payload is encrypted by the IRC server, no client IP address is visible), I could provide a free relay service for client developers who don't want to setup their own.
So far, I've implemented proof-of-concepts for soju and two client platforms:
References:
This is an incomplete specification. I opened this PR to gather feedback on the approach. Let me know what you think!
WEBPUSH UNREGISTER
Figure out how to specify "messages of interest"Consider turning the cap into anISUPPORT
ISUPPORT