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 = () => {