Skip to content
Draft
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
123 changes: 71 additions & 52 deletions android/src/main/java/io/curity/haapi/react/HaapiInteractionHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -67,83 +67,88 @@ 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)
}
}

@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<String, Any>, onSuccess: (HaapiResponse) -> Unit
parameters: Map<String, Any>,
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) {
withTokenManager(onSuccess) { tokenManager, context ->
fun exchangeCode(
codeResponse: OAuthAuthorizationResponseStep,
onSuccess: (TokenResponse) -> Unit,
onError: (FailedTokenManagerRequestException) -> 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: (FailedTokenManagerRequestException) -> 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: (FailedTokenManagerRequestException) -> 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)
Expand All @@ -155,18 +160,25 @@ 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}",
e
)
)
}
} finally {
_eventEmitter.sendEvent(EventType.HaapiFinishedLoading)
throw FailedHaapiRequestException("Failed to make HAAPI request: ${e.message}", e)
}

_eventEmitter.sendEvent(EventType.HaapiFinishedLoading)
}
}

@Throws(FailedTokenManagerRequestException::class)
private fun withTokenManager(
onSuccess: ((TokenResponse) -> Unit)? = null,
onError: ((FailedTokenManagerRequestException) -> Unit)? = null,
accessorRequest: suspend (tokenManager: OAuthTokenManager, coroutineContext: CoroutineContext) -> TokenResponse?
) {
_eventEmitter.sendEvent(HaapiLoading)
Expand All @@ -180,12 +192,19 @@ 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(
FailedTokenManagerRequestException(
"Failed to make HAAPI token request: ${e.message}",
e
)
)
}
} finally {
_eventEmitter.sendEvent(EventType.HaapiFinishedLoading)
throw FailedTokenManagerRequestException("Failed to make token request", e)
}

_eventEmitter.sendEvent(EventType.HaapiFinishedLoading)
}

}
Expand All @@ -199,4 +218,4 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext

throw HaapiNotInitializedException()
}
}
}
122 changes: 73 additions & 49 deletions android/src/main/java/io/curity/haapi/react/HaapiModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -64,8 +65,8 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : ReactCon
private val _webAuthnHandler = WebAuthnHandler(_reactContext)

init {
HaapiLogger.enabled = true
HaapiLogger.isDebugEnabled = true
HaapiLogger.enabled = BuildConfig.DEBUG
HaapiLogger.isDebugEnabled = BuildConfig.DEBUG
_reactContext.addLifecycleEventListener(this)
}

Expand Down Expand Up @@ -94,65 +95,74 @@ 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
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)
}

Expand All @@ -172,29 +182,36 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : ReactCon

}

private fun submitModel(model: FormActionModel, parameters: Map<String, Any>, promise: Promise) {
private fun submitModel(
model: FormActionModel,
parameters: Map<String, Any>,
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) {
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) {
Expand All @@ -212,7 +229,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)
}
Expand Down Expand Up @@ -241,9 +259,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
Expand Down