Skip to content

Commit

Permalink
Generate a credential offer using a custom URI (#164)
Browse files Browse the repository at this point in the history
  • Loading branch information
dzarras authored May 21, 2024
1 parent 9aa2907 commit 8a69730
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 24 deletions.
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ dependencies {
implementation("com.augustcellars.cose:cose-java:1.1.0") {
because("required by walt.id")
}
implementation(libs.uri.kmp) {
because("To generate Credentials Offer URIs using custom URIs")
}

testImplementation(kotlin("test"))
testImplementation(libs.kotlinx.coroutines.test)
Expand Down
4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ multiformat = "1.1.0"
resultMonad = "1.4.0"
keycloak = "24.0.3"
waltid = "1.0.2404292350-SNAPSHOT"
uri-kmp = "0.0.18"

[libraries]
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
Expand All @@ -41,7 +42,8 @@ did-common = { module = "decentralized-identity:did-common-java", version.ref =
multiformat = { module = "org.erwinkok.multiformat:multiformat", version.ref = "multiformat" }
result-monad = { module = "org.erwinkok.result:result-monad", version.ref = "resultMonad" }
keycloak-admin-client = { module = "org.keycloak:keycloak-admin-client", version.ref = "keycloak" }
waltid-mdoc-credentials = {module = "id.walt:waltid-mdoc-credentials-jvm", version.ref="waltid"}
waltid-mdoc-credentials = { module = "id.walt:waltid-mdoc-credentials-jvm", version.ref = "waltid" }
uri-kmp = { module = "com.eygraber:uri-kmp", version.ref = "uri-kmp" }

[plugins]
foojay-resolver-convention = { id = "org.gradle.toolchains.foojay-resolver-convention", version.ref = "foojay" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ fun beans(clock: Clock) = beans {
val enableMobileDrivingLicence = env.getProperty("issuer.mdl.enabled", true)
val enableMsoMdocPid = env.getProperty<Boolean>("issuer.pid.mso_mdoc.enabled") ?: true
val enableSdJwtVcPid = env.getProperty<Boolean>("issuer.pid.sd_jwt_vc.enabled") ?: true
val credentialsOfferUri = env.getRequiredProperty("issuer.credentialOffer.uri")

//
// Signing key
Expand Down Expand Up @@ -467,7 +468,7 @@ fun beans(clock: Clock) = beans {
}
bean(::GetDeferredCredential)
bean {
CreateCredentialsOffer(ref(), env.getRequiredProperty<String>("issuer.credentialOffer.uri"))
CreateCredentialsOffer(ref(), credentialsOfferUri)
}

//
Expand All @@ -476,7 +477,7 @@ fun beans(clock: Clock) = beans {
bean {
val metaDataApi = MetaDataApi(ref(), ref())
val walletApi = WalletApi(ref(), ref(), ref(), ref())
val issuerUi = IssuerUi(ref(), ref(), ref())
val issuerUi = IssuerUi(credentialsOfferUri, ref(), ref(), ref())
val issuerApi = IssuerApi(ref())
metaDataApi.route.and(walletApi.route).and(issuerUi.router).and(issuerApi.router)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi

class IssuerUi(
private val credentialsOfferUri: String,
private val metadata: CredentialIssuerMetaData,
private val createCredentialsOffer: CreateCredentialsOffer,
private val generateQrCode: GenerateQqCode,
Expand Down Expand Up @@ -63,19 +64,27 @@ class IssuerUi(
val credentialIds = metadata.credentialConfigurationsSupported.map { it.id.value }
return ServerResponse.ok()
.contentType(MediaType.TEXT_HTML)
.renderAndAwait("generate-credentials-offer-form", mapOf("credentialIds" to credentialIds))
.renderAndAwait(
"generate-credentials-offer-form",
mapOf(
"credentialIds" to credentialIds,
"credentialsOfferUri" to credentialsOfferUri,
),
)
}

@OptIn(ExperimentalEncodingApi::class)
private suspend fun handleGenerateCredentialsOffer(request: ServerRequest): ServerResponse {
log.info("Generating Credentials Offer")
val credentialIds = request.awaitFormData()["credentialIds"]
val formData = request.awaitFormData()
val credentialIds = formData["credentialIds"]
.orEmpty()
.map(::CredentialConfigurationId)
.toSet()
val credentialsOfferUri = formData["credentialsOfferUri"]?.firstOrNull { it.isNotBlank() }

return either {
val credentialsOffer = createCredentialsOffer(credentialIds)
val credentialsOffer = createCredentialsOffer(credentialIds, credentialsOfferUri)
log.info("Successfully generated Credentials Offer. URI: '{}'", credentialsOffer)

val qrCode =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@ import arrow.core.raise.Raise
import arrow.core.raise.ensure
import arrow.core.raise.ensureNotNull
import arrow.core.toNonEmptySetOrNull
import eu.europa.ec.eudi.pidissuer.domain.*
import com.eygraber.uri.Uri
import com.eygraber.uri.toURI
import eu.europa.ec.eudi.pidissuer.domain.CredentialConfiguration
import eu.europa.ec.eudi.pidissuer.domain.CredentialConfigurationId
import eu.europa.ec.eudi.pidissuer.domain.CredentialIssuerMetaData
import eu.europa.ec.eudi.pidissuer.port.input.CreateCredentialsOfferError.InvalidCredentialConfigurationId
import eu.europa.ec.eudi.pidissuer.port.input.CreateCredentialsOfferError.MissingCredentialConfigurationIds
import kotlinx.serialization.Required
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.springframework.web.util.UriComponentsBuilder
import java.net.URI

/**
Expand All @@ -45,6 +48,11 @@ sealed interface CreateCredentialsOfferError {
* The provided Credential Unique Ids are not valid.
*/
data class InvalidCredentialConfigurationId(val id: CredentialConfigurationId) : CreateCredentialsOfferError

/**
* Indicates the Credentials Offer URI cannot be generated.
*/
data class InvalidCredentialsOfferUri(val cause: Throwable) : CreateCredentialsOfferError
}

@Serializable
Expand Down Expand Up @@ -112,16 +120,22 @@ class CreateCredentialsOffer(
) {

context(Raise<CreateCredentialsOfferError>)
operator fun invoke(unvalidatedCredentialConfigurationIds: Set<CredentialConfigurationId>): URI {
operator fun invoke(
unvalidatedCredentialConfigurationIds: Set<CredentialConfigurationId>,
customCredentialsOfferUri: String? = null,
): URI {
val offer = with(metadata) {
val credentialConfigurationIds = validate(unvalidatedCredentialConfigurationIds)
authorizationCodeGrantOffer(credentialConfigurationIds)
}

return UriComponentsBuilder.fromUriString(credentialsOfferUri)
.queryParam("credential_offer", Json.encodeToString(offer))
.build()
.toUri()
return runCatching {
Uri.parse(customCredentialsOfferUri ?: credentialsOfferUri)
.buildUpon()
.appendQueryParameter("credential_offer", Json.encodeToString(offer))
.build()
.toURI()
}.getOrElse { raise(CreateCredentialsOfferError.InvalidCredentialsOfferUri(it)) }
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ issuer.mdl.enabled=true
issuer.mdl.mso_mdoc.encoder=Internal
issuer.mdl.mso_mdoc.encoder.duration=P5D
issuer.mdl.notifications.enabled=true
issuer.credentialOffer.uri=eudi-openid4ci://
issuer.credentialOffer.uri=eudi-openid4vci://
issuer.signing-key=GenerateRandom
issuer.dpop.proof-max-age=PT1M
issuer.dpop.cache-purge-interval=PT10M
Expand Down
10 changes: 7 additions & 3 deletions src/main/resources/i18n/messages.properties
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
eudiw=EU Digital Identity Wallet
generate-new-credentials-offer=Generate new Credentials Offer
please-select-credentials-to-issue=Please select the Credentials you would like to issue
credentials=Credentials
generate=Generate
issue-credentials=Issue Credentials
scan-qr-code-or-click-link-to-issue-credentials=Scan the generated QR Code or click on the link below to issue the requested Credentials
scan-qr-code-to-issue-credentials=Scan the generated QR Code to issue the requested Credentials
qr-code=QR Code
alternatively-you-can-click=Alternatively you can click this
link=Link
or-copy-the-link-below=Or copy the link below
go-back=Go Back
unable-to-generate-credentials-offer=Unable to Generate Credentials Offer
credentials-offer-could-not-be-generated-due-to=The Credentials Offer could not be generated due to the following errors
eu.europa.ec.eudi.pidissuer.port.input.CreateCredentialsOfferError.MissingCredentialUniqueIds=No Credentials selected
eu.europa.ec.eudi.pidissuer.port.input.CreateCredentialsOfferError.InvalidCredentialUniqueIds=Invalid Credentials
eu.europa.ec.eudi.pidissuer.port.input.CreateCredentialsOfferError.MissingCredentialConfigurationIds=No Credentials selected
eu.europa.ec.eudi.pidissuer.port.input.CreateCredentialsOfferError.InvalidCredentialConfigurationId=Invalid Credentials
eu.europa.ec.eudi.pidissuer.port.input.CreateCredentialsOfferError.InvalidCredentialsOfferUri=Invalid Credentials Offer URI
4 changes: 4 additions & 0 deletions src/main/resources/public/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@
min-height: 300px;
max-width: 300px;
max-height: 300px;
}

.credentials-offer-uri {
word-break: break-all;
}
27 changes: 20 additions & 7 deletions src/main/resources/templates/display-credentials-offer.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ <h2 th:text="#{issue-credentials}">Issue Credentials</h2>
</div>
<div class="row mb-2">
<div class="col">
<h4 th:text="#{scan-qr-code-or-click-link-to-issue-credentials} + ':'">
Scan the generated QR Code or click on the link below to issue the requested Credentials:
<h4 th:text="#{scan-qr-code-to-issue-credentials} + ':'">
Scan the generated QR Code to issue the requested Credentials:
</h4>
</div>
</div>
Expand All @@ -44,16 +44,29 @@ <h4 th:text="#{scan-qr-code-or-click-link-to-issue-credentials} + ':'">
th:alt="#{qr-code}"/>
</div>
</div>
<!--/*@thymesVar id="uri" type="kotlin.String"*/-->
<div class="row mb-2 p-2">
<!--/*@thymesVar id="uri" type="kotlin.String"*/-->
<div class="col d-flex justify-content-center align-content-center">
<p><span th:text="#{link} + ':'">Link:</span>
<a href="https://netcompany-intrasoft.com"
th:href="${uri}"
th:text="${uri}">https://netcompany-intrasoft.com</a>
<p>
<span th:text="#{alternatively-you-can-click}">Alternatively you can click this</span>
<a href="https://netcompany-intrasoft.com"
th:href="${uri}"
th:text="#{link}">Link</a>
</p>
</div>
</div>
<div class="mb-2">
<div class="row">
<div class="col d-flex justify-content-center align-content-center">
<p><span th:text="#{or-copy-the-link-below}">Or copy the link below</span></p>
</div>
</div>
<div class="row">
<div class="col d-flex justify-content-center align-content-center">
<code class="credentials-offer-uri" th:text="${uri}">https://netcompany-intrasoft.com</code>
</div>
</div>
</div>
<div class="row">
<div class="col d-flex justify-content-end align-content-center">
<a class="btn btn-warning"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ <h4 th:text="#{please-select-credentials-to-issue} + ':'">Please select the Cred
enctype="application/x-www-form-urlencoded"
th:action="@{/issuer/credentialsOffer/generate}">
<fieldset>
<label class="form-label mb-2" th:text="#{credentials}">Credentials</label>
<!--/*@thymesVar id="credentialIds" type="kotlin.collections.Set"*/-->
<div class="form-check mb-2" th:each="credentialId,iteration : ${credentialIds}">
<input class="form-check-input" type="checkbox" name="credentialIds"
Expand All @@ -47,6 +48,14 @@ <h4 th:text="#{please-select-credentials-to-issue} + ':'">Please select the Cred
for="credentialId-1" th:for="'credentialId-' + ${iteration.index}"
th:text="${credentialId}">credential-1</label>
</div>
<!--/*@thymesVar id="credentialsOfferUri" type="kotlin.String"*/-->
<div class="mt-4 mb-2">
<label for="credentialsOfferUri" class="form-label">Credentials Offer URI</label>
<input class="form-control" type="text"
name="credentialsOfferUri" id="credentialsOfferUri"
placeholder="eudi-openid4ci://" th:placeholder="${credentialsOfferUri}"
value="eudi-openid4ci://" th:value="${credentialsOfferUri}">
</div>
</fieldset>
</form>
</div>
Expand Down

0 comments on commit 8a69730

Please sign in to comment.