@@ -48,6 +48,7 @@ import androidx.core.widget.doAfterTextChanged
4848import eu.opencloud.android.BuildConfig
4949import eu.opencloud.android.MainApp.Companion.accountType
5050import eu.opencloud.android.R
51+ import eu.opencloud.android.data.authentication.KEY_PREFERRED_USERNAME
5152import eu.opencloud.android.data.authentication.KEY_USER_ID
5253import eu.opencloud.android.databinding.AccountSetupBinding
5354import eu.opencloud.android.domain.authentication.oauth.model.ClientRegistrationInfo
@@ -117,6 +118,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
117118 private lateinit var serverBaseUrl: String
118119
119120 private var oidcSupported = false
121+ private var preferredUsername: String? = null
120122
121123 private lateinit var binding: AccountSetupBinding
122124
@@ -177,10 +179,12 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
177179 if (loginAction != ACTION_CREATE ) {
178180 binding.accountUsername.isEnabled = false
179181 binding.accountUsername.isFocusable = false
180- userAccount?.name?.let {
181- username = getUsernameOfAccount(it)
182+ userAccount?.let { account ->
183+ // Prefer preferred_username from id_token (stored in AccountManager) for login_hint,
184+ // fall back to the account name part (which may be a UUID)
185+ username = AccountManager .get(this ).getUserData(account, KEY_PREFERRED_USERNAME )
186+ ? : getUsernameOfAccount(account.name)
182187 }
183-
184188 }
185189
186190 if (savedInstanceState == null ) {
@@ -555,6 +559,12 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
555559 resultBundle = intent.extras
556560 setResult(Activity .RESULT_OK , intent)
557561
562+ // Store preferred_username from id_token for login_hint on re-login
563+ preferredUsername?.let { prefUsername ->
564+ val account = Account (accountName, contextProvider.getString(R .string.account_type))
565+ AccountManager .get(this ).setUserData(account, KEY_PREFERRED_USERNAME , prefUsername)
566+ }
567+
558568 authenticationViewModel.discoverAccount(accountName = accountName, discoveryNeeded = loginAction == ACTION_CREATE )
559569 clearAuthState()
560570 }
@@ -777,6 +787,10 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
777787 Timber .d(" Tokens received ${uiResult.data} , trying to login, creating account and adding it to account manager" )
778788 val tokenResponse = uiResult.data ? : return @observe
779789
790+ // Extract preferred_username from id_token for login_hint on re-login
791+ preferredUsername = extractPreferredUsernameFromIdToken(tokenResponse.idToken)
792+ Timber .d(" Preferred username from id_token: $preferredUsername " )
793+
780794 // When webfinger provides a client_id without dynamic registration,
781795 // store it so AccountAuthenticator can use it for token refresh
782796 val effectiveClientRegistrationInfo = clientRegistrationInfo
@@ -1071,4 +1085,24 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
10711085 val prefs = getSharedPreferences(" auth_state" , android.content.Context .MODE_PRIVATE )
10721086 prefs.edit().clear().apply ()
10731087 }
1088+
1089+ /* *
1090+ * Extract preferred_username from an OIDC id_token JWT.
1091+ * Fallback chain: preferred_username -> email -> sub
1092+ */
1093+ private fun extractPreferredUsernameFromIdToken (idToken : String? ): String? {
1094+ if (idToken == null ) return null
1095+ return try {
1096+ val parts = idToken.split(" ." )
1097+ if (parts.size != 3 ) return null
1098+ val payload = String (android.util.Base64 .decode(parts[1 ], android.util.Base64 .URL_SAFE ))
1099+ val json = org.json.JSONObject (payload)
1100+ json.optString(" preferred_username" ).ifBlank { null }
1101+ ? : json.optString(" email" ).ifBlank { null }
1102+ ? : json.optString(" sub" ).ifBlank { null }
1103+ } catch (e: Exception ) {
1104+ Timber .e(e, " Failed to extract preferred_username from id_token" )
1105+ null
1106+ }
1107+ }
10741108}
0 commit comments