diff --git a/.coordinator.env b/.coordinator.env index 5c2e093..f07ce63 100644 --- a/.coordinator.env +++ b/.coordinator.env @@ -1,18 +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 -# 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, @@ -30,4 +30,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/.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/README.md b/README.md index 104b44a..f98db7e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ An express app that signs Verifiable Credentials. The app coordinates calls to a 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 @@ -17,48 +17,49 @@ Note that you needn't clone this repository to use the issuer - you can simply r - [Configuration](#configuration) - [Generate a New Key](#generate-a-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) - [Learner Credential Wallet](#learner-credential-wallet) - [Development](#development) + - [Installation](#installation) - [Testing](#testing) - [Contribute](#contribute) - [License](#license) ## Summary -Use this server to issue [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) with 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/) with a [status](https://www.w3.org/TR/vc-bitstring-status-list/) that can later be updated to revoke or suspend the credential. -Implements two [VC-API](https://w3c-ccg.github.io/vc-api/) http endpoints: +Implements two [VC-API](https://w3c-ccg.github.io/vc-api/) HTTP endpoints: * [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 wired together in a simple docker compose network that pulls images from DockerHub. +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 wired together in a simple Docker Compose network that pulls images from Docker Hub. We've made installation 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. ## Quick Start -These four step should take less than five minutes in total: +These four steps should take less than five minutes in total: ### 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 a file called docker-compose.yml and add the following +Create a file called `docker-compose.yml` and add the following: ``` version: '3.5' @@ -73,20 +74,20 @@ services: ### Run it -From the terminal in the same directory that contains your docker-compose.yml file: +From the terminal in the same directory that contains your `docker-compose.yml` file, run: ```docker compose up``` ### Issue -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: ``` 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", @@ -104,7 +105,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" @@ -131,12 +132,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)): ``` { "@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" ], @@ -155,7 +156,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" @@ -192,51 +193,68 @@ This should return a fully formed and signed credential printed to the terminal, 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](#enable-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). ## 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, the `major` number for each should match. At the time of writing, the versions for each are at 0.1.0, and the `major` number (the leftmost number) agrees across all three. +To ensure you've got compatible versions of the services and the coordinator, the `major` number for each should match. At the time of writing, the versions for each are at `0.1.0`, and the `major` number (the leftmost number) agrees across all three. -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, 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. These include, but may not be limited to: +* Your signing keys, which enable only you to sign your credentials +* Revocation/suspension support +* "Multi-tenant" signing, which enables you to use different keys for different credentialing purposes (e.g., signing credentials for different courses) The app is configured with three .env files: -* [.coordinator.env](./.coordinator.env) -* [.signing-service.env](./.signing-service.env) -* [.status-service.env](./.status-service.env) +* [.coordinator.env](.coordinator.env) +* [.signing-service.env](.signing-service.env) +* [.status-service.env](.status-service.env) -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 Quick Start `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. 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 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 a convenience endpoint in the issuer that you can use to generate a brand new key. You can hit the endpoint with the following CURL command (in a terminal): +To issue your own credentials, you must generate your own signing key and keep it private. At the moment, the issuer supports two [DID](https://www.w3.org/TR/did-core/) key formats/protocols: `did:key` and `did:web`. + +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 is signed by the old keys. -`curl --location 'http://localhost:4005/seedgen'` +We've tried to simplify key generation by providing convenience endpoints in the issuer that you can use to generate a brand new key. You can generate a DID key with these cURL commands (in a terminal): -This will return a json document with: +- `did:key`: + ``` + curl --location 'http://localhost:4005/did-key-generator' + ``` +- `did:web`: + ``` + curl \ + --location 'localhost:4006/did-web-generator' \ + --header 'Content-Type: application/json' \ + --data '{"url": "https://raw.githubusercontent.com/user-or-org/did-web-test/main"}' + ``` + +This will return a JSON document that looks something like this: - a seed - the corresponding DID - the corresponding DID Document -The returned result will look something like this: +The returned result will look something like this for `did:key`: ``` { @@ -265,17 +283,45 @@ The returned result will look something like this: } ``` -Now that you've got your key you'll want to enable it by adding a new tenant to use the seed... +...or this for `did:web` \*: + +``` +{ + "seed": "z1AcNXDnko1P6QMiZ3bxsraNvVtRbpXKeE8GNLDXjBJ5UHz", + "decodedSeed": {...}, + "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 want to enable it by adding a new tenant to use the seed. ### 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) @@ -296,15 +342,15 @@ 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 or similar. +We also suggest using IP filtering on your endpoints to only allow certain IPs to access the issuer. You can do this in Nginx or a similar server/traffic configuration tool. ##### .signing-service.env @@ -320,11 +366,13 @@ 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 a new key](#generate-a-new-key) step, which from the example in that section would be: +The seed value is exactly the value of the `seed` property for the key you generated in the [Generate a new key](#generate-a-new-key) step, which from the `did:key` example in that section, would be: -`"seed": "z1AjQUBZCNoiyPUC8zbbF29gLdZtHRqT6yPdFGtqJa5VfQ6"` +``` +"seed": "z1AjQUBZCNoiyPUC8zbbF29gLdZtHRqT6yPdFGtqJa5VfQ6" +``` -#### Use a tenant +#### Use a Tenant Tenant names are specified in the issuing endpoint like so: @@ -332,9 +380,9 @@ 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: ``` curl --location 'http://localhost:4005/instance/econ101/credentials/issue' \ @@ -342,7 +390,7 @@ curl --location 'http://localhost:4005/instance/econ101/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", @@ -360,7 +408,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" @@ -387,76 +435,69 @@ curl --location 'http://localhost:4005/instance/econ101/credentials/issue' \ }' ``` -### Enable Revocation - -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) +### Revocation and Suspension -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. +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. -Revoking a credential is described in [Usage - revoking](#revoking) +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. -#### Create Github repositories +### Environment Variables -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) +These are all of the general environment variables that you will need to configure in `.coordinator.env`: -Get a Github token with access to the repositories as described [here](https://github.com/digitalcredentials/credential-status-manager-git#generate-access-tokens) +| Key | Description | Type | Required | +| --- | --- | --- | --- | +| `SIGNING_SERVICE` | domain of signing service | string | no (default: `SIGNER:4006`) | +| `STATUS_SERVICE` | domain of status service | string | no (default: `STATUS:4008`) | +| `TENANT_TOKEN_{TENANT_NAME}` | HTTP authorization bearer token to secure service endpoint access for a given tenant | string | yes | +| `PORT` | HTTP port on which to run the express app | number | no (default: `4005`) | +| `ENABLE_ACCESS_LOGGING` | whether to enable access logging | boolean | no (default: `true`) | +| `ENABLE_STATUS_SERVICE` | whether to enable status | boolean | no (default: `true`) | +| `ENABLE_HTTPS_FOR_DEV` | whether to enable HTTPS in a development instance of the app | boolean | no (default: `true`) | -Now set these in the [.status-service.env](.status-service.env) file, which has the following options: +These are the environment variables that you will need to configure in `.signing-service.env`: -| Key | Description | Default | Required | +| Key | Description | Type | 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 | +| `TENANT_SEED_{TENANT_NAME}` | secret key deterministically associated with the issuer DID | string | yes | -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). +In addition to the variables defined above, you will also need to provide environment bindings for status related configurations in `.status-service.env`. Because there are two different implementations of a credential status manager - one for database storage and one for Git storage - you need to populate this file with different information, 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). ### 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) has to be confirmed as really belonging to that issuer. This is typically done by adding the DID to a well known registry that the verifier checks when verifying a credential. +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) has to be confirmed as really belonging to that issuer. This is typically done by adding the DID 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 which is one of the simpler implementations and doesn't require that the DID 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. We'll provide did:web support in time, but if you need it now just let us know. +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 revocation endpoint, like so: ``` { credentialId: 'id_added_by_status_manager_to_credentialStatus_propery_of_VC', credentialStatus: [{ - type: 'StatusList2021Credential', + type: 'BitstringStatusListCredential', status: 'revoked' }] } @@ -466,33 +507,33 @@ The important part there is the `credentialId`, which is listed in the `credenti ``` "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" - } + "id": "https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4#16", + "type": "BitstringStatusListEntry", + "statusPurpose": "revocation", + "statusListIndex": 16, + "statusListCredential": "https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4" +} ``` -and the id you need is in the `id` property. +...and the ID you need is in the `id` property. -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. +So again, an important point here is that you must store the `credentialStatus.id` value 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. -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](#enable-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. ## 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. And, similarly, 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: ``` npm install @@ -501,12 +542,12 @@ 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``` -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/src/app.js b/src/app.js index abea72b..dbde463 100644 --- a/src/app.js +++ b/src/app.js @@ -8,18 +8,27 @@ import invalidPathHandler from './middleware/invalidPathHandler.js' import verifyAuthHeader from './verifyAuthHeader.js' import { getConfig } from './config.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()) @@ -30,7 +39,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.', @@ -40,7 +49,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.', @@ -57,7 +66,7 @@ export async function build (opts = {}) { }) app.get('/seedgen', async (req, res, next) => { - const response = await axios.get(`http://${signingServiceEndpoint}/seedgen`) + const response = await axios.get(`http://${signingService}/seedgen`) return res.json(response.data) }) @@ -72,9 +81,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 @@ -84,7 +93,7 @@ export async function build (opts = {}) { }) // updates the status - // the body will look like: {credentialId: '23kdr', credentialStatus: [{type: 'StatusList2021Credential', status: 'revoked'}]} + // the body will look like: {credentialId: '23kdr', 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.') @@ -95,7 +104,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 32fc326..d1d7c7a 100644 --- a/src/app.test.js +++ b/src/app.test.js @@ -21,7 +21,7 @@ describe('api', () => { // 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 +39,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('/') diff --git a/src/config.js b/src/config.js index 5fd8751..c030439 100644 --- a/src/config.js +++ b/src/config.js @@ -5,8 +5,8 @@ const randomTenantName = 'random' const randtomTenantToken = 'UNPROTECTED' const defaultTenantToken = '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 @@ -37,8 +37,8 @@ function parseConfig () { enableHttpsForDev: env.ENABLE_HTTPS_FOR_DEV?.toLowerCase() === 'true', enableAccessLogging: env.ENABLE_ACCESS_LOGGING?.toLowerCase() === 'true', 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/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..a912e9f 100644 --- a/src/test-fixtures/nocks/protected_status_signing.js +++ b/src/test-fixtures/nocks/protected_status_signing.js @@ -1,10 +1,157 @@ -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' } } +import nock from 'nock'; + +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://jchartrand.github.io/status-test-three/DKSPRCX9WB#1", + "statusListCredential": "https://jchartrand.github.io/status-test-three/DKSPRCX9WB", + "statusListIndex": 1, + "statusPurpose": "revocation", + "type": "BitstringStatusListEntry" + }, + "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://jchartrand.github.io/status-test-three/DKSPRCX9WB#1", + "type": "BitstringStatusListEntry", + "statusPurpose": "revocation", + "statusListIndex": 1, + "statusListCredential": "https://jchartrand.github.io/status-test-three/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', @@ -21,11 +168,11 @@ export default () => { 'keep-alive', 'Keep-Alive', 'timeout=5' - ]) + ]); 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', @@ -42,5 +189,24 @@ export default () => { 'keep-alive', '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..07cd8b8 100644 --- a/src/test-fixtures/nocks/protected_status_update.js +++ b/src/test-fixtures/nocks/protected_status_update.js @@ -2,7 +2,7 @@ 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..8e0240e 100644 --- a/src/test-fixtures/nocks/unknown_status_id_nock.js +++ b/src/test-fixtures/nocks/unknown_status_id_nock.js @@ -2,7 +2,7 @@ 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..4c642e6 100644 --- a/src/test-fixtures/nocks/unprotected_status_signing.js +++ b/src/test-fixtures/nocks/unprotected_status_signing.js @@ -1,10 +1,157 @@ -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' } } +import nock from 'nock'; + +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://jchartrand.github.io/status-test-three/DKSPRCX9WB#1", + "type": "BitstringStatusListEntry", + "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 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://jchartrand.github.io/status-test-three/DKSPRCX9WB#1", + "type": "BitstringStatusListEntry", + "statusPurpose": "revocation", + "statusListIndex": 1, + "statusListCredential": "https://jchartrand.github.io/status-test-three/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', @@ -21,10 +168,10 @@ export default () => { 'keep-alive', 'Keep-Alive', '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', @@ -41,45 +188,47 @@ export default () => { 'keep-alive', 'Keep-Alive', '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..57187a0 100644 --- a/src/test-fixtures/nocks/unprotected_status_update.js +++ b/src/test-fixtures/nocks/unprotected_status_update.js @@ -2,7 +2,7 @@ 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..c1918c3 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', @@ -45,7 +43,7 @@ const unsignedVC = { // "credentialStatus": const credentialStatus = { id: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4#16', - type: 'StatusList2021Entry', + type: 'BitstringStatusListEntry', statusPurpose: 'revocation', statusListIndex: 16, statusListCredential: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4' @@ -68,6 +66,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 +};