Skip to content

feat(rtn-passkeys): add android support - 3b #14393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: feat/rtn-passkeys/3a-add-ios-support
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package com.amazonaws.amplify.rtnpasskeys

import androidx.annotation.ChecksSdkIntAtLeast
import androidx.credentials.CreateCredentialResponse
import androidx.credentials.CreatePublicKeyCredentialRequest
import androidx.credentials.CreatePublicKeyCredentialResponse
import androidx.credentials.CredentialManager
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetCredentialResponse
import androidx.credentials.GetPublicKeyCredentialOption
import androidx.credentials.PublicKeyCredential
import androidx.credentials.exceptions.CreateCredentialCancellationException
import androidx.credentials.exceptions.CreateCredentialProviderConfigurationException
import androidx.credentials.exceptions.CreateCredentialUnsupportedException
import androidx.credentials.exceptions.GetCredentialCancellationException
import androidx.credentials.exceptions.GetCredentialProviderConfigurationException
import androidx.credentials.exceptions.GetCredentialUnsupportedException
import androidx.credentials.exceptions.domerrors.DataError
import androidx.credentials.exceptions.domerrors.InvalidStateError
import androidx.credentials.exceptions.domerrors.NotAllowedError
import androidx.credentials.exceptions.publickeycredential.CreatePublicKeyCredentialDomException
import androidx.credentials.exceptions.publickeycredential.GetPublicKeyCredentialDomException

import com.facebook.fbreact.specs.NativeAmplifyRtnPasskeysSpec
import com.facebook.react.bridge.JSONArguments
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.module.annotations.ReactModule
import kotlinx.coroutines.CoroutineDispatcher

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

import org.json.JSONObject

@ReactModule(name = AmplifyRtnPasskeysModule.NAME)
class AmplifyRtnPasskeysModule(
reactContext: ReactApplicationContext,
dispatcher: CoroutineDispatcher = Dispatchers.Default
) :
NativeAmplifyRtnPasskeysSpec(reactContext) {

private val moduleScope = CoroutineScope(dispatcher)

override fun getName(): String {
return NAME
}

companion object {
const val NAME = "AmplifyRtnPasskeys"
}

@ChecksSdkIntAtLeast(api = android.os.Build.VERSION_CODES.P)
override fun getIsPasskeySupported(): Boolean {
// Requires Android SDK >= 28 (PIE)
return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P
}

override fun createPasskey(input: ReadableMap, promise: Promise) {
if (!isPasskeySupported) {
return promise.reject(
"NOT_SUPPORTED",
CreateCredentialUnsupportedException("CreatePasskeyNotSupported")
)
}

val credentialManager = CredentialManager.create(reactApplicationContext.applicationContext)

val requestJson = JSONObject(input.toHashMap()).toString()
val request =
CreatePublicKeyCredentialRequest(requestJson = requestJson)

moduleScope.launch {
try {
val result: CreateCredentialResponse =
credentialManager.createCredential(
context = currentActivity ?: reactApplicationContext,
request = request
)

val publicKeyResult =
result as? CreatePublicKeyCredentialResponse
?: throw Exception("CreatePasskeyFailed")

val jsonObject = JSONObject(publicKeyResult.registrationResponseJson)

promise.resolve(JSONArguments.fromJSONObject(jsonObject))
} catch (e: Exception) {
val errorCode = handlePasskeyFailure(e)
promise.reject(errorCode, e)
}
}
}

override fun getPasskey(input: ReadableMap, promise: Promise) {
if (!isPasskeySupported) {
return promise.reject(
"NOT_SUPPORTED",
GetCredentialUnsupportedException("GetPasskeyNotSupported")
)
}

val credentialManager = CredentialManager.create(reactApplicationContext.applicationContext)

val requestJson = JSONObject(input.toHashMap()).toString()
val options =
GetPublicKeyCredentialOption(requestJson = requestJson)
val request = GetCredentialRequest(credentialOptions = listOf(options))

moduleScope.launch {
try {
val result: GetCredentialResponse =
credentialManager.getCredential(
context = currentActivity ?: reactApplicationContext,
request = request
)

val publicKeyResult =
result.credential as? PublicKeyCredential ?: throw Exception("GetPasskeyFailed")

val jsonObject = JSONObject(publicKeyResult.authenticationResponseJson)

promise.resolve(JSONArguments.fromJSONObject(jsonObject))
} catch (e: Exception) {
val errorCode = handlePasskeyFailure(e)
promise.reject(errorCode, e)
}
}
}

private fun handlePasskeyFailure(e: Exception): String {
return when (e) {
is CreatePublicKeyCredentialDomException -> {
when (e.domError) {
is NotAllowedError -> "CANCELED"
is InvalidStateError -> "DUPLICATE"
is DataError -> "RELYING_PARTY_MISMATCH"
else -> "FAILED"
}
}

is GetPublicKeyCredentialDomException -> {
when (e.domError) {
is NotAllowedError -> "CANCELED"
is DataError -> "RELYING_PARTY_MISMATCH"
else -> "FAILED"
}
}

is CreateCredentialCancellationException,
is GetCredentialCancellationException -> "CANCELED"

is CreateCredentialUnsupportedException,
is CreateCredentialProviderConfigurationException,
is GetCredentialUnsupportedException,
is GetCredentialProviderConfigurationException -> "NOT_SUPPORTED"

else -> "FAILED"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.amazonaws.amplify.rtnpasskeys

import com.facebook.react.BaseReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider

class AmplifyRtnPasskeysPackage : BaseReactPackage() {
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
return if (name == AmplifyRtnPasskeysModule.NAME) {
AmplifyRtnPasskeysModule(reactContext)
} else {
null
}
}

override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
return ReactModuleInfoProvider {
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
moduleInfos[AmplifyRtnPasskeysModule.NAME] = ReactModuleInfo(
AmplifyRtnPasskeysModule.NAME,
AmplifyRtnPasskeysModule.NAME,
false, // canOverrideExistingModule
false, // needsEagerInit
false, // isCxxModule
true // isTurboModule
)
moduleInfos
}
}
}
Loading
Loading