diff --git a/docker-compose/.env b/docker-compose/.env index f41ca92a6..c29a06314 100644 --- a/docker-compose/.env +++ b/docker-compose/.env @@ -22,5 +22,5 @@ MSSQL_DB_PORT=1433 #sqlite | postgres | mssql DATABASE_ENGINE=postgres -VERSION_TAG=0.6.0 +VERSION_TAG=0.7.0 COMPOSE_PROFILES=$DATABASE_ENGINE diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index aac09283c..cbca0f497 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -85,10 +85,10 @@ services: depends_on: - caddy environment: - NEXT_PUBLIC_VC_REPO: "http://localhost:$VC_REPO_PORT" - NEXT_PUBLIC_ISSUER: "http://localhost:$ISSUER_API_PORT" - NEXT_PUBLIC_VERIFIER: "http://localhost:$VERIFIER_API_PORT" - NEXT_PUBLIC_WALLET: "http://localhost:$DEMO_WALLET_FRONTEND_PORT" + NEXT_PUBLIC_VC_REPO: "http://host.docker.internal:$VC_REPO_PORT" + NEXT_PUBLIC_ISSUER: "http://host.docker.internal:$ISSUER_API_PORT" + NEXT_PUBLIC_VERIFIER: "http://host.docker.internal:$VERIFIER_API_PORT" + NEXT_PUBLIC_WALLET: "http://host.docker.internal:$DEMO_WALLET_FRONTEND_PORT" PORT: $WEB_PORTAL_PORT vc-repo: diff --git a/waltid-applications/waltid-web-wallet/libs/composables/presentation.ts b/waltid-applications/waltid-web-wallet/libs/composables/presentation.ts index 234fdc4e7..7ec63a130 100644 --- a/waltid-applications/waltid-web-wallet/libs/composables/presentation.ts +++ b/waltid-applications/waltid-web-wallet/libs/composables/presentation.ts @@ -1,137 +1,191 @@ import { encodeDisclosure } from "./disclosures.ts"; import { useCurrentWallet } from "./accountWallet.ts"; -import { ref, type Ref, computed, watch } from "vue"; +import { computed, type Ref, ref, watch } from "vue"; import { decodeRequest } from "./siop-requests.ts"; import { navigateTo } from "nuxt/app"; export async function usePresentation(query: any) { - const index = ref(0); - const failed = ref(false); - const failMessage = ref("Unknown error occurred."); - - const currentWallet = useCurrentWallet(); - async function resolvePresentationRequest(request: string) { - try { - const response = await $fetch(`/wallet-api/wallet/${currentWallet.value}/exchange/resolvePresentationRequest`, { - method: "POST", - body: request - }); - return response; - } catch (e) { - failed.value = true; - throw e; - } + const index = ref(0); + const failed = ref(false); + const failMessage = ref("Unknown error occurred."); + + const currentWallet = useCurrentWallet(); + + async function resolvePresentationRequest(request: string) { + try { + const response = await $fetch( + `/wallet-api/wallet/${currentWallet.value}/exchange/resolvePresentationRequest`, + { + method: "POST", + body: request, + }, + ); + return response; + } catch (e) { + failed.value = true; + throw e; } - - const request = await resolvePresentationRequest(decodeRequest(query.request as string)); - const presentationUrl = new URL(request as string); - const presentationParams = presentationUrl.searchParams; - - const verifierHost = new URL(presentationParams.get("response_uri") ?? presentationParams.get("redirect_uri") ?? "").host; - const presentationDefinition = presentationParams.get("presentation_definition") as string; - const matchedCredentials = await $fetch>(`/wallet-api/wallet/${currentWallet.value}/exchange/matchCredentialsForPresentationDefinition`, { - method: "POST", - body: presentationDefinition - }); - - const selection = ref<{ [key: string]: boolean; }>({}); - const selectedCredentialIds = computed(() => Object.entries(selection.value).filter((it) => it[1]).map((it) => it[0])) - for (let credential of matchedCredentials) { - selection.value[credential.id] = true + } + + const request = await resolvePresentationRequest( + decodeRequest(query.request as string), + ); + const presentationUrl = new URL(request as string); + const presentationParams = presentationUrl.searchParams; + + const verifierHost = new URL( + presentationParams.get("response_uri") ?? + presentationParams.get("redirect_uri") ?? + "", + ).host; + const presentationDefinition = presentationParams.get( + "presentation_definition", + ) as string; + const matchedCredentials = await $fetch< + Array<{ + id: string; + document: string; + parsedDocument?: string; + disclosures?: string; + }> + >( + `/wallet-api/wallet/${currentWallet.value}/exchange/matchCredentialsForPresentationDefinition`, + { + method: "POST", + body: presentationDefinition, + }, + ); + + const selection = ref<{ [key: string]: boolean }>({}); + const selectedCredentialIds = computed(() => + Object.entries(selection.value) + .filter((it) => it[1]) + .map((it) => it[0]), + ); + for (let credential of matchedCredentials) { + selection.value[credential.id] = true; + } + + const disclosures: Ref<{ [key: string]: any[] }> = ref({}); + const encodedDisclosures = computed(() => { + if (JSON.stringify(disclosures.value) === "{}") return null; + + const m: { [key: string]: any[] } = {}; + for (let credId in disclosures.value) { + if (m[credId] === undefined) { + m[credId] = []; + } + + for (let disclosure of disclosures.value[credId]) { + m[credId].push(encodeDisclosure(disclosure)); + } } - const disclosures: Ref<{ [key: string]: any[] }> = ref({}); - const encodedDisclosures = computed(() => { - if (JSON.stringify(disclosures.value) === "{}") return null - - const m: { [key: string]: any[] } = {} - for (let credId in disclosures.value) { - if (m[credId] === undefined) { - m[credId] = [] - } - - for (let disclosure of disclosures.value[credId]) { - m[credId].push(encodeDisclosure(disclosure)) - } - } - - return m - }) - - function addDisclosure(credentialId: string, disclosure: string) { - if (disclosures.value[credentialId] === undefined) { - disclosures.value[credentialId] = [] - } - disclosures.value[credentialId].push(disclosure) - } + return m; + }); - function removeDisclosure(credentialId: string, disclosure: string) { - disclosures.value[credentialId] = disclosures.value[credentialId].filter((elem) => elem[0] != disclosure[0]) + function addDisclosure(credentialId: string, disclosure: string) { + if (disclosures.value[credentialId] === undefined) { + disclosures.value[credentialId] = []; } - - const disclosureModalState: Ref<{ [key: string]: boolean }> = ref({}); + disclosures.value[credentialId].push(disclosure); + } + + function removeDisclosure(credentialId: string, disclosure: string) { + disclosures.value[credentialId] = disclosures.value[credentialId].filter( + (elem) => elem[0] != disclosure[0], + ); + } + + const disclosureModalState: Ref<{ [key: string]: boolean }> = ref({}); + + for (let credential of matchedCredentials) { + disclosureModalState.value[credential.id] = false; + } + if (matchedCredentials[index.value]) { + disclosureModalState.value[matchedCredentials[index.value].id] = true; + } + + function toggleDisclosure(credentialId: string) { + disclosureModalState.value[credentialId] = + !disclosureModalState.value[credentialId]; + } + + // Disable all disclosure modals when switching between credentials and set the current one to active + watch(index, () => { for (let credential of matchedCredentials) { - disclosureModalState.value[credential.id] = false + disclosureModalState.value[credential.id] = false; } - disclosureModalState.value[matchedCredentials[index.value].id] = true - - function toggleDisclosure(credentialId: string) { - disclosureModalState.value[credentialId] = !disclosureModalState.value[credentialId] - } - // Disable all disclosure modals when switching between credentials and set the current one to active - watch(index, () => { - for (let credential of matchedCredentials) { - disclosureModalState.value[credential.id] = false - } - disclosureModalState.value[matchedCredentials[index.value].id] = true - }) - - async function acceptPresentation() { - const req = { - //did: String, // todo: choose DID of shared credential // for now wallet-api chooses the default wallet did - presentationRequest: request, - selectedCredentials: selectedCredentialIds.value, - disclosures: encodedDisclosures.value - }; - - const response = await fetch(`/wallet-api/wallet/${currentWallet.value}/exchange/usePresentationRequest`, { - method: "POST", - body: JSON.stringify(req), - redirect: "manual", - headers: { - "Content-Type": "application/json" - } + disclosureModalState.value[matchedCredentials[index.value].id] = true; + }); + + async function acceptPresentation() { + const req = { + //did: String, // todo: choose DID of shared credential // for now wallet-api chooses the default wallet did + presentationRequest: request, + selectedCredentials: selectedCredentialIds.value, + disclosures: encodedDisclosures.value, + }; + + const response = await fetch( + `/wallet-api/wallet/${currentWallet.value}/exchange/usePresentationRequest`, + { + method: "POST", + body: JSON.stringify(req), + redirect: "manual", + headers: { + "Content-Type": "application/json", + }, + }, + ); + + if (response.ok) { + const parsedResponse: { redirectUri: string } = await response.json(); + if (parsedResponse.redirectUri) { + navigateTo(parsedResponse.redirectUri, { + external: true, }); - - if (response.ok) { - const parsedResponse: { redirectUri: string } = await response.json(); - if (parsedResponse.redirectUri) { - navigateTo(parsedResponse.redirectUri, { - external: true - }); - } else { - window.alert("Presentation successful, no redirect URL supplied."); - navigateTo(`/wallet/${currentWallet.value}`, { - external: true - }); - } - } else { - failed.value = true; - const error: { message: string; redirectUri: string | null | undefined, errorMessage: string } = await response.json(); - failMessage.value = error.message; - - console.log("Error response: " + JSON.stringify(error)); - window.alert(error.errorMessage) - - if (error.redirectUri != null) { - navigateTo(error.redirectUri as string, { - external: true - }); - } - } - } - - return { - currentWallet, verifierHost, presentationDefinition, matchedCredentials, selectedCredentialIds, disclosures, selection, index, disclosureModalState, toggleDisclosure, addDisclosure, removeDisclosure, acceptPresentation, failed, failMessage + } else { + window.alert("Presentation successful, no redirect URL supplied."); + navigateTo(`/wallet/${currentWallet.value}`, { + external: true, + }); + } + } else { + failed.value = true; + const error: { + message: string; + redirectUri: string | null | undefined; + errorMessage: string; + } = await response.json(); + failMessage.value = error.message; + + console.log("Error response: " + JSON.stringify(error)); + window.alert(error.errorMessage); + + if (error.redirectUri != null) { + navigateTo(error.redirectUri as string, { + external: true, + }); + } } -} \ No newline at end of file + } + + return { + currentWallet, + verifierHost, + presentationDefinition, + matchedCredentials, + selectedCredentialIds, + disclosures, + selection, + index, + disclosureModalState, + toggleDisclosure, + addDisclosure, + removeDisclosure, + acceptPresentation, + failed, + failMessage, + }; +} diff --git a/waltid-libraries/credentials/waltid-verifiable-credentials/src/commonMain/kotlin/id/walt/credentials/verification/Verifier.kt b/waltid-libraries/credentials/waltid-verifiable-credentials/src/commonMain/kotlin/id/walt/credentials/verification/Verifier.kt index e06b33a64..12e142370 100644 --- a/waltid-libraries/credentials/waltid-verifiable-credentials/src/commonMain/kotlin/id/walt/credentials/verification/Verifier.kt +++ b/waltid-libraries/credentials/waltid-verifiable-credentials/src/commonMain/kotlin/id/walt/credentials/verification/Verifier.kt @@ -6,6 +6,7 @@ import id.walt.credentials.verification.models.PresentationResultEntry import id.walt.credentials.verification.models.PresentationVerificationResponse import id.walt.credentials.verification.policies.JwtSignaturePolicy import id.walt.crypto.utils.JwsUtils.decodeJws +import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex @@ -22,6 +23,8 @@ import kotlin.time.measureTime @JsExport object Verifier { + private val log = KotlinLogging.logger { } + private fun JsonObject.getW3CType() = (this["type"] ?: this["vc"]?.jsonObject?.get("type") ?: this["vp"]?.jsonObject?.get("type") ?: throw IllegalArgumentException("No `type` supplied: $this")).let { when (it) { diff --git a/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/VerifierApi.kt b/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/VerifierApi.kt index 5934493ff..986f2b30d 100644 --- a/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/VerifierApi.kt +++ b/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/VerifierApi.kt @@ -30,7 +30,7 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.util.* import io.ktor.util.* -import io.ktor.util.pipeline.PipelineContext +import io.ktor.util.pipeline.* import kotlinx.coroutines.runBlocking import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName @@ -193,32 +193,33 @@ fun Application.verfierApi() { }) { - val authorizeBaseUrl = context.request.header("authorizeBaseUrl") ?: defaultAuthorizeBaseUrl - val responseMode = - context.request.header("responseMode")?.let { ResponseMode.fromString(it) } - ?: ResponseMode.direct_post - val successRedirectUri = context.request.header("successRedirectUri") - val errorRedirectUri = context.request.header("errorRedirectUri") - val statusCallbackUri = context.request.header("statusCallbackUri") - val statusCallbackApiKey = context.request.header("statusCallbackApiKey") - val stateId = context.request.header("stateId") - val openId4VPProfile = context.request.header("openId4VPProfile") + val authorizeBaseUrl = context.request.header("authorizeBaseUrl") ?: defaultAuthorizeBaseUrl + val responseMode = + context.request.header("responseMode")?.let { ResponseMode.fromString(it) } + ?: ResponseMode.direct_post + val successRedirectUri = context.request.header("successRedirectUri") + val errorRedirectUri = context.request.header("errorRedirectUri") + val statusCallbackUri = context.request.header("statusCallbackUri") + val statusCallbackApiKey = context.request.header("statusCallbackApiKey") + val stateId = context.request.header("stateId") + val openId4VPProfile = context.request.header("openId4VPProfile") - val body = context.receive() + val body = context.receive() val session = verificationUseCase.createSession( vpPoliciesJson = body["vp_policies"], vcPoliciesJson = body["vc_policies"], requestCredentialsJson = body["request_credentials"] - ?: throw BadRequestException("Field request_credentials is required"), + ?: throw BadRequestException("Field request_credentials is required"), responseMode = responseMode, successRedirectUri = successRedirectUri, errorRedirectUri = errorRedirectUri, statusCallbackUri = statusCallbackUri, statusCallbackApiKey = statusCallbackApiKey, stateId = stateId, - openId4VPProfile = (body["openid_profile"]?.jsonPrimitive?.contentOrNull ?: openId4VPProfile)?.let { OpenId4VPProfile.valueOf(it.uppercase()) } - ?: OpenId4VPProfile.fromAuthorizeBaseURL(authorizeBaseUrl), + openId4VPProfile = (body["openid_profile"]?.jsonPrimitive?.contentOrNull + ?: openId4VPProfile)?.let { OpenId4VPProfile.valueOf(it.uppercase()) } + ?: OpenId4VPProfile.fromAuthorizeBaseURL(authorizeBaseUrl), trustedRootCAs = body["trusted_root_cas"]?.jsonArray ) @@ -266,17 +267,17 @@ fun Application.verfierApi() { } }) { - logger.info { "POST verify/state" } - val sessionId = call.parameters.getOrFail("state") - logger.info { "State: $sessionId" } - verificationUseCase.verify(sessionId, context.request.call.receiveParameters().toMap()) - .onSuccess { - processVerificationSuccessResult(sessionId, it) - }.onFailure { - processVerificationFailureResult(it, sessionId) - }.also { - verificationUseCase.notifySubscribers(sessionId) - } + logger.info { "POST verify/state" } + val sessionId = call.parameters.getOrFail("state") + logger.info { "State: $sessionId" } + verificationUseCase.verify(sessionId, context.request.call.receiveParameters().toMap()) + .onSuccess { + processVerificationSuccessResult(sessionId, it) + }.onFailure { + processVerificationFailureResult(sessionId, it) + }.also { + verificationUseCase.notifySubscribers(sessionId) + } } get("/session/{id}", { @@ -360,7 +361,7 @@ fun Application.verfierApi() { call.respondText(it, ContentType.parse("application/oauth-authz-req+jwt"), HttpStatusCode.OK) }.onFailure { logger.debug(it) { "Cannot view request session ($it)" } - throw NotFoundException(it.localizedMessage) + throw NotFoundException(it.message) } } } @@ -441,7 +442,7 @@ fun Application.verfierApi() { } } -private fun getErrorDescription(it: Throwable): String? = when (it.localizedMessage) { +private fun getErrorDescription(it: Throwable): String? = when (it.message) { "Verification policies did not succeed: expired" -> "<\$presentation_submission.descriptor_map[x].id> is expired" @@ -455,35 +456,40 @@ private fun getErrorDescription(it: Throwable): String? = when (it.localizedMess } private suspend fun PipelineContext.processError( - sessionId: String, exception: Throwable + sessionId: String, + error: Throwable ) { val session = verificationUseCase.getSession(sessionId) if (session.walletInitiatedAuthState != null) { val state = session.walletInitiatedAuthState context.respondRedirect( - "openid://?state=$state&error=invalid_request&error_description=${getErrorDescription(exception)}" + "openid://?state=$state&error=invalid_request&error_description=${getErrorDescription(error)}" ) + } else if (error is VerificationUseCase.FailedVerificationException && error.redirectUrl != null) { + context.respond(HttpStatusCode.BadRequest, error.redirectUrl) + } else { + throw error } } private suspend fun PipelineContext.processVerificationFailureResult( - it: Throwable, - sessionId: String? + sessionId: String?, + error: Throwable, ) { - logger.debug(it) { "Verification failed ($it)" } - val errorDescription = it.localizedMessage + logger.debug(error) { "Verification failed ($error)" } + val errorDescription = error.message ?: "Verification failed" logger.error { "Error: $errorDescription" } if (sessionId != null) { - processError(sessionId, it) + processError(sessionId, error) } else { - logger.error(it) { "/verify error: $errorDescription" } + logger.error(error) { "/verify error: $errorDescription" } call.respond(HttpStatusCode.BadRequest, errorDescription) } } private suspend fun PipelineContext.processVerificationSuccessResult( sessionId: String?, - it: String + redirectUrl: String, ) { val session = verificationUseCase.getSession(sessionId!!) if (session.walletInitiatedAuthState != null) { @@ -491,6 +497,6 @@ private suspend fun PipelineContext.processVerificationSu val code = UUID().toString() context.respondRedirect("openid://?code=$code&state=$state") } else { - call.respond(HttpStatusCode.OK, it) + call.respond(HttpStatusCode.OK, redirectUrl) } } diff --git a/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/oidc/VerificationUseCase.kt b/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/oidc/VerificationUseCase.kt index fe0397dfc..a9ee73f0b 100644 --- a/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/oidc/VerificationUseCase.kt +++ b/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/oidc/VerificationUseCase.kt @@ -34,7 +34,7 @@ import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec -import java.util.* +import java.util.Base64 class VerificationUseCase( val http: HttpClient, cryptoProvider: JWTCryptoProvider, @@ -53,7 +53,7 @@ class VerificationUseCase( stateId: String?, openId4VPProfile: OpenId4VPProfile = OpenId4VPProfile.DEFAULT, walletInitiatedAuthState: String? = null, - trustedRootCAs: JsonArray? = null + trustedRootCAs: JsonArray? = null, ) = let { val vpPolicies = vpPoliciesJson?.jsonArray?.parsePolicyRequests() ?: listOf(PolicyRequest(JwtSignaturePolicy())) @@ -117,18 +117,21 @@ class VerificationUseCase( stateId: String?, openId4VPProfile: OpenId4VPProfile = OpenId4VPProfile.DEFAULT, walletInitiatedAuthState: String? = null, - trustedRootCAs: JsonArray? = null + trustedRootCAs: JsonArray? = null, ): PresentationSession { return createSession( vpPoliciesJson, vcPoliciesJson, requestCredentialsJson = JsonArray(Json.decodeFromJsonElement(presentationDefinitionJson).inputDescriptors.map { - RequestedCredential(inputDescriptor = it, policies = null).toJsonElement() - }), responseMode, responseType, successRedirectUri, errorRedirectUri, statusCallbackUri, statusCallbackApiKey, stateId, - openId4VPProfile, walletInitiatedAuthState, trustedRootCAs) + RequestedCredential(inputDescriptor = it, policies = null).toJsonElement() + }), responseMode, responseType, successRedirectUri, errorRedirectUri, statusCallbackUri, statusCallbackApiKey, stateId, + openId4VPProfile, walletInitiatedAuthState, trustedRootCAs + ) } fun getSession(sessionId: String): PresentationSession = sessionId.let { OIDCVerifierService.getSession(it) } + data class FailedVerificationException(val redirectUrl: String?, override val cause: Throwable?, override val message: String = cause?.message ?: "Verification failed") : IllegalArgumentException() + fun verify(sessionId: String, tokenResponseParameters: Map>): Result { logger.debug { "Verifying session $sessionId" } val session = OIDCVerifierService.getSession(sessionId) @@ -163,16 +166,14 @@ class VerificationUseCase( val policyResults = OIDCVerifierService.policyResults[session.id] val redirectUri = sessionVerificationInfo.errorRedirectUri?.replace("\$id", session.id) - if (redirectUri != null) { - return Result.failure(Exception(redirectUri)) - } - return if (policyResults == null) { - Result.failure(Exception("Verification policies did not succeed")) + Result.failure(FailedVerificationException(redirectUri, IllegalArgumentException("Verification policies did not succeed"))) } else { val failedPolicies = policyResults.results.flatMap { it.policyResults.map { it } }.filter { !it.isSuccess } - Result.failure(IllegalArgumentException("Verification policies did not succeed: ${failedPolicies.joinToString { it.policy + " (${it.error})" }}")) + val errorCause = IllegalArgumentException("Verification policies did not succeed: ${failedPolicies.joinToString { it.policy + " (${it.error})" }}") + + Result.failure(FailedVerificationException(redirectUri, errorCause)) } } } @@ -220,7 +221,7 @@ class VerificationUseCase( private fun getClientIdScheme( openId4VPProfile: OpenId4VPProfile, - defaultClientIdScheme: ClientIdScheme + defaultClientIdScheme: ClientIdScheme, ): ClientIdScheme { return when (openId4VPProfile) { OpenId4VPProfile.ISO_18013_7_MDOC -> ClientIdScheme.X509SanDns @@ -250,8 +251,11 @@ object LspPotentialInteropEvent { "-----END PRIVATE KEY-----\n" const val POTENTIAL_ISSUER_KEY_ID = "potential-lsp-issuer-key-01" val POTENTIAL_ISSUER_CRYPTO_PROVIDER_INFO = loadPotentialIssuerKeys() - val POTENTIAL_JWT_CRYPTO_PROVIDER = SimpleJWTCryptoProvider(JWSAlgorithm.ES256, - ECDSASigner(ECKey.parseFromPEMEncodedObjects(POTENTIAL_ISSUER_PRIV + POTENTIAL_ISSUER_PUB).toECKey()), ECDSAVerifier(ECKey.parseFromPEMEncodedObjects(POTENTIAL_ISSUER_PUB).toECKey())) + val POTENTIAL_JWT_CRYPTO_PROVIDER = SimpleJWTCryptoProvider( + JWSAlgorithm.ES256, + ECDSASigner(ECKey.parseFromPEMEncodedObjects(POTENTIAL_ISSUER_PRIV + POTENTIAL_ISSUER_PUB).toECKey()), + ECDSAVerifier(ECKey.parseFromPEMEncodedObjects(POTENTIAL_ISSUER_PUB).toECKey()) + ) fun readKeySpec(pem: String): ByteArray { val publicKeyPEM = pem diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/SSIKit2WalletService.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/SSIKit2WalletService.kt index f4f204180..f7cf80ed9 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/SSIKit2WalletService.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/SSIKit2WalletService.kt @@ -52,6 +52,8 @@ import id.walt.webwallet.service.report.ReportService import id.walt.webwallet.service.settings.SettingsService import id.walt.webwallet.service.settings.WalletSetting import id.walt.webwallet.usecase.event.EventLogUseCase +import id.walt.webwallet.utils.StringUtils.couldBeJsonObject +import id.walt.webwallet.utils.StringUtils.parseAsJsonObject import id.walt.webwallet.web.controllers.exchange.PresentationRequestParameter import id.walt.webwallet.web.parameter.CredentialRequestParameter import io.github.oshai.kotlinlogging.KotlinLogging @@ -256,6 +258,7 @@ class SSIKit2WalletService( } else if (resp.status.isSuccess()) { Result.success(if (isResponseRedirectUrl) httpResponseBody else null) } else { + logger.debug { "Presentation failed, return = $httpResponseBody" } if (isResponseRedirectUrl) { Result.failure( PresentationError( @@ -268,11 +271,11 @@ class SSIKit2WalletService( Result.failure( PresentationError( message = - if (httpResponseBody != null) { - Json.parseToJsonElement(httpResponseBody).jsonObject["message"]?.jsonPrimitive?.content + httpResponseBody?.let { + if (it.couldBeJsonObject()) it.parseAsJsonObject().getOrNull()?.get("message")?.jsonPrimitive?.content ?: "Presentation failed" - } - else "Presentation failed", + else it + } ?: "Presentation failed", redirectUri = "" ) ) @@ -500,7 +503,7 @@ class SSIKit2WalletService( is UnsupportedMediaTypeException -> throw throwable is ConflictException -> throw throwable is IllegalStateException -> throw throwable - else -> throw BadRequestException("Unexpected error occurred: ${throwable.localizedMessage}", throwable) + else -> throw BadRequestException("Unexpected error occurred: ${throwable.message}", throwable) } } } diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/utils/Utils.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/utils/Utils.kt index 83ec67c05..ef6707a37 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/utils/Utils.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/utils/Utils.kt @@ -1,6 +1,8 @@ package id.walt.webwallet.utils import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject import java.io.InputStream import java.util.* @@ -9,6 +11,10 @@ object StringUtils { fun hexToInt(hex: String) = hex.removePrefix("0x").hexToInt() fun hexToByteArray(hex: String) = hex.removePrefix("0x").hexToByteArray() fun binToInt(bin: String) = bin.toInt(2) + + + fun String.couldBeJsonObject() = trim().let { s -> s.startsWith("{") && s.endsWith("}") } + fun String.parseAsJsonObject() = runCatching { Json.parseToJsonElement(this).jsonObject } } object HttpUtils { diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/DidCreation.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/DidCreation.kt index 9aa9c69a8..2532218a0 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/DidCreation.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/DidCreation.kt @@ -33,12 +33,6 @@ object DidCreation { post(DidJwkMethodName, { summary = "Create a did:jwk" - request { - queryParameter("keyId") { - description = "Optionally set key ID" - required = false - } - } }) { getWalletService().createDid( DidJwkMethodName, extractDidCreateParameters(DidJwkMethodName, context.request.queryParameters)