From 02d8fec7f6015efd40541526fb81290c54d4fc68 Mon Sep 17 00:00:00 2001 From: Kayode Ezike Date: Fri, 19 Apr 2024 23:08:54 -0400 Subject: [PATCH 1/9] adds db status service -and converts to bsl resolves merge conflicts --- .coordinator.env | 15 +- .env.example | 9 +- .signing-service.env | 6 +- README.md | 271 +++++++++++------- src/app.js | 33 ++- src/app.test.js | 4 +- src/config.js | 8 +- src/test-fixtures/.env.testing | 12 +- .../nocks/protected_status_signing.js | 184 +++++++++++- .../nocks/protected_status_update.js | 2 +- .../nocks/unknown_status_id_nock.js | 2 +- .../nocks/unprotected_status_signing.js | 243 +++++++++++++--- .../nocks/unprotected_status_update.js | 2 +- src/test-fixtures/vc.js | 18 +- 14 files changed, 593 insertions(+), 216 deletions(-) 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 93c178d..fc33754 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,64 +193,109 @@ 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.2.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.2.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 two convenience endpoints in the issuer 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 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. -#### did:key +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): -`curl --location 'http://localhost:4005/did-key-generator'` +- `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: -#### did:web + +The returned result will look something like this for `did:key`: `curl --location 'http://localhost:4005/did-web-generator'` 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). -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) @@ -270,21 +316,37 @@ 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 -The [signing-service README](https://github.com/digitalcredentials/signing-service/blob/main/README.md#didkey-generator) explains how to set your DID, whether using did:key or did:web. Note that the signing-service docs describe using convenience endpoints to generate new DIDs. You can call those endpoints directly in the signing-serive, or call the same endpoints in the 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. +Add a line like: -#### Use a tenant +``` +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 a new key](#generate-a-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: @@ -292,9 +354,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' \ @@ -302,7 +364,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", @@ -320,7 +382,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" @@ -347,76 +409,69 @@ 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) +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. -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. +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. -Revoking a credential is described in [Usage - revoking](#revoking) +### Environment Variables -#### Create Github repositories +These are all of the general environment variables that you will need to configure in `.coordinator.env`: -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) +| 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. - -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 +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. -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' }] } @@ -426,33 +481,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 @@ -461,12 +516,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 c145fc4..bfcdd14 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}/did-key-generator`) + const response = await axios.get(`http://${signingService}/seedgen`) return res.json(response.data) }) @@ -82,9 +91,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 @@ -94,7 +103,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.') @@ -105,7 +114,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 62fe589..634efbc 100644 --- a/src/config.js +++ b/src/config.js @@ -7,8 +7,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 @@ -40,8 +40,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 +}; From 61acf2585eaf3739750fb17bdbee4b63389595a4 Mon Sep 17 00:00:00 2001 From: Kayode Ezike Date: Tue, 23 Apr 2024 04:52:08 -0400 Subject: [PATCH 2/9] changes unnecssary "eql" invocation to "equal" invocation in test --- src/app.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app.test.js b/src/app.test.js index d1d7c7a..7a5c67a 100644 --- a/src/app.test.js +++ b/src/app.test.js @@ -82,7 +82,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) }) @@ -132,11 +132,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') }) }) @@ -214,7 +214,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 +225,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) }) }) }) From 187d8869b7ffab2e4149a685846e2c858d567e05 Mon Sep 17 00:00:00 2001 From: Kayode Ezike Date: Tue, 23 Apr 2024 15:33:13 -0400 Subject: [PATCH 3/9] fixes lint errors --- .eslintrc.cjs | 6 +- package.json | 4 +- src/app.js | 2 +- .../nocks/protected_status_signing.js | 292 +++++++++--------- .../nocks/unprotected_status_signing.js | 262 ++++++++-------- src/test-fixtures/vc.js | 2 +- 6 files changed, 284 insertions(+), 284 deletions(-) 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/package.json b/package.json index 755f194..76587cb 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "start": "node -r dotenv/config server.js", "dev": "nodemon -r dotenv/config server.js", "dev-noenv": "nodemon server.js", - "lint": "eslint . --ext .js", - "lint-fix": "eslint --fix . --ext .js", + "lint": "eslint . --ext .js", + "lint-fix": "eslint --fix . --ext .js", "test": "NODE_OPTIONS=--experimental-vm-modules npx mocha --timeout 10000 -r dotenv/config dotenv_config_path=src/test-fixtures/.env.testing src/app.test.js " }, "dependencies": { diff --git a/src/app.js b/src/app.js index bfcdd14..a148476 100644 --- a/src/app.js +++ b/src/app.js @@ -9,7 +9,7 @@ import verifyAuthHeader from './verifyAuthHeader.js' import { getConfig } from './config.js' class IssuingException extends Error { - constructor(code, message, error = null) { + constructor (code, message, error = null) { super(message) this.code = code this.error = error diff --git a/src/test-fixtures/nocks/protected_status_signing.js b/src/test-fixtures/nocks/protected_status_signing.js index a912e9f..8616393 100644 --- a/src/test-fixtures/nocks/protected_status_signing.js +++ b/src/test-fixtures/nocks/protected_status_signing.js @@ -1,152 +1,152 @@ -import nock from 'nock'; +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" + '@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" + 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/" + 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" + validFrom: '2020-01-01T00:00:00Z', + credentialSubject: { + achievement: { + criteria: { + narrative: 'Completion of CS50X, including ten problem sets, ten labs, and one final project.' }, - "type": "AchievementSubject" + 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" + 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" + 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" + '@context': [ + 'https://www.w3.org/ns/credentials/v2', + 'https://purl.imsglobal.org/spec/ob/v3p0/context.json' ], - "type": [ - "VerifiableCredential", - "OpenBadgeCredential" + 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" - } + 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" + 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.' }, - "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" - } + 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" + '@context': [ + 'https://www.w3.org/ns/credentials/v2', + 'https://purl.imsglobal.org/spec/ob/v3p0/context.json' ], - "type": [ - "VerifiableCredential", - "OpenBadgeCredential" + 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" - } + 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" + 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.' }, - "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" - } + 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" + 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 }) @@ -168,7 +168,7 @@ export default () => { 'keep-alive', 'Keep-Alive', 'timeout=5' - ]); + ]) nock('http://localhost:4006', { encodedQueryParams: true }) .post('/instance/protected_test/credentials/sign', unsignedVcWithStatus) @@ -189,24 +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' - ]); */ -}; + /* 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/unprotected_status_signing.js b/src/test-fixtures/nocks/unprotected_status_signing.js index 4c642e6..9b50bd6 100644 --- a/src/test-fixtures/nocks/unprotected_status_signing.js +++ b/src/test-fixtures/nocks/unprotected_status_signing.js @@ -1,152 +1,152 @@ -import nock from 'nock'; +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" + '@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" + 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" - } + 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" + 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.' }, - "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" - } + 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" + 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" + 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" + '@context': [ + 'https://www.w3.org/ns/credentials/v2', + 'https://purl.imsglobal.org/spec/ob/v3p0/context.json' ], - "type": [ - "VerifiableCredential", - "OpenBadgeCredential" + 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" - } + 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" + 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.' }, - "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" - } + 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" + '@context': [ + 'https://www.w3.org/ns/credentials/v2', + 'https://purl.imsglobal.org/spec/ob/v3p0/context.json' ], - "type": [ - "VerifiableCredential", - "OpenBadgeCredential" + 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" - } + 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" + 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.' }, - "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" - } + 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" + 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 }) @@ -168,7 +168,7 @@ export default () => { 'keep-alive', 'Keep-Alive', 'timeout=5' - ]); + ]) nock('http://localhost:4008', { encodedQueryParams: true }) .post('/credentials/status/allocate', unsignedVcWithoutStatus) .reply(200, unsignedVcWithStatus, [ @@ -188,7 +188,7 @@ 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) @@ -207,8 +207,8 @@ export default () => { 'Tue, 22 Aug 2023 20:11:09 GMT', 'Connection', 'close' - ]); */ -}; + ]) */ +} /* export default () => { nock('http://localhost:4006', { encodedQueryParams: true }) @@ -230,5 +230,5 @@ export default () => { 'keep-alive', 'Keep-Alive', 'timeout=5' - ]); -}; */ + ]) +} */ diff --git a/src/test-fixtures/vc.js b/src/test-fixtures/vc.js index c1918c3..94689a3 100644 --- a/src/test-fixtures/vc.js +++ b/src/test-fixtures/vc.js @@ -72,4 +72,4 @@ export { getUnsignedVCWithoutSuiteContext, getCredentialStatus, getUnsignedVCWithStatus -}; +} From f9168b163e5c295f76b878c23f8623d8b48e0302 Mon Sep 17 00:00:00 2001 From: Kayode Ezike Date: Tue, 23 Apr 2024 16:06:18 -0400 Subject: [PATCH 4/9] adds back renamed variable --- src/app.js | 5 +++-- src/test-fixtures/vc.js | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app.js b/src/app.js index a148476..d305c37 100644 --- a/src/app.js +++ b/src/app.js @@ -71,15 +71,16 @@ export async function build (opts = {}) { }) 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 { diff --git a/src/test-fixtures/vc.js b/src/test-fixtures/vc.js index 94689a3..36fea3a 100644 --- a/src/test-fixtures/vc.js +++ b/src/test-fixtures/vc.js @@ -40,7 +40,6 @@ const unsignedVC = { } } -// "credentialStatus": const credentialStatus = { id: 'https://digitalcredentials.github.io/credential-status-jc-test/XA5AAK1PV4#16', type: 'BitstringStatusListEntry', From 183b41e5e6485353f8bcf771419fbcd4525c162e Mon Sep 17 00:00:00 2001 From: Kayode Ezike Date: Tue, 23 Apr 2024 16:50:18 -0400 Subject: [PATCH 5/9] adds back missing data in readme --- README.md | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index fc33754..72c43d2 100644 --- a/README.md +++ b/README.md @@ -237,10 +237,12 @@ The `did:key` DID is one of the simpler DID implementations and doesn't require 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): - `did:key`: + ``` curl --location 'http://localhost:4005/did-key-generator' ``` - `did:web`: + ``` curl \ --location 'localhost:4006/did-web-generator' \ @@ -248,16 +250,42 @@ We've tried to simplify key generation by providing convenience endpoints in the --data '{"url": "https://raw.githubusercontent.com/user-or-org/did-web-test/main"}' ``` -This will return a JSON document that looks something like this: +These commands will return a JSON document that contains the following data: +- a secret seed +- the corresponding DID +- the corresponding DID Document -The returned result will look something like this for `did:key`: +Here is an example output for `did:key`: -`curl --location 'http://localhost:4005/did-web-generator'` - -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). +``` +{ + "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" + }] + } +} +``` -...or this for `did:web` \*: +...and here is an example output for `did:web` \*: ``` { From 7b863ba5d18a6189ad17064d378ff6c74bfe1a60 Mon Sep 17 00:00:00 2001 From: Kayode Ezike Date: Tue, 23 Apr 2024 17:04:09 -0400 Subject: [PATCH 6/9] removes unnecessary comments --- src/app.test.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/app.test.js b/src/app.test.js index 7a5c67a..9356018 100644 --- a/src/app.test.js +++ b/src/app.test.js @@ -18,7 +18,6 @@ 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: 'BitstringStatusListCredential', status: 'revoked' }] } @@ -123,7 +122,6 @@ describe('api', () => { }) it('returns signed vc for protected tenant', async () => { - // nock.recorder.rec() protectedNock() const sentCred = getUnsignedVC() const response = await request(app) @@ -158,7 +156,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 +201,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' From aecec73c3ca93b33a0ddb2b05f7f63f09782cf68 Mon Sep 17 00:00:00 2001 From: Kayode Ezike Date: Wed, 24 Apr 2024 04:45:08 -0400 Subject: [PATCH 7/9] additional cleanup --- .status-service.env | 31 ++++++-- README.md | 71 +++++++++---------- package-lock.json | 4 +- package.json | 2 +- src/app.js | 7 +- src/app.test.js | 5 +- .../nocks/protected_status_signing.js | 46 ++++++++---- .../nocks/protected_status_update.js | 5 +- .../nocks/unknown_status_id_nock.js | 5 +- .../nocks/unprotected_status_signing.js | 46 ++++++++---- .../nocks/unprotected_status_update.js | 5 +- src/test-fixtures/vc.js | 23 ++++-- 12 files changed, 162 insertions(+), 88 deletions(-) diff --git a/.status-service.env b/.status-service.env index 91b90fd..9f8be23 100644 --- a/.status-service.env +++ b/.status-service.env @@ -4,11 +4,30 @@ # 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 + +# 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 + +# 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= +STATUS_CRED_TABLE_NAME= +USER_CRED_TABLE_NAME= +CONFIG_TABLE_NAME= +EVENT_TABLE_NAME= +CRED_EVENT_TABLE_NAME= diff --git a/README.md b/README.md index 72c43d2..bc92e8d 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Note that you needn't clone this repository to use the issuer - you can simply r ## Summary -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. +Use this service to issue [Verifiable Credentials](https://www.w3.org/TR/vc-data-model-2.0/) 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: @@ -61,7 +61,7 @@ Docker has made this straightforward, with [installers for Windows, Mac, and Lin Create a file called `docker-compose.yml` and add the following: -``` +```yaml version: '3.5' services: coordinator: @@ -76,13 +76,15 @@ services: From the terminal in the same directory that contains your `docker-compose.yml` file, run: -```docker compose up``` +```bash +docker compose up +``` ### Issue 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 '{ @@ -134,7 +136,7 @@ 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 [JSONLint](https://jsonlint.com)): -``` +```json { "@context": [ "https://www.w3.org/ns/credentials/v2", @@ -188,7 +190,6 @@ 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) @@ -237,17 +238,17 @@ The `did:key` DID is one of the simpler DID implementations and doesn't require 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): - `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 '{"url": "https://raw.githubusercontent.com/user-or-org/did-web-test/main"}' + --data-raw '{ + "url": "https://raw.githubusercontent.com/user-or-org/did-web-test/main" + }' ``` These commands will return a JSON document that contains the following data: @@ -258,7 +259,7 @@ These commands will return a JSON document that contains the following data: Here is an example output for `did:key`: -``` +```json { "seed": "z1AjQUBZCNoiyPUC8zbbF29gLdZtHRqT6yPdFGtqJa5VfQ6", "did": "did:key:z6MkweTn1XVAiFfHjiH48oLknjNqRs43ayzguc8G8VbEAVm4", @@ -287,7 +288,7 @@ Here is an example output for `did:key`: ...and here is an example output for `did:web` \*: -``` +```json { "seed": "z1AcNXDnko1P6QMiZ3bxsraNvVtRbpXKeE8GNLDXjBJ5UHz", "decodedSeed": {...}, @@ -386,7 +387,7 @@ where `econ101` is the lower casing of the tenant name you'd have set in the env 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' \ @@ -493,35 +494,25 @@ The DCC provides another issuing service called the [exchange-coordinator](https ### Revoking and Suspending -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: +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: 'BitstringStatusListCredential', - 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": "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. +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` 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. +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 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. +**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 @@ -537,7 +528,7 @@ When running locally, the system picks up environment variables from the standar Clone code, cd into the directory, and run: -``` +```bash npm install npm run dev ``` @@ -546,7 +537,9 @@ npm run dev 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. diff --git a/package-lock.json b/package-lock.json index 4a5efb3..9672770 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 76587cb..2a7cafb 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 d305c37..2dd1f6e 100644 --- a/src/app.js +++ b/src/app.js @@ -104,7 +104,12 @@ export async function build (opts = {}) { }) // updates the status - // the body will look like: {credentialId: '23kdr', credentialStatus: [{type: 'BitstringStatusListCredential', 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.') diff --git a/src/app.test.js b/src/app.test.js index 9356018..29ad245 100644 --- a/src/app.test.js +++ b/src/app.test.js @@ -20,7 +20,10 @@ describe('api', () => { before(async () => { 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: 'BitstringStatusListCredential', status: 'revoked' }] } + statusUpdateBody = { + credentialId: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', + credentialStatus: [{ type: 'BitstringStatusListCredential', status: 'revoked' }] + } }) after(() => { diff --git a/src/test-fixtures/nocks/protected_status_signing.js b/src/test-fixtures/nocks/protected_status_signing.js index 8616393..c8c1107 100644 --- a/src/test-fixtures/nocks/protected_status_signing.js +++ b/src/test-fixtures/nocks/protected_status_signing.js @@ -41,13 +41,22 @@ const signedVcWithStatus = { }, 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' - }, + 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', @@ -139,13 +148,22 @@ const unsignedVcWithStatus = { 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' - } + 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 () => { diff --git a/src/test-fixtures/nocks/protected_status_update.js b/src/test-fixtures/nocks/protected_status_update.js index 07cd8b8..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: 'BitstringStatusListCredential', 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 8e0240e..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: 'BitstringStatusListCredential', 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 9b50bd6..58a4b63 100644 --- a/src/test-fixtures/nocks/unprotected_status_signing.js +++ b/src/test-fixtures/nocks/unprotected_status_signing.js @@ -41,13 +41,22 @@ const signedVcWithStatus = { 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' - }, + 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', @@ -139,13 +148,22 @@ const unsignedVcWithStatus = { 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' - } + 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 () => { diff --git a/src/test-fixtures/nocks/unprotected_status_update.js b/src/test-fixtures/nocks/unprotected_status_update.js index 57187a0..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: 'BitstringStatusListCredential', 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 36fea3a..1a0c5a9 100644 --- a/src/test-fixtures/vc.js +++ b/src/test-fixtures/vc.js @@ -40,13 +40,22 @@ const unsignedVC = { } } -const credentialStatus = { - 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' -} +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 = () => { From b05c62c11936b91e4686336453d2b93ea195995f Mon Sep 17 00:00:00 2001 From: Kayode Ezike Date: Thu, 9 May 2024 07:09:38 -0400 Subject: [PATCH 8/9] adds minor readme copy polish --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bc92e8d..9867cd0 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ 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. +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 Docker Hub. @@ -468,7 +468,7 @@ In addition to the variables defined above, you will also need to provide enviro ### 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](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 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 (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. @@ -541,8 +541,7 @@ Testing uses `supertest`, `mocha`, and `nock` to test the endpoints. To run test 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 From bdf941ea4da5f082d66d8b374836b4673cfa95e6 Mon Sep 17 00:00:00 2001 From: Kayode Ezike Date: Thu, 23 May 2024 10:48:49 -0400 Subject: [PATCH 9/9] differentiate between db and git status services; clean up readme; add changelog entry --- .gitignore | 3 +- .status-service-db.env | 18 ++++++ .status-service-git.env | 12 ++++ .status-service.env | 33 ----------- CHANGELOG.md | 9 +++ README.md | 123 +++++++++++++++++++++------------------- docker-compose.yml | 6 +- src/generate.js | 24 -------- 8 files changed, 111 insertions(+), 117 deletions(-) create mode 100644 .status-service-db.env create mode 100644 .status-service-git.env delete mode 100644 .status-service.env delete mode 100644 src/generate.js diff --git a/.gitignore b/.gitignore index 3d1c3a8..8078a01 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/.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 9f8be23..0000000 --- a/.status-service.env +++ /dev/null @@ -1,33 +0,0 @@ -# default port is 4008 -# PORT=4008 - -# ONLY for dev when need https; default is false -# ENABLE_HTTPS_FOR_DEV=false - -# Replace with your own values as described in the README - -# 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 - -# 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= -STATUS_CRED_TABLE_NAME= -USER_CRED_TABLE_NAME= -CONFIG_TABLE_NAME= -EVENT_TABLE_NAME= -CRED_EVENT_TABLE_NAME= 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 9867cd0..84388d0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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) @@ -14,8 +14,13 @@ Note that you needn't clone this repository to use the issuer - you can simply r - [Summary](#summary) - [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) - [.coordinator.env](#coordinatorenv) @@ -57,7 +62,7 @@ These four steps should take less than five minutes in total: 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 `docker-compose.yml` and add the following: @@ -72,7 +77,7 @@ services: image: digitalcredentials/signing-service:0.3.0 ``` -### Run it +### Run Service From the terminal in the same directory that contains your `docker-compose.yml` file, run: @@ -80,7 +85,7 @@ From the terminal in the same directory that contains your `docker-compose.yml` docker compose up ``` -### Issue +### Issue Credentials 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: @@ -196,7 +201,7 @@ WARNING: DO NOT USE THIS TO ISSUE `REAL` CREDENTIALS UNTIL YOU'VE [SET YOUR OWN 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: 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). +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). @@ -219,17 +224,17 @@ There are a few things you'll want to configure. These include, but may not be l * 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: +The app is configured with three `.env` files (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): * [.coordinator.env](.coordinator.env) * [.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) 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. +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. -### Generate a new key +### Generate New Key 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`. @@ -255,34 +260,38 @@ These commands will return a JSON document that contains the following data: - a secret seed - the corresponding DID -- the corresponding DID Document +- the corresponding DID document 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" - }] - } + "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" + }] + } } ``` @@ -290,25 +299,25 @@ Here is an example output for `did:key`: ```json { - "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" - } - ] - } + "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" + } + ] + } } ``` @@ -369,7 +378,7 @@ 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 `did:key` 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 New Key](#generate-new-key) step, which from the `did:key` example in that section, would be: ``` "seed": "z1AjQUBZCNoiyPUC8zbbF29gLdZtHRqT6yPdFGtqJa5VfQ6" @@ -464,7 +473,7 @@ These are the environment variables that you will need to configure in `.signing | --- | --- | --- | --- | | `TENANT_SEED_{TENANT_NAME}` | secret key deterministically associated with the issuer DID | string | yes | -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). +In addition to the variables defined above, you will also need to provide environment bindings for status related configurations in `.status-service-db.env` or `.status-service-git.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 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). ### DID Registries @@ -512,7 +521,7 @@ The important part there is the `credentialId`. If an issuer provides an `id` fi 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 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. +**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. ## Learner Credential Wallet @@ -520,9 +529,9 @@ You might now consider importing your new credential into the [Learner Credentia ## 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 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). +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 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/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 -}