Skip to content

Commit

Permalink
fix: cannot reuse the same credential-offer in oid4vci (#1361)
Browse files Browse the repository at this point in the history
Signed-off-by: Pat Losoponkul <[email protected]>
  • Loading branch information
patlo-iog authored Sep 16, 2024
1 parent cc10ba2 commit 6a0a3ea
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ case class CredentialIssuerControllerImpl(
nonce <- getNonceFromJwt(JWT(jwt))
.mapError(throwable => badRequestInvalidProof(jwt, throwable.getMessage))
session <- credentialIssuerService
.getIssuanceSessionByNonce(nonce)
.getPendingIssuanceSessionByNonce(nonce)
.mapError(_ => badRequestInvalidProof(jwt, "nonce is not associated to the issuance session"))
subjectDid <- parseDIDUrlFromKeyId(JWT(jwt))
.map(_.did)
Expand Down Expand Up @@ -240,11 +240,14 @@ case class CredentialIssuerControllerImpl(
request: NonceRequest
): IO[ErrorResponse, NonceResponse] = {
credentialIssuerService
.getIssuanceSessionByIssuerState(request.issuerState)
.getPendingIssuanceSessionByIssuerState(request.issuerState)
.map(session => NonceResponse(session.nonce))
.mapError(ue =>
internalServerError(detail = Some(s"Unexpected error while creating credential offer: ${ue.userFacingMessage}"))
)
// Ideally we don't want this here, but this is used by keycloak plugin and error is not bubbled to the user.
// We log it manually to help with debugging until we find a better way.
.tapError(error => ZIO.logWarning(error.toString()))
}

override def createCredentialIssuer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ trait OIDCCredentialIssuerService {

def getIssuanceSessionByIssuerState(issuerState: String): IO[Error, IssuanceSession]

def getIssuanceSessionByNonce(nonce: String): IO[Error, IssuanceSession]
def getPendingIssuanceSessionByIssuerState(issuerState: String): IO[Error, IssuanceSession]

def getPendingIssuanceSessionByNonce(nonce: String): IO[Error, IssuanceSession]

def updateIssuanceSession(issuanceSession: IssuanceSession): IO[Error, IssuanceSession]
}
Expand All @@ -85,6 +87,11 @@ object OIDCCredentialIssuerService {
s"Credential configuration with id $credentialConfigurationId not found for issuer $issuerId"
}

case class IssuanceSessionAlreadyIssued(issuerState: String) extends Error {
override def userFacingMessage: String =
s"Issuance session with issuerState $issuerState is already issued"
}

case class CredentialSchemaError(cause: org.hyperledger.identus.pollux.core.model.error.CredentialSchemaError)
extends Error {
override def userFacingMessage: String = cause.userFacingMessage
Expand Down Expand Up @@ -230,6 +237,10 @@ case class OIDCCredentialIssuerServiceImpl(
.mapError(e => ServiceError(s"Failed to get issuance session: ${e.message}"))
.someOrFail(ServiceError(s"The IssuanceSession with the issuerState $issuerState does not exist"))

override def getPendingIssuanceSessionByIssuerState(
issuerState: String
): IO[Error, IssuanceSession] = getIssuanceSessionByIssuerState(issuerState).ensurePendingSession

override def createCredentialOffer(
credentialIssuerBaseUrl: URL,
issuerId: UUID,
Expand Down Expand Up @@ -261,11 +272,12 @@ case class OIDCCredentialIssuerServiceImpl(
)
)

def getIssuanceSessionByNonce(nonce: String): IO[Error, IssuanceSession] = {
def getPendingIssuanceSessionByNonce(nonce: String): IO[Error, IssuanceSession] = {
issuanceSessionStorage
.getByNonce(nonce)
.mapError(e => ServiceError(s"Failed to get issuance session: ${e.message}"))
.someOrFail(ServiceError(s"The IssuanceSession with the nonce $nonce does not exist"))
.ensurePendingSession
}

override def updateIssuanceSession(issuanceSession: IssuanceSession): IO[Error, IssuanceSession] = {
Expand Down Expand Up @@ -295,6 +307,15 @@ case class OIDCCredentialIssuerServiceImpl(
issuingDid = issuerDid,
)
}

extension [R, A](result: ZIO[R, Error, IssuanceSession]) {
def ensurePendingSession: ZIO[R, Error, IssuanceSession] =
result.flatMap { session =>
if session.subjectDid.isEmpty
then ZIO.succeed(session)
else ZIO.fail(IssuanceSessionAlreadyIssued(session.issuerState))
}
}
}

object OIDCCredentialIssuerServiceImpl {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,6 @@ object OIDCCredentialIssuerServiceSpec
MockDIDNonSecretStorage.empty,
getCredentialConfigurationExpectations.toLayer,
layers
)
),
)
}

0 comments on commit 6a0a3ea

Please sign in to comment.