Skip to content

Commit 4484829

Browse files
committed
Refactor SLC publication code and implement auto-publish.
1 parent e24c4a6 commit 4484829

File tree

6 files changed

+189
-145
lines changed

6 files changed

+189
-145
lines changed

lib/CredentialStatusWriter.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,10 +286,11 @@ export class CredentialStatusWriter {
286286
// include all status information in `meta`
287287
credentialStatus: {
288288
id: `${statusListCredential}#${statusListIndex}`,
289-
type,
289+
// `type` is set below
290+
type: undefined,
290291
statusListCredential,
291292
// this is the index of the VC's status within the status list
292-
statusListIndex: `${statusListIndex}`,
293+
statusListIndex,
293294
statusPurpose
294295
}
295296
};
@@ -336,6 +337,8 @@ export class CredentialStatusWriter {
336337
credentialStatus.statusListIndex = `${statusListIndex}`;
337338
delete credentialStatus.terseStatusListIndex;
338339
}
340+
// ensure `meta.credentialStatus.type` is set
341+
meta.credentialStatus.type = credentialStatus.type;
339342

340343
// add credential status if it did not already exist
341344
if(Array.isArray(credential.credentialStatus)) {

lib/ListSource.js

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/*!
22
* Copyright (c) 2020-2024 Digital Bazaar, Inc. All rights reserved.
33
*/
4-
import * as slcs from './slcs.js';
54
import {
65
createList as createList2020,
76
createCredential as createRlc
@@ -61,7 +60,7 @@ export class ListSource {
6160

6261
// FIXME: status service should handle this instead
6362
async _ensureStatusListCredentialExists({id, statusPurpose, length}) {
64-
const {documentLoader, edvClient} = this;
63+
const {statusListConfig, documentLoader, edvClient} = this;
6564

6665
// try to create SLC credential and EDV doc...
6766
const {suite, issuer} = this;
@@ -76,41 +75,27 @@ export class ListSource {
7675
`This credential expresses status information for some ` +
7776
'other credentials in an encoded and compressed list.';
7877
// express date without milliseconds
79-
const now = (new Date()).toJSON();
80-
credential.issuanceDate = `${now.slice(0, -5)}Z`;
78+
const date = new Date();
79+
// TODO: use `validFrom` and `validUntil` for v2 VCs
80+
credential.issuanceDate = `${date.toISOString().slice(0, -5)}Z`;
81+
// FIXME: get validity period via status service instance config
82+
date.setDate(date.getDate() + 1);
83+
credential.expirationDate = `${date.toISOString().slice(0, -5)}Z`;
8184
const verifiableCredential = await issue(
8285
{credential, documentLoader, suite});
83-
let doc = {
86+
const doc = {
8487
id: await edvClient.generateId(),
8588
content: verifiableCredential,
8689
// include `meta.type` as a non-user input type to validate against
87-
meta: {type: 'VerifiableCredential'}
90+
meta: {type: 'VerifiableCredential', statusListConfig}
8891
};
8992
try {
90-
doc = await edvClient.update({doc});
93+
await edvClient.update({doc});
9194
} catch(e) {
9295
if(e.name !== 'DuplicateError') {
9396
throw e;
9497
}
95-
// duplicate, ignore as another process created the SLC... get it
96-
doc = await edvClient.get({id: doc.id});
97-
}
98-
99-
// ensure SLC is published
100-
const isPublished = await slcs.exists({id});
101-
if(!isPublished) {
102-
const {content: credential, sequence} = doc;
103-
try {
104-
// store SLC Doc for public serving
105-
await slcs.set({credential, sequence});
106-
} catch(e) {
107-
// safe to ignore conflicts, a newer version of the SLC was published
108-
// than the one that was retrieved
109-
if(e.name === 'InvalidStateError' || e.name === 'DuplicateError') {
110-
return;
111-
}
112-
throw e;
113-
}
98+
// duplicate, ignore as another process created the SLC
11499
}
115100
}
116101
}

lib/helpers.js

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,25 @@ import {AsymmetricKey, KmsClient} from '@digitalbazaar/webkms-client';
77
import {didIo} from '@bedrock/did-io';
88
import {documentStores} from '@bedrock/service-agent';
99
import {generateId} from 'bnid';
10-
import {
11-
getCredentialStatus as get2020CredentialStatus
12-
} from '@digitalbazaar/vc-revocation-list';
13-
import {getCredentialStatus} from '@digitalbazaar/vc-status-list';
1410
import {getSuiteParams} from './suites.js';
1511
import {httpsAgent} from '@bedrock/https-agent';
1612
import {serviceAgents} from '@bedrock/service-agent';
1713
import {serviceType} from './constants.js';
1814

1915
const {util: {BedrockError}} = bedrock;
2016

17+
export function assertSlcDoc({slcDoc, id} = {}) {
18+
if(!(slcDoc?.meta.type === 'VerifiableCredential' &&
19+
_isStatusListCredential({credential: slcDoc?.content}))) {
20+
throw new BedrockError(
21+
`Credential "${id}" is not a supported status list credential.`,
22+
'DataError', {
23+
httpStatusCode: 400,
24+
public: true
25+
});
26+
}
27+
}
28+
2129
export async function generateLocalId() {
2230
// 128-bit random number, base58 multibase + multihash encoded
2331
return generateId({
@@ -28,27 +36,6 @@ export async function generateLocalId() {
2836
});
2937
}
3038

31-
// FIXME: move elsewhere?
32-
export function getCredentialStatusInfo({credential, statusListConfig}) {
33-
const {type, statusPurpose} = statusListConfig;
34-
let credentialStatus;
35-
let statusListIndex;
36-
let statusListCredential;
37-
if(type === 'RevocationList2020') {
38-
// use legacy credential status
39-
credentialStatus = get2020CredentialStatus({credential});
40-
statusListIndex = parseInt(credentialStatus.revocationListIndex, 10);
41-
({revocationListCredential: statusListCredential} = credentialStatus);
42-
} else {
43-
// use modern status list (2021)
44-
credentialStatus = getCredentialStatus({credential, statusPurpose});
45-
statusListIndex = parseInt(credentialStatus.statusListIndex, 10);
46-
({statusListCredential} = credentialStatus);
47-
}
48-
// FIXME: support other `credentialStatus` types
49-
return {credentialStatus, statusListIndex, statusListCredential};
50-
}
51-
5239
export async function getDocumentStore({config}) {
5340
// ensure indexes are set for VCs
5441
const {documentStore} = await documentStores.get({config, serviceType});
@@ -103,3 +90,29 @@ export async function issue({credential, documentLoader, suite}) {
10390
credential = {...credential};
10491
return vc.issue({credential, documentLoader, suite});
10592
}
93+
94+
// check if `credential` is some known type of status list credential
95+
function _isStatusListCredential({credential}) {
96+
// FIXME: check for VC context as well
97+
if(!(credential['@context'] && Array.isArray(credential['@context']))) {
98+
return false;
99+
}
100+
if(!(credential.type && Array.isArray(credential.type) &&
101+
credential.type.includes('VerifiableCredential'))) {
102+
return false;
103+
}
104+
105+
for(const type of credential.type) {
106+
if(type === 'RevocationList2020Credential') {
107+
// FIXME: check for matching `@context` as well
108+
return true;
109+
}
110+
if(type === 'StatusList2021Credential') {
111+
// FIXME: check for matching `@context as well
112+
return true;
113+
}
114+
}
115+
// FIXME: check other types
116+
117+
return false;
118+
}

lib/http.js

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44
import * as bedrock from '@bedrock/core';
55
import * as slcs from './slcs.js';
6-
import {getMatchingStatusListConfig, issue, setStatus} from './issuer.js';
6+
import {issue, setStatus} from './issuer.js';
77
import {
88
issueCredentialBody,
99
publishSlcBody,
@@ -91,12 +91,9 @@ export async function addRoutes({app, service} = {}) {
9191
const {config} = req.serviceObject;
9292
const {credentialId, credentialStatus} = req.body;
9393

94-
// check `credentialStatus` against status list options from `config`
95-
const match = getMatchingStatusListConfig({config, credentialStatus});
96-
9794
// FIXME: support client requesting `status=false` as well
9895
await setStatus({
99-
id: credentialId, config, statusListConfig: match, status: true
96+
id: credentialId, config, credentialStatus, status: true
10097
});
10198

10299
res.status(200).end();
@@ -138,7 +135,41 @@ export async function addRoutes({app, service} = {}) {
138135
const {config} = req.serviceObject;
139136
const {slcId} = req.params;
140137
const id = `${config.id}${cfg.routes.slcs}/${encodeURIComponent(slcId)}`;
141-
const record = await slcs.get({id});
142-
res.json(record.credential);
138+
while(true) {
139+
console.log('in get while loop');
140+
try {
141+
const record = await slcs.get({id});
142+
// refresh SLC if it has expired; get `now` as a minute into the
143+
// future to ensure the returned VC is still valid once returned to
144+
// the client
145+
const now = new Date();
146+
now.setDate(now.getTime() + 1000 * 60);
147+
// FIXME: support v2 VCs w/`validUntil`
148+
const validUntil = new Date(record.credential.expirationDate);
149+
if(validUntil <= now) {
150+
res.json(record.credential);
151+
return;
152+
}
153+
console.log('**********AUTO REFRESHING SLCS************');
154+
const doc = await slcs.refresh({id, config});
155+
res.json(doc.content);
156+
return;
157+
} catch(e) {
158+
if(e.name !== 'NotFoundError') {
159+
// unrecoverable error
160+
throw e;
161+
}
162+
try {
163+
// SLC not found, perhaps not published; try auto-publish
164+
console.log('***********AUTO PUBLISHING***********');
165+
await slcs.publish({id, config});
166+
} catch(publishError) {
167+
// log publication error
168+
logger.error(publishError.message, {error: publishError});
169+
// if publication fails, throw original `NotFoundError`
170+
throw e;
171+
}
172+
}
173+
}
143174
}));
144175
}

0 commit comments

Comments
 (0)