Loading app/src/main/java/foundation/e/apps/data/login/api/LoginManager.kt +4 −2 Original line number Diff line number Diff line package foundation.e.apps.data.login.api interface LoginManager<T> { suspend fun login(): T import com.aurora.gplayapi.data.models.AuthData interface LoginManager { suspend fun login(): AuthData? } app/src/main/java/foundation/e/apps/data/login/playstore/MicrogAccountManager.kt→app/src/main/java/foundation/e/apps/data/login/microg/MicrogLoginManager.kt +185 −0 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ * */ package foundation.e.apps.data.login.playstore package foundation.e.apps.data.login.microg import android.accounts.Account import android.accounts.AccountManager Loading @@ -27,9 +27,12 @@ import android.os.Bundle import android.os.Parcelable import android.util.Base64 import androidx.core.os.BundleCompat import com.aurora.gplayapi.data.models.AuthData import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.data.login.api.LoginManager import foundation.e.apps.data.login.microg.MicrogCertUtil import foundation.e.apps.data.login.playstore.OauthAuthDataBuilder import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.preference.getSync import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import javax.inject.Inject Loading @@ -41,10 +44,12 @@ data class MicrogAccount( ) @Singleton class MicrogAccountManager @Inject constructor( class MicrogLoginManager @Inject constructor( @ApplicationContext val context: Context, private val accountManager: AccountManager ) : LoginManager<MicrogAccountManager.FetchResult> { private val accountManager: AccountManager, private val oauthAuthDataBuilder: OauthAuthDataBuilder, private val appLoungeDataStore: AppLoungeDataStore ) : LoginManager { sealed interface FetchResult { data class Success(val microgAccount: MicrogAccount) : FetchResult data class RequiresUserAction(val intent: Intent) : FetchResult Loading @@ -55,11 +60,25 @@ class MicrogAccountManager @Inject constructor( return accountManager.getAccountsByType(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE).isNotEmpty() } override suspend fun login(): FetchResult { return fetchMicrogAccount() override suspend fun login(): AuthData? { val oldToken = appLoungeDataStore.oauthToken.getSync() val shouldRefresh = hasMicrogAccount() && oldToken.startsWith(MICROG_TOKEN_PREFIX) val oauthToken = if (shouldRefresh) { fetchRefreshedToken(oldToken) } else { oldToken } if (oauthToken.isBlank()) { error(MICROG_TOKEN_MISSING) } return oauthAuthDataBuilder.build(oauthToken) ?: error(MICROG_TOKEN_MISSING) } suspend fun fetchMicrogAccount( accountName: String? = null accountName: String = "" ): FetchResult = withContext(Dispatchers.IO) { val accounts = accountManager.getAccountsByType(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE) if (accounts.isEmpty()) { Loading @@ -68,21 +87,14 @@ class MicrogAccountManager @Inject constructor( ) } val account = accountName?.let { name -> accounts.firstOrNull { it.name == name } } ?: accounts.first() val account = resolveAccount(accounts, accountName) return@withContext runCatching { val options = createAuthTokenOptions() val bundle = accountManager.getAuthToken( account, MicrogCertUtil.PLAY_AUTH_SCOPE, Bundle().apply { putString("overridePackage", MicrogCertUtil.GOOGLE_PLAY_PACKAGE) putByteArray( "overrideCertificate", Base64.decode(MicrogCertUtil.GOOGLE_PLAY_CERT_BASE64, Base64.DEFAULT) ) }, options, false, null, null Loading @@ -104,6 +116,59 @@ class MicrogAccountManager @Inject constructor( } } suspend fun invalidateAuthToken(accountName: String, oldToken: String) { if (oldToken.isBlank()) { return } withContext(Dispatchers.IO) { val accounts = accountManager.getAccountsByType(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE) if (accounts.isNotEmpty()) { val account = resolveAccount(accounts, accountName) accountManager.invalidateAuthToken(account.type, oldToken) } } } private suspend fun fetchRefreshedToken(oldToken: String): String { val accountName = appLoungeDataStore.emailData.getSync().ifBlank { "" } invalidateAuthToken(accountName, oldToken) val result = fetchMicrogAccount(accountName) return when (result) { is FetchResult.Success -> { appLoungeDataStore.saveGoogleLogin( result.microgAccount.account.name, result.microgAccount.oauthToken ) appLoungeDataStore.saveAasToken("") result.microgAccount.oauthToken } is FetchResult.RequiresUserAction -> error(MICROG_TOKEN_REFRESH_FAILURE) is FetchResult.Error -> error(MICROG_TOKEN_REFRESH_FAILURE) } } private fun resolveAccount(accounts: Array<Account>, accountName: String): Account { return if (accountName.isNotBlank()) { accounts.firstOrNull { it.name == accountName } ?: accounts.first() } else { accounts.first() } } private fun createAuthTokenOptions(): Bundle { return Bundle().apply { putString("overridePackage", MicrogCertUtil.GOOGLE_PLAY_PACKAGE) putByteArray( "overrideCertificate", Base64.decode(MicrogCertUtil.GOOGLE_PLAY_CERT_BASE64, Base64.DEFAULT) ) } } private fun <T : Parcelable> Bundle.parcelableCompat(key: String, clazz: Class<T>): T? { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { getParcelable(key, clazz) Loading @@ -111,4 +176,10 @@ class MicrogAccountManager @Inject constructor( BundleCompat.getParcelable(this, key, clazz) } } companion object { const val MICROG_TOKEN_PREFIX = "ya29." const val MICROG_TOKEN_MISSING = "MicroG token is missing" const val MICROG_TOKEN_REFRESH_FAILURE = "MicroG refresh failed" } } app/src/main/java/foundation/e/apps/data/login/playstore/AnonymousLoginManager.kt +1 −21 Original line number Diff line number Diff line Loading @@ -19,16 +19,13 @@ package foundation.e.apps.data.login.playstore import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.PlayResponse import com.aurora.gplayapi.helpers.AuthHelper import foundation.e.apps.data.login.api.LoginManager import foundation.e.apps.data.login.core.Auth import foundation.e.apps.data.playstore.utils.CustomAuthValidator import foundation.e.apps.data.playstore.utils.GPlayHttpClient import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import timber.log.Timber import java.net.HttpURLConnection import java.util.Locale import java.util.Properties Loading @@ -38,7 +35,7 @@ class AnonymousLoginManager @Inject constructor( private val gPlayHttpClient: GPlayHttpClient, private val nativeDeviceProperty: Properties, private val json: Json, ) : LoginManager<AuthData?> { ) : LoginManager { companion object { private const val TOKEN_DISPENSER_URL = "https://eu.gtoken.ecloud.global" Loading Loading @@ -77,21 +74,4 @@ class AnonymousLoginManager @Inject constructor( } return authData } suspend fun validate(authData: AuthData?): PlayResponse { if (authData == null) { return PlayResponse() } var result = PlayResponse() withContext(Dispatchers.IO) { try { val authValidator = CustomAuthValidator(authData).using(gPlayHttpClient) result = authValidator.getValidityResponse() } catch (e: Exception) { Timber.e(e, "AuthData validation failed for anonymous login") throw e } } return result } } app/src/main/java/foundation/e/apps/data/login/playstore/AuthDataProviders.ktdeleted 100644 → 0 +0 −116 Original line number Diff line number Diff line /* * Copyright (C) 2026 e Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ package foundation.e.apps.data.login.playstore import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.preference.getSync interface AuthDataProvider { suspend fun fetch(): ResultSupreme<AuthData?> } class AnonymousAuthDataProvider( private val loginProvider: suspend () -> ResultSupreme<AuthData?>, private val formatter: (AuthData) -> AuthData ) : AuthDataProvider { override suspend fun fetch(): ResultSupreme<AuthData?> { return loginProvider().run { if (isSuccess()) { ResultSupreme.Success(formatter(this.data!!)) } else { this } } } } class GoogleAasAuthDataProvider( private val appLoungeDataStore: AppLoungeDataStore, private val loginProvider: suspend () -> ResultSupreme<AuthData?> ) : AuthDataProvider { override suspend fun fetch(): ResultSupreme<AuthData?> { val aasToken = appLoungeDataStore.aasToken.getSync() if (aasToken.isNotBlank()) { return loginProvider() } return ResultSupreme.Error("AAS token is missing") } } class GoogleOauthAuthDataProvider( private val appLoungeDataStore: AppLoungeDataStore, private val loginProvider: suspend () -> ResultSupreme<AuthData?>, private val googleLoginManager: GoogleLoginManager, private val aasTokenProvider: suspend (GoogleLoginManager, String, String) -> ResultSupreme<String>, private val formatter: (AuthData) -> AuthData ) : AuthDataProvider { override suspend fun fetch(): ResultSupreme<AuthData?> { val email = appLoungeDataStore.emailData.getSync() val oauthToken = appLoungeDataStore.oauthToken.getSync() val aasTokenResponse = aasTokenProvider(googleLoginManager, email, oauthToken) return if (aasTokenResponse.isSuccess()) { fetchWithAasToken(aasTokenResponse.data.orEmpty()) } else { fetchWithOauthFallback(aasTokenResponse, oauthToken) } } private suspend fun fetchWithAasToken(aasTokenFetched: String): ResultSupreme<AuthData?> { if (aasTokenFetched.isBlank()) { return ResultSupreme.Error("Fetched AAS Token is blank") } appLoungeDataStore.saveAasToken(aasTokenFetched) return loginProvider().run { if (isSuccess()) { ResultSupreme.Success(formatter(this.data!!)) } else { this } } } private suspend fun fetchWithOauthFallback( aasTokenResponse: ResultSupreme<String>, oauthToken: String ): ResultSupreme<AuthData?> { val fallbackAuth = googleLoginManager.buildAuthDataFromOauthToken(oauthToken) return if (fallbackAuth != null) { ResultSupreme.Success(formatter(fallbackAuth)) } else { ResultSupreme.replicate(aasTokenResponse, null) } } } class GoogleAuthDataProvider( private val appLoungeDataStore: AppLoungeDataStore, private val aasProvider: AuthDataProvider, private val oauthProvider: AuthDataProvider ) : AuthDataProvider { override suspend fun fetch(): ResultSupreme<AuthData?> { return if (appLoungeDataStore.aasToken.getSync().isNotBlank()) { aasProvider.fetch() } else { oauthProvider.fetch() } } } app/src/main/java/foundation/e/apps/data/login/playstore/GoogleLoginManager.kt +4 −58 Original line number Diff line number Diff line Loading @@ -18,43 +18,20 @@ package foundation.e.apps.data.login.playstore import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.PlayResponse import com.aurora.gplayapi.helpers.AuthHelper import foundation.e.apps.data.login.api.LoginManager import foundation.e.apps.data.playstore.utils.AC2DMTask import foundation.e.apps.data.playstore.utils.CustomAuthValidator import foundation.e.apps.data.playstore.utils.GPlayHttpClient import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.preference.getSync import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import timber.log.Timber import java.util.Properties import javax.inject.Inject class GoogleLoginManager @Inject constructor( private val gPlayHttpClient: GPlayHttpClient, private val nativeDeviceProperty: Properties, private val aC2DMTask: AC2DMTask, private val appLoungeDataStore: AppLoungeDataStore, ) : LoginManager<AuthData?> { /** * Get PlayResponse for AC2DM Map. This allows us to get an error message too. * * An aasToken is extracted from this map. This is passed to [login] * to generate AuthData. This token is very important as it cannot be regenerated, * hence it must be saved for future use. * * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5680 */ suspend fun getAC2DMResponse(email: String, oauthToken: String): PlayResponse { var response: PlayResponse withContext(Dispatchers.IO) { response = aC2DMTask.getAC2DMResponse(email, oauthToken) } return response } private val oauthAuthDataBuilder: OauthAuthDataBuilder ) : LoginManager { /** * Login Loading @@ -68,10 +45,7 @@ class GoogleLoginManager @Inject constructor( var authData: AuthData? withContext(Dispatchers.IO) { // Prefer AAS if present, otherwise fall back to an AUTH token (microG ya29.*). val (token, tokenType) = when { aasToken.isNotBlank() && aasToken.startsWith("ya29.") -> aasToken to AuthHelper.Token.AUTH aasToken.isNotBlank() -> aasToken to AuthHelper.Token.AAS oauthToken.isNotBlank() -> Loading @@ -90,35 +64,7 @@ class GoogleLoginManager @Inject constructor( return authData } suspend fun buildAuthDataFromOauthToken(oauthToken: String): AuthData? { val email = appLoungeDataStore.emailData.getSync() var authData: AuthData? withContext(Dispatchers.IO) { authData = AuthHelper.build( email = email, token = oauthToken, tokenType = AuthHelper.Token.AUTH, isAnonymous = false, properties = nativeDeviceProperty ) } return authData } suspend fun validate(authData: AuthData?): PlayResponse { if (authData == null) { return PlayResponse() } var result = PlayResponse() withContext(Dispatchers.IO) { try { val authValidator = CustomAuthValidator(authData).using(gPlayHttpClient) result = authValidator.getValidityResponse() } catch (e: Exception) { Timber.e(e, "AuthData validation failed") throw e } } return result suspend fun loginWithOauthToken(oauthToken: String): AuthData? { return oauthAuthDataBuilder.build(oauthToken) } } Loading
app/src/main/java/foundation/e/apps/data/login/api/LoginManager.kt +4 −2 Original line number Diff line number Diff line package foundation.e.apps.data.login.api interface LoginManager<T> { suspend fun login(): T import com.aurora.gplayapi.data.models.AuthData interface LoginManager { suspend fun login(): AuthData? }
app/src/main/java/foundation/e/apps/data/login/playstore/MicrogAccountManager.kt→app/src/main/java/foundation/e/apps/data/login/microg/MicrogLoginManager.kt +185 −0 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ * */ package foundation.e.apps.data.login.playstore package foundation.e.apps.data.login.microg import android.accounts.Account import android.accounts.AccountManager Loading @@ -27,9 +27,12 @@ import android.os.Bundle import android.os.Parcelable import android.util.Base64 import androidx.core.os.BundleCompat import com.aurora.gplayapi.data.models.AuthData import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.data.login.api.LoginManager import foundation.e.apps.data.login.microg.MicrogCertUtil import foundation.e.apps.data.login.playstore.OauthAuthDataBuilder import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.preference.getSync import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import javax.inject.Inject Loading @@ -41,10 +44,12 @@ data class MicrogAccount( ) @Singleton class MicrogAccountManager @Inject constructor( class MicrogLoginManager @Inject constructor( @ApplicationContext val context: Context, private val accountManager: AccountManager ) : LoginManager<MicrogAccountManager.FetchResult> { private val accountManager: AccountManager, private val oauthAuthDataBuilder: OauthAuthDataBuilder, private val appLoungeDataStore: AppLoungeDataStore ) : LoginManager { sealed interface FetchResult { data class Success(val microgAccount: MicrogAccount) : FetchResult data class RequiresUserAction(val intent: Intent) : FetchResult Loading @@ -55,11 +60,25 @@ class MicrogAccountManager @Inject constructor( return accountManager.getAccountsByType(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE).isNotEmpty() } override suspend fun login(): FetchResult { return fetchMicrogAccount() override suspend fun login(): AuthData? { val oldToken = appLoungeDataStore.oauthToken.getSync() val shouldRefresh = hasMicrogAccount() && oldToken.startsWith(MICROG_TOKEN_PREFIX) val oauthToken = if (shouldRefresh) { fetchRefreshedToken(oldToken) } else { oldToken } if (oauthToken.isBlank()) { error(MICROG_TOKEN_MISSING) } return oauthAuthDataBuilder.build(oauthToken) ?: error(MICROG_TOKEN_MISSING) } suspend fun fetchMicrogAccount( accountName: String? = null accountName: String = "" ): FetchResult = withContext(Dispatchers.IO) { val accounts = accountManager.getAccountsByType(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE) if (accounts.isEmpty()) { Loading @@ -68,21 +87,14 @@ class MicrogAccountManager @Inject constructor( ) } val account = accountName?.let { name -> accounts.firstOrNull { it.name == name } } ?: accounts.first() val account = resolveAccount(accounts, accountName) return@withContext runCatching { val options = createAuthTokenOptions() val bundle = accountManager.getAuthToken( account, MicrogCertUtil.PLAY_AUTH_SCOPE, Bundle().apply { putString("overridePackage", MicrogCertUtil.GOOGLE_PLAY_PACKAGE) putByteArray( "overrideCertificate", Base64.decode(MicrogCertUtil.GOOGLE_PLAY_CERT_BASE64, Base64.DEFAULT) ) }, options, false, null, null Loading @@ -104,6 +116,59 @@ class MicrogAccountManager @Inject constructor( } } suspend fun invalidateAuthToken(accountName: String, oldToken: String) { if (oldToken.isBlank()) { return } withContext(Dispatchers.IO) { val accounts = accountManager.getAccountsByType(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE) if (accounts.isNotEmpty()) { val account = resolveAccount(accounts, accountName) accountManager.invalidateAuthToken(account.type, oldToken) } } } private suspend fun fetchRefreshedToken(oldToken: String): String { val accountName = appLoungeDataStore.emailData.getSync().ifBlank { "" } invalidateAuthToken(accountName, oldToken) val result = fetchMicrogAccount(accountName) return when (result) { is FetchResult.Success -> { appLoungeDataStore.saveGoogleLogin( result.microgAccount.account.name, result.microgAccount.oauthToken ) appLoungeDataStore.saveAasToken("") result.microgAccount.oauthToken } is FetchResult.RequiresUserAction -> error(MICROG_TOKEN_REFRESH_FAILURE) is FetchResult.Error -> error(MICROG_TOKEN_REFRESH_FAILURE) } } private fun resolveAccount(accounts: Array<Account>, accountName: String): Account { return if (accountName.isNotBlank()) { accounts.firstOrNull { it.name == accountName } ?: accounts.first() } else { accounts.first() } } private fun createAuthTokenOptions(): Bundle { return Bundle().apply { putString("overridePackage", MicrogCertUtil.GOOGLE_PLAY_PACKAGE) putByteArray( "overrideCertificate", Base64.decode(MicrogCertUtil.GOOGLE_PLAY_CERT_BASE64, Base64.DEFAULT) ) } } private fun <T : Parcelable> Bundle.parcelableCompat(key: String, clazz: Class<T>): T? { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { getParcelable(key, clazz) Loading @@ -111,4 +176,10 @@ class MicrogAccountManager @Inject constructor( BundleCompat.getParcelable(this, key, clazz) } } companion object { const val MICROG_TOKEN_PREFIX = "ya29." const val MICROG_TOKEN_MISSING = "MicroG token is missing" const val MICROG_TOKEN_REFRESH_FAILURE = "MicroG refresh failed" } }
app/src/main/java/foundation/e/apps/data/login/playstore/AnonymousLoginManager.kt +1 −21 Original line number Diff line number Diff line Loading @@ -19,16 +19,13 @@ package foundation.e.apps.data.login.playstore import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.PlayResponse import com.aurora.gplayapi.helpers.AuthHelper import foundation.e.apps.data.login.api.LoginManager import foundation.e.apps.data.login.core.Auth import foundation.e.apps.data.playstore.utils.CustomAuthValidator import foundation.e.apps.data.playstore.utils.GPlayHttpClient import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import timber.log.Timber import java.net.HttpURLConnection import java.util.Locale import java.util.Properties Loading @@ -38,7 +35,7 @@ class AnonymousLoginManager @Inject constructor( private val gPlayHttpClient: GPlayHttpClient, private val nativeDeviceProperty: Properties, private val json: Json, ) : LoginManager<AuthData?> { ) : LoginManager { companion object { private const val TOKEN_DISPENSER_URL = "https://eu.gtoken.ecloud.global" Loading Loading @@ -77,21 +74,4 @@ class AnonymousLoginManager @Inject constructor( } return authData } suspend fun validate(authData: AuthData?): PlayResponse { if (authData == null) { return PlayResponse() } var result = PlayResponse() withContext(Dispatchers.IO) { try { val authValidator = CustomAuthValidator(authData).using(gPlayHttpClient) result = authValidator.getValidityResponse() } catch (e: Exception) { Timber.e(e, "AuthData validation failed for anonymous login") throw e } } return result } }
app/src/main/java/foundation/e/apps/data/login/playstore/AuthDataProviders.ktdeleted 100644 → 0 +0 −116 Original line number Diff line number Diff line /* * Copyright (C) 2026 e Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ package foundation.e.apps.data.login.playstore import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.preference.getSync interface AuthDataProvider { suspend fun fetch(): ResultSupreme<AuthData?> } class AnonymousAuthDataProvider( private val loginProvider: suspend () -> ResultSupreme<AuthData?>, private val formatter: (AuthData) -> AuthData ) : AuthDataProvider { override suspend fun fetch(): ResultSupreme<AuthData?> { return loginProvider().run { if (isSuccess()) { ResultSupreme.Success(formatter(this.data!!)) } else { this } } } } class GoogleAasAuthDataProvider( private val appLoungeDataStore: AppLoungeDataStore, private val loginProvider: suspend () -> ResultSupreme<AuthData?> ) : AuthDataProvider { override suspend fun fetch(): ResultSupreme<AuthData?> { val aasToken = appLoungeDataStore.aasToken.getSync() if (aasToken.isNotBlank()) { return loginProvider() } return ResultSupreme.Error("AAS token is missing") } } class GoogleOauthAuthDataProvider( private val appLoungeDataStore: AppLoungeDataStore, private val loginProvider: suspend () -> ResultSupreme<AuthData?>, private val googleLoginManager: GoogleLoginManager, private val aasTokenProvider: suspend (GoogleLoginManager, String, String) -> ResultSupreme<String>, private val formatter: (AuthData) -> AuthData ) : AuthDataProvider { override suspend fun fetch(): ResultSupreme<AuthData?> { val email = appLoungeDataStore.emailData.getSync() val oauthToken = appLoungeDataStore.oauthToken.getSync() val aasTokenResponse = aasTokenProvider(googleLoginManager, email, oauthToken) return if (aasTokenResponse.isSuccess()) { fetchWithAasToken(aasTokenResponse.data.orEmpty()) } else { fetchWithOauthFallback(aasTokenResponse, oauthToken) } } private suspend fun fetchWithAasToken(aasTokenFetched: String): ResultSupreme<AuthData?> { if (aasTokenFetched.isBlank()) { return ResultSupreme.Error("Fetched AAS Token is blank") } appLoungeDataStore.saveAasToken(aasTokenFetched) return loginProvider().run { if (isSuccess()) { ResultSupreme.Success(formatter(this.data!!)) } else { this } } } private suspend fun fetchWithOauthFallback( aasTokenResponse: ResultSupreme<String>, oauthToken: String ): ResultSupreme<AuthData?> { val fallbackAuth = googleLoginManager.buildAuthDataFromOauthToken(oauthToken) return if (fallbackAuth != null) { ResultSupreme.Success(formatter(fallbackAuth)) } else { ResultSupreme.replicate(aasTokenResponse, null) } } } class GoogleAuthDataProvider( private val appLoungeDataStore: AppLoungeDataStore, private val aasProvider: AuthDataProvider, private val oauthProvider: AuthDataProvider ) : AuthDataProvider { override suspend fun fetch(): ResultSupreme<AuthData?> { return if (appLoungeDataStore.aasToken.getSync().isNotBlank()) { aasProvider.fetch() } else { oauthProvider.fetch() } } }
app/src/main/java/foundation/e/apps/data/login/playstore/GoogleLoginManager.kt +4 −58 Original line number Diff line number Diff line Loading @@ -18,43 +18,20 @@ package foundation.e.apps.data.login.playstore import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.PlayResponse import com.aurora.gplayapi.helpers.AuthHelper import foundation.e.apps.data.login.api.LoginManager import foundation.e.apps.data.playstore.utils.AC2DMTask import foundation.e.apps.data.playstore.utils.CustomAuthValidator import foundation.e.apps.data.playstore.utils.GPlayHttpClient import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.preference.getSync import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import timber.log.Timber import java.util.Properties import javax.inject.Inject class GoogleLoginManager @Inject constructor( private val gPlayHttpClient: GPlayHttpClient, private val nativeDeviceProperty: Properties, private val aC2DMTask: AC2DMTask, private val appLoungeDataStore: AppLoungeDataStore, ) : LoginManager<AuthData?> { /** * Get PlayResponse for AC2DM Map. This allows us to get an error message too. * * An aasToken is extracted from this map. This is passed to [login] * to generate AuthData. This token is very important as it cannot be regenerated, * hence it must be saved for future use. * * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5680 */ suspend fun getAC2DMResponse(email: String, oauthToken: String): PlayResponse { var response: PlayResponse withContext(Dispatchers.IO) { response = aC2DMTask.getAC2DMResponse(email, oauthToken) } return response } private val oauthAuthDataBuilder: OauthAuthDataBuilder ) : LoginManager { /** * Login Loading @@ -68,10 +45,7 @@ class GoogleLoginManager @Inject constructor( var authData: AuthData? withContext(Dispatchers.IO) { // Prefer AAS if present, otherwise fall back to an AUTH token (microG ya29.*). val (token, tokenType) = when { aasToken.isNotBlank() && aasToken.startsWith("ya29.") -> aasToken to AuthHelper.Token.AUTH aasToken.isNotBlank() -> aasToken to AuthHelper.Token.AAS oauthToken.isNotBlank() -> Loading @@ -90,35 +64,7 @@ class GoogleLoginManager @Inject constructor( return authData } suspend fun buildAuthDataFromOauthToken(oauthToken: String): AuthData? { val email = appLoungeDataStore.emailData.getSync() var authData: AuthData? withContext(Dispatchers.IO) { authData = AuthHelper.build( email = email, token = oauthToken, tokenType = AuthHelper.Token.AUTH, isAnonymous = false, properties = nativeDeviceProperty ) } return authData } suspend fun validate(authData: AuthData?): PlayResponse { if (authData == null) { return PlayResponse() } var result = PlayResponse() withContext(Dispatchers.IO) { try { val authValidator = CustomAuthValidator(authData).using(gPlayHttpClient) result = authValidator.getValidityResponse() } catch (e: Exception) { Timber.e(e, "AuthData validation failed") throw e } } return result suspend fun loginWithOauthToken(oauthToken: String): AuthData? { return oauthAuthDataBuilder.build(oauthToken) } }