From d9801f9469f042d3fcb08d18b6125176a21a4411 Mon Sep 17 00:00:00 2001 From: Subhankar Maiti Date: Tue, 15 Jul 2025 14:02:02 +0530 Subject: [PATCH 1/3] Refactor Auth0 module from Java to Kotlin - Removed the old Java implementation of A0Auth0Module and A0Auth0Package. - Introduced new Kotlin implementations for A0Auth0Module and A0Auth0Package. - Converted CredentialsParser and LocalAuthenticationOptionsParser from Java to Kotlin. - Updated A0Auth0Spec to Kotlin, maintaining the abstract structure for module methods. - Removed deprecated Java files related to the old architecture. - Ensured compatibility with the new architecture by implementing TurboReactPackage. --- .../java/com/auth0/react/A0Auth0Module.java | 349 ----------------- .../java/com/auth0/react/A0Auth0Module.kt | 353 ++++++++++++++++++ .../java/com/auth0/react/A0Auth0Package.java | 45 --- .../java/com/auth0/react/A0Auth0Package.kt | 37 ++ .../com/auth0/react/CredentialsParser.java | 51 --- .../java/com/auth0/react/CredentialsParser.kt | 47 +++ .../LocalAuthenticationOptionsParser.java | 55 --- .../react/LocalAuthenticationOptionsParser.kt | 50 +++ .../newarch/com/auth0/react/A0Auth0Spec.java | 12 - .../newarch/com/auth0/react/A0Auth0Spec.kt | 5 + .../oldarch/com/auth0/react/A0Auth0Spec.java | 66 ---- .../oldarch/com/auth0/react/A0Auth0Spec.kt | 84 +++++ 12 files changed, 576 insertions(+), 578 deletions(-) delete mode 100644 android/src/main/java/com/auth0/react/A0Auth0Module.java create mode 100644 android/src/main/java/com/auth0/react/A0Auth0Module.kt delete mode 100644 android/src/main/java/com/auth0/react/A0Auth0Package.java create mode 100644 android/src/main/java/com/auth0/react/A0Auth0Package.kt delete mode 100644 android/src/main/java/com/auth0/react/CredentialsParser.java create mode 100644 android/src/main/java/com/auth0/react/CredentialsParser.kt delete mode 100644 android/src/main/java/com/auth0/react/LocalAuthenticationOptionsParser.java create mode 100644 android/src/main/java/com/auth0/react/LocalAuthenticationOptionsParser.kt delete mode 100644 android/src/main/newarch/com/auth0/react/A0Auth0Spec.java create mode 100644 android/src/main/newarch/com/auth0/react/A0Auth0Spec.kt delete mode 100644 android/src/main/oldarch/com/auth0/react/A0Auth0Spec.java create mode 100644 android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt diff --git a/android/src/main/java/com/auth0/react/A0Auth0Module.java b/android/src/main/java/com/auth0/react/A0Auth0Module.java deleted file mode 100644 index 964ab9be..00000000 --- a/android/src/main/java/com/auth0/react/A0Auth0Module.java +++ /dev/null @@ -1,349 +0,0 @@ -package com.auth0.react; - -import android.app.Activity; -import android.content.Intent; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentActivity; - -import com.auth0.android.Auth0; -import com.auth0.android.authentication.AuthenticationException; -import com.auth0.android.authentication.storage.CredentialsManagerException; -import com.auth0.android.authentication.storage.LocalAuthenticationOptions; -import com.auth0.android.authentication.storage.SecureCredentialsManager; -import com.auth0.android.authentication.storage.SharedPreferencesStorage; -import com.auth0.android.provider.WebAuthProvider; -import com.auth0.android.result.Credentials; -import com.facebook.react.bridge.ActivityEventListener; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.UiThreadUtil; -import java.net.MalformedURLException; -import java.net.URL; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -public class A0Auth0Module extends A0Auth0Spec implements ActivityEventListener { - - private final Map ERROR_CODE_MAP = new HashMap<>() {{ - put(CredentialsManagerException.Companion.getINVALID_CREDENTIALS(), "INVALID_CREDENTIALS"); - put(CredentialsManagerException.Companion.getNO_CREDENTIALS(), "NO_CREDENTIALS"); - put(CredentialsManagerException.Companion.getNO_REFRESH_TOKEN(), "NO_REFRESH_TOKEN"); - put(CredentialsManagerException.Companion.getRENEW_FAILED(), "RENEW_FAILED"); - put(CredentialsManagerException.Companion.getSTORE_FAILED(), "STORE_FAILED"); - put(CredentialsManagerException.Companion.getREVOKE_FAILED(), "REVOKE_FAILED"); - put(CredentialsManagerException.Companion.getLARGE_MIN_TTL(), "LARGE_MIN_TTL"); - put(CredentialsManagerException.Companion.getINCOMPATIBLE_DEVICE(), "INCOMPATIBLE_DEVICE"); - put(CredentialsManagerException.Companion.getCRYPTO_EXCEPTION(), "CRYPTO_EXCEPTION"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_NO_ACTIVITY(), "BIOMETRIC_NO_ACTIVITY"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_STATUS_UNKNOWN(), "BIOMETRIC_ERROR_STATUS_UNKNOWN"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_UNSUPPORTED(), "BIOMETRIC_ERROR_UNSUPPORTED"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_HW_UNAVAILABLE(), "BIOMETRIC_ERROR_HW_UNAVAILABLE"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_NONE_ENROLLED(), "BIOMETRIC_ERROR_NONE_ENROLLED"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_NO_HARDWARE(), "BIOMETRIC_ERROR_NO_HARDWARE"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED(), "BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED"); - put(CredentialsManagerException.Companion.getBIOMETRIC_AUTHENTICATION_CHECK_FAILED(), "BIOMETRIC_AUTHENTICATION_CHECK_FAILED"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_DEVICE_CREDENTIAL_NOT_AVAILABLE(), "BIOMETRIC_ERROR_DEVICE_CREDENTIAL_NOT_AVAILABLE"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_STRONG_AND_DEVICE_CREDENTIAL_NOT_AVAILABLE(), "BIOMETRIC_ERROR_STRONG_AND_DEVICE_CREDENTIAL_NOT_AVAILABLE"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL(), "BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_NEGATIVE_BUTTON(), "BIOMETRIC_ERROR_NEGATIVE_BUTTON"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_HW_NOT_PRESENT(), "BIOMETRIC_ERROR_HW_NOT_PRESENT"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_NO_BIOMETRICS(), "BIOMETRIC_ERROR_NO_BIOMETRICS"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_USER_CANCELED(), "BIOMETRIC_ERROR_USER_CANCELED"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_LOCKOUT_PERMANENT(), "BIOMETRIC_ERROR_LOCKOUT_PERMANENT"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_VENDOR(), "BIOMETRIC_ERROR_VENDOR"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_LOCKOUT(), "BIOMETRIC_ERROR_LOCKOUT"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_CANCELED(), "BIOMETRIC_ERROR_CANCELED"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_NO_SPACE(), "BIOMETRIC_ERROR_NO_SPACE"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_TIMEOUT(), "BIOMETRIC_ERROR_TIMEOUT"); - put(CredentialsManagerException.Companion.getBIOMETRIC_ERROR_UNABLE_TO_PROCESS(), "BIOMETRIC_ERROR_UNABLE_TO_PROCESS"); - put(CredentialsManagerException.Companion.getBIOMETRICS_INVALID_USER(), "BIOMETRICS_INVALID_USER"); - put(CredentialsManagerException.Companion.getBIOMETRIC_AUTHENTICATION_FAILED(), "BIOMETRIC_AUTHENTICATION_FAILED"); - put(CredentialsManagerException.Companion.getAPI_ERROR(), "API_ERROR"); - put(CredentialsManagerException.Companion.getNO_NETWORK(), "NO_NETWORK"); - }}; - private static final String CREDENTIAL_MANAGER_ERROR_CODE = "a0.invalid_state.credential_manager_exception"; - private static final String INVALID_DOMAIN_URL_ERROR_CODE = "a0.invalid_domain_url"; - private static final String BIOMETRICS_AUTHENTICATION_ERROR_CODE = "a0.invalid_options_biometrics_authentication"; - private static final int LOCAL_AUTH_REQUEST_CODE = 150; - public static final int UNKNOWN_ERROR_RESULT_CODE = 1405; - - private final ReactApplicationContext reactContext; - private Auth0 auth0; - private SecureCredentialsManager secureCredentialsManager; - private Promise webAuthPromise; - - public A0Auth0Module(ReactApplicationContext reactContext) { - super(reactContext); - this.reactContext = reactContext; - this.reactContext.addActivityEventListener(this); - } - - @ReactMethod - @Override - public void webAuth(String scheme, String redirectUri, @Nullable String state, @Nullable String nonce, @Nullable String audience, @Nullable String scope, @Nullable String connection, @Nullable Double maxAge, @Nullable String organization, @Nullable String invitationUrl, @Nullable Double leeway, @Nullable Boolean ephemeralSession, @Nullable Double safariViewControllerPresentationStyle, @Nullable ReadableMap additionalParameters, Promise promise) { - this.webAuthPromise = promise; - Map cleanedParameters = new HashMap<>(); - - if(additionalParameters != null) { - for (Map.Entry entry : additionalParameters.toHashMap().entrySet()) { - if (entry.getValue() != null) { - cleanedParameters.put(entry.getKey(), entry.getValue().toString()); - } - } - } - WebAuthProvider.Builder builder = WebAuthProvider.login(this.auth0) - .withScheme(scheme); - if (state != null) { - builder.withState(state); - } - if (nonce != null) { - builder.withNonce(nonce); - } - if (audience != null) { - builder.withAudience(audience); - } - if (scope != null) { - builder.withScope(scope); - } - if (connection != null) { - builder.withConnection(connection); - } - if (maxAge != null && maxAge.intValue() != 0) { - builder.withMaxAge(maxAge.intValue()); - } - if (organization != null) { - builder.withOrganization(organization); - } - if (invitationUrl != null) { - builder.withInvitationUrl(invitationUrl); - } - if (leeway != null && leeway.intValue() != 0) { - builder.withIdTokenVerificationLeeway(leeway.intValue()); - } - if (redirectUri != null) { - builder.withRedirectUri(redirectUri); - } - builder.withParameters(cleanedParameters); - builder.start(reactContext.getCurrentActivity(), - new com.auth0.android.callback.Callback() { - @Override - public void onSuccess(Credentials result) { - ReadableMap map = CredentialsParser.toMap(result); - promise.resolve(map); - webAuthPromise = null; - } - - @Override - public void onFailure(@NonNull AuthenticationException error) { - handleError(error, promise); - webAuthPromise = null; - } - }); - } - - - @ReactMethod - public void getBundleIdentifier(Promise promise) { - String packageName = reactContext.getApplicationInfo().packageName; - promise.resolve(packageName); - } - - @ReactMethod - public void initializeAuth0WithConfiguration(String clientId, String domain, ReadableMap localAuthenticationOptions, Promise promise) { - this.auth0 = Auth0.getInstance(clientId, domain); - if (localAuthenticationOptions != null) { - Activity activity = getCurrentActivity(); - if (activity instanceof FragmentActivity) { - try { - LocalAuthenticationOptions localAuthOptions = LocalAuthenticationOptionsParser.fromMap(localAuthenticationOptions); - this.secureCredentialsManager = new SecureCredentialsManager( - reactContext, - auth0, - new SharedPreferencesStorage(reactContext), - (FragmentActivity) activity, - localAuthOptions); - promise.resolve(true); - return; - } catch (Exception e) { - this.secureCredentialsManager = getSecureCredentialsManagerWithoutBiometrics(); - promise.reject(BIOMETRICS_AUTHENTICATION_ERROR_CODE, "Failed to parse the Local Authentication Options, hence proceeding without Biometrics Authentication for handling Credentials"); - return; - } - } else { - this.secureCredentialsManager = getSecureCredentialsManagerWithoutBiometrics(); - promise.reject(BIOMETRICS_AUTHENTICATION_ERROR_CODE, "Biometrics Authentication for Handling Credentials are supported only on FragmentActivity, since a different activity is supplied, proceeding without it"); - return; - } - } - this.secureCredentialsManager = getSecureCredentialsManagerWithoutBiometrics(); - promise.resolve(true); - } - - private @NonNull SecureCredentialsManager getSecureCredentialsManagerWithoutBiometrics() { - return new SecureCredentialsManager( - reactContext, - auth0, - new SharedPreferencesStorage(reactContext)); - } - - @ReactMethod - public void hasValidAuth0InstanceWithConfiguration(String clientId, String domain, Promise promise) { - if(this.auth0 == null) { - promise.resolve(false); - return; - } - String currentDomain; - try { - URL domainUrl = new URL(this.auth0.getDomainUrl()); - currentDomain = domainUrl.getHost(); - } catch (MalformedURLException e) { - promise.reject(INVALID_DOMAIN_URL_ERROR_CODE, "Invalid domain URL", e); - return; - } - promise.resolve(this.auth0.getClientId().equals(clientId) && currentDomain.equals(domain)); - } - - @ReactMethod - public void getCredentials(String scope, double minTtl, ReadableMap parameters, boolean forceRefresh, - Promise promise) { - Map cleanedParameters = new HashMap<>(); - for (Map.Entry entry : parameters.toHashMap().entrySet()) { - if (entry.getValue() != null) { - cleanedParameters.put(entry.getKey(), entry.getValue().toString()); - } - } - - UiThreadUtil.runOnUiThread(() -> secureCredentialsManager.getCredentials(scope, (int) minTtl, cleanedParameters, forceRefresh, - new com.auth0.android.callback.Callback() { - @Override - public void onSuccess(Credentials credentials) { - ReadableMap map = CredentialsParser.toMap(credentials); - promise.resolve(map); - } - - @Override - public void onFailure(@NonNull CredentialsManagerException e) { - String errorCode = deduceErrorCode(e); - promise.reject(errorCode, e.getMessage(), e); - } - })); - } - - private String deduceErrorCode(@NonNull CredentialsManagerException e) { - return ERROR_CODE_MAP.getOrDefault(e, CREDENTIAL_MANAGER_ERROR_CODE); - } - - @ReactMethod - public void saveCredentials(ReadableMap credentials, Promise promise) { - try { - this.secureCredentialsManager.saveCredentials(CredentialsParser.fromMap(credentials)); - promise.resolve(true); - } catch (CredentialsManagerException e) { - String errorCode = deduceErrorCode(e); - promise.reject(errorCode, e.getMessage(), e); - } - } - - @ReactMethod - public void clearCredentials(Promise promise) { - this.secureCredentialsManager.clearCredentials(); - promise.resolve(true); - } - - @ReactMethod - public void hasValidCredentials(double minTtl, Promise promise) { - promise.resolve(this.secureCredentialsManager.hasValidCredentials((long) minTtl)); - } - - @Override - public Map getConstants() { - final Map constants = new HashMap<>(); - constants.put("bundleIdentifier", reactContext.getApplicationInfo().packageName); - return constants; - } - - @NonNull - @Override - public String getName() { - return NAME; - } - - @ReactMethod - public void webAuthLogout(String scheme, boolean federated, String redirectUri, Promise promise) { - WebAuthProvider.LogoutBuilder builder = WebAuthProvider.logout(this.auth0) - .withScheme(scheme); - if (federated) { - builder.withFederated(); - } - if (redirectUri != null) { - builder.withReturnToUrl(redirectUri); - } - builder.start(reactContext.getCurrentActivity(), - new com.auth0.android.callback.Callback() { - @Override - public void onSuccess(Void credentials) { - promise.resolve(true); - } - - @Override - public void onFailure(AuthenticationException e) { - handleError(e, promise); - } - }); - } - - @Override - public void resumeWebAuth(String url, Promise promise) { - // dummy function implementation, as this is only needed in iOS - promise.resolve(true); - } - - @Override - public void cancelWebAuth(Promise promise) { - // dummy function implementation, as this is only needed in iOS - promise.resolve(true); - } - - private void handleError(AuthenticationException error, Promise promise) { - if (error.isBrowserAppNotAvailable()) { - promise.reject("a0.browser_not_available", "No Browser application is installed.", error); - return; - } - if (error.isCanceled()) { - promise.reject("a0.session.user_cancelled", "User cancelled the Auth", error); - return; - } - if (error.isNetworkError()) { - promise.reject("a0.network_error", "Network error", error); - return; - } - if (error.isIdTokenValidationError()) { - promise.reject("a0.session.invalid_idtoken", "Error validating ID Token", error); - return; - } - String separator = error.getMessage().endsWith(".") ? "" : "."; - promise.reject(error.getCode(), error.getMessage() + separator + " CAUSE: " + error.getDescription(), error); - } - - @Override - public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { - // No-op - } - - @Override - public void onNewIntent(Intent intent) { - if (webAuthPromise != null) { - webAuthPromise.reject("a0.session.browser_terminated", - "The browser window was closed by a new instance of the application"); - webAuthPromise = null; - } - } - - public static final String NAME = "A0Auth0"; -} \ No newline at end of file diff --git a/android/src/main/java/com/auth0/react/A0Auth0Module.kt b/android/src/main/java/com/auth0/react/A0Auth0Module.kt new file mode 100644 index 00000000..7039a982 --- /dev/null +++ b/android/src/main/java/com/auth0/react/A0Auth0Module.kt @@ -0,0 +1,353 @@ +package com.auth0.react + +import android.app.Activity +import android.content.Intent +import androidx.fragment.app.FragmentActivity +import com.auth0.android.Auth0 +import com.auth0.android.authentication.AuthenticationException +import com.auth0.android.authentication.storage.CredentialsManagerException +import com.auth0.android.authentication.storage.LocalAuthenticationOptions +import com.auth0.android.authentication.storage.SecureCredentialsManager +import com.auth0.android.authentication.storage.SharedPreferencesStorage +import com.auth0.android.provider.WebAuthProvider +import com.auth0.android.result.Credentials +import com.facebook.react.bridge.ActivityEventListener +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.UiThreadUtil +import java.net.MalformedURLException +import java.net.URL + +class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0Spec(reactContext), ActivityEventListener { + + companion object { + const val NAME = "A0Auth0" + private const val CREDENTIAL_MANAGER_ERROR_CODE = "a0.invalid_state.credential_manager_exception" + private const val INVALID_DOMAIN_URL_ERROR_CODE = "a0.invalid_domain_url" + private const val BIOMETRICS_AUTHENTICATION_ERROR_CODE = "a0.invalid_options_biometrics_authentication" + private const val LOCAL_AUTH_REQUEST_CODE = 150 + const val UNKNOWN_ERROR_RESULT_CODE = 1405 + } + + private val errorCodeMap = mapOf( + CredentialsManagerException.INVALID_CREDENTIALS to "INVALID_CREDENTIALS", + CredentialsManagerException.NO_CREDENTIALS to "NO_CREDENTIALS", + CredentialsManagerException.NO_REFRESH_TOKEN to "NO_REFRESH_TOKEN", + CredentialsManagerException.RENEW_FAILED to "RENEW_FAILED", + CredentialsManagerException.STORE_FAILED to "STORE_FAILED", + CredentialsManagerException.REVOKE_FAILED to "REVOKE_FAILED", + CredentialsManagerException.LARGE_MIN_TTL to "LARGE_MIN_TTL", + CredentialsManagerException.INCOMPATIBLE_DEVICE to "INCOMPATIBLE_DEVICE", + CredentialsManagerException.CRYPTO_EXCEPTION to "CRYPTO_EXCEPTION", + CredentialsManagerException.BIOMETRIC_ERROR_NO_ACTIVITY to "BIOMETRIC_NO_ACTIVITY", + CredentialsManagerException.BIOMETRIC_ERROR_STATUS_UNKNOWN to "BIOMETRIC_ERROR_STATUS_UNKNOWN", + CredentialsManagerException.BIOMETRIC_ERROR_UNSUPPORTED to "BIOMETRIC_ERROR_UNSUPPORTED", + CredentialsManagerException.BIOMETRIC_ERROR_HW_UNAVAILABLE to "BIOMETRIC_ERROR_HW_UNAVAILABLE", + CredentialsManagerException.BIOMETRIC_ERROR_NONE_ENROLLED to "BIOMETRIC_ERROR_NONE_ENROLLED", + CredentialsManagerException.BIOMETRIC_ERROR_NO_HARDWARE to "BIOMETRIC_ERROR_NO_HARDWARE", + CredentialsManagerException.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED to "BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED", + CredentialsManagerException.BIOMETRIC_AUTHENTICATION_CHECK_FAILED to "BIOMETRIC_AUTHENTICATION_CHECK_FAILED", + CredentialsManagerException.BIOMETRIC_ERROR_DEVICE_CREDENTIAL_NOT_AVAILABLE to "BIOMETRIC_ERROR_DEVICE_CREDENTIAL_NOT_AVAILABLE", + CredentialsManagerException.BIOMETRIC_ERROR_STRONG_AND_DEVICE_CREDENTIAL_NOT_AVAILABLE to "BIOMETRIC_ERROR_STRONG_AND_DEVICE_CREDENTIAL_NOT_AVAILABLE", + CredentialsManagerException.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL to "BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL", + CredentialsManagerException.BIOMETRIC_ERROR_NEGATIVE_BUTTON to "BIOMETRIC_ERROR_NEGATIVE_BUTTON", + CredentialsManagerException.BIOMETRIC_ERROR_HW_NOT_PRESENT to "BIOMETRIC_ERROR_HW_NOT_PRESENT", + CredentialsManagerException.BIOMETRIC_ERROR_NO_BIOMETRICS to "BIOMETRIC_ERROR_NO_BIOMETRICS", + CredentialsManagerException.BIOMETRIC_ERROR_USER_CANCELED to "BIOMETRIC_ERROR_USER_CANCELED", + CredentialsManagerException.BIOMETRIC_ERROR_LOCKOUT_PERMANENT to "BIOMETRIC_ERROR_LOCKOUT_PERMANENT", + CredentialsManagerException.BIOMETRIC_ERROR_VENDOR to "BIOMETRIC_ERROR_VENDOR", + CredentialsManagerException.BIOMETRIC_ERROR_LOCKOUT to "BIOMETRIC_ERROR_LOCKOUT", + CredentialsManagerException.BIOMETRIC_ERROR_CANCELED to "BIOMETRIC_ERROR_CANCELED", + CredentialsManagerException.BIOMETRIC_ERROR_NO_SPACE to "BIOMETRIC_ERROR_NO_SPACE", + CredentialsManagerException.BIOMETRIC_ERROR_TIMEOUT to "BIOMETRIC_ERROR_TIMEOUT", + CredentialsManagerException.BIOMETRIC_ERROR_UNABLE_TO_PROCESS to "BIOMETRIC_ERROR_UNABLE_TO_PROCESS", + CredentialsManagerException.BIOMETRICS_INVALID_USER to "BIOMETRICS_INVALID_USER", + CredentialsManagerException.BIOMETRIC_AUTHENTICATION_FAILED to "BIOMETRIC_AUTHENTICATION_FAILED", + CredentialsManagerException.API_ERROR to "API_ERROR", + CredentialsManagerException.NO_NETWORK to "NO_NETWORK" + ) + + private var auth0: Auth0? = null + private lateinit var secureCredentialsManager: SecureCredentialsManager + private var webAuthPromise: Promise? = null + + init { + reactContext.addActivityEventListener(this) + } + + @ReactMethod + override fun webAuth( + scheme: String, + redirectUri: String?, + state: String?, + nonce: String?, + audience: String?, + scope: String?, + connection: String?, + maxAge: Double?, + organization: String?, + invitationUrl: String?, + leeway: Double?, + ephemeralSession: Boolean?, + safariViewControllerPresentationStyle: Double?, + additionalParameters: ReadableMap?, + promise: Promise + ) { + webAuthPromise = promise + val cleanedParameters = mutableMapOf() + + additionalParameters?.let { params -> + params.toHashMap().forEach { (key, value) -> + value?.let { cleanedParameters[key] = it.toString() } + } + } + + val builder = WebAuthProvider.login(auth0!!).withScheme(scheme) + + state?.let { builder.withState(it) } + nonce?.let { builder.withNonce(it) } + audience?.let { builder.withAudience(it) } + scope?.let { builder.withScope(it) } + connection?.let { builder.withConnection(it) } + maxAge?.let { if (it.toInt() != 0) builder.withMaxAge(it.toInt()) } + organization?.let { builder.withOrganization(it) } + invitationUrl?.let { builder.withInvitationUrl(it) } + leeway?.let { if (it.toInt() != 0) builder.withIdTokenVerificationLeeway(it.toInt()) } + redirectUri?.let { builder.withRedirectUri(it) } + + builder.withParameters(cleanedParameters) + builder.start(reactContext.currentActivity, + object : com.auth0.android.callback.Callback { + override fun onSuccess(result: Credentials) { + val map = CredentialsParser.toMap(result) + promise.resolve(map) + webAuthPromise = null + } + + override fun onFailure(error: AuthenticationException) { + handleError(error, promise) + webAuthPromise = null + } + }) + } + + @ReactMethod + override fun getBundleIdentifier(promise: Promise) { + val packageName = reactContext.applicationInfo.packageName + promise.resolve(packageName) + } + + @ReactMethod + override fun initializeAuth0WithConfiguration( + clientId: String, + domain: String, + localAuthenticationOptions: ReadableMap?, + promise: Promise + ) { + auth0 = Auth0.getInstance(clientId, domain) + + localAuthenticationOptions?.let { options -> + val activity = currentActivity + if (activity is FragmentActivity) { + try { + val localAuthOptions = LocalAuthenticationOptionsParser.fromMap(options) + secureCredentialsManager = SecureCredentialsManager( + reactContext, + auth0!!, + SharedPreferencesStorage(reactContext), + activity, + localAuthOptions + ) + promise.resolve(true) + return + } catch (e: Exception) { + secureCredentialsManager = getSecureCredentialsManagerWithoutBiometrics() + promise.reject( + BIOMETRICS_AUTHENTICATION_ERROR_CODE, + "Failed to parse the Local Authentication Options, hence proceeding without Biometrics Authentication for handling Credentials" + ) + return + } + } else { + secureCredentialsManager = getSecureCredentialsManagerWithoutBiometrics() + promise.reject( + BIOMETRICS_AUTHENTICATION_ERROR_CODE, + "Biometrics Authentication for Handling Credentials are supported only on FragmentActivity, since a different activity is supplied, proceeding without it" + ) + return + } + } + + secureCredentialsManager = getSecureCredentialsManagerWithoutBiometrics() + promise.resolve(true) + } + + private fun getSecureCredentialsManagerWithoutBiometrics(): SecureCredentialsManager { + return SecureCredentialsManager( + reactContext, + auth0!!, + SharedPreferencesStorage(reactContext) + ) + } + + @ReactMethod + override fun hasValidAuth0InstanceWithConfiguration(clientId: String, domain: String, promise: Promise) { + if (auth0 == null) { + promise.resolve(false) + return + } + + val currentDomain: String + try { + val domainUrl = URL(auth0!!.domainUrl.toString()) + currentDomain = domainUrl.host + } catch (e: MalformedURLException) { + promise.reject(INVALID_DOMAIN_URL_ERROR_CODE, "Invalid domain URL", e) + return + } + + promise.resolve(auth0!!.clientId == clientId && currentDomain == domain) + } + + @ReactMethod + override fun getCredentials( + scope: String?, + minTtl: Double, + parameters: ReadableMap, + forceRefresh: Boolean, + promise: Promise + ) { + val cleanedParameters = mutableMapOf() + parameters.toHashMap().forEach { (key, value) -> + value?.let { cleanedParameters[key] = it.toString() } + } + + UiThreadUtil.runOnUiThread { + secureCredentialsManager.getCredentials( + scope, + minTtl.toInt(), + cleanedParameters, + forceRefresh, + object : com.auth0.android.callback.Callback { + override fun onSuccess(credentials: Credentials) { + val map = CredentialsParser.toMap(credentials) + promise.resolve(map) + } + + override fun onFailure(e: CredentialsManagerException) { + val errorCode = deduceErrorCode(e) + promise.reject(errorCode, e.message, e) + } + } + ) + } + } + + private fun deduceErrorCode(e: CredentialsManagerException): String { + return errorCodeMap[e] ?: CREDENTIAL_MANAGER_ERROR_CODE + } + + @ReactMethod + override fun saveCredentials(credentials: ReadableMap, promise: Promise) { + try { + secureCredentialsManager.saveCredentials(CredentialsParser.fromMap(credentials)) + promise.resolve(true) + } catch (e: CredentialsManagerException) { + val errorCode = deduceErrorCode(e) + promise.reject(errorCode, e.message, e) + } + } + + @ReactMethod + override fun clearCredentials(promise: Promise) { + secureCredentialsManager.clearCredentials() + promise.resolve(true) + } + + @ReactMethod + override fun hasValidCredentials(minTtl: Double, promise: Promise) { + promise.resolve(secureCredentialsManager.hasValidCredentials(minTtl.toLong())) + } + + override fun getConstants(): Map { + return mapOf("bundleIdentifier" to reactContext.applicationInfo.packageName) + } + + override fun getName(): String = NAME + + @ReactMethod + override fun webAuthLogout(scheme: String, federated: Boolean, redirectUri: String?, promise: Promise) { + val builder = WebAuthProvider.logout(auth0!!).withScheme(scheme) + + if (federated) { + builder.withFederated() + } + + redirectUri?.let { builder.withReturnToUrl(it) } + + builder.start(reactContext.currentActivity as FragmentActivity, + object : com.auth0.android.callback.Callback { + override fun onSuccess(result: Void?) { + promise.resolve(true) + } + + override fun onFailure(e: AuthenticationException) { + handleError(e, promise) + } + }) + } + + override fun resumeWebAuth(url: String, promise: Promise) { + // dummy function implementation, as this is only needed in iOS + promise.resolve(true) + } + + override fun cancelWebAuth(promise: Promise) { + // dummy function implementation, as this is only needed in iOS + promise.resolve(true) + } + + private fun handleError(error: AuthenticationException, promise: Promise) { + when { + error.isBrowserAppNotAvailable -> { + promise.reject("a0.browser_not_available", "No Browser application is installed.", error) + return + } + error.isCanceled -> { + promise.reject("a0.session.user_cancelled", "User cancelled the Auth", error) + return + } + error.isNetworkError -> { + promise.reject("a0.network_error", "Network error", error) + return + } + error.isIdTokenValidationError -> { + promise.reject("a0.session.invalid_idtoken", "Error validating ID Token", error) + return + } + } + + val separator = if (error.message?.endsWith(".") == true) "" else "." + promise.reject( + error.getCode(), + "${error.message}$separator CAUSE: ${error.getDescription()}", + error + ) + } + + override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) { + // No-op + } + + override fun onNewIntent(intent: Intent) { + webAuthPromise?.let { promise -> + promise.reject( + "a0.session.browser_terminated", + "The browser window was closed by a new instance of the application" + ) + webAuthPromise = null + } + } +} \ No newline at end of file diff --git a/android/src/main/java/com/auth0/react/A0Auth0Package.java b/android/src/main/java/com/auth0/react/A0Auth0Package.java deleted file mode 100644 index eaad4832..00000000 --- a/android/src/main/java/com/auth0/react/A0Auth0Package.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.auth0.react; - -import androidx.annotation.NonNull; - -import com.facebook.react.TurboReactPackage; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.module.model.ReactModuleInfoProvider; -import com.facebook.react.module.model.ReactModuleInfo; - -import java.util.HashMap; -import java.util.Map; - -public class A0Auth0Package extends TurboReactPackage { - @Override - public NativeModule getModule(String name, ReactApplicationContext reactContext) { - if (name.equals(A0Auth0Module.NAME)) { - return new A0Auth0Module(reactContext); - } else { - return null; - } - } - - @NonNull - @Override - public ReactModuleInfoProvider getReactModuleInfoProvider() { - return () -> { - Map moduleInfos = new HashMap<>(); - boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; - moduleInfos.put( - A0Auth0Module.NAME, - new ReactModuleInfo( - A0Auth0Module.NAME, - A0Auth0Module.NAME, - false, // canOverrideExistingModule - false, // needsEagerInit - true, // hasConstants - false, // isCxxModule - isTurboModule // isTurboModule - ) - ); - return moduleInfos; - }; - } -} \ No newline at end of file diff --git a/android/src/main/java/com/auth0/react/A0Auth0Package.kt b/android/src/main/java/com/auth0/react/A0Auth0Package.kt new file mode 100644 index 00000000..7e47e03e --- /dev/null +++ b/android/src/main/java/com/auth0/react/A0Auth0Package.kt @@ -0,0 +1,37 @@ +package com.auth0.react + +import com.facebook.react.TurboReactPackage +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.NativeModule +import com.facebook.react.module.model.ReactModuleInfoProvider +import com.facebook.react.module.model.ReactModuleInfo + +class A0Auth0Package : TurboReactPackage() { + + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { + return if (name == A0Auth0Module.NAME) { + A0Auth0Module(reactContext) + } else { + null + } + } + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { + return ReactModuleInfoProvider { + val moduleInfos = mutableMapOf() + val isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + + moduleInfos[A0Auth0Module.NAME] = ReactModuleInfo( + A0Auth0Module.NAME, + A0Auth0Module.NAME, + false, // canOverrideExistingModule + false, // needsEagerInit + true, // hasConstants + false, // isCxxModule + isTurboModule // isTurboModule + ) + + moduleInfos + } + } +} \ No newline at end of file diff --git a/android/src/main/java/com/auth0/react/CredentialsParser.java b/android/src/main/java/com/auth0/react/CredentialsParser.java deleted file mode 100644 index fdcfb497..00000000 --- a/android/src/main/java/com/auth0/react/CredentialsParser.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.auth0.react; - -import com.auth0.android.authentication.storage.CredentialsManagerException; -import com.auth0.android.result.Credentials; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableNativeMap; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -public class CredentialsParser { - - private static final String ACCESS_TOKEN_KEY = "accessToken"; - private static final String ID_TOKEN_KEY = "idToken"; - private static final String EXPIRES_AT_KEY = "expiresAt"; - private static final String SCOPE = "scope"; - private static final String REFRESH_TOKEN_KEY = "refreshToken"; - private static final String TOKEN_TYPE_KEY = "tokenType"; - private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; - - public static ReadableMap toMap(Credentials credentials) { - WritableNativeMap map = new WritableNativeMap(); - map.putString(ACCESS_TOKEN_KEY, credentials.getAccessToken()); - map.putDouble(EXPIRES_AT_KEY, credentials.getExpiresAt().getTime() / 1000); - map.putString(ID_TOKEN_KEY, credentials.getIdToken()); - map.putString(SCOPE, credentials.getScope()); - map.putString(REFRESH_TOKEN_KEY, credentials.getRefreshToken()); - map.putString(TOKEN_TYPE_KEY, credentials.getType()); - return map; - } - - public static Credentials fromMap(ReadableMap map) { - String idToken = map.getString(ID_TOKEN_KEY); - String accessToken = map.getString(ACCESS_TOKEN_KEY); - String tokenType = map.getString(TOKEN_TYPE_KEY); - String refreshToken = map.getString(REFRESH_TOKEN_KEY); - String scope = map.getString(SCOPE); - Double expiresAtUnix = map.getDouble(EXPIRES_AT_KEY); - Date expiresAt = new Date(expiresAtUnix.longValue() * 1000); - return new Credentials( - idToken, - accessToken, - tokenType, - refreshToken, - expiresAt, - scope - ); - } -} diff --git a/android/src/main/java/com/auth0/react/CredentialsParser.kt b/android/src/main/java/com/auth0/react/CredentialsParser.kt new file mode 100644 index 00000000..ab2af622 --- /dev/null +++ b/android/src/main/java/com/auth0/react/CredentialsParser.kt @@ -0,0 +1,47 @@ +package com.auth0.react + +import com.auth0.android.result.Credentials +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.WritableNativeMap +import java.util.Date + +object CredentialsParser { + + private const val ACCESS_TOKEN_KEY = "accessToken" + private const val ID_TOKEN_KEY = "idToken" + private const val EXPIRES_AT_KEY = "expiresAt" + private const val SCOPE = "scope" + private const val REFRESH_TOKEN_KEY = "refreshToken" + private const val TOKEN_TYPE_KEY = "tokenType" + private const val DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + + fun toMap(credentials: Credentials): ReadableMap { + val map = WritableNativeMap() + map.putString(ACCESS_TOKEN_KEY, credentials.accessToken) + map.putDouble(EXPIRES_AT_KEY, credentials.expiresAt.time / 1000.0) + map.putString(ID_TOKEN_KEY, credentials.idToken) + map.putString(SCOPE, credentials.scope) + map.putString(REFRESH_TOKEN_KEY, credentials.refreshToken) + map.putString(TOKEN_TYPE_KEY, credentials.type) + return map + } + + fun fromMap(map: ReadableMap): Credentials { + val idToken = map.getString(ID_TOKEN_KEY) ?: "" + val accessToken = map.getString(ACCESS_TOKEN_KEY) ?: "" + val tokenType = map.getString(TOKEN_TYPE_KEY) ?: "" + val refreshToken = map.getString(REFRESH_TOKEN_KEY) + val scope = map.getString(SCOPE) + val expiresAtUnix = map.getDouble(EXPIRES_AT_KEY) + val expiresAt = Date((expiresAtUnix * 1000).toLong()) + + return Credentials( + idToken, + accessToken, + tokenType, + refreshToken, + expiresAt, + scope + ) + } +} diff --git a/android/src/main/java/com/auth0/react/LocalAuthenticationOptionsParser.java b/android/src/main/java/com/auth0/react/LocalAuthenticationOptionsParser.java deleted file mode 100644 index 32771fdd..00000000 --- a/android/src/main/java/com/auth0/react/LocalAuthenticationOptionsParser.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.auth0.react; - -import com.auth0.android.authentication.storage.AuthenticationLevel; -import com.auth0.android.authentication.storage.LocalAuthenticationOptions; -import com.facebook.react.bridge.ReadableMap; - -public class LocalAuthenticationOptionsParser { - private static final String TITLE_KEY = "title"; - private static final String SUBTITLE_KEY = "subtitle"; - private static final String DESCRIPTION_KEY = "description"; - private static final String CANCEL_TITLE_KEY = "cancel"; - private static final String AUTHENTICATION_LEVEL_KEY = "authenticationLevel"; - private static final String DEVICE_CREDENTIAL_FALLBACK_KEY = "deviceCredentialFallback"; - - - public static LocalAuthenticationOptions fromMap(ReadableMap map) { - String title = map.getString(TITLE_KEY); - if (title == null) { - throw new IllegalArgumentException("LocalAuthenticationOptionsParser: fromMap: The 'title' field is required"); - } - String subtitle = map.getString(SUBTITLE_KEY); - String description = map.getString(DESCRIPTION_KEY); - String cancelTitle = map.getString(CANCEL_TITLE_KEY); - - boolean deviceCredentialFallback = map.getBoolean(DEVICE_CREDENTIAL_FALLBACK_KEY); - LocalAuthenticationOptions.Builder builder = new LocalAuthenticationOptions.Builder() - .setTitle(title) - .setSubTitle(subtitle) - .setDescription(description) - .setDeviceCredentialFallback(deviceCredentialFallback); - - if (!map.hasKey(AUTHENTICATION_LEVEL_KEY)) { - builder.setAuthenticationLevel(AuthenticationLevel.STRONG); - } else { - AuthenticationLevel level = getAuthenticationLevelFromInt(map.getInt(AUTHENTICATION_LEVEL_KEY)); - builder.setAuthenticationLevel(level); - } - if (cancelTitle != null) { - builder.setNegativeButtonText(cancelTitle); - } - return builder.build(); - } - - static AuthenticationLevel getAuthenticationLevelFromInt(int level) { - switch (level) { - case 0: - return AuthenticationLevel.STRONG; - case 1: - return AuthenticationLevel.WEAK; - default: - return AuthenticationLevel.DEVICE_CREDENTIAL; - } - } -} - diff --git a/android/src/main/java/com/auth0/react/LocalAuthenticationOptionsParser.kt b/android/src/main/java/com/auth0/react/LocalAuthenticationOptionsParser.kt new file mode 100644 index 00000000..5c7299fb --- /dev/null +++ b/android/src/main/java/com/auth0/react/LocalAuthenticationOptionsParser.kt @@ -0,0 +1,50 @@ +package com.auth0.react + +import com.auth0.android.authentication.storage.AuthenticationLevel +import com.auth0.android.authentication.storage.LocalAuthenticationOptions +import com.facebook.react.bridge.ReadableMap + +object LocalAuthenticationOptionsParser { + private const val TITLE_KEY = "title" + private const val SUBTITLE_KEY = "subtitle" + private const val DESCRIPTION_KEY = "description" + private const val CANCEL_TITLE_KEY = "cancel" + private const val AUTHENTICATION_LEVEL_KEY = "authenticationLevel" + private const val DEVICE_CREDENTIAL_FALLBACK_KEY = "deviceCredentialFallback" + + fun fromMap(map: ReadableMap): LocalAuthenticationOptions { + val title = map.getString(TITLE_KEY) + ?: throw IllegalArgumentException("LocalAuthenticationOptionsParser: fromMap: The 'title' field is required") + + val subtitle = map.getString(SUBTITLE_KEY) + val description = map.getString(DESCRIPTION_KEY) + val cancelTitle = map.getString(CANCEL_TITLE_KEY) + val deviceCredentialFallback = map.getBoolean(DEVICE_CREDENTIAL_FALLBACK_KEY) + + val builder = LocalAuthenticationOptions.Builder() + .setTitle(title) + .setSubTitle(subtitle) + .setDescription(description) + .setDeviceCredentialFallback(deviceCredentialFallback) + + if (!map.hasKey(AUTHENTICATION_LEVEL_KEY)) { + builder.setAuthenticationLevel(AuthenticationLevel.STRONG) + } else { + val level = getAuthenticationLevelFromInt(map.getInt(AUTHENTICATION_LEVEL_KEY)) + builder.setAuthenticationLevel(level) + } + + cancelTitle?.let { builder.setNegativeButtonText(it) } + + return builder.build() + } + + private fun getAuthenticationLevelFromInt(level: Int): AuthenticationLevel { + return when (level) { + 0 -> AuthenticationLevel.STRONG + 1 -> AuthenticationLevel.WEAK + else -> AuthenticationLevel.DEVICE_CREDENTIAL + } + } +} + diff --git a/android/src/main/newarch/com/auth0/react/A0Auth0Spec.java b/android/src/main/newarch/com/auth0/react/A0Auth0Spec.java deleted file mode 100644 index 4844b82f..00000000 --- a/android/src/main/newarch/com/auth0/react/A0Auth0Spec.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.auth0.react; - -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReadableMap; - -public abstract class A0Auth0Spec extends NativeA0Auth0Spec { - protected A0Auth0Spec(ReactApplicationContext context) { - super(context); - } -} \ No newline at end of file diff --git a/android/src/main/newarch/com/auth0/react/A0Auth0Spec.kt b/android/src/main/newarch/com/auth0/react/A0Auth0Spec.kt new file mode 100644 index 00000000..49b3c4a7 --- /dev/null +++ b/android/src/main/newarch/com/auth0/react/A0Auth0Spec.kt @@ -0,0 +1,5 @@ +package com.auth0.react + +import com.facebook.react.bridge.ReactApplicationContext + +abstract class A0Auth0Spec(context: ReactApplicationContext) : NativeA0Auth0Spec(context) \ No newline at end of file diff --git a/android/src/main/oldarch/com/auth0/react/A0Auth0Spec.java b/android/src/main/oldarch/com/auth0/react/A0Auth0Spec.java deleted file mode 100644 index 036b34d9..00000000 --- a/android/src/main/oldarch/com/auth0/react/A0Auth0Spec.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.auth0.react; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.facebook.common.internal.DoNotStrip; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReadableMap; - -public abstract class A0Auth0Spec extends ReactContextBaseJavaModule { - - protected A0Auth0Spec(ReactApplicationContext context) { - super(context); - } - - @NonNull - @Override - public abstract String getName(); - - @ReactMethod - @DoNotStrip - public abstract void getBundleIdentifier(Promise promise); - - @ReactMethod - @DoNotStrip - public abstract void hasValidAuth0InstanceWithConfiguration(String clientId, String domain, Promise promise); - - @ReactMethod - @DoNotStrip - public abstract void initializeAuth0WithConfiguration(String clientId, String domain, @Nullable ReadableMap localAuthenticationOptions, Promise promise); - - @ReactMethod - @DoNotStrip - public abstract void saveCredentials(ReadableMap credentials, Promise promise); - - @ReactMethod - @DoNotStrip - public abstract void getCredentials(@Nullable String scope, double minTTL, ReadableMap parameters, boolean forceRefresh, Promise promise); - - @ReactMethod - @DoNotStrip - public abstract void hasValidCredentials(double minTTL, Promise promise); - - @ReactMethod - @DoNotStrip - public abstract void clearCredentials(Promise promise); - - @ReactMethod - @DoNotStrip - public abstract void webAuth(String scheme, String redirectUri, @Nullable String state, @Nullable String nonce, @Nullable String audience, @Nullable String scope, @Nullable String connection, @Nullable Double maxAge, @Nullable String organization, @Nullable String invitationUrl, @Nullable Double leeway, @Nullable Boolean ephemeralSession, @Nullable Double safariViewControllerPresentationStyle, @Nullable ReadableMap additionalParameters, Promise promise); - - @ReactMethod - @DoNotStrip - public abstract void webAuthLogout(String scheme, boolean federated, String redirectUri, Promise promise); - - @ReactMethod - @DoNotStrip - public abstract void resumeWebAuth(String url, Promise promise); - - @ReactMethod - @DoNotStrip - public abstract void cancelWebAuth(Promise promise); -} \ No newline at end of file diff --git a/android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt b/android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt new file mode 100644 index 00000000..88021156 --- /dev/null +++ b/android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt @@ -0,0 +1,84 @@ +package com.auth0.react + +import com.facebook.common.internal.DoNotStrip +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.ReadableMap + +abstract class A0Auth0Spec(context: ReactApplicationContext) : ReactContextBaseJavaModule(context) { + + abstract override fun getName(): String + + @ReactMethod + @DoNotStrip + abstract fun getBundleIdentifier(promise: Promise) + + @ReactMethod + @DoNotStrip + abstract fun hasValidAuth0InstanceWithConfiguration(clientId: String, domain: String, promise: Promise) + + @ReactMethod + @DoNotStrip + abstract fun initializeAuth0WithConfiguration( + clientId: String, + domain: String, + localAuthenticationOptions: ReadableMap?, + promise: Promise + ) + + @ReactMethod + @DoNotStrip + abstract fun saveCredentials(credentials: ReadableMap, promise: Promise) + + @ReactMethod + @DoNotStrip + abstract fun getCredentials( + scope: String?, + minTTL: Double, + parameters: ReadableMap, + forceRefresh: Boolean, + promise: Promise + ) + + @ReactMethod + @DoNotStrip + abstract fun hasValidCredentials(minTTL: Double, promise: Promise) + + @ReactMethod + @DoNotStrip + abstract fun clearCredentials(promise: Promise) + + @ReactMethod + @DoNotStrip + abstract fun webAuth( + scheme: String, + redirectUri: String?, + state: String?, + nonce: String?, + audience: String?, + scope: String?, + connection: String?, + maxAge: Double?, + organization: String?, + invitationUrl: String?, + leeway: Double?, + ephemeralSession: Boolean?, + safariViewControllerPresentationStyle: Double?, + additionalParameters: ReadableMap?, + promise: Promise + ) + + @ReactMethod + @DoNotStrip + abstract fun webAuthLogout(scheme: String, federated: Boolean, redirectUri: String?, promise: Promise) + + @ReactMethod + @DoNotStrip + abstract fun resumeWebAuth(url: String, promise: Promise) + + @ReactMethod + @DoNotStrip + abstract fun cancelWebAuth(promise: Promise) +} \ No newline at end of file From ea4e648190ec937f8dd388ef342a04ff0ab69538 Mon Sep 17 00:00:00 2001 From: Subhankar Maiti Date: Wed, 16 Jul 2025 21:42:01 +0530 Subject: [PATCH 2/3] chore: update build.gradle and gradle.properties for improved configuration and dependencies fix(A0Auth0Module): resolve activity casting issues and simplify domain validation --- android/build.gradle | 156 ++++++------------ android/gradle.properties | 5 + .../java/com/auth0/react/A0Auth0Module.kt | 15 +- 3 files changed, 56 insertions(+), 120 deletions(-) create mode 100644 android/gradle.properties diff --git a/android/build.gradle b/android/build.gradle index 8567b186..5c3bd388 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,17 +1,18 @@ buildscript { - ext.safeExtGet = {prop, fallback -> - rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback - } - repositories { - google() - mavenCentral() - } + ext.getExtOrDefault = {name -> + return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['A0Auth0_' + name] + } - dependencies { - // Matches recent template from React Native (0.67) - // https://github.com/facebook/react-native/blob/0.67-stable/template/android/build.gradle#L16 - classpath("com.android.tools.build:gradle:${safeExtGet('gradlePluginVersion', '4.2.2')}") - } + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "com.android.tools.build:gradle:8.7.2" + // noinspection DifferentKotlinGradleVersion + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}" + } } def reactNativeArchitectures() { @@ -23,38 +24,45 @@ def isNewArchitectureEnabled() { return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" } -apply plugin: 'com.android.library' -apply plugin: 'maven-publish' +apply plugin: "com.android.library" +apply plugin: "kotlin-android" if (isNewArchitectureEnabled()) { apply plugin: "com.facebook.react" } -// Matches values in recent template from React Native 0.62 -// https://github.com/facebook/react-native/blob/0.62-stable/template/android/build.gradle#L5-L8 -def DEFAULT_COMPILE_SDK_VERSION = 34 -def DEFAULT_BUILD_TOOLS_VERSION = "28.0.3" -def DEFAULT_MIN_SDK_VERSION = 16 -def DEFAULT_TARGET_SDK_VERSION = 34 +def getExtOrIntegerDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["A0Auth0_" + name]).toInteger() +} android { - compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION) - buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION) - namespace 'com.auth0.react' + namespace "com.auth0.react" + + compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") defaultConfig { - minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION) - targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION) + minSdkVersion getExtOrIntegerDefault("minSdkVersion") + targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()) versionCode 1 versionName "1.0" } - lintOptions { - abortOnError false - } + buildFeatures { buildConfig true } + + buildTypes { + release { + minifyEnabled false + } + } + + lintOptions { + disable "GradleCompatible" + abortOnError false + } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -74,89 +82,21 @@ android { } repositories { - maven { - // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm - // Matches recent template from React Native 0.62 - // https://github.com/facebook/react-native/blob/0.62-stable/template/android/build.gradle#L27 - url "$projectDir/../node_modules/react-native/android" - } - mavenCentral() -} - -dependencies { - implementation "com.facebook.react:react-native:${safeExtGet('reactnativeVersion', '+')}" - implementation "androidx.browser:browser:1.2.0" - implementation 'com.auth0.android:auth0:3.2.1' -} - -def configureReactNativePom(def pom) { - def packageJson = new groovy.json.JsonSlurper().parseText(file('../package.json').text) - - pom.project { - name packageJson.title - artifactId packageJson.name - version = packageJson.version - group = "com.auth0.react" - description packageJson.description - url packageJson.repository.baseUrl - - licenses { - license { - name packageJson.license - url packageJson.repository.baseUrl + '/blob/master/' + packageJson.licenseFilename - distribution 'repo' - } - } - - developers { - developer { - id packageJson.author.username - name packageJson.author.name - } - } - } + mavenCentral() + google() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$projectDir/../node_modules/react-native/android" + } } -afterEvaluate { project -> - - task androidJavadoc(type: Javadoc) { - source = android.sourceSets.main.java.srcDirs - classpath += files(android.bootClasspath) - include '**/*.java' - } - - task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) { - archiveClassifier = 'javadoc' - from androidJavadoc.destinationDir - } - - task androidSourcesJar(type: Jar) { - archiveClassifier = 'sources' - from android.sourceSets.main.java.srcDirs - include '**/*.java' - } - - android.libraryVariants.all { variant -> - def name = variant.name.capitalize() - def javaCompileTask = variant.javaCompileProvider.get() +def kotlin_version = getExtOrDefault("kotlinVersion") - task "jar${name}"(type: Jar, dependsOn: javaCompileTask) { - from javaCompileTask.destinationDir - } - } - - artifacts { - archives androidSourcesJar - archives androidJavadocJar - } - - publishing { - publications { - maven(MavenPublication) { - artifact androidSourcesJar - } - } - } +dependencies { + implementation "com.facebook.react:react-android" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation "androidx.browser:browser:1.2.0" + implementation 'com.auth0.android:auth0:3.2.1' } if (isNewArchitectureEnabled()) { diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 00000000..dad9fbe1 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,5 @@ +A0Auth0_kotlinVersion=2.0.21 +A0Auth0_minSdkVersion=24 +A0Auth0_targetSdkVersion=34 +A0Auth0_compileSdkVersion=35 +A0Auth0_ndkVersion=27.1.12297006 \ No newline at end of file diff --git a/android/src/main/java/com/auth0/react/A0Auth0Module.kt b/android/src/main/java/com/auth0/react/A0Auth0Module.kt index 7039a982..e612bc37 100644 --- a/android/src/main/java/com/auth0/react/A0Auth0Module.kt +++ b/android/src/main/java/com/auth0/react/A0Auth0Module.kt @@ -118,7 +118,7 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0 redirectUri?.let { builder.withRedirectUri(it) } builder.withParameters(cleanedParameters) - builder.start(reactContext.currentActivity, + builder.start(reactContext.currentActivity as Activity, object : com.auth0.android.callback.Callback { override fun onSuccess(result: Credentials) { val map = CredentialsParser.toMap(result) @@ -149,7 +149,7 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0 auth0 = Auth0.getInstance(clientId, domain) localAuthenticationOptions?.let { options -> - val activity = currentActivity + val activity = reactContext.currentActivity if (activity is FragmentActivity) { try { val localAuthOptions = LocalAuthenticationOptionsParser.fromMap(options) @@ -199,16 +199,7 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0 return } - val currentDomain: String - try { - val domainUrl = URL(auth0!!.domainUrl.toString()) - currentDomain = domainUrl.host - } catch (e: MalformedURLException) { - promise.reject(INVALID_DOMAIN_URL_ERROR_CODE, "Invalid domain URL", e) - return - } - - promise.resolve(auth0!!.clientId == clientId && currentDomain == domain) + promise.resolve(auth0!!.clientId == clientId && auth0!!.domain == domain) } @ReactMethod From 83e0cd7c0af729eaad22409fb546d0a3b3c7b407 Mon Sep 17 00:00:00 2001 From: Subhankar Maiti Date: Wed, 23 Jul 2025 00:24:35 +0530 Subject: [PATCH 3/3] feat: refactor A0Auth0Module for improved error handling and code organization --- .../java/com/auth0/react/A0Auth0Module.kt | 78 ++++++++++--------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/android/src/main/java/com/auth0/react/A0Auth0Module.kt b/android/src/main/java/com/auth0/react/A0Auth0Module.kt index e612bc37..2c96f8d5 100644 --- a/android/src/main/java/com/auth0/react/A0Auth0Module.kt +++ b/android/src/main/java/com/auth0/react/A0Auth0Module.kt @@ -24,11 +24,11 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0 companion object { const val NAME = "A0Auth0" + const val UNKNOWN_ERROR_RESULT_CODE = 1405 private const val CREDENTIAL_MANAGER_ERROR_CODE = "a0.invalid_state.credential_manager_exception" private const val INVALID_DOMAIN_URL_ERROR_CODE = "a0.invalid_domain_url" private const val BIOMETRICS_AUTHENTICATION_ERROR_CODE = "a0.invalid_options_biometrics_authentication" private const val LOCAL_AUTH_REQUEST_CODE = 150 - const val UNKNOWN_ERROR_RESULT_CODE = 1405 } private val errorCodeMap = mapOf( @@ -106,16 +106,18 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0 val builder = WebAuthProvider.login(auth0!!).withScheme(scheme) - state?.let { builder.withState(it) } - nonce?.let { builder.withNonce(it) } - audience?.let { builder.withAudience(it) } - scope?.let { builder.withScope(it) } - connection?.let { builder.withConnection(it) } - maxAge?.let { if (it.toInt() != 0) builder.withMaxAge(it.toInt()) } - organization?.let { builder.withOrganization(it) } - invitationUrl?.let { builder.withInvitationUrl(it) } - leeway?.let { if (it.toInt() != 0) builder.withIdTokenVerificationLeeway(it.toInt()) } - redirectUri?.let { builder.withRedirectUri(it) } + builder.apply { + state?.let { withState(it) } + nonce?.let { withNonce(it) } + audience?.let { withAudience(it) } + scope?.let { withScope(it) } + connection?.let { withConnection(it) } + maxAge?.let { if (it.toInt() != 0) withMaxAge(it.toInt()) } + organization?.let { withOrganization(it) } + invitationUrl?.let { withInvitationUrl(it) } + leeway?.let { if (it.toInt() != 0) withIdTokenVerificationLeeway(it.toInt()) } + redirectUri?.let { withRedirectUri(it) } + } builder.withParameters(cleanedParameters) builder.start(reactContext.currentActivity as Activity, @@ -184,14 +186,6 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0 promise.resolve(true) } - private fun getSecureCredentialsManagerWithoutBiometrics(): SecureCredentialsManager { - return SecureCredentialsManager( - reactContext, - auth0!!, - SharedPreferencesStorage(reactContext) - ) - } - @ReactMethod override fun hasValidAuth0InstanceWithConfiguration(clientId: String, domain: String, promise: Promise) { if (auth0 == null) { @@ -236,10 +230,6 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0 } } - private fun deduceErrorCode(e: CredentialsManagerException): String { - return errorCodeMap[e] ?: CREDENTIAL_MANAGER_ERROR_CODE - } - @ReactMethod override fun saveCredentials(credentials: ReadableMap, promise: Promise) { try { @@ -262,7 +252,7 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0 promise.resolve(secureCredentialsManager.hasValidCredentials(minTtl.toLong())) } - override fun getConstants(): Map { + override fun getConstants(): Map { return mapOf("bundleIdentifier" to reactContext.applicationInfo.packageName) } @@ -290,6 +280,20 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0 }) } + override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) { + // No-op + } + + override fun onNewIntent(intent: Intent) { + webAuthPromise?.let { promise -> + promise.reject( + "a0.session.browser_terminated", + "The browser window was closed by a new instance of the application" + ) + webAuthPromise = null + } + } + override fun resumeWebAuth(url: String, promise: Promise) { // dummy function implementation, as this is only needed in iOS promise.resolve(true) @@ -300,6 +304,18 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0 promise.resolve(true) } + private fun getSecureCredentialsManagerWithoutBiometrics(): SecureCredentialsManager { + return SecureCredentialsManager( + reactContext, + auth0!!, + SharedPreferencesStorage(reactContext) + ) + } + + private fun deduceErrorCode(e: CredentialsManagerException): String { + return errorCodeMap[e] ?: CREDENTIAL_MANAGER_ERROR_CODE + } + private fun handleError(error: AuthenticationException, promise: Promise) { when { error.isBrowserAppNotAvailable -> { @@ -327,18 +343,4 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0 error ) } - - override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) { - // No-op - } - - override fun onNewIntent(intent: Intent) { - webAuthPromise?.let { promise -> - promise.reject( - "a0.session.browser_terminated", - "The browser window was closed by a new instance of the application" - ) - webAuthPromise = null - } - } } \ No newline at end of file