diff --git a/src/main/kotlin/id/walt/db/models/WalletCredentials.kt b/src/main/kotlin/id/walt/db/models/WalletCredentials.kt index ce5757d..dbd22ed 100644 --- a/src/main/kotlin/id/walt/db/models/WalletCredentials.kt +++ b/src/main/kotlin/id/walt/db/models/WalletCredentials.kt @@ -1,8 +1,13 @@ package id.walt.db.models +import id.walt.crypto.utils.JwsUtils.decodeJws import kotlinx.datetime.Instant import kotlinx.datetime.toKotlinInstant import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.jsonObject import kotlinx.uuid.UUID import kotlinx.uuid.toKotlinUUID import org.jetbrains.exposed.sql.ResultRow @@ -28,7 +33,47 @@ data class WalletCredential( val document: String, val disclosures: String?, val addedOn: Instant, + + val parsedDocument: JsonObject? = parseDocument(document, id) ) { + + companion object { + fun parseDocument(document: String, id: String) = + runCatching { + when { + document.startsWith("{") -> Json.parseToJsonElement(document).jsonObject + document.startsWith("ey") -> document.decodeJws().payload + .run { jsonObject["vc"]?.jsonObject ?: jsonObject } + + else -> throw IllegalArgumentException("Unknown credential format") + }.toMutableMap().also { + it.putIfAbsent("id", JsonPrimitive(id)) + }.let { + JsonObject(it) + } + }.onFailure { it.printStackTrace() } + .getOrNull() + } + + /* + val parsedDocument: JsonObject? + get() = + runCatching { + when { + document.startsWith("{") -> Json.parseToJsonElement(document).jsonObject + document.startsWith("ey") -> document.decodeJws().payload + .run { jsonObject["vc"]?.jsonObject ?: jsonObject } + + else -> throw IllegalArgumentException("Unknown credential format") + }.toMutableMap().also { + it.putIfAbsent("id", JsonPrimitive(id)) + }.let { + JsonObject(it) + } + }.onFailure { it.printStackTrace() } + .getOrNull()*/ + + constructor(result: ResultRow) : this( wallet = result[WalletCredentials.wallet].value.toKotlinUUID(), id = result[WalletCredentials.id], diff --git a/src/main/kotlin/id/walt/service/SSIKit2WalletService.kt b/src/main/kotlin/id/walt/service/SSIKit2WalletService.kt index 34516ef..e35301f 100644 --- a/src/main/kotlin/id/walt/service/SSIKit2WalletService.kt +++ b/src/main/kotlin/id/walt/service/SSIKit2WalletService.kt @@ -18,6 +18,7 @@ import id.walt.did.helpers.WaltidServices import id.walt.did.utils.EnumUtils.enumValueIgnoreCase import id.walt.oid4vc.data.GrantType import id.walt.oid4vc.data.OpenIDProviderMetadata +import id.walt.oid4vc.data.dif.PresentationDefinition import id.walt.oid4vc.providers.OpenIDClientConfig import id.walt.oid4vc.providers.SIOPProviderConfig import id.walt.oid4vc.requests.* @@ -68,24 +69,7 @@ class SSIKit2WalletService(accountId: UUID, walletId: UUID) : WalletService(acco } } - override fun listCredentials(): List = CredentialsService.list(walletId).mapNotNull { - val credentialId = it.id - runCatching { - val cred = it.document - val parsedCred = when { - cred.startsWith("{") -> Json.parseToJsonElement(cred).jsonObject - cred.startsWith("ey") -> cred.decodeJws().payload - .run { jsonObject["vc"]?.jsonObject ?: jsonObject } - - else -> throw IllegalArgumentException("Unknown credential format") - } - Credential(parsedCred, cred) - }.onFailure { it.printStackTrace() }.getOrNull()?.let { cred -> - Credential(JsonObject(cred.parsedCredential.toMutableMap().also { - it.putIfAbsent("id", JsonPrimitive(credentialId)) - }), cred.rawCredential) - } - } + override fun listCredentials(): List = CredentialsService.list(walletId) override suspend fun listRawCredentials(): List = CredentialsService.list(walletId).map { it.document @@ -97,6 +81,44 @@ class SSIKit2WalletService(accountId: UUID, walletId: UUID) : WalletService(acco CredentialsService.get(walletId, credentialId) ?: throw IllegalArgumentException("WalletCredential not found for credentialId: $credentialId") + override fun matchCredentialsByPresentationDefinition(presentationDefinition: PresentationDefinition): List { + val credentialList = listCredentials() + + println("WalletCredential list is: ${credentialList.map { it.parsedDocument?.get("type")!!.jsonArray }}") + + data class TypeFilter(val path: String, val type: String? = null, val pattern: String) + + val filters = presentationDefinition.inputDescriptors.mapNotNull { + it.constraints?.fields?.map { + val path = it.path.first().removePrefix("$.") + val filterType = it.filter?.get("type")?.jsonPrimitive?.content + val filterPattern = it.filter?.get("pattern")?.jsonPrimitive?.content + ?: throw IllegalArgumentException("No filter pattern in presentation definition constraint") + + TypeFilter(path, filterType, filterPattern) + } + } + println("Using filters: $filters") + + val matchedCredentials = credentialList.filter { credential -> + filters.any { fields -> + fields.all { typeFilter -> + val credField = credential.parsedDocument!![typeFilter.path] ?: return@all false + + when (credField) { + is JsonPrimitive -> credField.jsonPrimitive.content == typeFilter.pattern + is JsonArray -> credField.jsonArray.last().jsonPrimitive.content == typeFilter.pattern + else -> false + } + } + } + } + println("Matched credentials: $matchedCredentials") + + + return matchedCredentials + } + private fun getQueryParams(url: String): Map> { val params: MutableMap> = HashMap() val urlParts = url.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() @@ -155,18 +177,24 @@ class SSIKit2WalletService(accountId: UUID, walletId: UUID) : WalletService(acco val redirectUri: String? ) : IllegalArgumentException(message) + /** * @return redirect uri */ - override suspend fun usePresentationRequest(request: String, did: String): Result { + override suspend fun usePresentationRequest(request: String, did: String, selectedCredentialIds: List): Result { val credentialWallet = getCredentialWallet(did) val authReq = AuthorizationRequest.fromHttpQueryString(Url(request).encodedQuery) println("Auth req: $authReq") - val walletSession = credentialWallet.initializeAuthorization(authReq, 60.seconds) - println("Resolved presentation definition: ${walletSession.authorizationRequest!!.presentationDefinition!!.toJSONString()}") - val tokenResponse = credentialWallet.processImplicitFlowAuthorization(walletSession.authorizationRequest!!) - val resp = ktorClient.submitForm(walletSession.authorizationRequest!!.responseUri!!, + + println("USING PRESENTATION REQUEST, SELECTED CREDENTIALS: $selectedCredentialIds") + + val presentationSession = credentialWallet.initializeAuthorization(authReq, 60.seconds) + .copy(selectedCredentialIds = selectedCredentialIds.toSet()) + println("Resolved presentation definition: ${presentationSession.authorizationRequest!!.presentationDefinition!!.toJSONString()}") + + val tokenResponse = credentialWallet.processImplicitFlowAuthorization(presentationSession.authorizationRequest!!) + val resp = ktorClient.submitForm(presentationSession.authorizationRequest!!.responseUri!!, parameters { tokenResponse.toHttpParameters().forEach { entry -> entry.value.forEach { append(entry.key, it) } @@ -174,7 +202,7 @@ class SSIKit2WalletService(accountId: UUID, walletId: UUID) : WalletService(acco }) val httpResponseBody = runCatching { resp.bodyAsText() }.getOrNull() val isResponseRedirectUrl = - httpResponseBody != null && httpResponseBody.take(8).lowercase().let { + httpResponseBody != null && httpResponseBody.take(10).lowercase().let { @Suppress("HttpUrlsUsage") it.startsWith("http://") || it.startsWith("https://") } @@ -545,6 +573,11 @@ class SSIKit2WalletService(accountId: UUID, walletId: UUID) : WalletService(acco override suspend fun getIssuer(name: String): IssuerDataTransferObject = IssuersService.get(walletId, name) ?: throw IllegalArgumentException("Issuer: $name not found for: $walletId") + override fun getCredentialsByIds(credentialIds: List): List { + // todo: select by SQL + return listCredentials().filter { it.id in credentialIds } + } + private fun getDidOptions(method: String, args: Map) = when (method.lowercase()) { "key" -> DidKeyCreateOptions( args["key"]?.let { enumValueIgnoreCase(it.content) } ?: KeyType.Ed25519, diff --git a/src/main/kotlin/id/walt/service/WalletKitWalletService.kt b/src/main/kotlin/id/walt/service/WalletKitWalletService.kt index 56bb4da..ba22ba7 100644 --- a/src/main/kotlin/id/walt/service/WalletKitWalletService.kt +++ b/src/main/kotlin/id/walt/service/WalletKitWalletService.kt @@ -3,6 +3,7 @@ package id.walt.service import id.walt.config.ConfigManager import id.walt.config.RemoteWalletConfig import id.walt.db.models.* +import id.walt.oid4vc.data.dif.PresentationDefinition import id.walt.service.dids.DidsService import id.walt.service.dto.LinkedWalletDataTransferObject import id.walt.service.dto.WalletDataTransferObject @@ -124,7 +125,7 @@ class WalletKitWalletService(accountId: UUID, walletId: UUID) : WalletService(ac override fun listCredentials() = runBlocking { authenticatedJsonGet("/api/wallet/credentials/list") - .body()["list"]!!.jsonArray.toList().map { Credential(it.jsonObject, it.toString()) } + .body()["list"]!!.jsonArray.toList().map { WalletCredential(walletId, it.jsonObject["id"]!!.jsonPrimitive.content, it.toString(), null, Instant.DISTANT_PAST) } } override suspend fun listRawCredentials(): List { @@ -139,10 +140,14 @@ class WalletKitWalletService(accountId: UUID, walletId: UUID) : WalletService(ac override suspend fun getCredential(credentialId: String) = WalletCredential( wallet = walletId, id = credentialId, - document = listCredentials().first { it.parsedCredential["id"]?.jsonPrimitive?.content == credentialId }.toString(), + document = listCredentials().first { it.parsedDocument?.get("id")?.jsonPrimitive?.content == credentialId }.toString(), disclosures = null, addedOn = Instant.DISTANT_PAST ) + + override fun matchCredentialsByPresentationDefinition(presentationDefinition: PresentationDefinition): List { + TODO("Not yet implemented") + } /*prettyJson.encodeToString(*/ //) /* override suspend fun getCredential(credentialId: String): String = @@ -195,7 +200,7 @@ class WalletKitWalletService(accountId: UUID, walletId: UUID) : WalletService(ac val state: String? ) - override suspend fun usePresentationRequest(request: String, did: String): Result { + override suspend fun usePresentationRequest(request: String, did: String, selectedCredentialIds: List): Result { val decoded = URLDecoder.decode(request, Charset.defaultCharset()) val queryParams = getQueryParams(decoded) val redirectUri = queryParams["redirect_uri"]?.first() @@ -371,5 +376,9 @@ class WalletKitWalletService(accountId: UUID, walletId: UUID) : WalletService(ac override suspend fun getIssuer(name: String): IssuerDataTransferObject { TODO("Not yet implemented") } + + override fun getCredentialsByIds(credentialIds: List): List { + TODO("Not yet implemented") + } } diff --git a/src/main/kotlin/id/walt/service/WalletService.kt b/src/main/kotlin/id/walt/service/WalletService.kt index 9603a17..499431b 100644 --- a/src/main/kotlin/id/walt/service/WalletService.kt +++ b/src/main/kotlin/id/walt/service/WalletService.kt @@ -3,6 +3,7 @@ package id.walt.service import id.walt.db.models.WalletCredential import id.walt.db.models.WalletDid import id.walt.db.models.WalletOperationHistory +import id.walt.oid4vc.data.dif.PresentationDefinition import id.walt.service.dto.LinkedWalletDataTransferObject import id.walt.service.dto.WalletDataTransferObject import id.walt.service.issuers.IssuerDataTransferObject @@ -13,13 +14,15 @@ import kotlinx.uuid.UUID abstract class WalletService(val accountId: UUID, val walletId: UUID) { // WalletCredentials - abstract fun listCredentials(): List + abstract fun listCredentials(): List abstract suspend fun listRawCredentials(): List abstract suspend fun deleteCredential(id: String): Boolean abstract suspend fun getCredential(credentialId: String): WalletCredential + abstract fun matchCredentialsByPresentationDefinition(presentationDefinition: PresentationDefinition): List + // SIOP - abstract suspend fun usePresentationRequest(request: String, did: String): Result + abstract suspend fun usePresentationRequest(request: String, did: String, selectedCredentialIds: List): Result abstract suspend fun resolvePresentationRequest(request: String): String abstract suspend fun useOfferRequest(offer: String, did: String) @@ -52,9 +55,9 @@ abstract class WalletService(val accountId: UUID, val walletId: UUID) { // Issuers TODO: move each such component to use-case abstract suspend fun listIssuers(): List abstract suspend fun getIssuer(name: String): IssuerDataTransferObject + abstract fun getCredentialsByIds(credentialIds: List): List // TODO: Push - // TODO: SIOP mid steps } diff --git a/src/main/kotlin/id/walt/service/oidc4vc/TestCredentialWallet.kt b/src/main/kotlin/id/walt/service/oidc4vc/TestCredentialWallet.kt index 555e42a..70a5044 100644 --- a/src/main/kotlin/id/walt/service/oidc4vc/TestCredentialWallet.kt +++ b/src/main/kotlin/id/walt/service/oidc4vc/TestCredentialWallet.kt @@ -10,14 +10,12 @@ import id.walt.oid4vc.data.dif.DescriptorMapping import id.walt.oid4vc.data.dif.PresentationDefinition import id.walt.oid4vc.data.dif.PresentationSubmission import id.walt.oid4vc.data.dif.VCFormat -import id.walt.oid4vc.errors.PresentationError import id.walt.oid4vc.interfaces.PresentationResult import id.walt.oid4vc.providers.SIOPCredentialProvider import id.walt.oid4vc.providers.SIOPProviderConfig import id.walt.oid4vc.providers.TokenTarget import id.walt.oid4vc.requests.AuthorizationRequest import id.walt.oid4vc.requests.TokenRequest -import id.walt.oid4vc.responses.TokenErrorCode import id.walt.service.SSIKit2WalletService import io.ktor.client.* import io.ktor.client.call.* @@ -45,7 +43,8 @@ class TestCredentialWallet( val did: String ) : SIOPCredentialProvider(WALLET_BASE_URL, config) { - private val sessionCache = mutableMapOf() + private val sessionCache = mutableMapOf() // TODO not stateless because of oidc4vc library + private val ktorClient = HttpClient(CIO) { install(io.ktor.client.plugins.contentnegotiation.ContentNegotiation) { json() @@ -104,54 +103,10 @@ class TestCredentialWallet( } override fun generatePresentationForVPToken(session: VPresentationSession, tokenRequest: TokenRequest): PresentationResult { - // find credential(s) matching the presentation definition - // for this test wallet implementation, present all credentials in the wallet - - val credentialList = runBlocking { walletService.listCredentials() } - println("WalletCredential list is: ${credentialList.map { it.parsedCredential["type"]!!.jsonArray }}") - - val presentationDefinition = session.presentationDefinition ?: throw PresentationError( - TokenErrorCode.invalid_request, - tokenRequest, - session.presentationDefinition - ) - - data class TypeFilter(val path: String, val type: String? = null, val pattern: String) - - val filters = presentationDefinition.inputDescriptors.mapNotNull { - it.constraints?.fields?.map { - val path = it.path.first().removePrefix("$.") - val filterType = it.filter?.get("type")?.jsonPrimitive?.content - val filterPattern = it.filter?.get("pattern")?.jsonPrimitive?.content - ?: throw IllegalArgumentException("No filter pattern in presentation definition constraint") - - TypeFilter(path, filterType, filterPattern) - } - } - println("Using filters: $filters") - - val matchedCredentials = credentialList.filter { credential -> - filters.any { fields -> - fields.all { typeFilter -> - val credField = credential.parsedCredential[typeFilter.path] ?: return@all false - - when (credField) { - is JsonPrimitive -> credField.jsonPrimitive.content == typeFilter.pattern - is JsonArray -> credField.jsonArray.last().jsonPrimitive.content == typeFilter.pattern - else -> false - } - } - } - } - println("Matched credentials: $matchedCredentials") - - /*val filterString = presentationDefinition.inputDescriptors.flatMap { it.constraints?.fields ?: listOf() } - .firstOrNull { field -> field.path.any { it.contains("type") } }?.filter?.jsonObject.toString() - println("Filter string is: $filterString") - val credentials = - credentialList.filter { filterString.contains(it.parsedCredential["type"]!!.jsonArray.last().jsonPrimitive.content) } - println("Will use credentials: ${credentials.map { it.parsedCredential["type"]!!.jsonArray }}")*/ + println("=== GENERATING PRESENTATION FOR VP TOKEN - Session: $session") + val selectedCredential = session.selectedCredentialIds.toList() + val matchedCredentials = walletService.getCredentialsByIds(selectedCredential) val vp = Json.encodeToString( mapOf( @@ -166,7 +121,7 @@ class TestCredentialWallet( "type" to listOf("VerifiablePresentation"), "id" to "urn:uuid:${UUID.generateUUID().toString().lowercase()}", "holder" to this.did, - "verifiableCredential" to matchedCredentials.map { it.rawCredential } + "verifiableCredential" to matchedCredentials.map { it.document } ) ).toJsonElement() ) @@ -200,8 +155,8 @@ class TestCredentialWallet( return PresentationResult( listOf(JsonPrimitive(signed)), PresentationSubmission( id = "submission 1", - definitionId = presentationDefinition.id, - descriptorMap = matchedCredentials.map { it.rawCredential }.mapIndexed { index, vcJwsStr -> + definitionId = session.presentationDefinition?.id ?: "", + descriptorMap = matchedCredentials.map { it.document }.mapIndexed { index, vcJwsStr -> val vcJws = vcJwsStr.base64UrlToBase64().decodeJws() val type = vcJws.payload["vc"]?.jsonObject?.get("type")?.jsonArray?.last()?.jsonPrimitive?.contentOrNull @@ -290,66 +245,4 @@ class TestCredentialWallet( fun parsePresentationRequest(request: String): AuthorizationRequest { return resolveVPAuthorizationParameters(AuthorizationRequest.fromHttpQueryString(Url(request).encodedQuery)) } - /* - fun start() { - embeddedServer(Netty, port = WALLET_PORT) { - install(ContentNegotiation) { - json() - } - routing { - get("/.well-known/openid-configuration") { - call.respond(metadata.toJSON()) - } - get("/authorize") { - val authReq = AuthorizationRequest.fromHttpParameters(call.parameters.toMap()) - try { - if (authReq.responseType != ResponseType.vp_token.name) { - throw AuthorizationError( - authReq, - AuthorizationErrorCode.unsupported_response_type, - "Only response type vp_token is supported" - ) - } - val tokenResponse = processImplicitFlowAuthorization(authReq) - val redirectLocation = if (authReq.responseMode == ResponseMode.direct_post) { - ktorClient.submitForm( - authReq.responseUri ?: throw AuthorizationError( - authReq, - AuthorizationErrorCode.invalid_request, - "No response_uri parameter found for direct_post response mode" - ), - parametersOf(tokenResponse.toHttpParameters()) - ).body().let { AuthorizationDirectPostResponse.fromJSON(it) }.redirectUri - } else { - tokenResponse.toRedirectUri( - authReq.redirectUri ?: throw AuthorizationError( - authReq, - AuthorizationErrorCode.invalid_request, - "No redirect uri found on authorization request" - ), - authReq.responseMode ?: ResponseMode.fragment - ) - } - if (!redirectLocation.isNullOrEmpty()) { - call.response.apply { - status(HttpStatusCode.Found) - header(HttpHeaders.Location, redirectLocation) - } - } - } catch (authExc: AuthorizationError) { - call.response.apply { - status(HttpStatusCode.Found) - header(HttpHeaders.Location, URLBuilder(authExc.authorizationRequest.redirectUri!!).apply { - parameters.appendAll( - parametersOf( - authExc.toAuthorizationErrorResponse().toHttpParameters() - ) - ) - }.buildString()) - } - } - } - } - }.start() - }*/ } diff --git a/src/main/kotlin/id/walt/service/oidc4vc/VPresentationSession.kt b/src/main/kotlin/id/walt/service/oidc4vc/VPresentationSession.kt index 2c12407..57b70ba 100644 --- a/src/main/kotlin/id/walt/service/oidc4vc/VPresentationSession.kt +++ b/src/main/kotlin/id/walt/service/oidc4vc/VPresentationSession.kt @@ -4,9 +4,9 @@ import id.walt.oid4vc.providers.SIOPSession import id.walt.oid4vc.requests.AuthorizationRequest import kotlinx.datetime.Instant -class VPresentationSession( - id: String, - authorizationRequest: AuthorizationRequest?, - expirationTimestamp: Instant, +data class VPresentationSession( + override val id: String, + override val authorizationRequest: AuthorizationRequest?, + override val expirationTimestamp: Instant, val selectedCredentialIds: Set ) : SIOPSession(id, authorizationRequest, expirationTimestamp) diff --git a/src/main/kotlin/id/walt/web/controllers/CredentialController.kt b/src/main/kotlin/id/walt/web/controllers/CredentialController.kt index fdf0aa7..3fb7e73 100644 --- a/src/main/kotlin/id/walt/web/controllers/CredentialController.kt +++ b/src/main/kotlin/id/walt/web/controllers/CredentialController.kt @@ -10,7 +10,6 @@ import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* import kotlinx.serialization.json.JsonObject -import org.jetbrains.exposed.sql.transactions.transaction fun Application.credentials() = walletRoute { route("credentials", { @@ -25,7 +24,7 @@ fun Application.credentials() = walletRoute { } } }) { - context.respond(getWalletService().run { transaction { listCredentials() } }.map { it.parsedCredential }) + context.respond(getWalletService().listCredentials().map { it.parsedDocument }) } put({ diff --git a/src/main/kotlin/id/walt/web/controllers/ExchangeController.kt b/src/main/kotlin/id/walt/web/controllers/ExchangeController.kt index 04011a5..9228c41 100644 --- a/src/main/kotlin/id/walt/web/controllers/ExchangeController.kt +++ b/src/main/kotlin/id/walt/web/controllers/ExchangeController.kt @@ -1,6 +1,7 @@ package id.walt.web.controllers import id.walt.db.models.WalletOperationHistory +import id.walt.oid4vc.data.dif.PresentationDefinition import id.walt.service.SSIKit2WalletService import id.walt.web.getWalletService import io.github.smiley4.ktorswaggerui.dsl.post @@ -9,6 +10,7 @@ import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* +import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonObject fun Application.exchange() = walletRoute { @@ -49,12 +51,33 @@ fun Application.exchange() = walletRoute { context.respond(HttpStatusCode.OK) } + post("matchCredentialsForPresentationDefinition", { + summary = "Returns the credentials stored in the wallet that match the passed presentation definition" + + request { + body { description = "Presentation definition to match credentials against" } + } + response { + HttpStatusCode.OK to { + body> { + description = "Credentials that match the presentation definition" + } + } + } + }) { + val presentationDefinition = PresentationDefinition.fromJSON(context.receive()) + + val wallet = getWalletService() + val matchedCredentials = wallet.matchCredentialsByPresentationDefinition(presentationDefinition) + + context.respond(matchedCredentials) + } + post("usePresentationRequest", { summary = "Present credential(s) to a Relying Party" request { - queryParameter("did") { description = "The DID to present the credential(s) from" } - body { description = "Presentation request" } + body() } response { HttpStatusCode.OK to { @@ -73,14 +96,19 @@ fun Application.exchange() = walletRoute { }) { val wallet = getWalletService() - val did = call.request.queryParameters["did"] + val req = call.receive() + println("req: $req") + + val request = req.presentationRequest + + val did = req.did ?: wallet.listDids().firstOrNull()?.did ?: throw IllegalArgumentException("No DID to use supplied") + val selectedCredentialIds = req.selectedCredentials + // TODO -> ?: auto matching - val request = call.receiveText() - - val result = wallet.usePresentationRequest(request, did) + val result = wallet.usePresentationRequest(request, did, selectedCredentialIds) if (result.isSuccess) { wallet.addOperationHistory( @@ -89,6 +117,7 @@ fun Application.exchange() = walletRoute { mapOf( "did" to did, "request" to request, + "selected-credentials" to selectedCredentialIds.joinToString(), "success" to "true", "redirect" to result.getOrThrow() ) // change string true to bool @@ -144,3 +173,10 @@ fun Application.exchange() = walletRoute { } } } + +@Serializable +data class UsePresentationRequest( + val did: String? = null, + val selectedCredentials: List, + val presentationRequest: String +) diff --git a/web/src/components/WalletPageHeader.vue b/web/src/components/WalletPageHeader.vue index eacc481..b232eda 100644 --- a/web/src/components/WalletPageHeader.vue +++ b/web/src/components/WalletPageHeader.vue @@ -21,7 +21,7 @@ -