11package com.descope.flutter
22
3- import android.app.PendingIntent
43import android.content.Context
5- import android.content.Intent
64import android.net.Uri
75import androidx.browser.customtabs.CustomTabsIntent
86import androidx.security.crypto.EncryptedSharedPreferences
97import androidx.security.crypto.MasterKeys
8+ import androidx.credentials.CreateCredentialRequest
9+ import androidx.credentials.CreatePublicKeyCredentialRequest
10+ import androidx.credentials.CreatePublicKeyCredentialResponse
1011import androidx.credentials.CredentialManager
1112import androidx.credentials.CredentialManagerCallback
1213import androidx.credentials.CustomCredential
1314import androidx.credentials.GetCredentialRequest
1415import androidx.credentials.GetCredentialResponse
15- import androidx.credentials.exceptions.GetCredentialCancellationException
16+ import androidx.credentials.GetPublicKeyCredentialOption
17+ import androidx.credentials.PublicKeyCredential
1618import androidx.credentials.exceptions.GetCredentialException
17- import com.google.android.gms.fido.Fido
18- import com.google.android.gms.fido.fido2.api.common.AuthenticatorAssertionResponse
19- import com.google.android.gms.fido.fido2.api.common.AuthenticatorAttestationResponse
20- import com.google.android.gms.fido.fido2.api.common.AuthenticatorErrorResponse
21- import com.google.android.gms.fido.fido2.api.common.ErrorCode.ABORT_ERR
22- import com.google.android.gms.fido.fido2.api.common.ErrorCode.TIMEOUT_ERR
23- import com.google.android.gms.fido.fido2.api.common.PublicKeyCredential
24- import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialType
19+ import androidx.credentials.exceptions.CreateCredentialCancellationException
20+ import androidx.credentials.exceptions.CreateCredentialInterruptedException
21+ import androidx.credentials.exceptions.CreateCredentialNoCreateOptionException
22+ import androidx.credentials.exceptions.CreateCredentialProviderConfigurationException
23+ import androidx.credentials.exceptions.CreateCredentialUnknownException
24+ import androidx.credentials.exceptions.GetCredentialCancellationException
25+ import androidx.credentials.exceptions.GetCredentialInterruptedException
26+ import androidx.credentials.exceptions.GetCredentialProviderConfigurationException
27+ import androidx.credentials.exceptions.GetCredentialUnknownException
28+ import androidx.credentials.exceptions.NoCredentialException
29+ import androidx.credentials.exceptions.publickeycredential.CreatePublicKeyCredentialDomException
30+ import androidx.credentials.exceptions.publickeycredential.GetPublicKeyCredentialDomException
2531import com.google.android.libraries.identity.googleid.GetGoogleIdOption
2632import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
2733import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
2834import org.json.JSONObject
35+ import kotlinx.coroutines.Dispatchers
36+ import kotlinx.coroutines.GlobalScope
37+ import kotlinx.coroutines.launch
2938
3039import io.flutter.embedding.engine.plugins.FlutterPlugin
3140import io.flutter.embedding.engine.plugins.activity.ActivityAware
@@ -148,18 +157,11 @@ class DescopePlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
148157 private fun createPasskey (call : MethodCall , res : Result ) {
149158 val context = this .context ? : return res.error(" NULLCONTEXT" , " Context is null" , null )
150159 val options = call.argument<String >(" options" ) ? : return res.error(" MISSINGARGS" , " 'options' is required for createPasskey" , null )
151- performRegister(context, options) { pendingIntent , e ->
160+ performRegister(context, options) { json , e ->
152161 if (e != null ) {
153162 res.error(" FAILED" , e.message, null )
154- } else if (pendingIntent != null ) {
155- activityHelper.startHelperActivity(context, pendingIntent) { code, intent ->
156- try {
157- val json = prepareRegisterResponse(code, intent)
158- res.success(json)
159- } catch (e: Exception ) {
160- res.error(" FAILED" , e.message, null )
161- }
162- }
163+ } else if (json != null ) {
164+ res.success(json)
163165 } else {
164166 res.error(" FAILED" , " Unxepected result when registering passkey" , null )
165167 }
@@ -169,95 +171,69 @@ class DescopePlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
169171 private fun usePasskey (call : MethodCall , res : Result ) {
170172 val context = this .context ? : return res.error(" NULLCONTEXT" , " Context is null" , null )
171173 val options = call.argument<String >(" options" ) ? : return res.error(" MISSINGARGS" , " 'options' is required for usePasskey" , null )
172- performAssertion(context, options) { pendingIntent , e ->
174+ performAssertion(context, options) { json , e ->
173175 if (e != null ) {
174176 res.error(" FAILED" , e.message, null )
175- } else if (pendingIntent != null ) {
176- activityHelper.startHelperActivity(context, pendingIntent) { code, intent ->
177- try {
178- val json = prepareAssertionResponse(code, intent)
179- res.success(json)
180- } catch (e: Exception ) {
181- res.error(" FAILED" , e.message, null )
182- }
183- }
177+ } else if (json != null ) {
178+ res.success(json)
184179 } else {
185- res.error(" FAILED" , " Unxepected result when registering passkey" , null )
180+ res.error(" FAILED" , " Unxepected result when using passkey" , null )
186181 }
187182 }
188183 }
189184
190- private fun performRegister (context : Context , options : String , callback : (PendingIntent ? , Exception ? ) -> Unit ) {
191- val client = Fido .getFido2ApiClient(context)
192- val opts = parsePublicKeyCredentialCreationOptions(convertOptions(options))
193- val task = client.getRegisterPendingIntent(opts)
194- task.addOnSuccessListener { callback(it, null ) }
195- task.addOnFailureListener { callback(null , it) }
196- }
197-
198- private fun performAssertion (context : Context , options : String , callback : (PendingIntent ? , Exception ? ) -> Unit ) {
199- val client = Fido .getFido2ApiClient(context)
200- val opts = parsePublicKeyCredentialRequestOptions(convertOptions(options))
201- val task = client.getSignPendingIntent(opts)
202- task.addOnSuccessListener { callback(it, null ) }
203- task.addOnFailureListener { callback(null , it) }
204- }
205-
206- private fun prepareRegisterResponse (resultCode : Int , intent : Intent ? ): String {
207- val credential = extractCredential(resultCode, intent)
208- val rawId = credential.rawId?.toBase64()
209- val response = credential.response as AuthenticatorAttestationResponse
210- return JSONObject ().apply {
211- put(" id" , rawId)
212- put(" type" , PublicKeyCredentialType .PUBLIC_KEY .toString())
213- put(" rawId" , rawId)
214- put(" response" , JSONObject ().apply {
215- put(" clientDataJson" , response.clientDataJSON.toBase64())
216- put(" attestationObject" , response.attestationObject.toBase64())
217- })
218- }.toString()
219- }
220-
221- private fun prepareAssertionResponse (resultCode : Int , intent : Intent ? ): String {
222- val credential = extractCredential(resultCode, intent)
223- val rawId = credential.rawId?.toBase64()
224- val response = credential.response as AuthenticatorAssertionResponse
225- return JSONObject ().apply {
226- put(" id" , rawId)
227- put(" type" , PublicKeyCredentialType .PUBLIC_KEY .toString())
228- put(" rawId" , rawId)
229- put(" response" , JSONObject ().apply {
230- put(" clientDataJson" , response.clientDataJSON.toBase64())
231- put(" authenticatorData" , response.authenticatorData.toBase64())
232- put(" signature" , response.signature.toBase64())
233- response.userHandle?.let { put(" userHandle" , it.toBase64()) }
234- })
235- }.toString()
236- }
237-
238- private fun extractCredential (resultCode : Int , intent : Intent ? ): PublicKeyCredential {
239- // check general response
240- if (resultCode == RESULT_CANCELED ) throw Exception (" Passkey canceled" )
241- if (intent == null ) throw Exception (" Null intent received from " )
242-
243- // get the credential from the intent extra
244- val credential = try {
245- val byteArray = intent.getByteArrayExtra(" FIDO2_CREDENTIAL_EXTRA" )!!
246- PublicKeyCredential .deserializeFromBytes(byteArray)
247- } catch (e: Exception ) {
248- throw Exception (" Failed to extract credential from intent" )
185+ private fun performRegister (context : Context , options : String , callback : (String? , Exception ? ) -> Unit ) {
186+ val publicKey = convertOptions(options)
187+ val request = CreatePublicKeyCredentialRequest (publicKey)
188+ GlobalScope .launch(Dispatchers .Main ) {
189+ try {
190+ val credentialManager = CredentialManager .create(context)
191+ val result = credentialManager.createCredential(context, request as CreateCredentialRequest ) as CreatePublicKeyCredentialResponse
192+ callback(result.registrationResponseJson, null )
193+ } catch (e: CreateCredentialCancellationException ) {
194+ callback(null , Exception (" Passkey canceled" ))
195+ } catch (e: CreatePublicKeyCredentialDomException ) {
196+ callback(null , Exception (" Error signing registration" ))
197+ } catch (e: CreateCredentialInterruptedException ) {
198+ callback(null , Exception (" Please try again" ))
199+ } catch (e: CreateCredentialProviderConfigurationException ) {
200+ callback(null , Exception (" Application might be improperly configured" ))
201+ } catch (e: CreateCredentialNoCreateOptionException ) {
202+ callback(null , Exception (" No option to create credentials" ))
203+ } catch (e: CreateCredentialUnknownException ) {
204+ callback(null , Exception (" Unknown failure" ))
205+ } catch (e: Exception ) {
206+ callback(null , Exception (" Unexpected failure" ))
207+ }
249208 }
209+ }
250210
251- // check for any logical failures
252- (credential.response as ? AuthenticatorErrorResponse )?.run {
253- when (errorCode) {
254- ABORT_ERR -> throw Exception (" Passkey canceled" )
255- TIMEOUT_ERR -> throw Exception (" The operation timed out" )
256- else -> throw Exception (" Passkey authentication failed (${errorCode.name} : $errorMessage )" )
211+ private fun performAssertion (context : Context , options : String , callback : (String? , Exception ? ) -> Unit ) {
212+ val publicKey = convertOptions(options)
213+ val option = GetPublicKeyCredentialOption (publicKey)
214+ val request = GetCredentialRequest (listOf (option))
215+ GlobalScope .launch(Dispatchers .Main ) {
216+ try {
217+ val credentialManager = CredentialManager .create(context)
218+ val result = credentialManager.getCredential(context, request)
219+ val credential = result.credential as PublicKeyCredential
220+ callback(credential.authenticationResponseJson, null )
221+ } catch (e: NoCredentialException ) {
222+ callback(null , Exception (" No available credentials" ))
223+ } catch (e: GetCredentialCancellationException ) {
224+ callback(null , Exception (" Passkey canceled" ))
225+ } catch (e: GetPublicKeyCredentialDomException ) {
226+ callback(null , Exception (" Error signing assertion" ))
227+ } catch (e: GetCredentialInterruptedException ) {
228+ callback(null , Exception (" Please try again" ))
229+ } catch (e: GetCredentialProviderConfigurationException ) {
230+ callback(null , Exception (" Application might be improperly configured" ))
231+ } catch (e: GetCredentialUnknownException ) {
232+ callback(null , Exception (" Unknown failure" ))
233+ } catch (e: Exception ) {
234+ callback(null , Exception (" Unexpected failure" ))
257235 }
258236 }
259-
260- return credential
261237 }
262238
263239 // Storage
0 commit comments