diff --git a/authentication/src/main/kotlin/com/uber/sdk2/auth/internal/AuthActivity.kt b/authentication/src/main/kotlin/com/uber/sdk2/auth/internal/AuthActivity.kt index 04dee84..ccb5a5a 100644 --- a/authentication/src/main/kotlin/com/uber/sdk2/auth/internal/AuthActivity.kt +++ b/authentication/src/main/kotlin/com/uber/sdk2/auth/internal/AuthActivity.kt @@ -43,13 +43,12 @@ class AuthActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val authContext = intent.getParcelableExtra(AUTH_CONTEXT) - authContext ?.let { - authProvider = AuthProvider(this, authContext) - } ?: run { - val responseIntent = Intent().apply { putExtra("EXTRA_ERROR", "AUTH_CONTEXT was null") } - setResult(RESULT_CANCELED, responseIntent) - finish() - } + authContext?.let { authProvider = AuthProvider(this, authContext) } + ?: run { + val responseIntent = Intent().apply { putExtra("EXTRA_ERROR", "AUTH_CONTEXT was null") } + setResult(RESULT_CANCELED, responseIntent) + finish() + } } private fun startAuth() { diff --git a/authentication/src/main/kotlin/com/uber/sdk2/auth/internal/AuthProvider.kt b/authentication/src/main/kotlin/com/uber/sdk2/auth/internal/AuthProvider.kt index 4a6ba05..9135201 100644 --- a/authentication/src/main/kotlin/com/uber/sdk2/auth/internal/AuthProvider.kt +++ b/authentication/src/main/kotlin/com/uber/sdk2/auth/internal/AuthProvider.kt @@ -92,16 +92,26 @@ class AuthProvider( private suspend fun sendPushedAuthorizationRequest(ssoConfig: SsoConfig) = authContext.prefillInfo?.let { - val response = - authService.loginParRequest( - ssoConfig.clientId, - RESPONSE_TYPE, - Base64Util.encodePrefillInfoToString(it), - ssoConfig.scope ?: "profile", - ) - val body = response.body() - body?.takeIf { response.isSuccessful } - ?: throw AuthException.ServerError("Bad response ${response.code()}") + try { + val response = + authService.loginParRequest( + ssoConfig.clientId, + RESPONSE_TYPE, + Base64Util.encodePrefillInfoToString(it), + ssoConfig.scope ?: "profile", + ) + val body = response.body() + body?.takeIf { response.isSuccessful } + ?: run { + // PAR request failed, but continue authentication without metadata + // User can still login, just without pre-filled information + PARResponse("", "") + } + } catch (e: Exception) { + // PAR request failed due to network or other error + // Continue authentication without metadata for better user experience + PARResponse("", "") + } } ?: PARResponse("", "") private fun getQueryParams(parResponse: PARResponse) = buildMap { diff --git a/authentication/src/test/kotlin/com/uber/sdk2/auth/internal/AuthProviderTest.kt b/authentication/src/test/kotlin/com/uber/sdk2/auth/internal/AuthProviderTest.kt index 1886879..26182ef 100644 --- a/authentication/src/test/kotlin/com/uber/sdk2/auth/internal/AuthProviderTest.kt +++ b/authentication/src/test/kotlin/com/uber/sdk2/auth/internal/AuthProviderTest.kt @@ -200,4 +200,85 @@ class AuthProviderTest : RobolectricTestBase() { assert(result is AuthResult.Success) assert((result as AuthResult.Success).uberToken.authCode == "authCode") } + + @Test + fun `test authenticate when PAR request fails should continue without metadata`() = runTest { + whenever(ssoLink.execute(any())).thenReturn("authCode") + // Mock PAR request to return error response (e.g., 500) + val errorResponse: Response = Response.error(500, mock()) + whenever(authService.loginParRequest(any(), any(), any(), any())).thenReturn(errorResponse) + val prefillInfo = PrefillInfo("email", "firstName", "lastName", "phoneNumber") + val authContext = + AuthContext( + AuthDestination.CrossAppSso(listOf(CrossApp.Rider)), + AuthType.AuthCode, + prefillInfo, + ) + val authProvider = AuthProvider(activity, authContext, authService, codeVerifierGenerator) + val argumentCaptor = argumentCaptor>() + val result = authProvider.authenticate() + // Verify PAR request was attempted + verify(authService).loginParRequest(any(), any(), any(), any()) + // Verify authentication continued without request_uri + verify(ssoLink).execute(argumentCaptor.capture()) + assert(argumentCaptor.lastValue.containsKey(REQUEST_URI).not()) + // Verify authentication succeeded + assert(result is AuthResult.Success) + assert((result as AuthResult.Success).uberToken.authCode == "authCode") + } + + @Test + fun `test authenticate when PAR request throws exception should continue without metadata`() = + runTest { + whenever(ssoLink.execute(any())).thenReturn("authCode") + // Mock PAR request to throw network exception + whenever(authService.loginParRequest(any(), any(), any(), any())) + .thenThrow(RuntimeException("Network error")) + val prefillInfo = PrefillInfo("email", "firstName", "lastName", "phoneNumber") + val authContext = + AuthContext( + AuthDestination.CrossAppSso(listOf(CrossApp.Rider)), + AuthType.AuthCode, + prefillInfo, + ) + val authProvider = AuthProvider(activity, authContext, authService, codeVerifierGenerator) + val argumentCaptor = argumentCaptor>() + val result = authProvider.authenticate() + // Verify PAR request was attempted + verify(authService).loginParRequest(any(), any(), any(), any()) + // Verify authentication continued without request_uri + verify(ssoLink).execute(argumentCaptor.capture()) + assert(argumentCaptor.lastValue.containsKey(REQUEST_URI).not()) + // Verify authentication succeeded + assert(result is AuthResult.Success) + assert((result as AuthResult.Success).uberToken.authCode == "authCode") + } + + @Test + fun `test authenticate with PKCE when PAR fails should still include code challenge`() = runTest { + whenever(ssoLink.execute(any())).thenReturn("authCode") + whenever(codeVerifierGenerator.generateCodeVerifier()).thenReturn("verifier") + whenever(codeVerifierGenerator.generateCodeChallenge("verifier")).thenReturn("challenge") + whenever(authService.token(any(), any(), any(), any(), any())) + .thenReturn(Response.success(UberToken(accessToken = "accessToken"))) + // Mock PAR request to fail + val errorResponse: Response = Response.error(500, mock()) + whenever(authService.loginParRequest(any(), any(), any(), any())).thenReturn(errorResponse) + val prefillInfo = PrefillInfo("email", "firstName", "lastName", "phoneNumber") + val authContext = + AuthContext(AuthDestination.CrossAppSso(listOf(CrossApp.Rider)), AuthType.PKCE(), prefillInfo) + val authProvider = AuthProvider(activity, authContext, authService, codeVerifierGenerator) + val argumentCaptor = argumentCaptor>() + val result = authProvider.authenticate() + // Verify PAR request was attempted + verify(authService).loginParRequest(any(), any(), any(), any()) + // Verify authentication continued with code challenge but without request_uri + verify(ssoLink).execute(argumentCaptor.capture()) + assert(argumentCaptor.lastValue.containsKey(REQUEST_URI).not()) + assert(argumentCaptor.lastValue[CODE_CHALLENGE_PARAM] == "challenge") + assert(argumentCaptor.lastValue[CODE_CHALLENGE_METHOD] == CODE_CHALLENGE_METHOD_VAL) + // Verify authentication succeeded + assert(result is AuthResult.Success) + assert((result as AuthResult.Success).uberToken.accessToken == "accessToken") + } } diff --git a/samples/login-with-auth-code-demo/gradle.properties b/samples/login-with-auth-code-demo/gradle.properties index a1c9d5e..794bda1 100644 --- a/samples/login-with-auth-code-demo/gradle.properties +++ b/samples/login-with-auth-code-demo/gradle.properties @@ -1,3 +1,3 @@ description=Login to Uber Sample UBER_CLIENT_ID=insert_your_client_id_here -UBER_REDIRECT_URI=insert_your_redirect_uri_here \ No newline at end of file +UBER_REDIRECT_URI=insert_your_redirect_uri_here