Add NIP 9a for push notifications#2194
Conversation
c63001a to
2c47af9
Compare
|
How do you distinguish whether a device is online or offline? Is it determined by whether there is a connection to the server? |
|
I'm going to add a |
2c47af9 to
34624f7
Compare
Yes, but on Android you have to use silent push. |
|
What is that |
|
That's the app's dedicated push server, which hold the various keys related to authenticating push notifications. Here's the flow: |
c4d74d0 to
0480065
Compare
|
Just updated the NIP to leave as much up to the client/push server as possible. Now, clients will have to talk directly to push servers to set up device tokens, which reduces the amount of information relays have. Clients can also implement client activity status directly with the push server, solving @sanah9's problem. Push servers can also serve as a central point of control, since they can return a 404 error to indicate the subscription should be deleted. |
|
We should make this a non-hex number just to make it easier on libraries that have a number type on the NIP-11 array of |
|
Relay implementations have to be updated non-trivially anyway to support it, so they might as well convert supported_nips to a string type while they're at it. |
|
Example push notifications for android without requiring a server, or third party service/app install. Just use websockets/connect to relay: Might this change how the NIP is written? |
|
I doubt it, from what I understand mobile platforms are picky about apps using background tasks because there's a necessary trade-off between keeping stuff active and battery life. Plus, this approach is a pain to implement, I was trying it earlier this week, but in capacitor you don't have access to websockets, which means you would have to write an http/websocket proxy, or write native code. It would be good if pokey could be improved to serve as a notifications hub for other nostr apps, but that comes with the UX tradeoff of users having to install another companion app. That's not a bad solution all-in-all though. I've also built anchor.coracle.social, which doesn't work at all because it has to open a separate websocket connection for each subscriber/relay combo to avoid exfiltrating data related to relay access controls. It also only approximates access by using invite codes, which means lots of content will still be inaccessible. Another alternative would be to share a bunker url pointing at the user's signer so that the push server could authenticate on the user's behalf. But this has the same problem with many websocket connections, and opens a blind signing attack vector. This push solution is good because:
The main tradeoffs are:
|
|
Just updated to encrypt messages |
6581759 to
882b1e5
Compare
9a.md
Outdated
|
|
||
| If a relay does not intend to fulfill the subscription, it SHOULD respond with an `OK` message with `false` as the result and a human-readable message. | ||
|
|
||
| Because client developers are expected to be running their own push server, any other features such as registration of device tokens, notification cancellation, or client status should be implemented directly between the client and the push server. |
There was a problem hiding this comment.
If this is the case, why are we developing a standard for registration only? Why isn't the registration just another hardcoded payload between the client and their push server? Why are we "forcing" all clients to support filters and ignore if they can make their own parameters that would probably be more specific than those?
Like, for Amethyst, I only send notifications from non-zero Web of Trust and zaps only over 10 sats. The tags here wouldn't support what we need. Every client would have something a little different.
The kind 30390 event would not be reusable between apps, so I am not sure why we are standardizing this.
There was a problem hiding this comment.
Because relays have to understand them in order to push them to the push server. The reason we need this is because relays might choose to implement authorization policies, so it needs to be up to them who they push stuff to.
Clients don't need to re-use the subscriptions, relays do need to know how to support them though. That's the part that needs to be interoperable. I tried doing pull-based, and it didn't work. |
|
Implemented on https://github.com/coracle-social/npb, which is a bridge that enables this functionality for any public relay, and on https://github.com/coracle-social/nps which is an implementation of a application push server. |
I don't actually understand why this is a relay. This is a client running as a service on a server. We shouldn't bother relay devs in supporting this feature if it can be a client. There is no need for it. |
|
Ok, back up: https://github.com/coracle-social/zooid is a relay for communities, which implements access controls. If we do this using a pull-based flow, push servers will be unable to request access-protected events from the community relay, because they are not a member of the relay. To solve this, you need push servers to be able to auth on behalf of users, which means users need to share their keys (or a bunker link). This is bad, and doesn't work anyway because the push server needs to maintain a separate connection per user since only one user can be authenticated on a connection (you don't want to broadcast the superset of permitted events to all users). Another alternative is to add the push server's key to the relay, but that doesn't account for the difference in permissions between different users. So this isn't about cargo-culting the relay interface to the push server, it's about allowing relays to selectively share access-controlled data with particular users via a push channel/callback url. |
|
Ok, so the goal here is to circumvent AUTH for NIP-29. That makes more sense. |
|
Not to circumvent it exactly (the relay is still authorizing the user directly, just sending stuff in a delayed fashion via a different middleman [still encrypted]). But yes. |
|
Feels like we could have put all the nip 29 nips in its own repo, like the BUDs. |
|
There are other types of access controlled relay, this is relevant for those too. |
But this doesn't work for my use case, in which I want to hide the event from the receiver.
Say relay A is running the plugin and relay B isn't, and I pass the same callback to both. What format do I expect? Both. And, it leaves it up to the relay to decide which one they send, not the client to decide in tandem with the server. Which means all receivers have to support both formats.
In the public use case, which is what you're talking about, you don't have to auth. That's your entire point, isn't it? The events aren't private? Which proves my point — push is only useful if the events are access controlled, in which case encryption makes sense. |
|
I think you are not understanding what I mean by running the plugin. Let's take the case of Flotilla App and Flotilla's Push Server. Flotilla App would register the push event with public tags like I designed, pointing the callback URL to the relay's encryption service callback. Something like: http://relay.com/forwarder?to=http://push.flotilla.social The HTTP service running on Which means the NIP-29 version of the "hook" API defines the standard for this local encryption service that NIP-29 apps want to use. We could say, for instance, that all supporting relays for your NIP must offer the In that way, the NIP-29 relay operator doesn't need to change the relay code. All the operator needs to do is to add the local forwarder to be compliant with the encryption on your NIP.
Most public relays require AUTH these days.. It's dumb but it is there. So, the "public" case exists. |
|
Also, the DM inbox relays could use the public hook interface since giftwraps are encrypted anyway. |
|
Don't get me wrong, making the redirection from the public hook API will be more complicated for your needs than this PR, so maybe it doesn't make sense for you, but it would be easier for everybody else that doesn't even have a pubkey for their relays and definitely doesn't have encryption capabilities in the relay itself. |
I understand what you're saying in terms of decoupling software architecture. But how does the client know it can use the relay itself as a forwarder? Probably another value in the supported NIPs field. So however you slice it there are multiple interfaces going on here, internal to the infrastructure-powering-the-relay.
Sure, but you can just auth with the relay's own key and pull, this still doesn't make push necessary. I still don't see how this architecture is useful for non-auth-gated content. |
If the supported NIPs include My version doesn't use NIP-11 tags anymore. It just checks if the OK return includes the "subscribed" message to see if the relay implements hooks and if the hook is active for the specific filter that the user/app wants. Knowing if the relay implements or not hooks without knowing the filters is somewhat irrelevant.
Picture a Web Of Trust provider. User signs in to the provider, but the user is using an AUTHed outbox relay for... reasons... The provider can't log in as a user, so the provider app makes the user sign for a hook in to their own outbox relay to send changes of the Follow List to the WoT provider. Now the WoT provider doesn't even need to keep Negentropy Syncing/REQing the follow list all the time. It's quite a solution. |
|
It is a nicer flow, I'll give you that. I still think this is sort of a contrived example, why would the outbox relay be auth'd except by mistake, or to protect the user's data? In which case, encryption seems like it abides by the spirit of the relay's policy. But I understand why you wouldn't want that overhead and complexity. So it sounds like we're basically doing entirely different things despite an architectural similarity. I don't want relays to have to implement two things that are so similar, but if you can't go with encryption and I can't go without, they're really two different interfaces. The two could be bolted onto each other as you described, but that seems like a nasty premature optimization to me. The thing that makes me sad is I'm probably going to have to support both approaches now, because relays are going to be more likely to implement the un-encrypted version because it's easier to sell as a "generic" protocol feature. Or I yolo route plaintext events through my server, which sort of betrays the "private" nature of the thing. 😕 |
Everything is a mistake in Nostr. I gave up understanding why users use things the way they do. Can't we do anything in the public version to make intercepting the callback to encrypt better/simpler for you? Another way could be that NIP-29 relay operators can use regular relay implementations with the public hook support and change the code directly to encrypt it. So, maybe there is an implementation detail that allows operators to know the Push Server is encrypted and then go through your POST scheme.
If the Push Server has to be run by the same dev of the app, who users trust, and since the app also has direct access to the event that the dev could steal, especially on a web app that can be updated at any time and the user won't even notice, I think raw events are fine for you. In the end, it's all trusted anyway |
You can't justify your use for push to circumvent auth based on poor implementations while also dismissing my inclination to encrypt events just in case relays are stupid.
No, there's a big difference between touching user data on the server and sending users code they run in their browsers. The threat model isn't that much different if you're talking about a malicious service provider, but having user data on your actual server exposes the server provider to different sorts of risks, and a way bigger attack surface area. |
|
I had an idea while falling asleep last night — what if the push payload was |
|
Requiring the app to connect and download the event is probably better for Push Notification systems since large payloads are punished and most services have size limits (FCM is 4KB) But that doesn't solve the AUTH problem that services have. So, maybe we do need 2 kinds. |
|
Ok, I just pushed a new update. I removed all the paranoid guard rails like |
I am trying to avoid events being broadcast everywhere to DDOS a receiver. |
Well, if you feature detect based on supported_nips and relays don't relay these subscriptions, you're ok. Still, I figured out I needed the relay for my bridge, so I put it back in. |
What do you mean? The client can reconnect using the user's key to AUTH. |
Not a WoT service receiving this event will not have the nsec to AUTH. So, it will only work if the event is sent. |
| - `include_event` - whether push payloads should include the complete `event` data | ||
|
|
||
| If a relay does not intend to fulfill the subscription, it SHOULD respond with an `OK` message with `false` as the result and a human-readable message. | ||
|
|
There was a problem hiding this comment.
Please add expiry information. If a relay accepts subscription they have to send expiry time of subscription. As currently it is unclear when client has to resync subscription with relay.
There was a problem hiding this comment.
I guess the relay should also delete the event when the subscription expires, then clients can check if the relay has the event if not send it again.
There was a problem hiding this comment.
@prhasn how do you imagine this working? OK messages aren't good for structured data. The relay could publish a subscription status event, but that seems like overkill. Maybe we should require an expiration tag on the subscription event, and relays can reject it if it's too far out?
330b62e to
635449e
Compare
Right, which IMO is sort of bad because it circumvents relay policies to allow for exfiltration. But with the |
|
Wait, with the current version, how does your callback server know which user the event is for so that it can get the device tokens for that user? |
|
Client generates token and sends to the push server, push server generates callback URL with embedded ID and sends it to the client, client shares this with relays via subscription event, relay posts to the callback url (hosted by the push server), push server maps callback url/id to device token, sends push notification. |
|
Ohh.. so basically the pubkey is still there... It's just "hashed" in the url itself. Can the push server also register the urls of the relays the user is registering these events and compare the incoming HTTP IP with the resolved DNS record? That does make a lot of sense. |
Sure, I don't see why not |
|
Implemented on zooid pending coracle-social/zooid#8 |
Read here