From 75c8272208ea8701e67a6d4b415aacf19a31053c Mon Sep 17 00:00:00 2001 From: James Chartrand Date: Wed, 4 Sep 2024 18:39:22 -0400 Subject: [PATCH 1/5] add status credential GET endpoint --- .dockerignore | 5 ++++- .gitignore | 6 +++++- src/app.js | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.dockerignore b/.dockerignore index 28fda52..f2651f8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,5 @@ **/*.env -node_modules \ No newline at end of file +node_modules +compose-health-test.yaml +compose-v2-test.yaml +.env.healthcheck.testing \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8078a01..93c629f 100644 --- a/.gitignore +++ b/.gitignore @@ -108,4 +108,8 @@ dist .tern-port # vscode -.vscode \ No newline at end of file +.vscode + +compose-health-test.yaml +compose-v2-test.yaml +.env.healthcheck.testing \ No newline at end of file diff --git a/src/app.js b/src/app.js index 2dd1f6e..d99323e 100644 --- a/src/app.js +++ b/src/app.js @@ -133,6 +133,20 @@ export async function build (opts = {}) { } }) + app.get('/status/:statusCredentialId', async function (req, res, next) { + const statusCredentialId = req.params.statusCredentialId + try { + const { data: statusCredential } = await axios.get(`http://${statusService}/${statusCredentialId}`) + return res.status(200).json(statusCredential) + } catch (error) { + next({ + message: error.message, + code: error.code + }) + } + return res.status(200).send({ message: 'status service is not configured.' }) + }) + // Attach the error handling middleware calls, in order they should run app.use(errorLogger) app.use(errorHandler) From bf6f34839c7deaa22eef202094b397ca3e504397 Mon Sep 17 00:00:00 2001 From: James Chartrand Date: Thu, 5 Sep 2024 07:45:12 -0400 Subject: [PATCH 2/5] return 405 when status service disabled --- src/app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app.js b/src/app.js index d99323e..f9545bf 100644 --- a/src/app.js +++ b/src/app.js @@ -134,6 +134,7 @@ export async function build (opts = {}) { }) app.get('/status/:statusCredentialId', async function (req, res, next) { + if (!enableStatusService) return res.status(405).send('The status service has not been enabled.') const statusCredentialId = req.params.statusCredentialId try { const { data: statusCredential } = await axios.get(`http://${statusService}/${statusCredentialId}`) @@ -144,7 +145,7 @@ export async function build (opts = {}) { code: error.code }) } - return res.status(200).send({ message: 'status service is not configured.' }) + return res.status(500).send({ message: 'Server error.' }) }) // Attach the error handling middleware calls, in order they should run From 4305f62b404669a6e8e1bd51c95f0fe5c36c8edc Mon Sep 17 00:00:00 2001 From: James Chartrand Date: Fri, 6 Sep 2024 08:57:49 -0400 Subject: [PATCH 3/5] add status list tests and fix 404 --- src/app.js | 11 +++--- src/app.test.js | 23 +++++++++++++ src/test-fixtures/nocks/status_list_nock.js | 34 +++++++++++++++++++ .../nocks/unknown_status_list_nock.js | 7 ++++ 4 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 src/test-fixtures/nocks/status_list_nock.js create mode 100644 src/test-fixtures/nocks/unknown_status_list_nock.js diff --git a/src/app.js b/src/app.js index f9545bf..de8b483 100644 --- a/src/app.js +++ b/src/app.js @@ -134,16 +134,17 @@ export async function build (opts = {}) { }) app.get('/status/:statusCredentialId', async function (req, res, next) { - if (!enableStatusService) return res.status(405).send('The status service has not been enabled.') + if (!enableStatusService) next({ code: 405, message: 'The status service has not been enabled.' }) const statusCredentialId = req.params.statusCredentialId try { const { data: statusCredential } = await axios.get(`http://${statusService}/${statusCredentialId}`) return res.status(200).json(statusCredential) } catch (error) { - next({ - message: error.message, - code: error.code - }) + if (error.response.status === 404) { + next({ code: 404, message: 'No status credential found for that id.' }) + } else { + next(error) + } } return res.status(500).send({ message: 'Server error.' }) }) diff --git a/src/app.test.js b/src/app.test.js index 29ad245..5f0c2eb 100644 --- a/src/app.test.js +++ b/src/app.test.js @@ -7,6 +7,8 @@ import protectedNock from './test-fixtures/nocks/protected_status_signing.js' import unprotectedStatusUpdateNock from './test-fixtures/nocks/unprotected_status_update.js' import unknownStatusIdNock from './test-fixtures/nocks/unknown_status_id_nock.js' import protectedStatusUpdateNock from './test-fixtures/nocks/protected_status_update.js' +import unknownStatusListNock from './test-fixtures/nocks/unknown_status_list_nock.js' +import statusListNock from './test-fixtures/nocks/status_list_nock.js' import { build } from './app.js' @@ -227,4 +229,25 @@ describe('api', () => { expect(response.status).to.equal(200) }) }) + + describe('GET /status/:statusCredentialId', () => { + it('returns 404 for unknown status credential id', async () => { + unknownStatusListNock() + const response = await request(app) + .get('/status/9898u') + expect(response.header['content-type']).to.have.string('json') + expect(response.status).to.equal(404) + }) + + it('returns credential status list from status service', async () => { + statusListNock() + const response = await request(app) + .get('/status/slAwJe6GGR6mBojlGW5U') + expect(response.header['content-type']).to.have.string('json') + expect(response.status).to.equal(200) + const returnedList = JSON.parse(JSON.stringify(response.body)) + // this proof value comes from the nock: + expect(returnedList.proof.proofValue).to.equal('z4y3GawinQg1aCqbYqZM8dmDpbmtFa3kE6tFefdXvLi5iby25dvmVwLNZrfcFPyhpshrhCWB76pdSZchVve3K1Znr') + }) + }) }) diff --git a/src/test-fixtures/nocks/status_list_nock.js b/src/test-fixtures/nocks/status_list_nock.js new file mode 100644 index 0000000..df6725e --- /dev/null +++ b/src/test-fixtures/nocks/status_list_nock.js @@ -0,0 +1,34 @@ +import nock from 'nock' + +const theList = `{ + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "https://sincere-bonefish-currently.ngrok-free.app/slAwJe6GGR6mBojlGW5U", + "type": [ + "VerifiableCredential", + "BitstringStatusListCredential" + ], + "credentialSubject": { + "id": "https://sincere-bonefish-currently.ngrok-free.app/slAwJe6GGR6mBojlGW5U#list", + "type": "BitstringStatusList", + "encodedList": "uH4sIAAAAAAAAA-3BIQEAAAACICf4f60vTEADAAAAAAAAAAAAAADwN_wEBkHUMAAA", + "statusPurpose": "revocation" + }, + "issuer": "did:key:z6Mkg165pEHaUPxkY4NxToor7suxzawEmdT1DEWq3e1Nr2VR", + "validFrom": "2024-09-03T15:24:19.685Z", + "proof": { + "type": "Ed25519Signature2020", + "created": "2024-09-03T15:24:19Z", + "verificationMethod": "did:key:z6Mkg165pEHaUPxkY4NxToor7suxzawEmdT1DEWq3e1Nr2VR#z6Mkg165pEHaUPxkY4NxToor7suxzawEmdT1DEWq3e1Nr2VR", + "proofPurpose": "assertionMethod", + "proofValue": "z4y3GawinQg1aCqbYqZM8dmDpbmtFa3kE6tFefdXvLi5iby25dvmVwLNZrfcFPyhpshrhCWB76pdSZchVve3K1Znr" + } +}` + +export default () => { + nock('http://localhost:4008') + .get('/slAwJe6GGR6mBojlGW5U') + .reply(200, theList) +} diff --git a/src/test-fixtures/nocks/unknown_status_list_nock.js b/src/test-fixtures/nocks/unknown_status_list_nock.js new file mode 100644 index 0000000..6bb349a --- /dev/null +++ b/src/test-fixtures/nocks/unknown_status_list_nock.js @@ -0,0 +1,7 @@ +import nock from 'nock' + +export default () => { + nock('http://localhost:4008') + .get('/9898u') + .reply(404, { code: 404, message: 'No status credential found for that id.' }) +} From 409e63700cc1f551f073eef9c593947157671374 Mon Sep 17 00:00:00 2001 From: James Chartrand Date: Fri, 6 Sep 2024 09:51:26 -0400 Subject: [PATCH 4/5] add status list explanation to readme --- README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 84388d0..7302af6 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,16 @@ Use this service to issue [Verifiable Credentials](https://www.w3.org/TR/vc-data 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) +Also implements an endpoint that returns a status list if the underlying status service itself returns the list - essentially then just acts as a public proxy within the docker compose, forwarding the request for the list to the status service and returning the result. For the moment, our [mongo-backed status service](https://github.com/digitalcredentials/status-service-db) is the only status service (we know of) that returns a list. The endpoint then is: + + * [POST /status/:listId](https://w3c-ccg.github.io/vc-api/#update-status) + +Where the :listId is the identifier of the list + 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. @@ -95,7 +102,7 @@ curl --location 'http://localhost:4005/instance/test/credentials/issue' \ --data-raw '{ "@context": [ "https://www.w3.org/ns/credentials/v2", - "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json" + "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json" ], "id": "urn:uuid:2fe53dc9-b2ec-4939-9b2c-0d00f6663b6c", "type": [ @@ -145,7 +152,7 @@ This should return a fully formed and signed credential printed to the terminal, { "@context": [ "https://www.w3.org/ns/credentials/v2", - "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json", + "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json", "https://w3id.org/security/suites/ed25519-2020/v1" ], "id": "urn:uuid:2fe53dc9-b2ec-4939-9b2c-0d00f6663b6c", @@ -403,7 +410,7 @@ curl --location 'http://localhost:4005/instance/econ101/credentials/issue' \ --data-raw '{ "@context": [ "https://www.w3.org/ns/credentials/v2", - "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json" + "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json" ], "id": "urn:uuid:2fe53dc9-b2ec-4939-9b2c-0d00f6663b6c", "type": [ From 907e9e7cba15440db34505cdad7fdad4a8b9d1f4 Mon Sep 17 00:00:00 2001 From: James Chartrand Date: Fri, 6 Sep 2024 11:02:24 -0400 Subject: [PATCH 5/5] update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c03c091..9093f00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ # issuer-coordinator Changelog -## 0.3.0 - TBD +## 0.3.0 - 2024-09-06 ### 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. +- Add endpoint to retrieve status list from underlying database status service. ## 0.2.0 - 2024-04-22