diff --git a/.coordinator.env b/.coordinator.env index 58dfe79..1239378 100644 --- a/.coordinator.env +++ b/.coordinator.env @@ -1,20 +1,18 @@ # default port is 4005 -# PORT=4005 +# PORT=4005 # ONLY for development when we need https; default is false -# ENABLE_HTTPS_FOR_DEV=false +# ENABLE_HTTPS_FOR_DEV=false # default is false # ENABLE_ACCESS_LOGGING=true -# default is false -# ENABLE_STATUS_SERVICE=true +# default is false +ENABLE_STATUS_SERVICE=true # set the service endpoints -# defaults are as follows, -# and point at the service names -# within the docker compose network -# STATUS_SERVICE_ENDPOINT=STATUS:4008 -# SIGNING_SERVICE_ENDPOINT=SIGNER:4006 +# defaults are as follows +# SIGNING_SERVICE=SIGNER:4006 +# STATUS_SERVICE=STATUS:4008 # Tokens for protecting tenant endpoints. # Add a token for any tenant name, @@ -33,4 +31,3 @@ TENANT_TOKEN_RANDOM_TESTING=UNPROTECTED # (for tenant name econ101): # http://myhost.org/instance/econ101/credentials/issue # http://myhost.org/instance/econ101/credentials/status - diff --git a/.env.example b/.env.example index d65229c..56ec8c7 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,10 @@ PORT=4005 #default port is 4005 -ENABLE_HTTPS_FOR_DEV=false # ONLY for development when need https; default is false -ENABLE_ACCESS_LOGGING=true +ENABLE_HTTPS_FOR_DEV=false # ONLY for development when need https; default is false +ENABLE_ACCESS_LOGGING=true ENABLE_STATUS_SERVICE=false -STATUS_SERVICE_ENDPOINT=localhost:4008 -SIGNING_SERVICE_ENDPOINT=localhost:4006 +SIGNING_SERVICE=localhost:4006 +STATUS_SERVICE=localhost:4008 # Tokens for protecting tenant endpoints. # Add a token for any tenant name, @@ -18,4 +18,3 @@ TENANT_TOKEN_TESTING=ohno # The tenant name is specified in the issuing/status invocations like so: # http://myhost.org/instance/econ101/credentials/issue # http://myhost.org/instance/econ101/credentials/status - diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 9adbdf9..5ad9460 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,7 +1,7 @@ module.exports = { - "env": { - "node": true, - "mocha": true + env: { + node: true, + mocha: true }, extends: [ 'standard' diff --git a/.gitignore b/.gitignore index 1fa6f36..554cad6 100644 --- a/.gitignore +++ b/.gitignore @@ -73,7 +73,8 @@ typings/ .env.test .signing-service.env .coordinator.env -.status-service.env +.status-service-db.env +.status-service-git.env # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/.signing-service.env b/.signing-service.env index bff6398..b6a55d9 100644 --- a/.signing-service.env +++ b/.signing-service.env @@ -1,8 +1,8 @@ #default port is 4006 -#PORT=4006 +#PORT=4006 # ONLY for dev when need https; default is false #ENABLE_HTTPS_FOR_DEV=false - + # DID seeds for generating signing keys. # One seed per 'tenant'. # Add DID SEEDS with the pattern TENANT_SEED_[tenant name] @@ -15,4 +15,4 @@ # will be destroyed on restart TENANT_SEED_UN_PROTECTED_TEST=z1AoLPRWHSKasPH1unbY1A6ZFF2Pdzzp7D2CkpK6YYYdKTN TENANT_SEED_PROTECTED_TEST=z1AhT5czCXgNw8fjgz8y3s8AHjBYcpRKH8i9YYbjdCwVRak -TENANT_SEED_RANDOM_TESTING=generate \ No newline at end of file +TENANT_SEED_RANDOM_TESTING=generate diff --git a/.status-service-db.env b/.status-service-db.env new file mode 100644 index 0000000..de5b403 --- /dev/null +++ b/.status-service-db.env @@ -0,0 +1,18 @@ +PORT=4008 # default port is 4008 +ENABLE_HTTPS_FOR_DEV=false # ONLY for dev when need https; default is false + +# Database specific environment variables +CRED_STATUS_SERVICE=mongodb +CRED_STATUS_DID_SEED=z1AackbUm8U69ohKnihoRRFkXcXJd4Ra1PkAboQ2ZRy1ngB +STATUS_CRED_SITE_ORIGIN=https://credentials.example.edu +CRED_STATUS_DB_URL=mongodb+srv://user:pass@domain.mongodb.net?retryWrites=false +CRED_STATUS_DB_HOST=domain.mongodb.net # ignored if CRED_STATUS_DB_URL is configured +CRED_STATUS_DB_PORT=27017 # ignored if CRED_STATUS_DB_URL is configured +CRED_STATUS_DB_USER=testuser # ignored if CRED_STATUS_DB_URL is configured +CRED_STATUS_DB_PASS=testpass # ignored if CRED_STATUS_DB_URL is configured +CRED_STATUS_DB_NAME= # autogenerated if omitted +STATUS_CRED_TABLE_NAME= # autogenerated if omitted +USER_CRED_TABLE_NAME= # autogenerated if omitted +CONFIG_TABLE_NAME= # autogenerated if omitted +EVENT_TABLE_NAME= # autogenerated if omitted +CRED_EVENT_TABLE_NAME= # autogenerated if omitted diff --git a/.status-service-git.env b/.status-service-git.env new file mode 100644 index 0000000..fdcccfc --- /dev/null +++ b/.status-service-git.env @@ -0,0 +1,12 @@ +PORT=4008 # default port is 4008 +ENABLE_HTTPS_FOR_DEV=false # ONLY for dev when need https; default is false + +# Git specific environment variables +CRED_STATUS_SERVICE=github +CRED_STATUS_DID_SEED=z1AackbUm8U69ohKnihoRRFkXcXJd4Ra1PkAboQ2ZRy1ngB +CRED_STATUS_REPO_OWNER=digitalcredentials +CRED_STATUS_REPO_NAME=credential-status-test-jc +CRED_STATUS_REPO_ID=12345678 # only required when CRED_STATUS_SERVICE = 'gitlab' +CRED_STATUS_META_REPO_NAME=credential-status-metadata-test-jc +CRED_STATUS_META_REPO_ID=87654321 # only required when CRED_STATUS_SERVICE = 'gitlab' +CRED_STATUS_ACCESS_TOKEN=REPLACE_THIS_WITH_A_GITHUB_ACCESS_TOKEN diff --git a/.status-service.env b/.status-service.env deleted file mode 100644 index 91b90fd..0000000 --- a/.status-service.env +++ /dev/null @@ -1,14 +0,0 @@ -# default port is 4008 -# PORT=4008 - -# ONLY for dev when need https; default is false -# ENABLE_HTTPS_FOR_DEV=false - -# the CRED_STATUS_* values are used to instantiate the status list manager -# Replace with your own values as described in the README -CRED_STATUS_REPO_OWNER=jchartrand -CRED_STATUS_REPO_NAME=status-test-three -CRED_STATUS_META_REPO_NAME=status-test-meta-three -CRED_STATUS_ACCESS_TOKEN=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 -# replace the following did seed with your own -CRED_STATUS_DID_SEED=z1AackbUm8U69ohKnihoRRFkXcXJd4Ra1PkAboQ2ZRy1ngB diff --git a/CHANGELOG.md b/CHANGELOG.md index 288fb22..c03c091 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # issuer-coordinator Changelog +## 0.3.0 - TBD + +### Changed +- Convert Status List 2021 to Bitstring Status List. +- Differentiate between database status service and Git status service. +- Rename environment variables. +- Update revocation and suspension instructions. + ## 0.2.0 - 2024-04-22 + ### Changed - add did-web-generator and did-key-generator endpoints - update README diff --git a/README.md b/README.md index 7d0170f..8abee24 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ [![Build status](https://img.shields.io/github/actions/workflow/status/digitalcredentials/issuer-coordinator/main.yml?branch=main)](https://github.com/digitalcredentials/issuer-coordinator/actions?query=workflow%3A%22Node.js+CI%22) -An express app that signs Verifiable Credentials. The app coordinates calls to a signing service and a status service, each themselves running as express apps, with all three apps running together within Docker Compose. +An Express app that signs Verifiable Credentials. The app coordinates calls to a signing service and a status service, each themselves running as express apps, with all three apps running together within Docker Compose. ![services-flow](./images/services-flow.jpg) Try it in five minutes or less with our [Quick Start](#quick-start). -Note that you needn't clone this repository to use the issuer - you can simply run the provided docker-compose file, which pulls pre-built images from DockerHub. +Note that you needn't clone this repository to use the issuer - you can simply run the provided docker-compose file, which pulls pre-built images from Docker Hub. ## Table of Contents @@ -18,34 +18,45 @@ Note that you needn't clone this repository to use the issuer - you can simply r - [DID Generators](#did-generators) - [healthz endpoint](#healthz-endpoint) - [Quick Start](#quick-start) + - [Install Docker](#install-docker) + - [Create Docker Compose File](#create-docker-compose-file) + - [Run Service](#run-service) + - [Issue Credentials](#issue-credentials) +- [Versioning](#versioning) - [Configuration](#configuration) - - [Generate a New Key](#generate-a-new-key) + - [Generate New Key](#generate-new-key) - [Tenants](#tenants) - - [Add a Tenant ](#add-a-tenant) - - [Use a Tenant](#use-a-tenant) - - [Enable Revocation](#enable-revocation) + - [Add a Tenant](#add-a-tenant) + - [.coordinator.env](#coordinatorenv) + - [.signing-service.env](#signing-serviceenv) + - [Use a Tenant](#use-a-tenant) + - [Revocation and Suspension](#revocation-and-suspension) + - [Environment Variables](#environment-variables) - [DID Registries](#did-registries) - - [did:key](#didkey) - - [did:web](#didweb) - - [Revoking](#revoking) - [Usage](#usage) - [Issuing](#issuing) - - [Revoking](#revoking) + - [Revoking and Suspending](#revoking-and-suspending) - [Logging](#logging) - [Health Check](#health-check) - [Learner Credential Wallet](#learner-credential-wallet) - [Development](#development) + - [Installation](#installation) - [Testing](#testing) - [Contribute](#contribute) - [License](#license) ## Summary -Use this app to issue [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) with or without a [revocation status](https://www.w3.org/TR/vc-status-list/) that can later be updated to revoke the credential. +Use this service to issue [Verifiable Credentials](https://www.w3.org/TR/vc-data-model-2.0/) with or without a [status](https://www.w3.org/TR/vc-bitstring-status-list/) that can later be updated to revoke or suspend the credential. -We've tried hard to make this simple to install and maintain, and consequently easy to evaluate and understand as you consider whether digital credentials are useful for your project, and whether this issuer would work for you. +Implements two [VC-API](https://w3c-ccg.github.io/vc-api/) HTTP endpoints: -In particular, we've separated the discrete parts of an issuer into smaller self-contained apps that are therefore easier to understand and evaluate, and easier to *wire* together to compose functionality. The apps are typically wired together in a simple docker compose network that pulls images from DockerHub. + * [POST /credentials/issue](https://w3c-ccg.github.io/vc-api/#issue-credential) + * [POST /credentials/status](https://w3c-ccg.github.io/vc-api/#update-status) + +We've tried hard to make this simple to install and maintain, and correspondingly easy to evaluate and understand as you consider whether digital credentials are useful for your project, and whether this issuer would work for you. + +In particular, we've separated the discrete parts of an issuer into smaller self-contained apps that are consequently easier to understand and evaluate, and easier to *wire* together to compose functionality. The apps are typically wired together in a simple Docker Compose network that pulls images from Docker Hub. We've made installation and evaluation a gradual process starting with a simple version that can be up and running in about five minutes, and then progressing with configuration as needed. @@ -56,13 +67,13 @@ These four steps should take less than five minutes, and will get you started wi ### Install Docker -Docker have made this straightforward, with [installers for Windows, Mac, and Linux](https://docs.docker.com/engine/install/) that make it as easy to install Docker as any other application. +Docker has made this straightforward, with [installers for Windows, Mac, and Linux](https://docs.docker.com/engine/install/) that make it as easy to install Docker as any other application. -### Make a Docker Compose file +### Create Docker Compose File -Create a file called compose.yaml and add the following +Create a file called `docker-compose.yml` and add the following: -``` +```yaml version: '3.5' services: coordinator: @@ -73,22 +84,24 @@ services: image: digitalcredentials/signing-service:0.4.0 ``` -### Run it +### Run Service -From the terminal in the same directory that contains your compose.yaml file: +From the terminal in the same directory that contains your `docker-compose.yml` file, run: -```docker compose up``` +```bash +docker compose up +``` -### Issue +### Issue Credentials -Issue cryptographhically signed credentials by posting unsigned verifiable credentials to the issue endpoint, which signs the credential and returns it. Try out your test issuer with this CURL command, which you simply paste into the terminal: +Issue cryptographically signed credentials by posting unsigned Verifiable Credentials to the issue endpoint, which signs the credential and returns it. Try out your test issuer with this cURL command, which you simply paste into the terminal: -``` +```bash curl --location 'http://localhost:4005/instance/test/credentials/issue' \ --header 'Content-Type: application/json' \ --data-raw '{ "@context": [ - "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/ns/credentials/v2", "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json" ], "id": "urn:uuid:2fe53dc9-b2ec-4939-9b2c-0d00f6663b6c", @@ -106,7 +119,7 @@ curl --location 'http://localhost:4005/instance/test/credentials/issue' \ "url": "https://dcconsortium.org", "image": "https://user-images.githubusercontent.com/752326/230469660-8f80d264-eccf-4edd-8e50-ea634d407778.png" }, - "issuanceDate": "2023-08-02T17:43:32.903Z", + "validFrom": "2023-08-02T17:43:32.903Z", "credentialSubject": { "type": [ "AchievementSubject" @@ -133,12 +146,12 @@ curl --location 'http://localhost:4005/instance/test/credentials/issue' \ }' ``` -This should return a fully formed and signed credential printed to the terminal, that should look something like this (it will be all smushed up, but you can format it in something like [json lint](https://jsonlint.com)): +This should return a fully formed and signed credential printed to the terminal, that should look something like this (it will be all smushed up, but you can format it in something like [JSONLint](https://jsonlint.com)): -``` +```json { "@context": [ - "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/ns/credentials/v2", "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json", "https://w3id.org/security/suites/ed25519-2020/v1" ], @@ -157,7 +170,7 @@ This should return a fully formed and signed credential printed to the terminal, "url": "https://dcconsortium.org", "image": "https://user-images.githubusercontent.com/752326/230469660-8f80d264-eccf-4edd-8e50-ea634d407778.png" }, - "issuanceDate": "2023-08-02T17:43:32.903Z", + "validFrom": "2023-08-02T17:43:32.903Z", "credentialSubject": { "type": [ "AchievementSubject" @@ -189,34 +202,42 @@ This should return a fully formed and signed credential printed to the terminal, "proofValue": "z5fk6gq9upyZvcFvJdRdeL5KmvHr69jxEkyDEd2HyQdyhk9VnDEonNSmrfLAcLEDT9j4gGdCG24WHhojVHPbRsNER" } } - ``` WARNING: DO NOT USE THIS TO ISSUE `REAL` CREDENTIALS UNTIL YOU'VE [SET YOUR OWN SIGNING KEY](#generate-a-new-key) -NOTE: CURL can get a bit clunky if you want to experiment, so you might consider trying [Postman](https://www.postman.com/downloads/) which makes it very easy to construct and send http calls. +NOTE: cURL can get a bit clunky if you want to experiment, so you might consider trying [Postman](https://www.postman.com/downloads/) which makes it very easy to construct and send HTTP calls. -NOTE: Revocation is not enabled in the Quick Start. You've got to setup a couple of thigs to [enable revocation](#enable-revocation). +NOTE: Status updates are not enabled in the Quick Start. You've got to setup a couple of things to [enable revocation and suspension](#revocation-and-suspension). Great - you've issued a cryptographically signed credential. Now you'll want to configure the application to issue credentials signed with your own private key (the credential you just issued was signed with a test key that is freely shared so can't be used in production). First a quick word about versioning, and then on to configuration... ## Versioning -The issuer-coordinator and the services it coordinates are all intended to run as docker images within a docker compose network. For convenience we've published those images to Docker Hub so that you don't have to build them locally yourself from the github repositories. +The `issuer-coordinator` and the services it coordinates are all intended to run as Docker images within a Docker Compose network. For convenience, we've published those images to Docker Hub so that you don't have to build them locally yourself from the GitHub repositories. -The images on Docker Hub will of course be updated to add new functionality and fix bugs. Rather than overwrite the default (`latest`) version on Docker Hub for each update, we've adopted the [Semantic Versioning Guidelines](https://semver.org) with our docker image tags. +The images on Docker Hub will of course be updated to add new functionality and fix bugs. Rather than overwrite the default (`latest`) version on Docker Hub for each update, we've adopted the [Semantic Versioning Guidelines](https://semver.org) with our Docker image tags. -We DO NOT provide a `latest` tag so you must provide a tag name (i.e, the version number) for the images in your docker compose file, as we've done [here](./docker-compose.yml). +We DO NOT provide a `latest` tag so you must provide a tag name (i.e, the version number) for the images in your Docker Compose file, as we've done [here](docker-compose.yml). To ensure you've got compatible versions of the services and the coordinator, take a look at our [sample compose files](https://github.com/digitalcredentials/docs/blob/main/deployment-guide/DCCDeploymentGuide.md#docker-compose-examples). -If you do ever want to work from the source code in the repository and build your own images, we've tagged the commits in Github that were used to build the corresponding Docker image. So a github tag of v0.1.0 coresponds to a docker image tag of 0.1.0 +If you do ever want to work from the source code in the repository and build your own images, we've tagged the commits in GitHub that were used to build the corresponding Docker image. So a GitHub tag of `v0.1.0` coresponds to a Docker image tag of `0.1.0`. ## Configuration -There are a few things you'll want to configure, in particular setting your own signing keys (so that only you can sign your credentials). Other options include enabling revocation, enabling healthchecks, and allowing for 'multi-tenant' signing, which you might use, for example, to sign credentials for different courses with a different key. +There are a few things you'll want to configure, in particular setting your own signing keys +(so that only you can sign your credentials). + +Other options include: -Because the issuer-coordinator coordinates calls to other microservices, you'll need to configure both the coordinator itself, and the microservices it calls. Read about configuring the status-service in the [Enable Revocation](#enable-revocation) section and read about configuring the signing-service in the [Add a Tenant](#add-a-tenant) section. +* enabling revocation +* enabling healthchecks +* allowing for 'multi-tenant' signing, which you might use, for example, to sign credentials for different courses with a different key. + +Because the issuer-coordinator coordinates calls to other microservices, you'll also nbeed to configure the microservices it calls. +Read about configuring the status-service in the [Enable Revocation](#enable-revocation) section and +read about configuring the signing-service in the [Add a Tenant](#add-a-tenant) section. You can set the environment variables in any of the usual ways that environment variables are set, including .env files or even setting the variables directly in the docker compose yaml file. Our quick start compose files, for example, all set the variables directly in the compose so as to make it possible to start up the compose with a single command. Further below we describe sample .env files for the coordinator and the dependent services. @@ -244,54 +265,146 @@ The variables that can be configured specifically for the issuer-coordinator: | `HEALTH_CHECK_SERVICE_URL` | local url for this service - see [Health Check](#health-check) | http://SIGNER:4006/healthz | no | | `HEALTH_CHECK_SERVICE_NAME` | service name to use in error messages - see [Health Check](#health-check) | SIGNING-SERVICE | no | | `ENABLE_STATUS_SERVICE` | whether to allocate status - see [Enable Revocation](#enable-revocation) | false | no | -| `STATUS_SERVICE_ENDPOINT` | the endpoint of the status service | STATUS:4008 | no | -| `SIGNING_SERVICE_ENDPOINT` | the endpoint of the signing service | SIGNER:4006 | no | +| `SIGNING_SERVICE` | endpoint for signing service | string | no (default: `SIGNER:4006`) | +| `STATUS_SERVICE` | endpoint for status service | string | no (default: `STATUS:4008`) | -The environment variables can be set directly in the docker compose using the ENV directive, or alternatively within an .env file like this one: +The environment variables can be set directly in the docker compose using the ENV directive, or +alternatively within an .env file like this one: * [.coordinator.env](./.coordinator.env) You'll also need .env files for the signing and status services, something like so: * [.signing-service.env](./.signing-service.env) -* [.status-service.env](./.status-service.env) +* [.status-service-db.env](.status-service-db.env) OR [.status-service-git.env](.status-service-git.env) + +(Note that you only need to configure one of the `.status-service-*.env` files, +depending on if you are using the database status manager or the Git status manager) because +there are two different implementations of a credential status manager - one for database +storage and one for Git storage - you need to populate the appropriate file, depending on +which one you want to use. For the database solution, please define at least the required +fields specified [here](https://github.com/digitalcredentials/status-service-db/blob/main/README.md#environment-variables) +and for the Git solution, please define at least the required fields specified +[here](https://github.com/digitalcredentials/status-service-git/blob/main/README.md#environment-variables). Note: the env variables for the status service and signing service are described below in the [Enable Revocation](#enable-revocation) and [Add a Tenant](#add-a-tenant) sections respectively. -If you've used the QuickStart docker-compose.yml, then you'll have to change it a bit to point at these files. Alternatively, we've pre-configured this [docker-compose.yml](./docker-compose.yml), though, so you can just use that. +If you've used the QuickStart docker-compose.yml, then you'll have to change to point at these files. Alternatively, we've pre-configured this [docker-compose.yml](./docker-compose.yml), though, so you can just use that. + +The issuer is pre-configured with a default signing key for testing that can only be used for testing and evaluation. Any credentials signed with this key are meaningless because anyone else can use it to sign credentials, and so could create fake copies of your credentials which would appear to be properly signed. There would be no way to know that it was fake. So, you'll want to add our own key which you do by generating a new key and setting it for a new tenant name. -The issuer is pre-configured with a preset signing key for testing that can only be used for testing and evaluation. Any credentials signed with this key are meaningless because anyone else can use it to sign credentials, and so could create fake copies of your credentials which would appear to be properly signed. There would be no way to know that it was fake. So, you'll want to add our own key which you do by generating a new key and setting it for a new tenant name. +### Generate New Key -### Generate a new key +To issue your own credentials you must generate your own signing key and keep it private. +We've tried to make that a little easier by providing two endpoints in the signing-service that you +can use to generate a brand new random key - one using the did:key method and one using the did:web method. -To issue your own credentials you must generate your own signing key and keep it private. We've tried to make that a little easier by providing two endpoints in the signing-service that you can use to generate a brand new random key - one using the did:key method and one using the did:web method. You can hit the endpoints directly on the signing-service or if you've got your issuer-coordinator running, you can hit the following convenience endpoints, which simply forward the request to the signer (and return the result) with the following CURL commands (in a terminal): +The `did:key` DID is one of the simpler DID implementations and doesn't require that the DID document be hosted anywhere. However, many organizations are likely to prefer the `did:web` DID for production deployments. This DID format and protocol allows the owner to rotate (change) their signing key without having to update every credential that was signed by old keys. -#### did:key +You can hit the endpoints directly on the signing-service or if you've got your issuer-coordinator running, +you can hit the following convenience endpoints, which simply forward the request to the signer +(and return the result) with the following CURL commands (in a terminal): -`curl --location 'http://localhost:4005/did-key-generator'` +- `did:key`: + ```bash + curl --location 'http://localhost:4005/did-key-generator' + ``` +- `did:web`: + ```bash + curl \ + --location 'localhost:4006/did-web-generator' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "url": "https://raw.githubusercontent.com/user-or-org/did-web-test/main" + }' + ``` -#### did:web +These commands will return a JSON document that contains the following data: -`curl --location 'http://localhost:4005/did-web-generator'` +- a secret seed +- the corresponding DID +- the corresponding DID document Again, both endpoints simply forward your call to the equivalent endpoint in the signing-service. You can read about the endpoints in the [Signing Key section of the signing-service README](https://github.com/digitalcredentials/signing-service/blob/main/README.md#didkey-generator). +Here is an example output for `did:key`: + +```json +{ + "seed": "z1AjQUBZCNoiyPUC8zbbF29gLdZtHRqT6yPdFGtqJa5VfQ6", + "did": "did:key:z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4", + "didDocument": { + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2020/v1", + "https://w3id.org/security/suites/x25519-2020/v1" + ], + "id": "did:key:z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4", + "verificationMethod": [{ + "id": "did:key:z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4#z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4", + "type": "Ed25519VerificationKey2020", + "controller": "did:key:z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4", + "publicKeyMultibase": "z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4" + }], + "authentication": ["did:key:z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4#z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4"], + "assertionMethod": ["did:key:z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4#z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4"], + "capabilityDelegation": ["did:key:z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4#z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4"], + "capabilityInvocation": ["did:key:z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4#z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4"], + "keyAgreement": [{ + "id": "did:key:z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4#z6LSnYW9e4Q4EXTvdjDhKyr2D1ghBfSLa5dJGBfzjG6hyPEt", + "type": "X25519KeyAgreementKey2020", + "controller": "did:key:z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4", + "publicKeyMultibase": "z6LSnYW9e4Q4EXTvdjDhKyr2D1ghBfSLa5dJGBfzjG6hyPEt" + }] + } +} +``` + +...and here is an example output for `did:web` \*: + +```json +{ + "seed": "z1AcNXDnko1P6QMiZ3bxsraNvVtRbpXKeE8GNLDXjBJ5UHz", + "decodedSeed": "DecodedUint8ArraySeed", + "did": "did:web:raw.githubusercontent.com:user-or-org:did-web-test:main", + "didDocument": { + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2020/v1", + "https://w3id.org/security/suites/x25519-2020/v1" + ], + "id": "did:web:raw.githubusercontent.com:user-or-org:did-web-test:main", + "assertionMethod": [ + { + "id": "did:web:raw.githubusercontent.com:user-or-org:did-web-test:main#z6MkfGZKFTyxiH9HgFUHbPQigEWh8PtFaRkESt9oQLiTvhVq", + "type": "Ed25519VerificationKey2020", + "controller": "did:web:raw.githubusercontent.com:user-or-org:did-web-test:main", + "publicKeyMultibase": "z6MkfGZKFTyxiH9HgFUHbPQigEWh8PtFaRkESt9oQLiTvhVq" + } + ] + } +} +``` + +**\* Note:** For the `did:web` key, the value of `didDocument` needs to be hosted at `${DID_WEB_URL}/.well-known/did.json`, where `DID_WEB_URL` is the issuer controlled URL that was passed as the `url` field of the request body in the `did:web` cURL command above. In the example above, this URL is https://raw.githubusercontent.com/user-or-org/did-web-test/main, because we are using GitHub to host a DID document in a repo named `did-web-test`, owned by user/org `user-or-org`, at the path `/.well-known/did.json`. In a production deployment, this might be something like https://registrar.example.edu. + Now that you've got your key you'll need to do two things: -* register it with the signing-service -* enable it the issuer-coordinator +* register it with the signing-service as a 'tenant' +* enable it the issuer-coordinator, possibly with a token protecting it We describe both next... ### Tenants -You might want to allow more than one signing key/DID to be used with the issuer. For example, you might want to sign university/college degree diplomas with a DID that is only used by the registrar, but also allow certificates for individual courses to be signed by by different DIDS that are owned by the faculty or department or even the instructors that teach the courses. +You might want to allow more than one signing key/DID to be used with the issuer. For example, you might +want to sign university/college degree diplomas with a DID that is only used by the registrar, but also allow certificates for individual courses to be signed by different DIDS that are owned by the faculty or department or even the instructors that teach the courses. -We're calling these differents signing authorities 'tenants'. +We're calling these different signing authorities 'tenants'. #### Add a Tenant -Adding a tenant amounts to adding one line each to +Adding a tenant amounts to adding one line each to these environment files: * [.coordinator.env](.coordinator.env) * [.signing-service.env](.signing-service.env) @@ -312,15 +425,16 @@ For example: TENANT_TOKEN_ECON101=988DKLAJH93KDSFV ``` -The token can be anything you like (e.g. a UUID). To leave the endpoint unprotected, set the token value to 'UNPROTECTED', e.g., +The token can be anything you like (e.g. a UUID). To leave the endpoint unprotected, set the token value to `UNPROTECTED`. For example: ``` -TENANT_SEED_ECON101=UNPROTECTED +TENANT_TOKEN_ECON101=UNPROTECTED ``` -If you set a value other than UNPROTECTED then that value must be included as a Bearer token in the Authorization header of any calls to the endpoint. +If you set a value other than `UNPROTECTED`, then that value must be included as a Bearer token in the Authorization header of any calls to the endpoint. -We also suggest using IP filtering on your endpoints to only allow set IPs to access the issuer. Set filtering in your nginx config, or similar. + +We also suggest using IP filtering on your endpoints to only allow set IPs to access the issuer. You can set filters in Nginx or a similar server/traffic configuration tool. ##### .signing-service.env @@ -328,7 +442,25 @@ The [signing-service README](https://github.com/digitalcredentials/signing-servi Note that the signing-service docs describe using convenience endpoints to generate new DIDs. You can call those endpoints directly in the signing-service, or call the same endpoints in the issuer-coordinator, as described above in the [Generate a new key section](#generate-a-new-key). The coordinator endpoints simply forward the request to the signing-service. -#### Use a tenant +In short, add a line like the following to your .signing-service.env (or any other place you'd like to set your env variables): + +``` +TENANT_SEED_{TENANT_NAME}={SEED} +``` + +For example: + +``` +TENANT_SEED_ECON101=z1AjQUBZCNoiyPUC8zbbF29gLdZtHRqT6yPdFGtqJa5VfQ6 +``` + +The seed value is exactly the value of the `seed` property for the key you generated in the [Generate New Key](#generate-new-key) step, which from the `did:key` example in that section, would be: + +``` +"seed": "z1AjQUBZCNoiyPUC8zbbF29gLdZtHRqT6yPdFGtqJa5VfQ6" +``` + +#### Use a Tenant Tenant names are specified in the issuing endpoint like so: @@ -336,17 +468,17 @@ Tenant names are specified in the issuing endpoint like so: http://myhost.org/instance/econ101/credentials/issue ``` -where `econ101` is the tenant name you'd have set in the env files. +where `econ101` is the lower casing of the tenant name you'd have set in the environment files. -If you set a token for the tenant, you'll have to include that token in the auth header as a Bearer token. A curl command to issue on the `econ101` endpoint would then look exactly like the call in the example above, but with the bearer token set in the 'Authorization' header like so: +If you set a token for the tenant, you'll have to include that token in the auth header as a Bearer token. A cURL command to issue from the `econ101` tenant would then look exactly like the call in the example above, but with the bearer token set in the `Authorization` header like so: -``` +```bash curl --location 'http://localhost:4005/instance/econ101/credentials/issue' \ --header 'Authorization: Bearer 988DKLAJH93KDSFV' \ --header 'Content-Type: application/json' \ --data-raw '{ "@context": [ - "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/ns/credentials/v2", "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json" ], "id": "urn:uuid:2fe53dc9-b2ec-4939-9b2c-0d00f6663b6c", @@ -364,7 +496,7 @@ curl --location 'http://localhost:4005/instance/econ101/credentials/issue' \ "url": "https://dcconsortium.org", "image": "https://user-images.githubusercontent.com/752326/230469660-8f80d264-eccf-4edd-8e50-ea634d407778.png" }, - "issuanceDate": "2023-08-02T17:43:32.903Z", + "validFrom": "2023-08-02T17:43:32.903Z", "credentialSubject": { "type": [ "AchievementSubject" @@ -391,98 +523,59 @@ curl --location 'http://localhost:4005/instance/econ101/credentials/issue' \ }' ``` -### Enable Revocation +### Revocation and Suspension -The issuer provides an optional revocation (or 'status') mechanism that implements the [StatusList2021 specification](https://www.w3.org/TR/vc-status-list/), using Github to store the access list. So to use the list you'll have to create two new github repositories that will be used exclusively to manage the status. Full details of the implementation are [here](https://github.com/digitalcredentials/status-list-manager-git) - -For this MVP implementation of the issuer we've only exposed the github options, but if you would like to use gitlab instead, just let us know and we can expose those options. - -Revoking a credential is described in [Usage - revoking](#revoking) - -#### Create Github repositories - -Create two repositories, one public and one private. Call them anything you like, but something like myproject-status-list (public) and myproject-status-list-meta (private) are good choices. If you need help, instructions are [here](https://github.com/digitalcredentials/credential-status-manager-git#create-credential-status-repositories) - -Get a Github token with access to the repositories as described [here](https://github.com/digitalcredentials/credential-status-manager-git#generate-access-tokens) - -Now set these in the [.status-service.env](.status-service.env) file, which has the following options: - -| Key | Description | Default | Required | -| --- | --- | --- | --- | -| `PORT` | http port on which to run the express app | 4005 | no | -| `CRED_STATUS_OWNER` | name of the owner account (personal or organization) in the source control service that will host the credential status resources | no | yes if ENABLE_STATUS_ALLOCATION is true | -| `CRED_STATUS_REPO_NAME` | name of the credential status repository | no | yes if ENABLE_STATUS_ALLOCATION is true | -| `CRED_STATUS_META_REPO_NAME` | name of the credential status metadata repository | no | yes if ENABLE_STATUS_ALLOCATION is true | -| `CRED_STATUS_ACCESS_TOKEN` | Github access token for the credential status repositories | no | yes if ENABLE_STATUS_ALLOCATION is true | -| `CRED_STATUS_DID_SEED` | seed used to deterministically generate DID | no | yes if ENABLE_STATUS_ALLOCATION is true | +The issuer provides an optional revocation/suspension mechanism that implements [Bitstring Status List](https://www.w3.org/TR/vc-bitstring-status-list/), using [database services](https://github.com/digitalcredentials/credential-status-manager-db) or [Git services](https://github.com/digitalcredentials/status-list-manager-git) to store the status list. We recommend using the database implementation for production and test deployments and the Git implementation only for light testing/experimental purposes. -The `CRED_STATUS_DID_SEED` is set to a default seed, usable by anyone for testing. You'll have to change that to use your own seed. Follow the instructions in [Generate a new Key](#generate-a-new-key) to generate a new key seed, and set the value (from the 'seed' property of the object returned from the seed generator). +To enable status updates, set `ENABLE_STATUS_SERVICE` to `true` in `.coordinator.env`. To perform revocations and suspensions, see the [Usage - Revoking and Suspending](#revoking-and-suspending) section below. ### DID Registries To know that a credential was signed with a key that is in fact owned by the claimed issuer, the key (encoded as a [DID](https://www.w3.org/TR/did-core/)) has to be confirmed as really belonging to that issuer. This is typically done by adding the [DID](https://www.w3.org/TR/did-core/) to a well known registry that the verifier checks when verifying a credential. -The DCC provides a number of registries that work with the verifiers in the Learner Credential Wallet and in the online web based [Verifier Plus](https://verifierplus.org). The DCC registries use Github for storage. To request that your DID be added to a registry, submit a pull request in which you've added your [DID](https://www.w3.org/TR/did-core/) to the registry file. - -### did:key - -For the moment, the issuer is set up to use the did:key implemenation of a [DID](https://www.w3.org/TR/did-core/) which is one of the simpler implementations and doesn't require that the [DID](https://www.w3.org/TR/did-core/) document be hosted anywhere. - -### did:web - -The did:web implementation is likely where many implementations will end up, and so you'll eventually want to move to becuase it allows you to rotate (change) your signing keys whithout having to update every document that points at the old keys. +The DCC provides a number of registries that work with the verifiers in the [Learner Credential Wallet (LCW)](#learner-credential-wallet) and in the online web based [Verifier Plus](https://verifierplus.org). The DCC registries use Github for storage. To request that your DID be added to a registry, submit a pull request in which you've added your [DID](https://www.w3.org/TR/did-core/) to the registry file. ## Usage ### Issuing -Pretty much just follow the example in the Quick Start, substituting your own tenant names on the endpoint, and posting your own Verifiable Credential. +To get started issuing your own credentials, just follow the example in the [Quick Start](#quick-start), using your tenant name in the endpoint, and posting your own Verifiable Credential. It is likely that you'll use this issuer as part of some larger system of your own where your flow goes something like: -* student opens a web page on your school site to request their credential -* you authenticate the student with campus authentication -* you retrieve the data for the student's credential from wherever you keep the data -* you create a verifiale credential by adding the student specific data to some verifiable credential template you've preconstructed -* you pass the populated verifiable credential to this issuer -* the issuer signs it and returns it to your calling code -* your code returns the credential to the student -* the student can then share the credential with others -* the student might also want to import the credential into a wallet like the [Learner Credential Wallet (LCW)](#learner-credential-wallet) +* Student opens a web page on your school site to request their credential +* Student signs into their student portal/dashboard +* System fetches the data for the student's credential from wherever you keep the data +* System creates a Verifiable Credential by adding the student specific data to a credential template preconstructed by an admin +* System passes the populated Verifiable Credential to this issuer +* Issuer signs and optionally attaches revocation/suspension status to the credential before returning it to API client +* User interface returns the credential to the student +* Student can share the credential with others +* Student might also want to import the credential into a wallet like the [Learner Credential Wallet (LCW)](#learner-credential-wallet) -The DCC provides another issuing service called the [exchange-coordinator](https://github.com/digitalcredentials/exchange-coordinator) which can make it a bit easier to directly issue credentials to the Learner Credential Wallet. It is used similarly to this issuer, but incorporates a direct 'exchange' with the Learner Credential Wallet. +The DCC provides another issuing service called the [exchange-coordinator](https://github.com/digitalcredentials/exchange-coordinator) which can make it a bit easier to directly issue credentials to the Learner Credential Wallet. It is used similarly to this issuer, but enables a direct "exchange" with the Learner Credential Wallet. -### Revoking +### Revoking and Suspending -Revocation is more fully explained in the StatusList2021 specification and the DCC [git based status implemenation](https://github.com/digitalcredentials/credential-status-manager-git), but it amounts to POSTing an object to the revocation endpoint, like so: +Revocation and suspension are more fully explained in the [Bitstring Status List](https://www.w3.org/TR/vc-bitstring-status-list/) specification and our implemenations thereof, but effectively, it amounts to POSTing an object to the status update endpoint, like so: -``` -{ - credentialId: 'id_added_by_status_manager_to_credentialStatus_propery_of_VC', - credentialStatus: [{ - type: 'StatusList2021Credential', - status: 'revoked' +```bash +curl --location 'http://localhost:4005/instance/test/credentials/status' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "credentialId": "urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1", + "credentialStatus": [{ + "type": "BitstringStatusListCredential", + "status": "revoked" }] -} -``` - -The important part there is the `credentialId`, which is listed in the `credentialStatus` section of the issued credential (`credentialStatus` is added by the status service), and which you have to store at the point when you issue the credential. The `credentialStatus` section looks like this: - -``` -"credentialStatus": { - "id": "https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4#16", - "type": "StatusList2021Entry", - "statusPurpose": "revocation", - "statusListIndex": 16, - "statusListCredential": "https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4" - } +}' ``` -and the id you need is in the `id` property. +The important part there is the `credentialId`. If an issuer provides an `id` field on a credential, the status service will pick this up and save the credential under this ID, as long as it is a valid VC ID, per [these guidelines](https://www.w3.org/TR/vc-data-model-2.0/#identifiers) (e.g., URL, URN). If an ID is not provided, the status service will automatically generate one and attach it to the credential as the `id` field. -So again, an important point here is that you must store the credentialStatus.id for all credentials that you issue. A common approach might be to add another column to whatever local database you are using for your credential records, which would then later make it easier for you to find the id you need by searching the other fields like student name or student id. +It is important that you save this value in your system during the issuance process, as you will need it to perform revocations and suspensions in the future. A common approach might be to add another column to whatever local database you are using for your credential records, which would then later make it easier for you to find the ID you need by searching the other fields like student name or student ID. -NOTE: you'll of course have to have [set up revocation](#enable-revocation) for this to work. If you've only done the QuickStart then you'll not be able to revoke. +**Note:** You'll of course have to enable [status updates](#revocation-and-suspension) for this to work. If you've only done the Quick Start then you'll not be able to revoke and suspend. ### API @@ -570,31 +663,32 @@ If you want failing health notifications sent to an email address, you'll need a ## Learner Credential Wallet -You might now consider importing your new credential into the [Learner Credential Wallet](https://lcw.app) to see how credentials can be managed and shared from an app based wallet. Simply copy the verifiable credential you just generated and paste it into the text box on the 'add credential' screen of the wallet. +You might now consider importing your new credential into the [Learner Credential Wallet](https://lcw.app) to see how credentials can be managed and shared from an app based wallet. Simply copy the Verifiable Credential you just generated and paste it into the text box on the `Add Credential` screen of the wallet. ## Development -To run the issuer-coordinator locally from the cloned repository, you'll also have to clone the repository for the [signing-service](https://github.com/digitalcredentials/signing-service) and have it running locally at the same time. And, similarly, if you want to include status allocation, you'll also have to clone the repository for the [status-service](https://github.com/digitalcredentials/signing-service) and run that locally as well. +To run the `issuer-coordinator` locally from the cloned repository, you'll also have to clone the repository for the [signing-service](https://github.com/digitalcredentials/signing-service) and have it running locally at the same time. Additionally, if you want to include status allocation, you'll also have to clone one of the status service repositories: [status-service-db](https://github.com/digitalcredentials/status-service-db), [status-service-git](https://github.com/digitalcredentials/status-service-git). -When running locally, the system picks up environment variables from the standard [.env](./.env) file, rather than from the env files that we recommend using with docker compose. +When running locally, the system picks up environment variables from the standard [.env](.env) file, rather than from the `.env` files that we recommend using with Docker Compose. ### Installation -Clone code then cd into directory and: +Clone code, cd into the directory, and run: -``` +```bash npm install npm run dev ``` ### Testing -Testing uses supertest, mocha, and nock to test the endpoints. To run tests: +Testing uses `supertest`, `mocha`, and `nock` to test the endpoints. To run tests: -```npm run test``` +```bash +npm run test +``` -Note that when testing we don't actually want to make live http calls to the services, -so we've used nock to intercept the http calls and return precanned data. +Note that when testing we don't actually want to make live HTTP calls to the services, so we've used Nock to intercept the HTTP calls and return precanned data. ## Contribute diff --git a/docker-compose.yml b/docker-compose.yml index 64d85ad..f7874a0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,8 @@ services: env_file: - ./.signing-service.env status: - image: digitalcredentials/status-service:0.1.0 + image: digitalcredentials/status-service-db:0.1.0 + # image: digitalcredentials/status-service-git:0.1.0 env_file: - - ./.status-service.env + - ./.status-service-db.env + # - ./.status-service-git.env diff --git a/package-lock.json b/package-lock.json index 5d6cdce..8f2a3ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@digigatlcredentials/issuer-coordinator", + "name": "@digitalcredentials/issuer-coordinator", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@digigatlcredentials/issuer-coordinator", + "name": "@digitalcredentials/issuer-coordinator", "version": "0.0.0", "dependencies": { "axios": "^1.4.0", diff --git a/package.json b/package.json index af03100..f69cbc5 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@digigatlcredentials/issuer-coordinator", + "name": "@digitalcredentials/issuer-coordinator", "version": "0.0.0", "private": true, "type": "module", diff --git a/src/app.js b/src/app.js index 3adfe77..0d706c7 100644 --- a/src/app.js +++ b/src/app.js @@ -9,18 +9,27 @@ import verifyAuthHeader from './verifyAuthHeader.js' import { getConfig, defaultTenantName } from './config.js' import { getUnsignedVC } from './test-fixtures/vc.js' -function IssuingException (code, message, error = null) { - this.code = code - this.error = error - this.message = message +class IssuingException extends Error { + constructor (code, message, error = null) { + super(message) + this.code = code + this.error = error + this.message = message + } } + async function callService (endpoint, body) { const { data } = await axios.post(endpoint, body) return data } export async function build (opts = {}) { - const { enableStatusService, statusServiceEndpoint, signingServiceEndpoint } = getConfig() + const { + enableStatusService, + statusService, + signingService + } = getConfig() + const app = express() // Add the middleware to write access logs app.use(accessLogger()) @@ -48,7 +57,7 @@ export async function build (opts = {}) { app.get('/', async function (req, res, next) { if (enableStatusService) { try { - await axios.get(`http://${statusServiceEndpoint}/`) + await axios.get(`http://${statusService}/`) } catch (e) { next({ message: 'status service is NOT running.', @@ -58,7 +67,7 @@ export async function build (opts = {}) { } } try { - await axios.get(`http://${signingServiceEndpoint}/`) + await axios.get(`http://${signingService}/`) } catch (e) { next({ message: 'signing service is NOT running.', @@ -75,20 +84,21 @@ export async function build (opts = {}) { }) app.get('/seedgen', async (req, res, next) => { - const response = await axios.get(`http://${signingServiceEndpoint}/did-key-generator`) + const response = await axios.get(`http://${signingService}/seedgen`) return res.json(response.data) }) app.get('/did-key-generator', async (req, res, next) => { - const response = await axios.get(`http://${signingServiceEndpoint}/did-key-generator`) + const response = await axios.get(`http://${signingService}/did-key-generator`) return res.json(response.data) }) app.post('/did-web-generator', async (req, res, next) => { const body = req.body - const response = await axios.post(`http://${signingServiceEndpoint}/did-web-generator`, body) + const response = await axios.post(`http://${signingService}/did-web-generator`, body) return res.json(response.data) }) + app.post('/instance/:tenantName/credentials/issue', async (req, res, next) => { try { @@ -100,9 +110,9 @@ export async function build (opts = {}) { // NOTE: we throw the error here which will then be caught by middleware errorhandler if (!unSignedVC || !Object.keys(unSignedVC).length) throw new IssuingException(400, 'A verifiable credential must be provided in the body') const vcWithStatus = enableStatusService - ? await callService(`http://${statusServiceEndpoint}/credentials/status/allocate`, unSignedVC) + ? await callService(`http://${statusService}/credentials/status/allocate`, unSignedVC) : unSignedVC - const signedVC = await callService(`http://${signingServiceEndpoint}/instance/${tenantName}/credentials/sign`, vcWithStatus) + const signedVC = await callService(`http://${signingService}/instance/${tenantName}/credentials/sign`, vcWithStatus) return res.json(signedVC) } catch (error) { // have to catch async errors and forward error handling @@ -112,7 +122,12 @@ export async function build (opts = {}) { }) // updates the status - // the body will look like: {credentialId: '23kdr', credentialStatus: [{type: 'StatusList2021Credential', status: 'revoked'}]} + /* + { + "credentialId": "urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1", + "credentialStatus": [{ "type": "BitstringStatusListCredential", "status": "revoked" }] + } + */ app.post('/instance/:tenantName/credentials/status', async (req, res, next) => { if (!enableStatusService) return res.status(405).send('The status service has not been enabled.') @@ -123,7 +138,7 @@ export async function build (opts = {}) { await verifyAuthHeader(authHeader, tenantName) // NOTE: we throw the error here which will then be caught by middleware errorhandler if (!statusUpdate || !Object.keys(statusUpdate).length) throw new IssuingException(400, 'A status update must be provided in the body.') - const updateResult = await callService(`http://${statusServiceEndpoint}/credentials/status`, statusUpdate) + const updateResult = await callService(`http://${statusService}/credentials/status`, statusUpdate) return res.json(updateResult) } catch (error) { if (error.response?.status === 404) { diff --git a/src/app.test.js b/src/app.test.js index 7542791..d240820 100644 --- a/src/app.test.js +++ b/src/app.test.js @@ -18,10 +18,12 @@ let app describe('api', () => { before(async () => { - // testDIDSeed = await decodeSeed(process.env.TENANT_SEED_TESTING) testTenantToken = process.env.TENANT_TOKEN_PROTECTED_TEST testTenantToken2 = process.env.TENANT_TOKEN_PROTECTED_TEST_2 - statusUpdateBody = { credentialId: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', credentialStatus: [{ type: 'StatusList2021Credential', status: 'revoked' }] } + statusUpdateBody = { + credentialId: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', + credentialStatus: [{ type: 'BitstringStatusListCredential', status: 'revoked' }] + } }) after(() => { @@ -39,7 +41,7 @@ describe('api', () => { describe('GET /', () => { it('GET / => hello', done => { nock('http://localhost:4006').get('/').reply(200, 'signing-service server status: ok.') - nock('http://localhost:4008').get('/').reply(200, 'signing-service server status: ok.') + nock('http://localhost:4008').get('/').reply(200, 'status-service server status: ok.') request(app) .get('/') @@ -82,7 +84,7 @@ describe('api', () => { .send(getUnsignedVC()) expect(response.header['content-type']).to.have.string('json') - expect(response.status).to.eql(200) + expect(response.status).to.equal(200) expect(response.body) }) @@ -123,7 +125,6 @@ describe('api', () => { }) it('returns signed vc for protected tenant', async () => { - // nock.recorder.rec() protectedNock() const sentCred = getUnsignedVC() const response = await request(app) @@ -132,11 +133,11 @@ describe('api', () => { .send(sentCred) expect(response.header['content-type']).to.have.string('json') - expect(response.status).to.eql(200) + expect(response.status).to.equal(200) const returnedCred = JSON.parse(JSON.stringify(response.body)) // this proof value comes from the nock: - expect(returnedCred.proof.proofValue).to.eql('z5QQ12zr5JvEsKvbnEN2EYZ6punR6Pa5wMJzywGJ2dCh6SSA5oQb9hBiGADsNTbs57bopArwdBHE9kEVemMxcu1Fq') + expect(returnedCred.proof.proofValue).to.equal('z5QQ12zr5JvEsKvbnEN2EYZ6punR6Pa5wMJzywGJ2dCh6SSA5oQb9hBiGADsNTbs57bopArwdBHE9kEVemMxcu1Fq') }) }) @@ -158,7 +159,6 @@ describe('api', () => { }) it('update unprotected status when token not set for tenant in config', done => { - // nock.recorder.rec() unprotectedStatusUpdateNock() request(app) .post('/instance/un_protected_test/credentials/status') @@ -204,7 +204,6 @@ describe('api', () => { }) it('returns 404 for unknown cred id', async () => { - // nock.recorder.rec() unknownStatusIdNock() const statusUpdateBodyWithUnknownId = JSON.parse(JSON.stringify(statusUpdateBody)) statusUpdateBodyWithUnknownId.credentialId = 'kj09ij' @@ -214,7 +213,7 @@ describe('api', () => { .send(statusUpdateBodyWithUnknownId) expect(response.header['content-type']).to.have.string('json') - expect(response.status).to.eql(404) + expect(response.status).to.equal(404) }) it('calls status manager for protected tenant', async () => { @@ -225,7 +224,7 @@ describe('api', () => { .send(statusUpdateBody) expect(response.header['content-type']).to.have.string('json') - expect(response.status).to.eql(200) + expect(response.status).to.equal(200) }) }) diff --git a/src/config.js b/src/config.js index f9650ec..334c115 100644 --- a/src/config.js +++ b/src/config.js @@ -9,8 +9,8 @@ const randtomTenantToken = 'UNPROTECTED' const defaultTenantToken = 'UNPROTECTED' const demoTenantToken = 'UNPROTECTED' -const defaultStatusServiceEndpoint = 'STATUS:4008' -const defaultSigningServiceEndpoint = 'SIGNER:4006' +const defaultSigningService = 'SIGNER:4006' +const defaultStatusService = 'STATUS:4008' // we set a default tenant // It will be overwritten by whatever value is set for default in .env @@ -39,7 +39,6 @@ function parseTenantTokens () { function parseConfig () { const env = process.env const config = Object.freeze({ - port: env.PORT ? parseInt(env.PORT) : defaultPort, enableHttpsForDev: env.ENABLE_HTTPS_FOR_DEV?.toLowerCase() === 'true', enableAccessLogging: env.ENABLE_ACCESS_LOGGING?.toLowerCase() === 'true', consoleLogLevel: env.CONSOLE_LOG_LEVEL?.toLocaleLowerCase() || defaultConsoleLogLevel, @@ -47,8 +46,9 @@ function parseConfig () { errorLogFile: env.ERROR_LOG_FILE, logAllFile: env.LOG_ALL_FILE, enableStatusService: env.ENABLE_STATUS_SERVICE?.toLowerCase() === 'true', - statusServiceEndpoint: env.STATUS_SERVICE_ENDPOINT ? env.STATUS_SERVICE_ENDPOINT : defaultStatusServiceEndpoint, - signingServiceEndpoint: env.SIGNING_SERVICE_ENDPOINT ? env.SIGNING_SERVICE_ENDPOINT : defaultSigningServiceEndpoint + signingService: env.SIGNING_SERVICE ?? defaultSigningService, + statusService: env.STATUS_SERVICE ?? defaultStatusService, + port: env.PORT ? parseInt(env.PORT) : defaultPort }) return config } diff --git a/src/generate.js b/src/generate.js deleted file mode 100644 index f51c785..0000000 --- a/src/generate.js +++ /dev/null @@ -1,24 +0,0 @@ -import { generateSecretKeySeed, decodeSecretKeySeed } from '@digitalcredentials/bnid' -import { driver } from '@digitalcredentials/did-method-key' - -export default async function generateSeed () { - const seed = await generateSecretKeySeed() - const decodedSeed = await decodeSeed(seed) - const didKeyDriver = driver() - const { didDocument } = await didKeyDriver.generate({ seed: decodedSeed }) - const did = didDocument.id - return { seed, did, didDocument } -} - -const decodeSeed = async (secretKeySeed) => { - let secretKeySeedBytes // Uint8Array; - if (secretKeySeed.startsWith('z')) { - // This is a multibase-decoded key seed, like those generated by @digitalcredentials/did-cli - secretKeySeedBytes = decodeSecretKeySeed({ secretKeySeed }) - } else if (secretKeySeed.length >= 32) { - secretKeySeedBytes = (new TextEncoder()).encode(secretKeySeed).slice(0, 32) - } else { - throw TypeError('"secretKeySeed" must be at least 32 bytes, preferably multibase-encoded.') - } - return secretKeySeedBytes -} diff --git a/src/test-fixtures/.env.testing b/src/test-fixtures/.env.testing index ec3b1d6..48d631d 100644 --- a/src/test-fixtures/.env.testing +++ b/src/test-fixtures/.env.testing @@ -1,13 +1,11 @@ - #PORT=4007 -#ENABLE_HTTPS_FOR_DEV=false -SIGNING_SERVICE_ENDPOINT=localhost:4006 -STATUS_SERVICE_ENDPOINT=localhost:4008 +#ENABLE_HTTPS_FOR_DEV=false +SIGNING_SERVICE=localhost:4006 +STATUS_SERVICE=localhost:4008 ENABLE_STATUS_SERVICE=true - - # we deliberately don't set a token for the third tenant to test that the call is still allowed - # i.e,. we want to allow some tenants to work without a token. +# we deliberately don't set a token for the third tenant to test that the call is still allowed +# i.e,. we want to allow some tenants to work without a token. TENANT_TOKEN_UN_PROTECTED_TEST=UNPROTECTED TENANT_TOKEN_PROTECTED_TEST=jds TENANT_TOKEN_PROTECTED_TEST_2=hgf diff --git a/src/test-fixtures/nocks/protected_status_signing.js b/src/test-fixtures/nocks/protected_status_signing.js index a7244fe..c8c1107 100644 --- a/src/test-fixtures/nocks/protected_status_signing.js +++ b/src/test-fixtures/nocks/protected_status_signing.js @@ -1,10 +1,175 @@ import nock from 'nock' -const signedVC = { '@context': ['https://www.w3.org/2018/credentials/v1', 'https://purl.imsglobal.org/spec/ob/v3p0/context.json', 'https://w3id.org/vc/status-list/2021/v1', 'https://w3id.org/security/suites/ed25519-2020/v1', 'https://w3id.org/vc/status-list/2021/v1'], id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', type: ['VerifiableCredential', 'OpenBadgeCredential'], issuer: { id: 'did:key:z6MkrDLfZytHX5acEFds6wAYJbvMbT9UuhKhub4qaguDESCF', type: 'Profile', name: 'Dr David Malan', description: 'Gordon McKay Professor of the Practice of Computer Science, Harvard University', url: 'https://cs.harvard.edu/malan/', image: { id: 'https://certificates.cs50.io/static/success.jpg', type: 'Image' } }, issuanceDate: '2020-01-01T00:00:00Z', name: 'Introduction to Computer Science - CS50x', credentialSubject: { type: 'AchievementSubject', identifier: { type: 'IdentityObject', identityHash: 'jc.chartrand@gmail.com', hashed: 'false' }, achievement: { id: 'http://cs50.harvard.edu', type: 'Achievement', criteria: { narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' }, description: 'CS50 congratulates on completion of CS50x.', name: 'Introduction to Computer Science - CS50x' } }, credentialStatus: { id: 'https://jchartrand.github.io/status-test-three/DKSPRCX9WB#1', type: 'StatusList2021Entry', statusPurpose: 'revocation', statusListIndex: 1, statusListCredential: 'https://jchartrand.github.io/status-test-three/DKSPRCX9WB' }, proof: { type: 'Ed25519Signature2020', created: '2023-08-23T12:44:15Z', verificationMethod: 'did:key:z6MkrDLfZytHX5acEFds6wAYJbvMbT9UuhKhub4qaguDESCF#z6MkrDLfZytHX5acEFds6wAYJbvMbT9UuhKhub4qaguDESCF', proofPurpose: 'assertionMethod', proofValue: 'z5QQ12zr5JvEsKvbnEN2EYZ6punR6Pa5wMJzywGJ2dCh6SSA5oQb9hBiGADsNTbs57bopArwdBHE9kEVemMxcu1Fq' } } + +const signedVcWithStatus = { + '@context': [ + 'https://www.w3.org/ns/credentials/v2', + 'https://purl.imsglobal.org/spec/ob/v3p0/context.json', + 'https://w3id.org/security/suites/ed25519-2020/v1' + ], + type: [ + 'VerifiableCredential', + 'OpenBadgeCredential' + ], + id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', + name: 'Introduction to Computer Science - CS50x', + issuer: { + description: 'Gordon McKay Professor of the Practice of Computer Science, Harvard University', + id: 'did:key:z6MkrDLfZytHX5acEFds6wAYJbvMbT9UuhKhub4qaguDESCF', + image: { + id: 'https://certificates.cs50.io/static/success.jpg', + type: 'Image' + }, + name: 'Dr David Malan', + type: 'Profile', + url: 'https://cs.harvard.edu/malan/' + }, + validFrom: '2020-01-01T00:00:00Z', + credentialSubject: { + achievement: { + criteria: { + narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' + }, + description: 'CS50 congratulates on completion of CS50x.', + id: 'http://cs50.harvard.edu', + name: 'Introduction to Computer Science - CS50x', + type: 'Achievement' + }, + identifier: { + hashed: 'false', + identityHash: 'jc.chartrand@gmail.com', + type: 'IdentityObject' + }, + type: 'AchievementSubject' + }, + credentialStatus: [ + { + id: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4#2', + type: 'BitstringStatusListEntry', + statusPurpose: 'revocation', + statusListIndex: 2, + statusListCredential: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4' + }, + { + id: 'https://digitalcredentials.github.io/credential-status-jc-test/DKSPRCX9WB#5', + type: 'BitstringStatusListEntry', + statusPurpose: 'suspension', + statusListIndex: 5, + statusListCredential: 'https://digitalcredentials.github.io/credential-status-jc-test/DKSPRCX9WB' + } + ], + proof: { + created: '2023-08-23T12:44:15Z', + proofPurpose: 'assertionMethod', + proofValue: 'z5QQ12zr5JvEsKvbnEN2EYZ6punR6Pa5wMJzywGJ2dCh6SSA5oQb9hBiGADsNTbs57bopArwdBHE9kEVemMxcu1Fq', + type: 'Ed25519Signature2020', + verificationMethod: 'did:key:z6MkrDLfZytHX5acEFds6wAYJbvMbT9UuhKhub4qaguDESCF#z6MkrDLfZytHX5acEFds6wAYJbvMbT9UuhKhub4qaguDESCF' + } +} + +const unsignedVcWithoutStatus = { + '@context': [ + 'https://www.w3.org/ns/credentials/v2', + 'https://purl.imsglobal.org/spec/ob/v3p0/context.json' + ], + type: [ + 'VerifiableCredential', + 'OpenBadgeCredential' + ], + id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', + name: 'Introduction to Computer Science - CS50x', + issuer: { + id: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC', + type: 'Profile', + name: 'Dr David Malan', + description: 'Gordon McKay Professor of the Practice of Computer Science, Harvard University', + url: 'https://cs.harvard.edu/malan/', + image: { + id: 'https://certificates.cs50.io/static/success.jpg', + type: 'Image' + } + }, + validFrom: '2020-01-01T00:00:00Z', + credentialSubject: { + type: 'AchievementSubject', + identifier: { + type: 'IdentityObject', + identityHash: 'jc.chartrand@gmail.com', + hashed: 'false' + }, + achievement: { + id: 'http://cs50.harvard.edu', + type: 'Achievement', + criteria: { + narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' + }, + description: 'CS50 congratulates on completion of CS50x.', + name: 'Introduction to Computer Science - CS50x' + } + } +} + +const unsignedVcWithStatus = { + '@context': [ + 'https://www.w3.org/ns/credentials/v2', + 'https://purl.imsglobal.org/spec/ob/v3p0/context.json' + ], + type: [ + 'VerifiableCredential', + 'OpenBadgeCredential' + ], + id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', + name: 'Introduction to Computer Science - CS50x', + issuer: { + id: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC', + type: 'Profile', + name: 'Dr David Malan', + description: 'Gordon McKay Professor of the Practice of Computer Science, Harvard University', + url: 'https://cs.harvard.edu/malan/', + image: { + id: 'https://certificates.cs50.io/static/success.jpg', + type: 'Image' + } + }, + validFrom: '2020-01-01T00:00:00Z', + credentialSubject: { + type: 'AchievementSubject', + identifier: { + type: 'IdentityObject', + identityHash: 'jc.chartrand@gmail.com', + hashed: 'false' + }, + achievement: { + id: 'http://cs50.harvard.edu', + type: 'Achievement', + criteria: { + narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' + }, + description: 'CS50 congratulates on completion of CS50x.', + name: 'Introduction to Computer Science - CS50x' + } + }, + credentialStatus: [ + { + id: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4#2', + type: 'BitstringStatusListEntry', + statusPurpose: 'revocation', + statusListIndex: 2, + statusListCredential: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4' + }, + { + id: 'https://digitalcredentials.github.io/credential-status-jc-test/DKSPRCX9WB#5', + type: 'BitstringStatusListEntry', + statusPurpose: 'suspension', + statusListIndex: 5, + statusListCredential: 'https://digitalcredentials.github.io/credential-status-jc-test/DKSPRCX9WB' + } + ] +} export default () => { nock('http://localhost:4008', { encodedQueryParams: true }) - .post('/credentials/status/allocate', { '@context': ['https://www.w3.org/2018/credentials/v1', 'https://purl.imsglobal.org/spec/ob/v3p0/context.json', 'https://w3id.org/vc/status-list/2021/v1', 'https://w3id.org/security/suites/ed25519-2020/v1'], id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', type: ['VerifiableCredential', 'OpenBadgeCredential'], issuer: { id: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC', type: 'Profile', name: 'Dr David Malan', description: 'Gordon McKay Professor of the Practice of Computer Science, Harvard University', url: 'https://cs.harvard.edu/malan/', image: { id: 'https://certificates.cs50.io/static/success.jpg', type: 'Image' } }, issuanceDate: '2020-01-01T00:00:00Z', name: 'Introduction to Computer Science - CS50x', credentialSubject: { type: 'AchievementSubject', identifier: { type: 'IdentityObject', identityHash: 'jc.chartrand@gmail.com', hashed: 'false' }, achievement: { id: 'http://cs50.harvard.edu', type: 'Achievement', criteria: { narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' }, description: 'CS50 congratulates on completion of CS50x.', name: 'Introduction to Computer Science - CS50x' } } }) - .reply(200, { '@context': ['https://www.w3.org/2018/credentials/v1', 'https://purl.imsglobal.org/spec/ob/v3p0/context.json', 'https://w3id.org/vc/status-list/2021/v1', 'https://w3id.org/security/suites/ed25519-2020/v1', 'https://w3id.org/vc/status-list/2021/v1'], id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', type: ['VerifiableCredential', 'OpenBadgeCredential'], issuer: { id: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC', type: 'Profile', name: 'Dr David Malan', description: 'Gordon McKay Professor of the Practice of Computer Science, Harvard University', url: 'https://cs.harvard.edu/malan/', image: { id: 'https://certificates.cs50.io/static/success.jpg', type: 'Image' } }, issuanceDate: '2020-01-01T00:00:00Z', name: 'Introduction to Computer Science - CS50x', credentialSubject: { type: 'AchievementSubject', identifier: { type: 'IdentityObject', identityHash: 'jc.chartrand@gmail.com', hashed: 'false' }, achievement: { id: 'http://cs50.harvard.edu', type: 'Achievement', criteria: { narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' }, description: 'CS50 congratulates on completion of CS50x.', name: 'Introduction to Computer Science - CS50x' } }, credentialStatus: { id: 'https://jchartrand.github.io/status-test-three/DKSPRCX9WB#1', type: 'StatusList2021Entry', statusPurpose: 'revocation', statusListIndex: 1, statusListCredential: 'https://jchartrand.github.io/status-test-three/DKSPRCX9WB' } }, [ + .post('/credentials/status/allocate', unsignedVcWithoutStatus) + .reply(200, unsignedVcWithStatus, [ 'X-Powered-By', 'Express', 'Access-Control-Allow-Origin', @@ -24,8 +189,8 @@ export default () => { ]) nock('http://localhost:4006', { encodedQueryParams: true }) - .post('/instance/protected_test/credentials/sign', { '@context': ['https://www.w3.org/2018/credentials/v1', 'https://purl.imsglobal.org/spec/ob/v3p0/context.json', 'https://w3id.org/vc/status-list/2021/v1', 'https://w3id.org/security/suites/ed25519-2020/v1', 'https://w3id.org/vc/status-list/2021/v1'], id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', type: ['VerifiableCredential', 'OpenBadgeCredential'], issuer: { id: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC', type: 'Profile', name: 'Dr David Malan', description: 'Gordon McKay Professor of the Practice of Computer Science, Harvard University', url: 'https://cs.harvard.edu/malan/', image: { id: 'https://certificates.cs50.io/static/success.jpg', type: 'Image' } }, issuanceDate: '2020-01-01T00:00:00Z', name: 'Introduction to Computer Science - CS50x', credentialSubject: { type: 'AchievementSubject', identifier: { type: 'IdentityObject', identityHash: 'jc.chartrand@gmail.com', hashed: 'false' }, achievement: { id: 'http://cs50.harvard.edu', type: 'Achievement', criteria: { narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' }, description: 'CS50 congratulates on completion of CS50x.', name: 'Introduction to Computer Science - CS50x' } }, credentialStatus: { id: 'https://jchartrand.github.io/status-test-three/DKSPRCX9WB#1', type: 'StatusList2021Entry', statusPurpose: 'revocation', statusListIndex: 1, statusListCredential: 'https://jchartrand.github.io/status-test-three/DKSPRCX9WB' } }) - .reply(200, signedVC, [ + .post('/instance/protected_test/credentials/sign', unsignedVcWithStatus) + .reply(200, signedVcWithStatus, [ 'X-Powered-By', 'Express', 'Access-Control-Allow-Origin', @@ -43,4 +208,23 @@ export default () => { 'Keep-Alive', 'timeout=5' ]) + + /* nock('http://127.0.0.1:55225', { encodedQueryParams: true }) + .post('/instance/un_protected_test/credentials/issue', unsignedVcWithoutStatus) + .reply(200, signedVcWithStatus, [ + 'X-Powered-By', + 'Express', + 'Access-Control-Allow-Origin', + '*', + 'Content-Type', + 'application/json; charset=utf-8', + 'Content-Length', + '1810', + 'ETag', + 'W/"712-fUBsd5PM46QPKrivsShMP8gvwtc"', + 'Date', + 'Tue, 22 Aug 2023 20:11:09 GMT', + 'Connection', + 'close' + ]) */ } diff --git a/src/test-fixtures/nocks/protected_status_update.js b/src/test-fixtures/nocks/protected_status_update.js index b8409f9..8cda10b 100644 --- a/src/test-fixtures/nocks/protected_status_update.js +++ b/src/test-fixtures/nocks/protected_status_update.js @@ -2,7 +2,10 @@ import nock from 'nock' export default () => { nock('http://localhost:4008', { encodedQueryParams: true }) - .post('/credentials/status', { credentialId: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', credentialStatus: [{ type: 'StatusList2021Credential', status: 'revoked' }] }) + .post('/credentials/status', { + credentialId: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', + credentialStatus: [{ type: 'BitstringStatusListCredential', status: 'revoked' }] + }) .reply(200, { code: 200, message: 'Credential status successfully updated.' }, [ 'X-Powered-By', 'Express', diff --git a/src/test-fixtures/nocks/unknown_status_id_nock.js b/src/test-fixtures/nocks/unknown_status_id_nock.js index 1908174..dc967eb 100644 --- a/src/test-fixtures/nocks/unknown_status_id_nock.js +++ b/src/test-fixtures/nocks/unknown_status_id_nock.js @@ -2,7 +2,10 @@ import nock from 'nock' export default () => { nock('http://localhost:4008', { encodedQueryParams: true }) - .post('/credentials/status', { credentialId: 'kj09ij', credentialStatus: [{ type: 'StatusList2021Credential', status: 'revoked' }] }) + .post('/credentials/status', { + credentialId: 'kj09ij', + credentialStatus: [{ type: 'BitstringStatusListCredential', status: 'revoked' }] + }) .reply(404, { code: 404, message: 'Credential ID not found.' }, [ 'X-Powered-By', 'Express', diff --git a/src/test-fixtures/nocks/unprotected_status_signing.js b/src/test-fixtures/nocks/unprotected_status_signing.js index 11f52d2..58a4b63 100644 --- a/src/test-fixtures/nocks/unprotected_status_signing.js +++ b/src/test-fixtures/nocks/unprotected_status_signing.js @@ -1,10 +1,175 @@ import nock from 'nock' -const signedVC = { '@context': ['https://www.w3.org/2018/credentials/v1', 'https://purl.imsglobal.org/spec/ob/v3p0/context.json', 'https://w3id.org/vc/status-list/2021/v1', 'https://w3id.org/security/suites/ed25519-2020/v1', 'https://w3id.org/vc/status-list/2021/v1'], id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', type: ['VerifiableCredential', 'OpenBadgeCredential'], issuer: { id: 'did:key:z6Mkf2rgv7ef8FmLJ5Py87LMa7nofQgv6AstdkgsXiiCUJEy', type: 'Profile', name: 'Dr David Malan', description: 'Gordon McKay Professor of the Practice of Computer Science, Harvard University', url: 'https://cs.harvard.edu/malan/', image: { id: 'https://certificates.cs50.io/static/success.jpg', type: 'Image' } }, issuanceDate: '2020-01-01T00:00:00Z', name: 'Introduction to Computer Science - CS50x', credentialSubject: { type: 'AchievementSubject', identifier: { type: 'IdentityObject', identityHash: 'jc.chartrand@gmail.com', hashed: 'false' }, achievement: { id: 'http://cs50.harvard.edu', type: 'Achievement', criteria: { narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' }, description: 'CS50 congratulates on completion of CS50x.', name: 'Introduction to Computer Science - CS50x' } }, credentialStatus: { id: 'https://jchartrand.github.io/status-test-three/DKSPRCX9WB#1', type: 'StatusList2021Entry', statusPurpose: 'revocation', statusListIndex: 1, statusListCredential: 'https://jchartrand.github.io/status-test-three/DKSPRCX9WB' }, proof: { type: 'Ed25519Signature2020', created: '2023-08-22T20:11:09Z', verificationMethod: 'did:key:z6Mkf2rgv7ef8FmLJ5Py87LMa7nofQgv6AstdkgsXiiCUJEy#z6Mkf2rgv7ef8FmLJ5Py87LMa7nofQgv6AstdkgsXiiCUJEy', proofPurpose: 'assertionMethod', proofValue: 'z51uH32BFx2mNntaGE55MeHwespoAjetxDkTHBMKtbgGDdc5XiGSTaEGrRgANtT8DV5a6rTNnhT8FKRD4oVnhnxtG' } } + +const signedVcWithStatus = { + '@context': [ + 'https://www.w3.org/ns/credentials/v2', + 'https://purl.imsglobal.org/spec/ob/v3p0/context.json', + 'https://w3id.org/security/suites/ed25519-2020/v1' + ], + type: [ + 'VerifiableCredential', + 'OpenBadgeCredential' + ], + id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', + name: 'Introduction to Computer Science - CS50x', + issuer: { + id: 'did:key:z6Mkf2rgv7ef8FmLJ5Py87LMa7nofQgv6AstdkgsXiiCUJEy', + type: 'Profile', + name: 'Dr David Malan', + description: 'Gordon McKay Professor of the Practice of Computer Science, Harvard University', + url: 'https://cs.harvard.edu/malan/', + image: { + id: 'https://certificates.cs50.io/static/success.jpg', + type: 'Image' + } + }, + validFrom: '2020-01-01T00:00:00Z', + credentialSubject: { + type: 'AchievementSubject', + identifier: { + type: 'IdentityObject', + identityHash: 'jc.chartrand@gmail.com', + hashed: 'false' + }, + achievement: { + id: 'http://cs50.harvard.edu', + type: 'Achievement', + criteria: { + narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' + }, + description: 'CS50 congratulates on completion of CS50x.', + name: 'Introduction to Computer Science - CS50x' + } + }, + credentialStatus: [ + { + id: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4#2', + type: 'BitstringStatusListEntry', + statusPurpose: 'revocation', + statusListIndex: 2, + statusListCredential: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4' + }, + { + id: 'https://digitalcredentials.github.io/credential-status-jc-test/DKSPRCX9WB#5', + type: 'BitstringStatusListEntry', + statusPurpose: 'suspension', + statusListIndex: 5, + statusListCredential: 'https://digitalcredentials.github.io/credential-status-jc-test/DKSPRCX9WB' + } + ], + proof: { + type: 'Ed25519Signature2020', + created: '2023-08-22T20:11:09Z', + verificationMethod: 'did:key:z6Mkf2rgv7ef8FmLJ5Py87LMa7nofQgv6AstdkgsXiiCUJEy#z6Mkf2rgv7ef8FmLJ5Py87LMa7nofQgv6AstdkgsXiiCUJEy', + proofPurpose: 'assertionMethod', + proofValue: 'z51uH32BFx2mNntaGE55MeHwespoAjetxDkTHBMKtbgGDdc5XiGSTaEGrRgANtT8DV5a6rTNnhT8FKRD4oVnhnxtG' + } +} + +const unsignedVcWithoutStatus = { + '@context': [ + 'https://www.w3.org/ns/credentials/v2', + 'https://purl.imsglobal.org/spec/ob/v3p0/context.json' + ], + type: [ + 'VerifiableCredential', + 'OpenBadgeCredential' + ], + id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', + name: 'Introduction to Computer Science - CS50x', + issuer: { + id: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC', + type: 'Profile', + name: 'Dr David Malan', + description: 'Gordon McKay Professor of the Practice of Computer Science, Harvard University', + url: 'https://cs.harvard.edu/malan/', + image: { + id: 'https://certificates.cs50.io/static/success.jpg', + type: 'Image' + } + }, + validFrom: '2020-01-01T00:00:00Z', + credentialSubject: { + type: 'AchievementSubject', + identifier: { + type: 'IdentityObject', + identityHash: 'jc.chartrand@gmail.com', + hashed: 'false' + }, + achievement: { + id: 'http://cs50.harvard.edu', + type: 'Achievement', + criteria: { + narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' + }, + description: 'CS50 congratulates on completion of CS50x.', + name: 'Introduction to Computer Science - CS50x' + } + } +} + +const unsignedVcWithStatus = { + '@context': [ + 'https://www.w3.org/ns/credentials/v2', + 'https://purl.imsglobal.org/spec/ob/v3p0/context.json' + ], + type: [ + 'VerifiableCredential', + 'OpenBadgeCredential' + ], + id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', + name: 'Introduction to Computer Science - CS50x', + issuer: { + id: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC', + type: 'Profile', + name: 'Dr David Malan', + description: 'Gordon McKay Professor of the Practice of Computer Science, Harvard University', + url: 'https://cs.harvard.edu/malan/', + image: { + id: 'https://certificates.cs50.io/static/success.jpg', + type: 'Image' + } + }, + validFrom: '2020-01-01T00:00:00Z', + credentialSubject: { + type: 'AchievementSubject', + identifier: { + type: 'IdentityObject', + identityHash: 'jc.chartrand@gmail.com', + hashed: 'false' + }, + achievement: { + id: 'http://cs50.harvard.edu', + type: 'Achievement', + criteria: { + narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' + }, + description: 'CS50 congratulates on completion of CS50x.', + name: 'Introduction to Computer Science - CS50x' + } + }, + credentialStatus: [ + { + id: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4#2', + type: 'BitstringStatusListEntry', + statusPurpose: 'revocation', + statusListIndex: 2, + statusListCredential: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4' + }, + { + id: 'https://digitalcredentials.github.io/credential-status-jc-test/DKSPRCX9WB#5', + type: 'BitstringStatusListEntry', + statusPurpose: 'suspension', + statusListIndex: 5, + statusListCredential: 'https://digitalcredentials.github.io/credential-status-jc-test/DKSPRCX9WB' + } + ] +} export default () => { nock('http://localhost:4006', { encodedQueryParams: true }) - .post('/instance/un_protected_test/credentials/sign', { '@context': ['https://www.w3.org/2018/credentials/v1', 'https://purl.imsglobal.org/spec/ob/v3p0/context.json', 'https://w3id.org/vc/status-list/2021/v1', 'https://w3id.org/security/suites/ed25519-2020/v1', 'https://w3id.org/vc/status-list/2021/v1'], id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', type: ['VerifiableCredential', 'OpenBadgeCredential'], issuer: { id: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC', type: 'Profile', name: 'Dr David Malan', description: 'Gordon McKay Professor of the Practice of Computer Science, Harvard University', url: 'https://cs.harvard.edu/malan/', image: { id: 'https://certificates.cs50.io/static/success.jpg', type: 'Image' } }, issuanceDate: '2020-01-01T00:00:00Z', name: 'Introduction to Computer Science - CS50x', credentialSubject: { type: 'AchievementSubject', identifier: { type: 'IdentityObject', identityHash: 'jc.chartrand@gmail.com', hashed: 'false' }, achievement: { id: 'http://cs50.harvard.edu', type: 'Achievement', criteria: { narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' }, description: 'CS50 congratulates on completion of CS50x.', name: 'Introduction to Computer Science - CS50x' } }, credentialStatus: { id: 'https://jchartrand.github.io/status-test-three/DKSPRCX9WB#1', type: 'StatusList2021Entry', statusPurpose: 'revocation', statusListIndex: 1, statusListCredential: 'https://jchartrand.github.io/status-test-three/DKSPRCX9WB' } }) - .reply(200, signedVC, [ + .post('/instance/un_protected_test/credentials/sign', unsignedVcWithStatus) + .reply(200, signedVcWithStatus, [ 'X-Powered-By', 'Express', 'Access-Control-Allow-Origin', @@ -23,8 +188,8 @@ export default () => { 'timeout=5' ]) nock('http://localhost:4008', { encodedQueryParams: true }) - .post('/credentials/status/allocate', { '@context': ['https://www.w3.org/2018/credentials/v1', 'https://purl.imsglobal.org/spec/ob/v3p0/context.json', 'https://w3id.org/vc/status-list/2021/v1', 'https://w3id.org/security/suites/ed25519-2020/v1'], id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', type: ['VerifiableCredential', 'OpenBadgeCredential'], issuer: { id: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC', type: 'Profile', name: 'Dr David Malan', description: 'Gordon McKay Professor of the Practice of Computer Science, Harvard University', url: 'https://cs.harvard.edu/malan/', image: { id: 'https://certificates.cs50.io/static/success.jpg', type: 'Image' } }, issuanceDate: '2020-01-01T00:00:00Z', name: 'Introduction to Computer Science - CS50x', credentialSubject: { type: 'AchievementSubject', identifier: { type: 'IdentityObject', identityHash: 'jc.chartrand@gmail.com', hashed: 'false' }, achievement: { id: 'http://cs50.harvard.edu', type: 'Achievement', criteria: { narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' }, description: 'CS50 congratulates on completion of CS50x.', name: 'Introduction to Computer Science - CS50x' } } }) - .reply(200, { '@context': ['https://www.w3.org/2018/credentials/v1', 'https://purl.imsglobal.org/spec/ob/v3p0/context.json', 'https://w3id.org/vc/status-list/2021/v1', 'https://w3id.org/security/suites/ed25519-2020/v1', 'https://w3id.org/vc/status-list/2021/v1'], id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', type: ['VerifiableCredential', 'OpenBadgeCredential'], issuer: { id: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC', type: 'Profile', name: 'Dr David Malan', description: 'Gordon McKay Professor of the Practice of Computer Science, Harvard University', url: 'https://cs.harvard.edu/malan/', image: { id: 'https://certificates.cs50.io/static/success.jpg', type: 'Image' } }, issuanceDate: '2020-01-01T00:00:00Z', name: 'Introduction to Computer Science - CS50x', credentialSubject: { type: 'AchievementSubject', identifier: { type: 'IdentityObject', identityHash: 'jc.chartrand@gmail.com', hashed: 'false' }, achievement: { id: 'http://cs50.harvard.edu', type: 'Achievement', criteria: { narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' }, description: 'CS50 congratulates on completion of CS50x.', name: 'Introduction to Computer Science - CS50x' } }, credentialStatus: { id: 'https://jchartrand.github.io/status-test-three/DKSPRCX9WB#1', type: 'StatusList2021Entry', statusPurpose: 'revocation', statusListIndex: 1, statusListCredential: 'https://jchartrand.github.io/status-test-three/DKSPRCX9WB' } }, [ + .post('/credentials/status/allocate', unsignedVcWithoutStatus) + .reply(200, unsignedVcWithStatus, [ 'X-Powered-By', 'Express', 'Access-Control-Allow-Origin', @@ -43,43 +208,45 @@ export default () => { 'timeout=5' ]) -/* nock('http://127.0.0.1:55225', {"encodedQueryParams":true}) - .post('/instance/un_protected_test/credentials/issue', {"@context":["https://www.w3.org/2018/credentials/v1","https://purl.imsglobal.org/spec/ob/v3p0/context.json","https://w3id.org/vc/status-list/2021/v1","https://w3id.org/security/suites/ed25519-2020/v1"],"id":"urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1","type":["VerifiableCredential","OpenBadgeCredential"],"issuer":{"id":"did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC","type":"Profile","name":"Dr David Malan","description":"Gordon McKay Professor of the Practice of Computer Science, Harvard University","url":"https://cs.harvard.edu/malan/","image":{"id":"https://certificates.cs50.io/static/success.jpg","type":"Image"}},"issuanceDate":"2020-01-01T00:00:00Z","name":"Introduction to Computer Science - CS50x","credentialSubject":{"type":"AchievementSubject","identifier":{"type":"IdentityObject","identityHash":"jc.chartrand@gmail.com","hashed":"false"},"achievement":{"id":"http://cs50.harvard.edu","type":"Achievement","criteria":{"narrative":"Completion of CS50X, including ten problem sets, ten labs, and one final project."},"description":"CS50 congratulates on completion of CS50x.","name":"Introduction to Computer Science - CS50x"}}}) - .reply(200, {"@context":["https://www.w3.org/2018/credentials/v1","https://purl.imsglobal.org/spec/ob/v3p0/context.json","https://w3id.org/vc/status-list/2021/v1","https://w3id.org/security/suites/ed25519-2020/v1","https://w3id.org/vc/status-list/2021/v1"],"id":"urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1","type":["VerifiableCredential","OpenBadgeCredential"],"issuer":{"id":"did:key:z6Mkf2rgv7ef8FmLJ5Py87LMa7nofQgv6AstdkgsXiiCUJEy","type":"Profile","name":"Dr David Malan","description":"Gordon McKay Professor of the Practice of Computer Science, Harvard University","url":"https://cs.harvard.edu/malan/","image":{"id":"https://certificates.cs50.io/static/success.jpg","type":"Image"}},"issuanceDate":"2020-01-01T00:00:00Z","name":"Introduction to Computer Science - CS50x","credentialSubject":{"type":"AchievementSubject","identifier":{"type":"IdentityObject","identityHash":"jc.chartrand@gmail.com","hashed":"false"},"achievement":{"id":"http://cs50.harvard.edu","type":"Achievement","criteria":{"narrative":"Completion of CS50X, including ten problem sets, ten labs, and one final project."},"description":"CS50 congratulates on completion of CS50x.","name":"Introduction to Computer Science - CS50x"}},"credentialStatus":{"id":"https://jchartrand.github.io/status-test-three/DKSPRCX9WB#1","type":"StatusList2021Entry","statusPurpose":"revocation","statusListIndex":1,"statusListCredential":"https://jchartrand.github.io/status-test-three/DKSPRCX9WB"},"proof":{"type":"Ed25519Signature2020","created":"2023-08-22T20:11:09Z","verificationMethod":"did:key:z6Mkf2rgv7ef8FmLJ5Py87LMa7nofQgv6AstdkgsXiiCUJEy#z6Mkf2rgv7ef8FmLJ5Py87LMa7nofQgv6AstdkgsXiiCUJEy","proofPurpose":"assertionMethod","proofValue":"z51uH32BFx2mNntaGE55MeHwespoAjetxDkTHBMKtbgGDdc5XiGSTaEGrRgANtT8DV5a6rTNnhT8FKRD4oVnhnxtG"}}, [ - 'X-Powered-By', - 'Express', - 'Access-Control-Allow-Origin', - '*', - 'Content-Type', - 'application/json; charset=utf-8', - 'Content-Length', - '1810', - 'ETag', - 'W/"712-fUBsd5PM46QPKrivsShMP8gvwtc"', - 'Date', - 'Tue, 22 Aug 2023 20:11:09 GMT', - 'Connection', - 'close' -]); */ + /* nock('http://127.0.0.1:55225', { encodedQueryParams: true }) + .post('/instance/un_protected_test/credentials/issue', unsignedVcWithoutStatus) + .reply(200, signedVcWithStatus, [ + 'X-Powered-By', + 'Express', + 'Access-Control-Allow-Origin', + '*', + 'Content-Type', + 'application/json; charset=utf-8', + 'Content-Length', + '1810', + 'ETag', + 'W/"712-fUBsd5PM46QPKrivsShMP8gvwtc"', + 'Date', + 'Tue, 22 Aug 2023 20:11:09 GMT', + 'Connection', + 'close' + ]) */ } -/* export default () => {nock('http://localhost:4006', {"encodedQueryParams":true}) - .post('/instance/testing3/credentials/sign', {"@context":["https://www.w3.org/2018/credentials/v1","https://purl.imsglobal.org/spec/ob/v3p0/context.json","https://w3id.org/vc/status-list/2021/v1","https://w3id.org/security/suites/ed25519-2020/v1","https://w3id.org/vc/status-list/2021/v1"],"id":"urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1","type":["VerifiableCredential","OpenBadgeCredential"],"issuer":{"id":"did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC","type":"Profile","name":"Dr David Malan","description":"Gordon McKay Professor of the Practice of Computer Science, Harvard University","url":"https://cs.harvard.edu/malan/","image":{"id":"https://certificates.cs50.io/static/success.jpg","type":"Image"}},"issuanceDate":"2020-01-01T00:00:00Z","name":"Introduction to Computer Science - CS50x","credentialSubject":{"type":"AchievementSubject","identifier":{"type":"IdentityObject","identityHash":"jc.chartrand@gmail.com","hashed":"false"},"achievement":{"id":"http://cs50.harvard.edu","type":"Achievement","criteria":{"narrative":"Completion of CS50X, including ten problem sets, ten labs, and one final project."},"description":"CS50 congratulates on completion of CS50x.","name":"Introduction to Computer Science - CS50x"}},"credentialStatus":{"id":"https://jchartrand.github.io/status-test-three/DKSPRCX9WB#1","type":"StatusList2021Entry","statusPurpose":"revocation","statusListIndex":1,"statusListCredential":"https://jchartrand.github.io/status-test-three/DKSPRCX9WB"}}) - .reply(200, {"@context":["https://www.w3.org/2018/credentials/v1","https://purl.imsglobal.org/spec/ob/v3p0/context.json","https://w3id.org/vc/status-list/2021/v1","https://w3id.org/security/suites/ed25519-2020/v1","https://w3id.org/vc/status-list/2021/v1"],"id":"urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1","type":["VerifiableCredential","OpenBadgeCredential"],"issuer":{"id":"did:key:z6Mkuoj16AELhDkUk8tvTLA6e6yenGXSNoZ5urtprJoqhuww","type":"Profile","name":"Dr David Malan","description":"Gordon McKay Professor of the Practice of Computer Science, Harvard University","url":"https://cs.harvard.edu/malan/","image":{"id":"https://certificates.cs50.io/static/success.jpg","type":"Image"}},"issuanceDate":"2020-01-01T00:00:00Z","name":"Introduction to Computer Science - CS50x","credentialSubject":{"type":"AchievementSubject","identifier":{"type":"IdentityObject","identityHash":"jc.chartrand@gmail.com","hashed":"false"},"achievement":{"id":"http://cs50.harvard.edu","type":"Achievement","criteria":{"narrative":"Completion of CS50X, including ten problem sets, ten labs, and one final project."},"description":"CS50 congratulates on completion of CS50x.","name":"Introduction to Computer Science - CS50x"}},"credentialStatus":{"id":"https://jchartrand.github.io/status-test-three/DKSPRCX9WB#1","type":"StatusList2021Entry","statusPurpose":"revocation","statusListIndex":1,"statusListCredential":"https://jchartrand.github.io/status-test-three/DKSPRCX9WB"},"proof":{"type":"Ed25519Signature2020","created":"2023-08-03T17:27:29Z","verificationMethod":"did:key:z6Mkuoj16AELhDkUk8tvTLA6e6yenGXSNoZ5urtprJoqhuww#z6Mkuoj16AELhDkUk8tvTLA6e6yenGXSNoZ5urtprJoqhuww","proofPurpose":"assertionMethod","proofValue":"z53EF47PshAVsVtRBTBBv8A1vJvWptWn5p4QupVnAeZYWZJnTGAcABmAVYRZ4CR1xAjWyPrg7ktXerJ9PfUgSLfTh"}}, [ - 'X-Powered-By', - 'Express', - 'Access-Control-Allow-Origin', - '*', - 'Content-Type', - 'application/json; charset=utf-8', - 'Content-Length', - '1810', - 'ETag', - 'W/"712-0nL+TtiN38hiHrSNvQHR9Iqira4"', - 'Date', - 'Thu, 03 Aug 2023 17:27:29 GMT', - 'Connection', - 'keep-alive', - 'Keep-Alive', - 'timeout=5' -])} */ +/* export default () => { + nock('http://localhost:4006', { encodedQueryParams: true }) + .post('/instance/testing3/credentials/sign', unsignedVcWithStatus) + .reply(200, signedVcWithStatus, [ + 'X-Powered-By', + 'Express', + 'Access-Control-Allow-Origin', + '*', + 'Content-Type', + 'application/json; charset=utf-8', + 'Content-Length', + '1810', + 'ETag', + 'W/"712-0nL+TtiN38hiHrSNvQHR9Iqira4"', + 'Date', + 'Thu, 03 Aug 2023 17:27:29 GMT', + 'Connection', + 'keep-alive', + 'Keep-Alive', + 'timeout=5' + ]) +} */ diff --git a/src/test-fixtures/nocks/unprotected_status_update.js b/src/test-fixtures/nocks/unprotected_status_update.js index b46a522..c3228ec 100644 --- a/src/test-fixtures/nocks/unprotected_status_update.js +++ b/src/test-fixtures/nocks/unprotected_status_update.js @@ -2,7 +2,10 @@ import nock from 'nock' export default () => { nock('http://localhost:4008', { encodedQueryParams: true }) - .post('/credentials/status', { credentialId: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', credentialStatus: [{ type: 'StatusList2021Credential', status: 'revoked' }] }) + .post('/credentials/status', { + credentialId: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', + credentialStatus: [{ type: 'BitstringStatusListCredential', status: 'revoked' }] + }) .reply(200, 'Credential status successfully updated', [ 'X-Powered-By', 'Express', diff --git a/src/test-fixtures/vc.js b/src/test-fixtures/vc.js index 0a48632..1a0c5a9 100644 --- a/src/test-fixtures/vc.js +++ b/src/test-fixtures/vc.js @@ -1,9 +1,7 @@ const unsignedVC = { '@context': [ - 'https://www.w3.org/2018/credentials/v1', - 'https://purl.imsglobal.org/spec/ob/v3p0/context.json', - 'https://w3id.org/vc/status-list/2021/v1', - 'https://w3id.org/security/suites/ed25519-2020/v1' + 'https://www.w3.org/ns/credentials/v2', + 'https://purl.imsglobal.org/spec/ob/v3p0/context.json' ], id: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', type: [ @@ -21,7 +19,7 @@ const unsignedVC = { type: 'Image' } }, - issuanceDate: '2020-01-01T00:00:00Z', + validFrom: '2020-01-01T00:00:00Z', name: 'Introduction to Computer Science - CS50x', credentialSubject: { type: 'AchievementSubject', @@ -42,14 +40,22 @@ const unsignedVC = { } } -// "credentialStatus": -const credentialStatus = { - id: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4#16', - type: 'StatusList2021Entry', - statusPurpose: 'revocation', - statusListIndex: 16, - statusListCredential: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4' -} +const credentialStatus = [ + { + id: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4#2', + type: 'BitstringStatusListEntry', + statusPurpose: 'revocation', + statusListIndex: 2, + statusListCredential: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4' + }, + { + id: 'https://digitalcredentials.github.io/credential-status-jc-test/DKSPRCX9WB#5', + type: 'BitstringStatusListEntry', + statusPurpose: 'suspension', + statusListIndex: 5, + statusListCredential: 'https://digitalcredentials.github.io/credential-status-jc-test/DKSPRCX9WB' + } +] const getUnsignedVC = () => JSON.parse(JSON.stringify(unsignedVC)) const getUnsignedVCWithoutSuiteContext = () => { @@ -68,6 +74,10 @@ const getUnsignedVCWithStatus = () => { return unsignedVCWithStatus } const suiteContext = 'https://w3id.org/security/suites/ed25519-2020/v1' -const statusListContext = 'https://w3id.org/vc/status-list/2021/v1' -export { getUnsignedVC, getUnsignedVCWithoutSuiteContext, getCredentialStatus, getUnsignedVCWithStatus, statusListContext } +export { + getUnsignedVC, + getUnsignedVCWithoutSuiteContext, + getCredentialStatus, + getUnsignedVCWithStatus +}