Skip to content

Commit

Permalink
Move setStatus into slcs.js.
Browse files Browse the repository at this point in the history
  • Loading branch information
dlongley committed Feb 19, 2024
1 parent 4484829 commit 8ab3766
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 119 deletions.
4 changes: 2 additions & 2 deletions lib/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
*/
import * as bedrock from '@bedrock/core';
import * as slcs from './slcs.js';
import {issue, setStatus} from './issuer.js';
import {
issueCredentialBody,
publishSlcBody,
Expand All @@ -13,6 +12,7 @@ import {metering, middleware} from '@bedrock/service-core';
import {asyncHandler} from '@bedrock/express';
import bodyParser from 'body-parser';
import cors from 'cors';
import {issue} from './issuer.js';
import {logger} from './logger.js';
import {createValidateMiddleware as validate} from '@bedrock/validation';

Expand Down Expand Up @@ -92,7 +92,7 @@ export async function addRoutes({app, service} = {}) {
const {credentialId, credentialStatus} = req.body;

// FIXME: support client requesting `status=false` as well
await setStatus({
await slcs.setStatus({
id: credentialId, config, credentialStatus, status: true
});

Expand Down
104 changes: 0 additions & 104 deletions lib/issuer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
import * as bedrock from '@bedrock/core';
import {
issue as _issue,
assertSlcDoc,
getDocumentStore, getIssuerAndSuite
} from './helpers.js';
import assert from 'assert-plus';
import {createDocumentLoader} from './documentLoader.js';
import {CredentialStatusIssuer} from './CredentialStatusIssuer.js';
import {CredentialStatusWriter} from './CredentialStatusWriter.js';
import {decodeList} from '@digitalbazaar/vc-status-list';

const {util: {BedrockError}} = bedrock;

Expand Down Expand Up @@ -122,105 +120,3 @@ export async function issue({credential, config} = {}) {

return verifiableCredential;
}

// FIXME: move to `slcs.js`
// FIXME: call out to status service
export async function setStatus({id, config, credentialStatus, status} = {}) {
assert.string(id, 'id');
assert.object(config, 'config');
assert.object(credentialStatus, 'credentialStatus');
assert.bool(status, 'status');

const [documentLoader, documentStore] = await Promise.all([
createDocumentLoader({config}),
getDocumentStore({config})
]);

// check `credentialStatus` against credential meta information
const {meta} = await documentStore.get({id});
const {
statusListCredential, statusListIndex
} = getMatchingStatusListCredentialMeta({
meta, credentialStatus
});

// get SLC document; do not use cache to ensure latest doc is retrieved
let slcDoc = await documentStore.get(
{id: statusListCredential, useCache: false});
assertSlcDoc({slcDoc, id: statusListCredential});

// TODO: use `documentStore.upsert` and `mutator` feature
const {edvClient} = documentStore;

while(true) {
try {
// check if `credential` status is already set, if so, done
const slc = slcDoc.content;
const {credentialSubject: {encodedList}} = slc;
const list = await decodeList({encodedList});
if(list.getStatus(statusListIndex) === status) {
return;
}

// update issuer
const {meta: {statusListConfig}} = slcDoc;
const {issuer, suite} = await getIssuerAndSuite({
config, suiteName: statusListConfig.suiteName
});
slc.issuer = issuer;

// use index to set status
list.setStatus(statusListIndex, status);
slc.credentialSubject.encodedList = await list.encode();

// express date without milliseconds
const date = new Date();
// TODO: use `validFrom` and `validUntil` for v2 VCs
slc.issuanceDate = `${date.toISOString().slice(0, -5)}Z`;
// FIXME: get validity period via status service instance config
date.setDate(date.getDate() + 1);
slc.expirationDate = `${date.toISOString().slice(0, -5)}Z`;
// delete existing proof and reissue SLC VC
delete slc.proof;
slcDoc.content = await _issue({credential: slc, documentLoader, suite});

// update SLC doc
await edvClient.update({doc: slcDoc});
return;
} catch(e) {
if(e.name !== 'InvalidStateError') {
throw e;
}
// ignore conflict, read and try again
slcDoc = await edvClient.get({id: slcDoc.id});
}
}
}

export function getMatchingStatusListCredentialMeta({
meta, credentialStatus
} = {}) {
// return match against `meta.credentialStatus` where the status entry
// type and status purpose match
const candidates = meta.credentialStatus || [];
for(const c of candidates) {
if(c.type === credentialStatus.type &&
(credentialStatus.type === 'RevocationList2020Status' ||
c.statusPurpose === credentialStatus.statusPurpose)) {
return c;
}
}

let purposeMessage = '';
if(credentialStatus.statusPurpose) {
purposeMessage =
`with status purpose "${credentialStatus.statusPurpose}" `;
}

throw new BedrockError(
`Credential status type "${credentialStatus.type}" ${purposeMessage}` +
'is not supported by this issuer instance.', 'NotSupportedError', {
httpStatusCode: 400,
public: true
});
}
101 changes: 101 additions & 0 deletions lib/slcs.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from './helpers.js';
import assert from 'assert-plus';
import {createDocumentLoader} from './documentLoader.js';
import {decodeList} from '@digitalbazaar/vc-status-list';
import {LruCache} from '@digitalbazaar/lru-memoize';

const {util: {BedrockError}} = bedrock;
Expand Down Expand Up @@ -203,6 +204,106 @@ export async function refresh({id, config} = {}) {
}
}

export async function setStatus({id, config, credentialStatus, status} = {}) {
assert.string(id, 'id');
assert.object(config, 'config');
assert.object(credentialStatus, 'credentialStatus');
assert.bool(status, 'status');

const [documentLoader, documentStore] = await Promise.all([
createDocumentLoader({config}),
getDocumentStore({config})
]);

// check `credentialStatus` against credential meta information
const {meta} = await documentStore.get({id});
const {
statusListCredential, statusListIndex
} = _getMatchingStatusListCredentialMeta({
meta, credentialStatus
});

// get SLC document; do not use cache to ensure latest doc is retrieved
let slcDoc = await documentStore.get(
{id: statusListCredential, useCache: false});
assertSlcDoc({slcDoc, id: statusListCredential});

// TODO: use `documentStore.upsert` and `mutator` feature
const {edvClient} = documentStore;

while(true) {
try {
// check if `credential` status is already set, if so, done
const slc = slcDoc.content;
const {credentialSubject: {encodedList}} = slc;
const list = await decodeList({encodedList});
if(list.getStatus(statusListIndex) === status) {
return;
}

// update issuer
const {meta: {statusListConfig}} = slcDoc;
const {issuer, suite} = await getIssuerAndSuite({
config, suiteName: statusListConfig.suiteName
});
slc.issuer = issuer;

// use index to set status
list.setStatus(statusListIndex, status);
slc.credentialSubject.encodedList = await list.encode();

// express date without milliseconds
const date = new Date();
// TODO: use `validFrom` and `validUntil` for v2 VCs
slc.issuanceDate = `${date.toISOString().slice(0, -5)}Z`;
// FIXME: get validity period via status service instance config
date.setDate(date.getDate() + 1);
slc.expirationDate = `${date.toISOString().slice(0, -5)}Z`;
// delete existing proof and reissue SLC VC
delete slc.proof;
slcDoc.content = await issue({credential: slc, documentLoader, suite});

// update SLC doc
await edvClient.update({doc: slcDoc});
return;
} catch(e) {
if(e.name !== 'InvalidStateError') {
throw e;
}
// ignore conflict, read and try again
slcDoc = await edvClient.get({id: slcDoc.id});
}
}
}

export function _getMatchingStatusListCredentialMeta({
meta, credentialStatus
} = {}) {
// return match against `meta.credentialStatus` where the status entry
// type and status purpose match
const candidates = meta.credentialStatus || [];
for(const c of candidates) {
if(c.type === credentialStatus.type &&
(credentialStatus.type === 'RevocationList2020Status' ||
c.statusPurpose === credentialStatus.statusPurpose)) {
return c;
}
}

let purposeMessage = '';
if(credentialStatus.statusPurpose) {
purposeMessage =
`with status purpose "${credentialStatus.statusPurpose}" `;
}

throw new BedrockError(
`Credential status type "${credentialStatus.type}" ${purposeMessage}` +
'is not supported by this issuer instance.', 'NotSupportedError', {
httpStatusCode: 400,
public: true
});
}

async function _getUncachedRecord({id}) {
const collection = database.collections[COLLECTION_NAME];
const record = await collection.findOne(
Expand Down
Loading

0 comments on commit 8ab3766

Please sign in to comment.