Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add database status service and convert to Bitstring Status List #17

Merged
merged 9 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions .coordinator.env
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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

9 changes: 4 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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

6 changes: 3 additions & 3 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
"env": {
"node": true,
"mocha": true
env: {
node: true,
mocha: true
},
extends: [
'standard'
Expand Down
6 changes: 3 additions & 3 deletions .signing-service.env
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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
TENANT_SEED_RANDOM_TESTING=generate
31 changes: 25 additions & 6 deletions .status-service.env
Original file line number Diff line number Diff line change
Expand Up @@ -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=
336 changes: 206 additions & 130 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "@digigatlcredentials/issuer-coordinator",
"name": "@digitalcredentials/issuer-coordinator",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"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": {
Expand Down
43 changes: 29 additions & 14 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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.',
Expand All @@ -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.',
Expand All @@ -57,20 +66,21 @@ export async function build (opts = {}) {
})

app.get('/seedgen', async (req, res, next) => {
const response = await axios.get(`http://${signingServiceEndpoint}/did-key-generator`)
const response = await axios.get(`http://${signingService}/seedgen`)
return res.json(response.data)
})

app.get('/did-key-generator', async (req, res, next) => {
const response = await axios.get(`http://${signingServiceEndpoint}/did-key-generator`)
const response = await axios.get(`http://${signingService}/did-key-generator`)
return res.json(response.data)
})

app.post('/did-web-generator', async (req, res, next) => {
const body = req.body
const response = await axios.post(`http://${signingServiceEndpoint}/did-web-generator`, body)
const response = await axios.post(`http://${signingService}/did-web-generator`, body)
return res.json(response.data)
})

app.post('/instance/:tenantName/credentials/issue',
async (req, res, next) => {
try {
Expand All @@ -82,9 +92,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
Expand All @@ -94,7 +104,12 @@ export async function build (opts = {}) {
})

// updates the status
// the body will look like: {credentialId: '23kdr', credentialStatus: [{type: 'StatusList2021Credential', status: 'revoked'}]}
/*
{
"credentialId": "urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1",
"credentialStatus": [{ "type": "BitstringStatusListCredential", "status": "revoked" }]
}
*/
app.post('/instance/:tenantName/credentials/status',
async (req, res, next) => {
if (!enableStatusService) return res.status(405).send('The status service has not been enabled.')
Expand All @@ -105,7 +120,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) {
Expand Down
21 changes: 10 additions & 11 deletions src/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ let app

describe('api', () => {
before(async () => {
// testDIDSeed = await decodeSeed(process.env.TENANT_SEED_TESTING)
testTenantToken = process.env.TENANT_TOKEN_PROTECTED_TEST
testTenantToken2 = process.env.TENANT_TOKEN_PROTECTED_TEST_2
statusUpdateBody = { credentialId: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1', credentialStatus: [{ type: 'StatusList2021Credential', status: 'revoked' }] }
statusUpdateBody = {
credentialId: 'urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1',
credentialStatus: [{ type: 'BitstringStatusListCredential', status: 'revoked' }]
}
})

after(() => {
Expand All @@ -39,7 +41,7 @@ describe('api', () => {
describe('GET /', () => {
it('GET / => hello', done => {
nock('http://localhost:4006').get('/').reply(200, 'signing-service server status: ok.')
nock('http://localhost:4008').get('/').reply(200, 'signing-service server status: ok.')
nock('http://localhost:4008').get('/').reply(200, 'status-service server status: ok.')

request(app)
.get('/')
Expand Down Expand Up @@ -82,7 +84,7 @@ describe('api', () => {
.send(getUnsignedVC())

expect(response.header['content-type']).to.have.string('json')
expect(response.status).to.eql(200)
expect(response.status).to.equal(200)
expect(response.body)
})

Expand Down Expand Up @@ -123,7 +125,6 @@ describe('api', () => {
})

it('returns signed vc for protected tenant', async () => {
// nock.recorder.rec()
protectedNock()
const sentCred = getUnsignedVC()
const response = await request(app)
Expand All @@ -132,11 +133,11 @@ describe('api', () => {
.send(sentCred)

expect(response.header['content-type']).to.have.string('json')
expect(response.status).to.eql(200)
expect(response.status).to.equal(200)

const returnedCred = JSON.parse(JSON.stringify(response.body))
// this proof value comes from the nock:
expect(returnedCred.proof.proofValue).to.eql('z5QQ12zr5JvEsKvbnEN2EYZ6punR6Pa5wMJzywGJ2dCh6SSA5oQb9hBiGADsNTbs57bopArwdBHE9kEVemMxcu1Fq')
expect(returnedCred.proof.proofValue).to.equal('z5QQ12zr5JvEsKvbnEN2EYZ6punR6Pa5wMJzywGJ2dCh6SSA5oQb9hBiGADsNTbs57bopArwdBHE9kEVemMxcu1Fq')
})
})

Expand All @@ -158,7 +159,6 @@ describe('api', () => {
})

it('update unprotected status when token not set for tenant in config', done => {
// nock.recorder.rec()
unprotectedStatusUpdateNock()
request(app)
.post('/instance/un_protected_test/credentials/status')
Expand Down Expand Up @@ -204,7 +204,6 @@ describe('api', () => {
})

it('returns 404 for unknown cred id', async () => {
// nock.recorder.rec()
unknownStatusIdNock()
const statusUpdateBodyWithUnknownId = JSON.parse(JSON.stringify(statusUpdateBody))
statusUpdateBodyWithUnknownId.credentialId = 'kj09ij'
Expand All @@ -214,7 +213,7 @@ describe('api', () => {
.send(statusUpdateBodyWithUnknownId)

expect(response.header['content-type']).to.have.string('json')
expect(response.status).to.eql(404)
expect(response.status).to.equal(404)
})

it('calls status manager for protected tenant', async () => {
Expand All @@ -225,7 +224,7 @@ describe('api', () => {
.send(statusUpdateBody)

expect(response.header['content-type']).to.have.string('json')
expect(response.status).to.eql(200)
expect(response.status).to.equal(200)
})
})
})
8 changes: 4 additions & 4 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 5 additions & 7 deletions src/test-fixtures/.env.testing
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading
Loading