[GH-ISSUE #1529] Android: Attachment download not using User #1078

Closed
opened 2026-05-07 00:30:06 +02:00 by BreizhHardware · 14 comments

Originally created by @ManInDark on GitHub (Dec 31, 2025).
Original GitHub issue: https://github.com/binwiederhier/ntfy/issues/1529

🐞 Describe the bug

For protected topics (or hosting ntfy behind a reverse proxy with basic auth in my case) you can set a username password pair by creating a user with those credentials.

The issue is that those credentials are not used when attempting to download attachments.

💻 Components impacted

Android App

💡 Screenshots and/or logs

Image

🔮 Additional context

I've already started looking into this, the solution is likely to change the DownloadAttachmentWorker.kt around the line 65 to have the Request.Builder() also use the credentials like they are in ApiService.kt in the requestBuilder function.

I am currently struggeling as I'm not quite familiar with how the live api works in Android and if I try to retrieve the users via said api I only get null.

Originally created by @ManInDark on GitHub (Dec 31, 2025). Original GitHub issue: https://github.com/binwiederhier/ntfy/issues/1529 :lady_beetle: **Describe the bug** <!-- A clear and concise description of the problem. --> For protected topics (or hosting ntfy behind a reverse proxy with basic auth in my case) you can set a username password pair by creating a user with those credentials. The issue is that those credentials are not used when attempting to download attachments. :computer: **Components impacted** <!-- ntfy server, Android app, iOS app, web app --> Android App :bulb: **Screenshots and/or logs** <!-- If applicable, add screenshots or share logs help explain your problem. To get logs from the ... - ntfy server: Enable "log-level: trace" in your server.yml file - Android app: Go to "Settings" -> "Record logs", then eventually "Copy/upload logs" - web app: Press "F12" and find the "Console" window --> <img width="194" height="34" alt="Image" src="https://github.com/user-attachments/assets/fd7bcc7a-ef78-401d-bbbf-a9e5225862c3" /> :crystal_ball: **Additional context** <!-- Add any other context about the problem here. --> I've already started looking into this, the solution is likely to change the `DownloadAttachmentWorker.kt` around the line 65 to have the `Request.Builder()` also use the credentials like they are in `ApiService.kt` in the `requestBuilder` function. I am currently struggeling as I'm not quite familiar with how the live api works in Android and if I try to retrieve the users via said api I only get null.
BreizhHardware 2026-05-07 00:30:06 +02:00
  • closed this issue
  • added the
    🪲 bug
    label
Author
Owner

@binwiederhier commented on GitHub (Dec 31, 2025):

ntfy behind a reverse proxy with basic auth in my case

This is not a supported use case.

Typically, attachments do not need credentials. The message ID is the password. It cannot be guessed. You can exclude server.com/file/* from the basic auth requirement in your proxy.

Which version of the Android app are you on? It may be easy enough add that code, so I may still do it

<!-- gh-comment-id:3702574909 --> @binwiederhier commented on GitHub (Dec 31, 2025): > ntfy behind a reverse proxy with basic auth in my case This is not a supported use case. Typically, attachments do not need credentials. The message ID is the password. It cannot be guessed. You can exclude `server.com/file/*` from the basic auth requirement in your proxy. Which version of the Android app are you on? It may be easy enough add that code, so I may still do it
Author
Owner

@wunter8 commented on GitHub (Dec 31, 2025):

Making the attachment URLs require auth would be a pretty big breaking change, no?

<!-- gh-comment-id:3702851710 --> @wunter8 commented on GitHub (Dec 31, 2025): Making the attachment URLs require auth would be a pretty big breaking change, no?
Author
Owner

@binwiederhier commented on GitHub (Dec 31, 2025):

I would just make the Android client send the credentials when downloading, so that his authenticated proxy would be happy.

Turns out that if you use the same basic auth in your proxy that you use for the ntfy user, it'll work apparently. At lease according to @ManInDark

<!-- gh-comment-id:3702859224 --> @binwiederhier commented on GitHub (Dec 31, 2025): I would just make the Android client send the credentials when downloading, so that his authenticated proxy would be happy. Turns out that if you use the same basic auth in your proxy that you use for the ntfy user, it'll work apparently. At lease according to @ManInDark
Author
Owner

@wunter8 commented on GitHub (Dec 31, 2025):

Wouldn't the "custom request headers" PR fix this scenario?

<!-- gh-comment-id:3702863051 --> @wunter8 commented on GitHub (Dec 31, 2025): Wouldn't the "custom request headers" PR fix this scenario?
Author
Owner

@ManInDark commented on GitHub (Dec 31, 2025):

ntfy behind a reverse proxy with basic auth in my case

This is not a supported use case.

Oh, good to know, I just put the whole app behind the proxy since I already had other apps with the same setup and naively assumed it would just work.

Which version of the Android app are you on? It may be easy enough add that code, so I may still do it

FDroid 1.19.4, I could also change the subpath access but that would be greatly appreciated.

<!-- gh-comment-id:3702947684 --> @ManInDark commented on GitHub (Dec 31, 2025): > > ntfy behind a reverse proxy with basic auth in my case > > This is not a supported use case. Oh, good to know, I just put the whole app behind the proxy since I already had other apps with the same setup and naively assumed it would just work. > Which version of the Android app are you on? It may be easy enough add that code, so I may still do it FDroid 1.19.4, I could also change the subpath access but that would be greatly appreciated.
Author
Owner

@ManInDark commented on GitHub (Dec 31, 2025):

Turns out that if you use the same basic auth in your proxy that you use for the ntfy user, it'll work apparently. At lease according to @ManInDark

I actually don't use the users feature since I already have access control using the proxy.

Though it could also be done twice, you just have to proxy the auth headers too.

<!-- gh-comment-id:3702957752 --> @ManInDark commented on GitHub (Dec 31, 2025): > Turns out that if you use the same basic auth in your proxy that you use for the ntfy user, it'll work apparently. At lease according to @ManInDark I actually don't use the users feature since I already have access control using the proxy. Though it could also be done twice, you just have to proxy the auth headers too.
Author
Owner

@ManInDark commented on GitHub (Jan 1, 2026):

diff --git a/app/src/main/java/io/heckel/ntfy/msg/DownloadAttachmentWorker.kt b/app/src/main/java/io/heckel/ntfy/msg/DownloadAttachmentWorker.kt
index f1cf32a9..edaec46d 100644
--- a/app/src/main/java/io/heckel/ntfy/msg/DownloadAttachmentWorker.kt
+++ b/app/src/main/java/io/heckel/ntfy/msg/DownloadAttachmentWorker.kt
@@ -15,10 +15,12 @@ import io.heckel.ntfy.app.Application
 import io.heckel.ntfy.db.*
 import io.heckel.ntfy.util.Log
 import io.heckel.ntfy.util.ensureSafeNewFile
+import okhttp3.Credentials
 import okhttp3.OkHttpClient
 import okhttp3.Request
 import okhttp3.Response
 import java.io.File
+import java.nio.charset.StandardCharsets.UTF_8
 import java.util.concurrent.TimeUnit

 class DownloadAttachmentWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) {
@@ -61,10 +63,14 @@ class DownloadAttachmentWorker(private val context: Context, params: WorkerParam
         Log.d(TAG, "Downloading attachment from ${attachment.url}")

         try {
-            val request = Request.Builder()
+            val user = repository.getUsersLiveData().value?.first { user -> attachment.url.contains(user.baseUrl) }
+            val requestBuilder = Request.Builder()
                 .url(attachment.url)
                 .addHeader("User-Agent", ApiService.USER_AGENT)
-                .build()
+            if (user != null) {
+                requestBuilder.addHeader("Authorization", Credentials.basic(user.username, user.password, UTF_8))
+            }
+            val request = requestBuilder.build()
             client.newCall(request).execute().use { response ->
                 Log.d(TAG, "Download: headers received: $response")
                 if (!response.isSuccessful) {

Something like this should theoretically work, but for some reason I can't quite figure out the repository.getUsersLiveData() returns just null instead of the user list.

So all in all it's probably a relatively small change without any differences for those not using proxy auth.

<!-- gh-comment-id:3703096466 --> @ManInDark commented on GitHub (Jan 1, 2026): ```diff diff --git a/app/src/main/java/io/heckel/ntfy/msg/DownloadAttachmentWorker.kt b/app/src/main/java/io/heckel/ntfy/msg/DownloadAttachmentWorker.kt index f1cf32a9..edaec46d 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/DownloadAttachmentWorker.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/DownloadAttachmentWorker.kt @@ -15,10 +15,12 @@ import io.heckel.ntfy.app.Application import io.heckel.ntfy.db.* import io.heckel.ntfy.util.Log import io.heckel.ntfy.util.ensureSafeNewFile +import okhttp3.Credentials import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import java.io.File +import java.nio.charset.StandardCharsets.UTF_8 import java.util.concurrent.TimeUnit class DownloadAttachmentWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) { @@ -61,10 +63,14 @@ class DownloadAttachmentWorker(private val context: Context, params: WorkerParam Log.d(TAG, "Downloading attachment from ${attachment.url}") try { - val request = Request.Builder() + val user = repository.getUsersLiveData().value?.first { user -> attachment.url.contains(user.baseUrl) } + val requestBuilder = Request.Builder() .url(attachment.url) .addHeader("User-Agent", ApiService.USER_AGENT) - .build() + if (user != null) { + requestBuilder.addHeader("Authorization", Credentials.basic(user.username, user.password, UTF_8)) + } + val request = requestBuilder.build() client.newCall(request).execute().use { response -> Log.d(TAG, "Download: headers received: $response") if (!response.isSuccessful) { ``` Something like this _should_ theoretically work, but for some reason I can't quite figure out the `repository.getUsersLiveData()` returns just null instead of the user list. So all in all it's probably a relatively small change without any differences for those not using proxy auth.
Author
Owner

@binwiederhier commented on GitHub (Jan 5, 2026):

This is solved in https://github.com/binwiederhier/ntfy-android/pull/149

<!-- gh-comment-id:3710704331 --> @binwiederhier commented on GitHub (Jan 5, 2026): This is solved in https://github.com/binwiederhier/ntfy-android/pull/149
Author
Owner

@ManInDark commented on GitHub (Jan 6, 2026):

I've tested it with 45c36b8f (HEAD -> mtls, origin/mtls) Review as the last commit (git log -1 --oneline), it didn't work.

I assume that the user object isn't passed correctly, but not sure on that, I'll do some more detailed debugging later.

<!-- gh-comment-id:3715298542 --> @ManInDark commented on GitHub (Jan 6, 2026): I've tested it with `45c36b8f (HEAD -> mtls, origin/mtls) Review` as the last commit (`git log -1 --oneline`), it didn't work. I assume that the user object isn't passed correctly, but not sure on that, I'll do some more detailed debugging later.
Author
Owner

@ManInDark commented on GitHub (Jan 6, 2026):

Apparently my assumption was correct, in DownloadAttachmentWorker.kt:65 no user is passed to the requestBuilder function:

val request = HttpUtil.requestBuilder(attachment.url).build()

This function only needs the second parameter to be set to the user: (HttpUtil.kt:59)

fun requestBuilder(url: String, user: User? = null, customHeaders: List<CustomHeader> = emptyList()): Request.Builder

I still have no idea how to get an instance of the user list at this point in the code, so sorry that I can't help further than that.

<!-- gh-comment-id:3715359550 --> @ManInDark commented on GitHub (Jan 6, 2026): Apparently my assumption was correct, in `DownloadAttachmentWorker.kt:65` no user is passed to the `requestBuilder` function: ```kt val request = HttpUtil.requestBuilder(attachment.url).build() ``` This function only needs the second parameter to be set to the user: (`HttpUtil.kt:59`) ```kt fun requestBuilder(url: String, user: User? = null, customHeaders: List<CustomHeader> = emptyList()): Request.Builder ``` I still have no idea how to get an instance of the user list at this point in the code, so sorry that I can't help further than that.
Author
Owner

@binwiederhier commented on GitHub (Jan 6, 2026):

You are a hero. Thanks for testing. Fixed in a latest commit (untested)

<!-- gh-comment-id:3715555949 --> @binwiederhier commented on GitHub (Jan 6, 2026): You are a hero. Thanks for testing. Fixed in a latest commit (untested)
Author
Owner

@ManInDark commented on GitHub (Jan 6, 2026):

I've tested this commit:

$ git log --oneline -1
eb11eec9 (HEAD -> mtls, origin/mtls) Fixed download worker

And it works! Thank you very much for implementing this feature!

<!-- gh-comment-id:3716576789 --> @ManInDark commented on GitHub (Jan 6, 2026): I've tested this commit: ```sh $ git log --oneline -1 eb11eec9 (HEAD -> mtls, origin/mtls) Fixed download worker ``` And it works! Thank you very much for implementing this feature!
Author
Owner

@binwiederhier commented on GitHub (Jan 16, 2026):

Will be part of 1.22.x

<!-- gh-comment-id:3757762734 --> @binwiederhier commented on GitHub (Jan 16, 2026): Will be part of 1.22.x
Author
Owner

@ManInDark commented on GitHub (Feb 1, 2026):

I've just confirmed that it works perfectly with the version 1.22.2

Thank you very much!

Should I add an example with basic auth to tje documentation or do you want to keep this as an unsupported setup?

<!-- gh-comment-id:3830695457 --> @ManInDark commented on GitHub (Feb 1, 2026): I've just confirmed that it works perfectly with the version 1.22.2 Thank you very much! Should I add an example with basic auth to tje documentation or do you want to keep this as an unsupported setup?
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/ntfy#1078
No description provided.