Skip to content

Commit

Permalink
Updates for codelab steps (#38)
Browse files Browse the repository at this point in the history
* Updates for codelab steps

Added new TODOs so the user handles activity results and extracts the
PublicKeyCredential. Updated remaining TODOs accordingly.

Change-Id: Iccd78846d4efd8ba3f1a8ee64b925f02ced84c67

* Update some TODO instructions

Change-Id: Iec77b17223d22bb8af4af0893a55031c492087c7
  • Loading branch information
jdkoren authored Sep 3, 2021
1 parent f099b7b commit 46ba00b
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,9 @@ class AuthRepository @Inject constructor(
// TODO(1): Call the server API: /registerRequest
// - Use api.registerRequest to get an ApiResult of
// PublicKeyCredentialCreationOptions.
// - Call fido2ApiClient.getRegisterIntent and create an intent to generate a
// new credential.
// - Pass the intent back to the `result` LiveData so that the UI can open the
// - Call fido2ApiClient.getRegisterIntent to create a PendingIntent to generate a
// new credential. This method returns a Task object.
// - Call await() on the Task and return the result so that the UI can open the
// fingerprint dialog.

} catch (e: Exception) {
Expand All @@ -250,15 +250,12 @@ class AuthRepository @Inject constructor(
try {
val sessionId = dataStore.read(SESSION_ID)!!

// TODO(3): Call the server API: /registerResponse
// - Create an AuthenticatorAttestationResponse from the data intent generated by
// the fingerprint dialog.
// TODO(4): Call the server API: /registerResponse
// - Use api.registerResponse to send the response back to the server.
// - Save the returned list of credentials into the SharedPreferences. The key is
// PREF_CREDENTIALS.
// - Also save the newly added credential ID into the SharedPreferences. The key is
// PREF_LOCAL_CREDENTIAL_ID. The ID can be obtained from the `keyHandle` field of
// the AuthenticatorAttestationResponse object.
// - Save the returned list of credentials into the DataStore. The key is CREDENTIALS.
// - Also save the newly added credential ID into the DataStore. The key is
// LOCAL_CREDENTIAL_ID. The ID can be obtained from the `rawId` field of
// the PublicKeyCredential object.

} catch (e: ApiException) {
Log.e(TAG, "Cannot call registerResponse", e)
Expand Down Expand Up @@ -289,11 +286,11 @@ class AuthRepository @Inject constructor(
val sessionId = dataStore.read(SESSION_ID)!!
val credentialId = dataStore.read(LOCAL_CREDENTIAL_ID)

// TODO(4): Call the server API: /signinRequest
// TODO(5): Call the server API: /signinRequest
// - Use api.signinRequest to get a PublicKeyCredentialRequestOptions.
// - Call fido2ApiClient.getSignIntent and create an intent to assert the
// credential.
// - Pass the intent to the `result` LiveData so that the UI can open the
// - Call fido2ApiClient.getSignIntent to create a PendingIntent to assert the
// credential. This method returns a Task object.
// - Call await() on the Task and return the result so that the UI can open the
// fingerprint dialog.

}
Expand All @@ -309,17 +306,15 @@ class AuthRepository @Inject constructor(
val username = dataStore.read(USERNAME)!!
val sessionId = dataStore.read(SESSION_ID)!!

// TODO(6): Call the server API: /signinResponse
// - Create an AuthenticatorAssertionResponse from the data intent generated by
// the fingerprint dialog.
// TODO(8): Call the server API: /signinResponse
// - Use api.signinResponse to send the response back to the server.
// - Save the returned list of credentials into the SharedPreferences. The key is
// PREF_CREDENTIALS.
// - Also save the credential ID into the SharedPreferences. The key is
// PREF_LOCAL_CREDENTIAL_ID. The ID can be obtained from the `keyHandle` field of
// the AuthenticatorAssertionResponse object.
// - Save the returned list of credentials into the DataStore. The key is CREDENTIALS.
// - Also save the credential ID into the DataStore. The key is LOCAL_CREDENTIAL_ID. The
// ID can be obtained from the `rawId` field of the PublicKeyCredential object.
// - Notify the UI that the sign-in has succeeded. This can be done by calling
// `invokeSignInStateListeners(SignInState.SignedIn(username))`
// `signInStateMutable.emit(SignInState.SignedIn(username))`.
// - Call refreshCredentials to fetch the user's credentials so they can be listed in
// the UI.

} catch (e: ApiException) {
Log.e(TAG, "Cannot call registerResponse", e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,12 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.Toast
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.example.android.fido2.R
import com.example.android.fido2.databinding.AuthFragmentBinding
import com.google.android.gms.fido.Fido
import com.google.android.gms.fido.fido2.api.common.AuthenticatorErrorResponse
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredential
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
Expand All @@ -45,7 +40,10 @@ class AuthFragment : Fragment() {
private val viewModel: AuthViewModel by viewModels()
private lateinit var binding: AuthFragmentBinding

private lateinit var signIntentLauncher: ActivityResultLauncher<IntentSenderRequest>
private val signIntentLauncher = registerForActivityResult(
ActivityResultContracts.StartIntentSenderForResult(),
::handleSignResult
)

override fun onCreateView(
inflater: LayoutInflater,
Expand All @@ -59,11 +57,6 @@ class AuthFragment : Fragment() {
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
signIntentLauncher = registerForActivityResult(
ActivityResultContracts.StartIntentSenderForResult(),
::handleSignResult
)

binding.inputPassword.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_GO) {
viewModel.submitPassword()
Expand All @@ -77,7 +70,7 @@ class AuthFragment : Fragment() {
launch {
viewModel.signinRequests.collect { intent ->

// TODO(5): Open the fingerprint dialog.
// TODO(6): Open the fingerprint dialog.
// - Open the fingerprint dialog by launching the intent from FIDO2 API.

}
Expand All @@ -95,21 +88,23 @@ class AuthFragment : Fragment() {
}

private fun handleSignResult(activityResult: ActivityResult) {
val bytes = activityResult.data?.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)

// TODO(7): Handle the ActivityResult
// - Extract byte array from result data using Fido.FIDO2_KEY_CREDENTIAL_EXTRA.
// (continued below)
val bytes: ByteArray? = null

when {
activityResult.resultCode != Activity.RESULT_OK ->
Toast.makeText(requireContext(), R.string.cancelled, Toast.LENGTH_SHORT).show()
bytes == null ->
Toast.makeText(requireContext(), R.string.auth_error, Toast.LENGTH_SHORT).show()
else -> {
val credential = PublicKeyCredential.deserializeFromBytes(bytes)
val response = credential.response
if (response is AuthenticatorErrorResponse) {
Toast.makeText(requireContext(), response.errorMessage, Toast.LENGTH_SHORT)
.show()
} else {
viewModel.signinResponse(credential)
}

// - Deserialize bytes into a PublicKeyCredential.
// - Check if the response is an AuthenticationErrorResponse. If so, show a toast.
// - Otherwise, pass the credential to the viewModel.

}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,13 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.example.android.fido2.R
import com.example.android.fido2.databinding.HomeFragmentBinding
import com.google.android.gms.fido.Fido
import com.google.android.gms.fido.fido2.api.common.AuthenticatorErrorResponse
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredential
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
Expand All @@ -50,7 +45,10 @@ class HomeFragment : Fragment(), DeleteConfirmationFragment.Listener {
private val viewModel: HomeViewModel by viewModels()
private lateinit var binding: HomeFragmentBinding

private lateinit var createCredentialLauncher: ActivityResultLauncher<IntentSenderRequest>
private val createCredentialIntentLauncher = registerForActivityResult(
ActivityResultContracts.StartIntentSenderForResult(),
::handleCreateCredentialResult
)

override fun onCreateView(
inflater: LayoutInflater,
Expand Down Expand Up @@ -93,11 +91,6 @@ class HomeFragment : Fragment(), DeleteConfirmationFragment.Listener {
}
}

createCredentialLauncher = registerForActivityResult(
ActivityResultContracts.StartIntentSenderForResult(),
::handleCreateCredentialResult
)

viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.processing.collect { processing ->
if (processing) {
Expand Down Expand Up @@ -127,22 +120,24 @@ class HomeFragment : Fragment(), DeleteConfirmationFragment.Listener {
}

private fun handleCreateCredentialResult(activityResult: ActivityResult) {
val bytes = activityResult.data?.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)

// TODO(3): Receive ActivityResult with the new Credential
// - Extract byte array from result data using Fido.FIDO2_KEY_CREDENTIAL_EXTRA.
// (continued below
val bytes: ByteArray? = null

when {
activityResult.resultCode != Activity.RESULT_OK ->
Toast.makeText(requireContext(), R.string.cancelled, Toast.LENGTH_SHORT).show()
bytes == null ->
Toast.makeText(requireContext(), R.string.credential_error, Toast.LENGTH_SHORT)
.show()
else -> {
val credential = PublicKeyCredential.deserializeFromBytes(bytes)
val response = credential.response
if (response is AuthenticatorErrorResponse) {
Toast.makeText(requireContext(), response.errorMessage, Toast.LENGTH_SHORT)
.show()
} else {
viewModel.registerResponse(credential)
}

// - Deserialize bytes into a PublicKeyCredential.
// - Check if the response is an AuthenticationErrorResponse. If so, show a toast.
// - Otherwise, pass the credential to the viewModel.

}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.Toast
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
Expand All @@ -45,7 +44,10 @@ class AuthFragment : Fragment() {
private val viewModel: AuthViewModel by viewModels()
private lateinit var binding: AuthFragmentBinding

private lateinit var signIntentLauncher: ActivityResultLauncher<IntentSenderRequest>
private val signIntentLauncher = registerForActivityResult(
ActivityResultContracts.StartIntentSenderForResult(),
::handleSignResult
)

override fun onCreateView(
inflater: LayoutInflater,
Expand All @@ -59,11 +61,6 @@ class AuthFragment : Fragment() {
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
signIntentLauncher = registerForActivityResult(
ActivityResultContracts.StartIntentSenderForResult(),
::handleSignResult
)

binding.inputPassword.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_GO) {
viewModel.submitPassword()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.isVisible
Expand All @@ -50,7 +49,10 @@ class HomeFragment : Fragment(), DeleteConfirmationFragment.Listener {
private val viewModel: HomeViewModel by viewModels()
private lateinit var binding: HomeFragmentBinding

private lateinit var createCredentialIntentLauncher: ActivityResultLauncher<IntentSenderRequest>
private val createCredentialIntentLauncher = registerForActivityResult(
ActivityResultContracts.StartIntentSenderForResult(),
::handleCreateCredentialResult
)

override fun onCreateView(
inflater: LayoutInflater,
Expand Down Expand Up @@ -93,11 +95,6 @@ class HomeFragment : Fragment(), DeleteConfirmationFragment.Listener {
}
}

createCredentialIntentLauncher = registerForActivityResult(
ActivityResultContracts.StartIntentSenderForResult(),
::handleCreateCredentialResult
)

viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.processing.collect { processing ->
if (processing) {
Expand Down

0 comments on commit 46ba00b

Please sign in to comment.