From 40096b6e7523dd91299953ad801557a8e404cc64 Mon Sep 17 00:00:00 2001 From: Christian Bach Date: Wed, 8 Jan 2025 12:34:17 +0100 Subject: [PATCH 1/5] Update HaapiInteractionHandler.kt --- .../haapi/react/HaapiInteractionHandler.kt | 100 +++++++++++------- 1 file changed, 59 insertions(+), 41 deletions(-) diff --git a/android/src/main/java/io/curity/haapi/react/HaapiInteractionHandler.kt b/android/src/main/java/io/curity/haapi/react/HaapiInteractionHandler.kt index daf0906..db0a13b 100644 --- a/android/src/main/java/io/curity/haapi/react/HaapiInteractionHandler.kt +++ b/android/src/main/java/io/curity/haapi/react/HaapiInteractionHandler.kt @@ -68,11 +68,12 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext * Starts the flow by performing an authorization request to the configured server. * This will perform attestation and obtain an API access token * - * @throws FailedHaapiRequestException if the request fails */ - @Throws(FailedHaapiRequestException::class) - fun startAuthentication(onSuccess: (HaapiResponse) -> Unit) { - withHaapiManager(onSuccess) { haapiManager, context -> + fun startAuthentication( + onSuccess: (HaapiResponse) -> Unit, + onError: (FailedHaapiRequestException) -> Unit + ) { + withHaapiManager(onSuccess, onError) { haapiManager, context -> haapiManager.start(context) } } @@ -95,55 +96,58 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext } @Throws(FailedTokenManagerRequestException::class) - fun exchangeCode(codeResponse: OAuthAuthorizationResponseStep, onSuccess: (TokenResponse) -> Unit) { - withTokenManager(onSuccess) { tokenManager, context -> + fun exchangeCode( + codeResponse: OAuthAuthorizationResponseStep, + onSuccess: (TokenResponse) -> Unit, + onError: (FailedHaapiRequestException) -> Unit + ) { + withTokenManager(onSuccess, onError) { tokenManager, context -> tokenManager.fetchAccessToken(codeResponse.properties.code, context) } } - @Throws(FailedTokenManagerRequestException::class) - fun refreshAccessToken(refreshToken: String, onSuccess: (TokenResponse) -> Unit) { + fun refreshAccessToken( + refreshToken: String, + onSuccess: (TokenResponse) -> Unit, + onError: (FailedHaapiRequestException) -> Unit + ) { Log.d(TAG, "Refreshing access token") - try { - withTokenManager(onSuccess) { tokenManager, coroutineContext -> - tokenManager.refreshAccessToken(refreshToken, coroutineContext) - } - } catch (e: Exception) { - Log.d(TAG, "Failed to refresh tokens: ${e.message}") - throw FailedTokenManagerRequestException("Failed to refresh token", e) + withTokenManager(onSuccess, onError) { tokenManager, coroutineContext -> + tokenManager.refreshAccessToken(refreshToken, coroutineContext) } } - fun logoutAndRevokeTokens(accessToken: String, refreshToken: String? = null) { - try { - if (refreshToken != null) { - Log.d(TAG, "Revoking refresh token") - withTokenManager { tokenManager, context -> - tokenManager.revokeRefreshToken(refreshToken!!, context) - null - } - } else { - Log.d(TAG, "Revoking access token") - withTokenManager { tokenManager, context -> - tokenManager.revokeAccessToken(accessToken, context) - null - } + fun logoutAndRevokeTokens( + accessToken: String, + refreshToken: String? = null, + onSuccess: (TokenResponse) -> Unit, + onError: (FailedHaapiRequestException) -> Unit + ) { + if (refreshToken != null) { + Log.d(TAG, "Revoking refresh token") + withTokenManager(onSuccess, onError) { tokenManager, context -> + tokenManager.revokeRefreshToken(refreshToken, context) + null + } + } else { + Log.d(TAG, "Revoking access token") + withTokenManager(onSuccess, onError) { tokenManager, context -> + tokenManager.revokeAccessToken(accessToken, context) + null } - } catch (e: Exception) { - Log.d(TAG, "Failed to revoke tokens: ${e.message}") } - _accessorRepository?.close() + closeHaapiConnection() } fun closeHaapiConnection() { _accessorRepository?.close() } - @Throws(FailedHaapiRequestException::class) private fun withHaapiManager( onSuccess: (HaapiResponse) -> Unit, + onError: ((FailedHaapiRequestException) -> Unit)? = null, accessorRequest: suspend (manager: HaapiManager, context: CoroutineContext) -> HaapiResponse ) { _eventEmitter.sendEvent(HaapiLoading) @@ -157,16 +161,23 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext } catch (e: Exception) { Log.w(TAG, "Failed to make HAAPI request: ${e.message}") _eventEmitter.sendEvent(EventType.HaapiFinishedLoading) - throw FailedHaapiRequestException("Failed to make HAAPI request: ${e.message}", e) + if (onError != null) { + onError( + FailedHaapiRequestException( + "Failed to make HAAPI request: ${e.message}", + e + ) + ) + } + } finally { + _eventEmitter.sendEvent(EventType.HaapiFinishedLoading) } - - _eventEmitter.sendEvent(EventType.HaapiFinishedLoading) } } - @Throws(FailedTokenManagerRequestException::class) private fun withTokenManager( onSuccess: ((TokenResponse) -> Unit)? = null, + onError: ((FailedHaapiRequestException) -> Unit)? = null, accessorRequest: suspend (tokenManager: OAuthTokenManager, coroutineContext: CoroutineContext) -> TokenResponse? ) { _eventEmitter.sendEvent(HaapiLoading) @@ -182,10 +193,17 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext } catch (e: Exception) { Log.w(TAG, "Failed to make token request: ${e.message}") _eventEmitter.sendEvent(EventType.HaapiFinishedLoading) - throw FailedTokenManagerRequestException("Failed to make token request", e) + if (onError != null) { + onError( + FailedHaapiRequestException( + "Failed to make HAAPI request: ${e.message}", + e + ) + ) + } + } finally { + _eventEmitter.sendEvent(EventType.HaapiFinishedLoading) } - - _eventEmitter.sendEvent(EventType.HaapiFinishedLoading) } } @@ -199,4 +217,4 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext throw HaapiNotInitializedException() } -} \ No newline at end of file +} From 7c199b6cd90349422d79b20e05e7a8cca9c517ef Mon Sep 17 00:00:00 2001 From: Christian Bach Date: Wed, 8 Jan 2025 12:34:39 +0100 Subject: [PATCH 2/5] Update HaapiModule.kt --- .../java/io/curity/haapi/react/HaapiModule.kt | 84 ++++++++++++------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/android/src/main/java/io/curity/haapi/react/HaapiModule.kt b/android/src/main/java/io/curity/haapi/react/HaapiModule.kt index 861b851..faf1e19 100644 --- a/android/src/main/java/io/curity/haapi/react/HaapiModule.kt +++ b/android/src/main/java/io/curity/haapi/react/HaapiModule.kt @@ -51,7 +51,8 @@ import se.curity.identityserver.haapi.android.sdk.models.oauth.TokenResponse const val TAG = "HaapiNative" -class HaapiModule(private val _reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(_reactContext), +class HaapiModule(private val _reactContext: ReactApplicationContext) : + ReactContextBaseJavaModule(_reactContext), LifecycleEventListener { override fun getName() = "HaapiModule" @@ -94,41 +95,50 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : ReactCon @ReactMethod fun start(promise: Promise) { Log.d(TAG, "Start was called") - - try { - _handler.startAuthentication { response -> handleHaapiResponse(response, promise) } - } catch (e: Exception) { - Log.e(TAG, e.message ?: "Failed to attest $e") - rejectRequest(e, promise) - } + _handler.startAuthentication( + onSuccess = {response -> handleHaapiResponse(response, promise)}, + onError = {e -> + Log.e(TAG, e.message ?: "Failed to attest $e") + rejectRequest(e, promise) + } + ) } @ReactMethod fun logout(promise: Promise) { Log.d(TAG, "Logout was called, revoking tokens") - if (_tokenResponse != null) { - _handler.logoutAndRevokeTokens(_tokenResponse!!.accessToken, _tokenResponse!!.refreshToken) + _handler.logoutAndRevokeTokens( + _tokenResponse!!.accessToken, + _tokenResponse!!.refreshToken, + onSuccess = { + _tokenResponse = null + resolveRequest(LoggedOut, "{}", promise) + }, + onError = {e -> + Log.w(TAG, "Failed to logout: ${e.message}") + rejectRequest(e, promise) + } + ) } else { _handler.closeHaapiConnection() + _tokenResponse = null + resolveRequest(LoggedOut, "{}", promise) } - - _tokenResponse = null - resolveRequest(LoggedOut, "{}", promise) } @ReactMethod fun refreshAccessToken(refreshToken: String, promise: Promise) { Log.d(TAG, "Refreshing access token") - - try { - _handler.refreshAccessToken(refreshToken) { tokenResponse -> + _handler.refreshAccessToken( + refreshToken, + onSuccess = { tokenResponse -> handleTokenResponse(tokenResponse, promise) + }, + onError = { e -> + rejectRequest(e, promise) } - } catch (e: Exception) { - Log.d(TAG, "Failed to revoke tokens: ${e.message}") - rejectRequest(e, promise) - } + ) } @ReactMethod @@ -172,7 +182,11 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : ReactCon } - private fun submitModel(model: FormActionModel, parameters: Map, promise: Promise) { + private fun submitModel( + model: FormActionModel, + parameters: Map, + promise: Promise + ) { Log.d(TAG, "Submitting form $model") try { _handler.submitForm(model, parameters) { response -> @@ -186,15 +200,16 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : ReactCon } private fun handleCodeResponse(response: OAuthAuthorizationResponseStep, promise: Promise) { - try { - _handler.exchangeCode(response) { tokenResponse -> + _handler.exchangeCode( + response, + onSuccess = { tokenResponse -> handleTokenResponse(tokenResponse, promise) + }, + onError = { e -> + Log.w(TAG, "Failed to exchange code: ${e.message}") + rejectRequest(e, promise) } - - } catch (e: Exception) { - Log.w(TAG, "Failed to exchange code: ${e.message}") - rejectRequest(e, promise) - } + ) } private fun handleTokenResponse(tokenResponse: TokenResponse, promise: Promise) { @@ -212,7 +227,8 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : ReactCon } else { tokenResponse as ErrorTokenResponse val errorResponseMap = mapOf( - "error" to tokenResponse.error, "error_description" to tokenResponse.errorDescription + "error" to tokenResponse.error, + "error_description" to tokenResponse.errorDescription ) resolveRequest(TokenResponseError, JsonUtil.toJsonString(errorResponseMap), promise) } @@ -241,9 +257,15 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : ReactCon _haapiResponse = response when (response) { - is WebAuthnRegistrationClientOperationStep -> handleWebAuthnRegistration(response, promise) + is WebAuthnRegistrationClientOperationStep -> handleWebAuthnRegistration( + response, + promise + ) - is WebAuthnAuthenticationClientOperationStep -> handleWebAuthnAuthentication(response, promise) + is WebAuthnAuthenticationClientOperationStep -> handleWebAuthnAuthentication( + response, + promise + ) is AuthenticatorSelectorStep -> resolveRequest( AuthenticationSelectorStep, response.toJsonString(), promise From 25c14801c22671cf717276969ddb4f20bd6aa5ab Mon Sep 17 00:00:00 2001 From: Christian Bach Date: Wed, 8 Jan 2025 12:52:19 +0100 Subject: [PATCH 3/5] cleanup code --- .../haapi/react/HaapiInteractionHandler.kt | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/android/src/main/java/io/curity/haapi/react/HaapiInteractionHandler.kt b/android/src/main/java/io/curity/haapi/react/HaapiInteractionHandler.kt index db0a13b..7ad0cd6 100644 --- a/android/src/main/java/io/curity/haapi/react/HaapiInteractionHandler.kt +++ b/android/src/main/java/io/curity/haapi/react/HaapiInteractionHandler.kt @@ -67,7 +67,6 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext /** * Starts the flow by performing an authorization request to the configured server. * This will perform attestation and obtain an API access token - * */ fun startAuthentication( onSuccess: (HaapiResponse) -> Unit, @@ -78,28 +77,31 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext } } - @Throws(FailedHaapiRequestException::class) - fun followLink(link: Link, onSuccess: (HaapiResponse) -> Unit) { - withHaapiManager(onSuccess) { haapiManager, context -> + fun followLink( + link: Link, + onSuccess: (HaapiResponse) -> Unit, + onError: (FailedHaapiRequestException) -> Unit + ) { + withHaapiManager(onSuccess, onError) { haapiManager, context -> haapiManager.followLink(link, context) } } - @Throws(FailedHaapiRequestException::class) fun submitForm( form: FormActionModel, - parameters: Map, onSuccess: (HaapiResponse) -> Unit + parameters: Map, + onSuccess: (HaapiResponse) -> Unit, + onError: (FailedHaapiRequestException) -> Unit ) { - withHaapiManager(onSuccess) { haapiManager, context -> + withHaapiManager(onSuccess, onError) { haapiManager, context -> haapiManager.submitForm(form, parameters, context) } } - @Throws(FailedTokenManagerRequestException::class) fun exchangeCode( codeResponse: OAuthAuthorizationResponseStep, onSuccess: (TokenResponse) -> Unit, - onError: (FailedHaapiRequestException) -> Unit + onError: (FailedTokenManagerRequestException) -> Unit ) { withTokenManager(onSuccess, onError) { tokenManager, context -> tokenManager.fetchAccessToken(codeResponse.properties.code, context) @@ -109,10 +111,9 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext fun refreshAccessToken( refreshToken: String, onSuccess: (TokenResponse) -> Unit, - onError: (FailedHaapiRequestException) -> Unit + onError: (FailedTokenManagerRequestException) -> Unit ) { Log.d(TAG, "Refreshing access token") - withTokenManager(onSuccess, onError) { tokenManager, coroutineContext -> tokenManager.refreshAccessToken(refreshToken, coroutineContext) } @@ -122,7 +123,7 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext accessToken: String, refreshToken: String? = null, onSuccess: (TokenResponse) -> Unit, - onError: (FailedHaapiRequestException) -> Unit + onError: (FailedTokenManagerRequestException) -> Unit ) { if (refreshToken != null) { Log.d(TAG, "Revoking refresh token") @@ -159,12 +160,12 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext val response = accessorRequest(manager, this.coroutineContext) onSuccess(response) } catch (e: Exception) { - Log.w(TAG, "Failed to make HAAPI request: ${e.message}") + Log.w(TAG, "Failed to make Haapi request: ${e.message}") _eventEmitter.sendEvent(EventType.HaapiFinishedLoading) if (onError != null) { onError( FailedHaapiRequestException( - "Failed to make HAAPI request: ${e.message}", + "Failed to make Haapi request: ${e.message}", e ) ) @@ -177,7 +178,7 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext private fun withTokenManager( onSuccess: ((TokenResponse) -> Unit)? = null, - onError: ((FailedHaapiRequestException) -> Unit)? = null, + onError: ((FailedTokenManagerRequestException) -> Unit)? = null, accessorRequest: suspend (tokenManager: OAuthTokenManager, coroutineContext: CoroutineContext) -> TokenResponse? ) { _eventEmitter.sendEvent(HaapiLoading) @@ -191,12 +192,12 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext onSuccess(response) } } catch (e: Exception) { - Log.w(TAG, "Failed to make token request: ${e.message}") + Log.w(TAG, "Failed to make HAAPI token request: ${e.message}") _eventEmitter.sendEvent(EventType.HaapiFinishedLoading) if (onError != null) { onError( - FailedHaapiRequestException( - "Failed to make HAAPI request: ${e.message}", + FailedTokenManagerRequestException( + "Failed to make HAAPI token request: ${e.message}", e ) ) From 26046f48de523aee99619ad78a66778184f9145b Mon Sep 17 00:00:00 2001 From: Christian Bach Date: Wed, 8 Jan 2025 12:53:09 +0100 Subject: [PATCH 4/5] code cleanup --- .../java/io/curity/haapi/react/HaapiModule.kt | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/android/src/main/java/io/curity/haapi/react/HaapiModule.kt b/android/src/main/java/io/curity/haapi/react/HaapiModule.kt index faf1e19..eecf35b 100644 --- a/android/src/main/java/io/curity/haapi/react/HaapiModule.kt +++ b/android/src/main/java/io/curity/haapi/react/HaapiModule.kt @@ -96,8 +96,8 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : fun start(promise: Promise) { Log.d(TAG, "Start was called") _handler.startAuthentication( - onSuccess = {response -> handleHaapiResponse(response, promise)}, - onError = {e -> + onSuccess = { response -> handleHaapiResponse(response, promise) }, + onError = { e -> Log.e(TAG, e.message ?: "Failed to attest $e") rejectRequest(e, promise) } @@ -115,7 +115,7 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : _tokenResponse = null resolveRequest(LoggedOut, "{}", promise) }, - onError = {e -> + onError = { e -> Log.w(TAG, "Failed to logout: ${e.message}") rejectRequest(e, promise) } @@ -138,31 +138,31 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : onError = { e -> rejectRequest(e, promise) } - ) + ) } @ReactMethod fun navigate(linkMap: ReadableMap, promise: Promise) { - val linkJson = _gson.toJson(linkMap.toHashMap()) val link = _gson.fromJson(linkJson, Link::class.java) - try { - _handler.followLink(link) { response -> handleHaapiResponse(response, promise) } - } catch (e: Exception) { - Log.d(TAG, "Failed to navigate to link: ${e.message}") - rejectRequest(e, promise) - } + + _handler.followLink( + link, + onSuccess = { response -> handleHaapiResponse(response, promise) }, + onError = { e -> + Log.d(TAG, "Failed to navigate to link: ${e.message}") + rejectRequest(e, promise) + } + ) } @ReactMethod fun submitForm(actionMap: ReadableMap, parameters: ReadableMap, promise: Promise) { - val action = findAction(actionMap, _haapiResponse as HaapiRepresentation) if (action == null) { Log.d(TAG, "Failed to find action to submit. Possible re-submit") return } - submitModel(action.model, parameters.toHashMap(), promise) } @@ -188,15 +188,17 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : promise: Promise ) { Log.d(TAG, "Submitting form $model") - try { - _handler.submitForm(model, parameters) { response -> + _handler.submitForm( + model, + parameters, + onSuccess = { response -> handleHaapiResponse(response, promise) + }, + onError = { e -> + Log.w(TAG, "Failed to submit form: ${e.message}") + rejectRequest(e, promise) } - } catch (e: Exception) { - Log.w(TAG, "Failed to submit form: ${e.message}") - rejectRequest(e, promise) - } - + ) } private fun handleCodeResponse(response: OAuthAuthorizationResponseStep, promise: Promise) { From 274dec887b0cddebf5b371c9aa6887d8024ee14e Mon Sep 17 00:00:00 2001 From: Christian Bach Date: Wed, 8 Jan 2025 13:45:51 +0100 Subject: [PATCH 5/5] only enable logger on debug builds --- android/src/main/java/io/curity/haapi/react/HaapiModule.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/io/curity/haapi/react/HaapiModule.kt b/android/src/main/java/io/curity/haapi/react/HaapiModule.kt index eecf35b..d2f676a 100644 --- a/android/src/main/java/io/curity/haapi/react/HaapiModule.kt +++ b/android/src/main/java/io/curity/haapi/react/HaapiModule.kt @@ -65,8 +65,8 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : private val _webAuthnHandler = WebAuthnHandler(_reactContext) init { - HaapiLogger.enabled = true - HaapiLogger.isDebugEnabled = true + HaapiLogger.enabled = BuildConfig.DEBUG + HaapiLogger.isDebugEnabled = BuildConfig.DEBUG _reactContext.addLifecycleEventListener(this) }