From 9d25e75f62a984b9e21a64c66530dedbb663b9d2 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Mon, 25 May 2026 11:51:46 -0600 Subject: [PATCH 1/2] Add getApplicationPasswordClient to WpApiClientProvider Companion to `getWpApiClient` that always returns a direct-host Basic-auth client built from the SiteModel's application-password credentials, regardless of WP.com routing. `getWpApiClient` sends WPCom-flagged sites (including Atomic) through the bearer-token path and the WP.com REST proxy, which doesn't expose all of the application-password-authenticated routes. The list-screen change in the next commit needs to talk to the direct host; the auto-mint + validator work in the headless-creation PR uses this same method. --- .../network/rest/wpapi/rs/WpApiClientProvider.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt index 5647227ef2e0..f0598b43b748 100644 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt @@ -70,6 +70,20 @@ class WpApiClientProvider @Inject constructor( } } + /** + * Always returns a Basic-auth client against the direct host using the SiteModel's + * application-password credentials, regardless of WP.com routing. Use this when you need to + * talk to the site's own REST endpoints with the application password — `getWpApiClient` + * routes WPCom-flagged sites (including Atomic) through the bearer-token path and the WP.com + * REST proxy, which doesn't expose application-password-authenticated routes like + * `/wp/v2/users/{id}/application-passwords`. + */ + @Synchronized + fun getApplicationPasswordClient(site: SiteModel): WpApiClient = + selfHostedClients.getOrPut(site.id) { + createSelfHostedClient(site, uploadListener = null) + } + private fun createSelfHostedClient( site: SiteModel, uploadListener: WpRequestExecutor.UploadListener?, From 2828efee237cf019a3a13b9a037613c1eb85cca7 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Mon, 25 May 2026 10:41:37 -0600 Subject: [PATCH 2/2] Route the application-passwords list screen through Basic auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ApplicationPasswordsViewModel.getApplicationPasswordsList` called `WpApiClientProvider.getWpApiClient(site)`, which for any WPCom-flagged site (including Atomic) routes through the WP.com REST proxy. That proxy doesn't expose the `application-passwords` routes — every request 404s with `rest_no_route`, the same upstream wordpress-rs limitation that drove dropping the headless mint attempt in this project (Automattic/wordpress-rs#1350). As a result, the list screen appeared empty / errored on Atomic sites: `getCurrentUserId` would 404, return null, and `getApplicationPasswordsList` would early-return an empty list. Switch to `WpApiClientProvider.getApplicationPasswordClient(site)`, which always builds a direct-host Basic-auth client from the stored application-password credentials. The direct host serves the `application-passwords` routes on every WordPress install, so the listing call works for Atomic and Jetpack-WPCom-REST sites in addition to true self-hosted. For self-hosted, behavior is unchanged: both client providers route self-hosted sites through `selfHostedClients.getOrPut(site.id) { createSelfHostedClient(site, ...) }`, returning the same Basic-auth client. Precondition: the SiteModel must already have credentials — typically populated by the My Site auto-mint flow on first foreground. A user reaching the list screen without ever visiting My Site would 401 instead of seeing an empty screen. That edge case can be handled separately (e.g. by minting on demand here) and is out of scope. --- .../applicationpassword/ApplicationPasswordsViewModel.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordsViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordsViewModel.kt index 65fde978c160..faafa774c871 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordsViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordsViewModel.kt @@ -186,7 +186,13 @@ class ApplicationPasswordsViewModel @Inject constructor( } private suspend fun getApplicationPasswordsList(site: SiteModel): List { - val wpApiClient = wpApiClientProvider.getWpApiClient(site) + // Always use the direct-host Basic-auth client. `getWpApiClient` routes WPCom-flagged + // sites (including Atomic) through the WP.com REST proxy, which doesn't expose the + // `application-passwords` routes — every call 404s with `rest_no_route`. Talking to the + // site directly with the stored application password sidesteps that limitation. + // Requires the SiteModel to have credentials already (e.g. via the My Site auto-mint + // flow); otherwise the call will 401. + val wpApiClient = wpApiClientProvider.getApplicationPasswordClient(site) val currentUserId = getCurrentUserId(wpApiClient) return if (currentUserId == null) {