Skip to content

Handle anonymous read restrictions by sending a poll_request event #1287

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

Merged

Conversation

barart
Copy link
Contributor

@barart barart commented Mar 5, 2025

If a topic does not allow anonymous reads, this change ensures that we send a "poll_request" event instead of relaying the message via Firebase. Additionally, we include generic text in the title and body/message. This way, if the client cannot retrieve the actual message, the user will still receive a notification, prompting them to update the client manually.

If a topic does not allow anonymous reads, this change ensures that we send a "poll_request" event instead of relaying the message via Firebase. Additionally, we include generic text in the title and body/message. This way, if the client cannot retrieve the actual message, the user will still receive a notification, prompting them to update the client manually.
@binwiederhier
Copy link
Owner

Holy shit, is this the issue why some users do not receive message on iOS if they have a Pro subscription?

@wunter8
Copy link
Contributor

wunter8 commented May 22, 2025

@binwiederhier Yes, I'm pretty sure this is the source of/fix for that bug! I think I worked through this with barart on Discord

@barart
Copy link
Contributor Author

barart commented May 22, 2025

@binwiederhier I’m not sure what features Pro subscription users have, but this fix addresses the issue with the iOS app and APNS when the topic is private. Additionally, this other fix is also required in the iOS app:
binwiederhier/ntfy-ios#20

I host my own ntfy server, compiled my own version of the ntfy iOS app, and use my own APNS configuration via my Apple Developer account, so I’m not entirely sure what the Pro subscription includes or how this fix might impact it. But I’d be happy to help investigate if you can guide me on what issue your Pro users are facing.

And yep, we worked on this with @wunter8 on Discord that day, he gave me the clue that led to the fix.

@wunter8
Copy link
Contributor

wunter8 commented May 22, 2025

That's exactly the issue binwiederhier is talking about: Pro subscriptions let people make a certain number of topics private on the public ntfy.sh instance. However, once they make the topic private, they don't receive push notifications through the iOS app anymore

@barart
Copy link
Contributor Author

barart commented May 22, 2025

So yes, this will fix the problem 👌🏻

@binwiederhier
Copy link
Owner

So I don't think I agree with the tags and such being part of the poll request. I'll merge your PR, but then later empty-out these fields. I don't want to leak data.

@binwiederhier
Copy link
Owner

Thank you. I hope this works!

@binwiederhier binwiederhier merged commit faa4dcb into binwiederhier:main May 23, 2025
@barart
Copy link
Contributor Author

barart commented May 23, 2025

@binwiederhier It works, I've been using this fix ever since. 😉

The tags are necessary exactly to avoid data leaks and to show something generic to iOS in case the device can't retrieve the actual message from the server.
If you send those tags empty, the notification won't show because there's nothing to display.
Before my fix, the ntfy server would send the actual title and message -even if the content was private- directly in the APNs payload. The device would receive this plaintext message with the pollrequest tag and then try to fetch the real message from the server.
If the message could be retrieved, iOS would replace the content with the one from the server (which was usually the same). But if it couldn’t, iOS would just display the original message it received — which was the real, sensitive content.
So, in practice, the real message was always displayed, even when the device couldn’t fetch it, which resulted in a clear data leak.
So, the ntfy server now sends generic tags to the device via APNs with the following content:
Title: New notification received
Message: Message can't be retrieved. Open the app and refresh content.
If, for some reason, the device can't fetch the actual message, iOS shows this default notification. But if the actual message can be retrieved, iOS replaces it and displays it as expected.
This way, the user always receives a notification, regardless of whether their device was able to retrieve the actual message or not — ensuring that no notifications are missed.

@binwiederhier
Copy link
Owner

My brain is not fully functioning anymore, so I'll have to re-read what you wrote tomorrow, but please look at what I did here and tell me if you think this would still work: 6d15b9f

You could try it out 😬

Are you sure that ALL the fields are necessary, or only the poll_id? I think that was missing.

@barart
Copy link
Contributor Author

barart commented May 23, 2025

@binwiederhier Nope, all fields are required iOS needs them (working on the fix i read that) is very strict specially on mute messages (this is the case), once you have time, read my previous post so you can understand, i already tried a lot of things to figure and protect the messages and that was my result of a lot of debug, what you did will leak the messages, not trigger the MutableContent function (that was my first try) and some notifications will be lost

@binwiederhier
Copy link
Owner

I wrote a big whole thing about how you are incorrect, and then I finally got it. My misunderstanding was that I was thinking that data was already censored, but we're in fact looking at m, which was not censored/converted to a poll request.

I'll update the code tonight to fix my fuckup.

@binwiederhier
Copy link
Owner

binwiederhier commented May 25, 2025

@barart Here you go, this should make the entire thing a little more elegant I think: #1353

It works like this:

  • If this is an "anonymous read" scenario, we convert m into a poll request, keeping only the important fields
  • We then follow the same serialization logic as before.

Easiest to read like this:

if auther != nil {
// If "anonymous read" for a topic is not allowed, we cannot send the message along
// via Firebase. Instead, we send a "poll_request" message, asking the client to poll.
//
// The data map needs to contain all the fields for it to function properly. If not all
// fields are set, the iOS app fails to decode the message.
//
// See https://github.com/binwiederhier/ntfy/pull/1345
if err := auther.Authorize(nil, m.Topic, user.PermissionRead); err != nil {
m = toPollRequest(m)
}
}

@binwiederhier
Copy link
Owner

You say that all fields are required for it to work in iOS, though this code does not have all the fields:

case pollRequestEvent:
data = map[string]string{
"id": m.ID,
"time": fmt.Sprintf("%d", m.Time),
"event": m.Event,
"topic": m.Topic,
"message": newMessageBody,
"poll_id": m.PollID,
}
apnsConfig = createAPNSAlertConfig(m, data)

Can you explain why all the fields would be necessary? This^^ code would not work I think (this is for self-hosted folks), if all fields were necessary.

@binwiederhier
Copy link
Owner

@barart If you have some time, would you kindly look at the currently committed code ^ and let me know if you believe that it would work? I currently don't have a good testing setup, so I'll probably just release it to prod and roll back if it breaks anything.

Here: https://github.com/binwiederhier/ntfy/blob/main/server/server_firebase.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants