diff --git a/app/src/main/java/com/kickstarter/viewmodels/OAuthViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/OAuthViewModel.kt index f02a24dabe..d5d34fc76a 100644 --- a/app/src/main/java/com/kickstarter/viewmodels/OAuthViewModel.kt +++ b/app/src/main/java/com/kickstarter/viewmodels/OAuthViewModel.kt @@ -56,7 +56,7 @@ class OAuthViewModel( } private var mutableUIState = MutableStateFlow(OAuthUiState()) - lateinit var codeVerifier: String + private var codeVerifier: String? = null val uiState: StateFlow get() = mutableUIState.asStateFlow() .stateIn( @@ -73,9 +73,9 @@ class OAuthViewModel( val host = uri.host val code = uri.getQueryParameter("code") - if (scheme == REDIRECT_URI_SCHEMA && host == REDIRECT_URI_HOST && !code.isNullOrBlank()) { + if (isAfterRedirectionStep(scheme, host, code, codeVerifier)) { Timber.d("$logcat retrieve token after redirectionDeeplink: $code") - apiClient.loginWithCodes(codeVerifier, code, clientID) + apiClient.loginWithCodes(requireNotNull(codeVerifier), requireNotNull(code), clientID) .asFlow() .flatMapLatest { token -> Timber.d("$logcat About to persist token to currentUser: $token") @@ -106,32 +106,27 @@ class OAuthViewModel( ) analyticEvents.trackLogInButtonCtaClicked() } - } - - if (scheme == REDIRECT_URI_SCHEMA && host == REDIRECT_URI_HOST && code.isNullOrBlank()) { - val error = "$logcat No code after redirection" - Timber.e(error) - mutableUIState.emit( - OAuthUiState( - error = error, - user = null - ) - ) + } else { + mutableUIState.emit(OAuthUiState(error = "$code / $codeVerifier empty or null or wrong redirection", user = null)) } if (intent.data == null) { codeVerifier = verifier.generateRandomCodeVerifier(entropy = CodeVerifier.MIN_CODE_VERIFIER_ENTROPY) - val url = generateAuthorizationUrlWithParams() - Timber.d("$logcat isAuthorizationStep $url and codeVerifier: $codeVerifier") - mutableUIState.emit( - OAuthUiState( - authorizationUrl = url, - isAuthorizationStep = true, + codeVerifier?.let { verifier -> + val url = generateAuthorizationUrlWithParams(verifier) + Timber.d("$logcat isAuthorizationStep $url and codeVerifier: $codeVerifier") + mutableUIState.emit( + OAuthUiState( + authorizationUrl = url, + isAuthorizationStep = true, + ) ) - ) + } } } } + fun isAfterRedirectionStep(scheme: String?, host: String?, code: String?, codeVerifier: String?) = + scheme == REDIRECT_URI_SCHEMA && host == REDIRECT_URI_HOST && !code.isNullOrBlank() && !codeVerifier.isNullOrBlank() private fun processThrowable(throwable: Throwable): String { if (!throwable.message.isNullOrBlank()) return throwable.message ?: "" @@ -145,13 +140,13 @@ class OAuthViewModel( return "error while getting the token or user" } - private fun generateAuthorizationUrlWithParams(): String { + private fun generateAuthorizationUrlWithParams(verifier: String): String { val authParams = mapOf( "redirect_uri" to REDIRECT_URI_SCHEMA, "scope" to "1", // profile/email "client_id" to clientID, "response_type" to "1", // code - "code_challenge" to verifier.generateCodeChallenge(codeVerifier), + "code_challenge" to this.verifier.generateCodeChallenge(verifier), "code_challenge_method" to "S256" ).map { (k, v) -> "${(k)}=$v" }.joinToString("&") return "$hostEndpoint/oauth/authorizations/new?$authParams" diff --git a/app/src/test/java/com/kickstarter/viewmodels/OAuthViewModelTest.kt b/app/src/test/java/com/kickstarter/viewmodels/OAuthViewModelTest.kt index 06814452b7..852de8fa51 100644 --- a/app/src/test/java/com/kickstarter/viewmodels/OAuthViewModelTest.kt +++ b/app/src/test/java/com/kickstarter/viewmodels/OAuthViewModelTest.kt @@ -187,6 +187,47 @@ class OAuthViewModelTest : KSRobolectricTestCase() { this@OAuthViewModelTest.segmentTrack.assertValues(EventName.CTA_CLICKED.eventName) } + @Test + fun `test attempt to obtain Token And User with invalid codeVerifier will generate Error state`() = runTest { + + val environment = environment() + .toBuilder() + .apiClientV2(MockApiClientV2()) + .currentUserV2(MockCurrentUserV2()) + .build() + + val mockCodeVerifier = object : PKCE { + override fun generateCodeChallenge(codeVerifier: String): String { + return "" // invalid + } + + override fun generateRandomCodeVerifier(entropy: Int): String { + return "" // invalid + } + } + + setUpEnvironment(environment, mockCodeVerifier) + + val testCode = "" // invalid + val state = mutableListOf() + val redirectionUrl = "ksrauth2://authenticate?code=$testCode&redirect_uri=ksrauth2&response_type=1&scope=1" + + backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + vm.produceState(Intent()) + vm.produceState(Intent().setData(Uri.parse(redirectionUrl))) + vm.uiState.toList(state) + } + + // - First empty emission due the initialization + assertEquals( + listOf( + OAuthUiState(authorizationUrl = "", isAuthorizationStep = false, user = null, error = ""), + OAuthUiState(authorizationUrl = "", isAuthorizationStep = false, user = null, error = " / empty or null or wrong redirection"), + ), + state + ) + } + @Test fun testProduceState_getTokeAndUser_ErrorWhileFetchUser() = runTest { @@ -298,4 +339,12 @@ class OAuthViewModelTest : KSRobolectricTestCase() { assertEquals(currentUserV2.accessToken, null) } + + @Test + fun `test invalid information after redirection`() { + setUpEnvironment(environment(), CodeVerifier()) + + assertFalse(vm.isAfterRedirectionStep(null, null, null, null)) + assertFalse(vm.isAfterRedirectionStep("http", "someHost.com", "", "")) + } }