Skip to content

Commit

Permalink
docs(prism-agent): ATL-6272 improve issue credentials OAS doc (#907)
Browse files Browse the repository at this point in the history
Signed-off-by: Benjamin Voiturier <[email protected]>
Co-authored-by: Yurii Shynbuiev - IOHK <[email protected]>
  • Loading branch information
bvoiturier and yshyn-iohk authored Feb 28, 2024
1 parent ab1ab64 commit adb4b9e
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import sttp.apispec.{SecurityScheme, Tag}
import sttp.model.headers.AuthenticationScheme

import scala.collection.immutable.ListMap
import io.iohk.atala.issue.controller.IssueEndpoints

object DocModels {

Expand Down Expand Up @@ -110,6 +111,7 @@ object DocModels {
.tags(
List(
ConnectionEndpoints.tag,
IssueEndpoints.tag,
VerificationPolicyEndpoints.tag,
SchemaRegistryEndpoints.tag,
CredentialDefinitionRegistryEndpoints.tag,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,10 @@ object ConnectionEndpoints {
private val tagName = "Connections Management"
private val tagDescription =
s"""
|The '$tagName' endpoints facilitate the initiation of connection flows between the current Agent and peer Agents, regardless of whether they reside in Cloud Agent or edge environments.
|<br>
|The __${tagName}__ endpoints facilitate the initiation of connection flows between the current Agent and peer Agents, regardless of whether they reside in Cloud Agent or edge environments.
|This implementation adheres to the DIDComm Messaging v2.0 - [Out of Band Messages](https://identity.foundation/didcomm-messaging/spec/v2.0/#out-of-band-messages) specification [section 9.5.4](https://identity.foundation/didcomm-messaging/spec/v2.0/#invitation) - to generate invitations.
|The <b>from</b> field of the out-of-band invitation message contains a freshly generated Peer DID that complies with the [did:peer:2](https://identity.foundation/peer-did-method-spec/#generating-a-didpeer2) specification.
|This Peer DID includes the 'uri' location of the DIDComm messaging service, essential for the invitee's subsequent execution of the connection flow.
|<br>
|Upon accepting an invitation, the invitee sends a connection request to the inviter's DIDComm messaging service endpoint.
|The connection request's 'type' attribute must be specified as "https://atalaprism.io/mercury/connections/1.0/request".
|The inviter agent responds with a connection response message, indicated by a 'type' attribute of "https://atalaprism.io/mercury/connections/1.0/response".
Expand Down Expand Up @@ -111,8 +109,7 @@ object ConnectionEndpoints {
)
.description("""
|Retrieve a specific connection flow record from the Agent's database based in its unique `connectionId`.
|The API returns a comprehensive collection of connection flow records within the system, regardless of their state.
|The returned connection item includes essential metadata such as connection ID, thread ID, state, role, participant information, and other relevant details.
|The returned item includes essential metadata such as connection ID, thread ID, state, role, participant information, and other relevant details.
|""".stripMargin)
.tag(tagName)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
package io.iohk.atala.issue.controller

import io.iohk.atala.api.http.EndpointOutputs.*
import io.iohk.atala.api.http.ErrorResponse
import io.iohk.atala.api.http.RequestContext
import io.iohk.atala.api.http.model.PaginationInput
import io.iohk.atala.api.http.{ErrorResponse, RequestContext}
import io.iohk.atala.iam.authentication.apikey.ApiKeyCredentials
import io.iohk.atala.iam.authentication.apikey.ApiKeyEndpointSecurityLogic.apiKeyHeader
import io.iohk.atala.iam.authentication.oidc.JwtCredentials
import io.iohk.atala.iam.authentication.oidc.JwtSecurityLogic.jwtAuthHeader
import io.iohk.atala.issue.controller.http.*
import sttp.apispec.Tag
import sttp.model.StatusCode
import sttp.tapir.*
import sttp.tapir.json.zio.jsonBody

object IssueEndpoints {

private val tagName = "Issue Credentials Protocol"
private val tagDescription =
s"""
|The __${tagName}__ endpoints facilitate the initiation of credential issuance flows between the current Agent and peer Agents, regardless of whether they reside in Cloud Agent or edge environments.
|This implementation adheres to the [Issue Credential Protocol 3.0](https://github.com/decentralized-identity/waci-didcomm/tree/main/issue_credential) specification to execute credential issuance flows.
|The flow is initiated by the issuer who creates a [credential offer](https://github.com/decentralized-identity/waci-didcomm/tree/main/issue_credential#offer-credential) and sends it to the holder's DIDComm messaging service endpoint.
|Upon accepting the received offer, the holder sends a [credential request](https://github.com/decentralized-identity/waci-didcomm/tree/main/issue_credential#request-credential) to the issuer.
|The issuer agent will then issue the credential (JWT or AnonCreds) and send an [issue credential](https://github.com/decentralized-identity/waci-didcomm/tree/main/issue_credential#issue-credential) message containing the verifiable credential to the holder.
|The current implementation only supports one of the three alternative beginnings proposed in the spec, which is "the Issuer begin with an offer".
|""".stripMargin

val tag = Tag(tagName, Some(tagDescription))

private val paginationInput: EndpointInput[PaginationInput] = EndpointInput.derived[PaginationInput]

val createCredentialOffer: Endpoint[
Expand All @@ -29,13 +44,27 @@ object IssueEndpoints {
.in(extractFromRequest[RequestContext](RequestContext.apply))
.in("issue-credentials" / "credential-offers")
.in(jsonBody[CreateIssueCredentialRecordRequest].description("The credential offer object."))
.errorOut(basicFailuresWith(FailureVariant.forbidden, FailureVariant.notFound, FailureVariant.badRequest))
.out(statusCode(StatusCode.Created))
.errorOut(
oneOf(
FailureVariant.forbidden,
FailureVariant.badRequest,
FailureVariant.internalServerError,
FailureVariant.notFound
)
)
.out(
statusCode(StatusCode.Created)
.description("The credential issuance record was created successfully, and is returned in the response body.")
)
.out(jsonBody[IssueCredentialRecord].description("The issue credential record."))
.tag("Issue Credentials Protocol")
.summary("As a credential issuer, create a new credential offer to be sent to a holder.")
.description("Creates a new credential offer in the database")
.name("createCredentialOffer")
.summary("As a credential issuer, create a new credential offer that will be sent to a holder Agent.")
.description("""
|Creates a new credential offer that will be delivered, through a previously established DIDComm connection, to a holder Agent.
|The subsequent credential offer message adheres to the [Issue Credential Protocol 3.0 - Offer Credential](https://github.com/decentralized-identity/waci-didcomm/tree/main/issue_credential#offer-credential) specification.
|The created offer can be of two types: 'JWT' or 'AnonCreds'.
|""".stripMargin)
.tag(tagName)

val getCredentialRecords: Endpoint[
(ApiKeyCredentials, JwtCredentials),
Expand All @@ -50,13 +79,29 @@ object IssueEndpoints {
.in(extractFromRequest[RequestContext](RequestContext.apply))
.in("issue-credentials" / "records")
.in(paginationInput)
.in(query[Option[String]]("thid").description("The thid of a DIDComm communication."))
.errorOut(basicFailuresAndForbidden)
.out(jsonBody[IssueCredentialRecordPage].description("The list of issue credential records."))
.tag("Issue Credentials Protocol")
.summary("Gets the list of issue credential records.")
.description("Get the list of issue credential records paginated")
.in(
query[Option[String]]("thid")
.description("The thread ID associated with a specific credential issue flow execution.")
)
.errorOut(
oneOf(
FailureVariant.forbidden,
FailureVariant.badRequest,
FailureVariant.internalServerError
)
)
.out(
jsonBody[IssueCredentialRecordPage]
.description("The list of issue credential records available found in the Agent's database.")
)
.name("getCredentialRecords")
.summary("Retrieves the list of issue credential records from the Agent's database.")
.description("""
|Retrieves the list of issue credential records from the Agent's database.
|The API returns a comprehensive collection of issue credential flow records within the system, regardless of their state.
|The returned items include essential metadata such as record ID, thread ID, state, role, issued credential, and other relevant details.
|""".stripMargin)
.tag(tagName)

val getCredentialRecord: Endpoint[
(ApiKeyCredentials, JwtCredentials),
Expand All @@ -70,16 +115,28 @@ object IssueEndpoints {
.securityIn(jwtAuthHeader)
.in(extractFromRequest[RequestContext](RequestContext.apply))
.in(
"issue-credentials" / "records" / path[String]("recordId").description(
"The unique identifier of the issue credential record."
"issue-credentials" / "records" / path[String]("recordId")
.description("The `recordId` uniquely identifying the issue credential flow record.")
)
.errorOut(
oneOf(
FailureVariant.forbidden,
FailureVariant.badRequest,
FailureVariant.internalServerError,
FailureVariant.notFound
)
)
.errorOut(basicFailureAndNotFoundAndForbidden)
.out(jsonBody[IssueCredentialRecord].description("The issue credential record."))
.tag("Issue Credentials Protocol")
.summary("Gets an existing issue credential record by its unique identifier.")
.description("Gets issue credential records by record id")
.out(jsonBody[IssueCredentialRecord].description("The specific issue credential flow record."))
.name("getCredentialRecord")
.summary(
"Retrieves a specific issue credential flow record from the Agent's database based on its unique `recordId`."
)
.description("""
|Retrieves a specific issue credential flow record from the Agent's database based on its unique `recordId`.
|The API returns a comprehensive collection of issue credential flow records within the system, regardless of their state.
|The returned items include essential metadata such as record ID, thread ID, state, role, issued credential, and other relevant details.
|""".stripMargin)
.tag(tagName)

val acceptCredentialOffer: Endpoint[
(ApiKeyCredentials, JwtCredentials),
Expand All @@ -93,18 +150,35 @@ object IssueEndpoints {
.securityIn(jwtAuthHeader)
.in(extractFromRequest[RequestContext](RequestContext.apply))
.in(
"issue-credentials" / "records" / path[String]("recordId").description(
"The unique identifier of the issue credential record."
)
"issue-credentials" / "records" / path[String]("recordId")
.description("The `recordId` uniquely identifying the issue credential flow record.")
)
.in("accept-offer")
.in(jsonBody[AcceptCredentialOfferRequest].description("The accept credential offer request object."))
.errorOut(basicFailureAndNotFoundAndForbidden)
.out(jsonBody[IssueCredentialRecord].description("The issue credential offer was successfully accepted."))
.tag("Issue Credentials Protocol")
.summary("As a holder, accepts a credential offer received from an issuer.")
.description("Accepts a credential offer received from a VC issuer and sends back a credential request.")
.in(
jsonBody[AcceptCredentialOfferRequest]
.description("The accept credential offer request object.")
)
.errorOut(
oneOf(
FailureVariant.forbidden,
FailureVariant.badRequest,
FailureVariant.internalServerError,
FailureVariant.notFound
)
)
.out(
jsonBody[IssueCredentialRecord]
.description(
"The issue credential offer was successfully accepted, and the updated record is returned in the response body."
)
)
.name("acceptCredentialOffer")
.summary("As a holder, accept a new credential offer received from another issuer Agent.")
.description("""
|As a holder, accept a new credential offer received from an issuer Agent.
|The subsequent credential request message sent to the issuer adheres to the [Issue Credential Protocol 3.0 - Request Credential](https://github.com/decentralized-identity/waci-didcomm/tree/main/issue_credential#request-credential) specification.
|""".stripMargin)
.tag(tagName)

val issueCredential: Endpoint[
(ApiKeyCredentials, JwtCredentials),
Expand All @@ -118,22 +192,33 @@ object IssueEndpoints {
.securityIn(jwtAuthHeader)
.in(extractFromRequest[RequestContext](RequestContext.apply))
.in(
"issue-credentials" / "records" / path[String]("recordId").description(
"The unique identifier of the issue credential record."
)
"issue-credentials" / "records" / path[String]("recordId")
.description("The `recordId` uniquely identifying the issue credential flow record.")
)
.in("issue-credential")
.errorOut(basicFailureAndNotFoundAndForbidden)
.out(
jsonBody[IssueCredentialRecord].description(
"The request was processed successfully and the credential will be issued asynchronously."
.errorOut(
oneOf(
FailureVariant.forbidden,
FailureVariant.badRequest,
FailureVariant.internalServerError,
FailureVariant.notFound
)
)
.tag("Issue Credentials Protocol")
.summary("As an issuer, issues the verifiable credential related to the specified record.")
.description(
"Sends credential to a holder (holder DID is specified in credential as subjectDid). Credential is constructed from the credential records found by credential id."
.out(
jsonBody[IssueCredentialRecord]
.description("""
|The issue credential request was successfully processed, and the updated record is returned in the response body.
|The credential will be generated and sent to the holder Agent asynchronously.
|""".stripMargin)
)
.name("issueCredential")

.summary("As an issuer, issues the verifiable credential related the identified issuance flow record.")
.description(
"""
|As an issuer, issues the verifiable credential related the identified issuance flow record.
|The JWT or AnonCreds credential will be generated and sent to the holder Agent asynchronously and through DIDComm.
|Note that this endpoint should only be called when automatic issuance is disabled for this record (i.e. `automaticIssuance` attribute set to `false` at offer creation time).
|""".stripMargin
)
.tag(tagName)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ object AcceptCredentialOfferRequest {
object annotations {
object subjectId
extends Annotation[Option[String]](
description = "The short-form subject Prism DID to which the JWT verifiable credential will be issued." +
"This parameter is used for JWT credentials only.",
description = """
|The short-form subject Prism DID to which the JWT verifiable credential will be issued.
|This parameter only applies if the offer is of type 'JWT'.
|""".stripMargin,
example = Some("did:prism:3bb0505d13fcb04d28a48234edb27b0d4e6d7e18a81e2c1abab58f3bbc21ce6f")
)

Expand Down
Loading

0 comments on commit adb4b9e

Please sign in to comment.