@digitalcredentials/credential-status-manager-git
v1.0.0
Published
A Typescript library for managing the status of Verifiable Credentials in Git using Status List 2021
Downloads
5
Keywords
Readme
credential-status-manager-git
A Typescript library for managing the status of Verifiable Credentials in Git using Status List 2021
Table of Contents
Background
Credentials are dynamic artifacts with a robust lifecycle that goes well beyond issuance. This lifecycle is liable to span revocation, suspension, and expiry, among other common states. Many proposals have been put forth to capture this model in Verifiable Credentials. One of the most mature specifications for this is Status List 2021. This library provides an implementation of this specification that leverages Git source control services like GitHub and GitLab for storage and authentication.
Install
- Node.js 16+ is recommended.
NPM
To install via NPM:
npm install @digitalcredentials/credential-status-manager-git
Development
To install locally (for development):
git clone https://github.com/digitalcredentials/credential-status-manager-git.git
cd credential-status-manager-git
npm install
Usage
Create credential status manager
The createStatusManager
function is the only exported pure function of this library. It is an asynchronous function that accepts configuration options and returns a credential status manager that aligns with these options. Here are all the possible configuration options:
| Key | Description | Type | Required |
| --- | --- | --- | --- |
| service
| name of the source control service that will host the credential status resources | github
| gitlab
| yes |
| ownerAccountName
| name of the owner account (personal or organization) in the source control service that will host the credential status resources | string | yes |
| repoName
| name of the credential status repository | string | yes |
| repoId
| ID of the credential status repository | string | yes (if service
= gitlab
) |
| metaRepoName
| name of the credential status metadata repository | string | yes |
| metaRepoId
| ID of the credential status metadata repository | string | yes (if service
= gitlab
) |
| repoAccessToken
| access token for the credential status repository in the source control service API | string | yes |
| metaRepoAccessToken
| access token for the credential status metadata repository in the source control service API | string | yes |
| didMethod
| name of the DID method used for signing | key
| web
| yes |
| didSeed
| seed used to deterministically generate DID | string | yes |
| didWebUrl
| URL for did:web
| string | yes (if didMethod
= web
) |
| signUserCredential
| whether or not to sign user credentials | boolean | no (default: false
) |
| signStatusCredential
| whether or not to sign status credentials | boolean | no (default: false
) |
Here is a sample call to createStatusManager
:
import { createStatusManager } from '@digitalcredentials/credential-status-manager-git';
const statusManager = await createStatusManager({
service: 'github',
ownerAccountName: 'university-xyz', // Please create an owner account (personal or organization) in your source control service of choice
repoName: 'credential-status', // Please create a unique credential status repository in the owner account
metaRepoName: 'credential-status-metadata', // Please create a unique credential status metadata repository in the owner account
repoAccessToken: 'abc123', // Please create your own access token in your source control service of choice (see Dependencies section for detailed instructions)
metaRepoAccessToken: 'def456', // Please create your own access token in your source control service of choice (see Dependencies section for detailed instructions)
didMethod: 'key',
didSeed: 'DsnrHBHFQP0ab59dQELh3uEwy7i5ArcOTwxkwRO2hM87CBRGWBEChPO7AjmwkAZ2', // Please create your own DID seed (see Dependencies section for detailed instructions)
signStatusCredential: true
});
Note: A Status List 2021 credential can be found in the designated status repository (repoName
) of the designated owner account (ownerAccountName
) which is populated by createStatusManager
. Additionally, relevant historical data can be found in the designated status metadata repository (metaRepoName
) in the same owner account. Note that these repositories need to be manually created prior to calling createStatusManager
. Finally, you can find a publicly visible version of the aforementioned Status List 2021 credential at the relevant URL for hosted sites in the source control service of choice (e.g., https://ownerAccountName
.github.io/repoName
/statusListId
for GitHub, where statusListId
is the name of a file that is automatically generated in repoName
).
Allocate status for credential
The allocateStatus
is an instance method that is called on a credential status manager initialized by createStatusManager
. It is an asynchronous method that accepts a credential as input, records its status in the caller's source control service of choice, and returns the credential with status metadata attached.
Here is a sample call to allocateStatus
:
const credential = {
'@context': [
'https://www.w3.org/2018/credentials/v1',
'https://w3id.org/security/suites/ed25519-2020/v1'
],
id: 'https://university-xyz.edu/credentials/3732',
type: [
'VerifiableCredential'
],
issuer: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC',
issuanceDate: '2020-03-10T04:24:12.164Z',
credentialSubject: {
id: 'did:example:abcdef'
}
};
const credentialWithStatus = await statusManager.allocateStatus(credential);
console.log(credentialWithStatus);
/*
{
'@context': [
'https://www.w3.org/2018/credentials/v1',
'https://w3id.org/security/suites/ed25519-2020/v1',
'https://w3id.org/vc/status-list/2021/v1'
],
id: 'https://university-xyz.edu/credentials/3732',
type: [ 'VerifiableCredential' ],
issuer: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC',
issuanceDate: '2020-03-10T04:24:12.164Z',
credentialSubject: { id: 'did:example:abcdef' },
credentialStatus: {
id: 'https://university-xyz.github.io/credential-status/V27UAUYPNR#1',
type: 'StatusList2021Entry',
statusPurpose: 'revocation',
statusListIndex: 1,
statusListCredential: 'https://university-xyz.github.io/credential-status/V27UAUYPNR'
}
}
*/
Note: If the caller invokes allocateStatus
multiple times with the same credential ID against the same instance of a credential status manager, the library will not allocate a new status list entry. It will just return a credential with the same status info as it did in the previous invocation.
Update status of credential
The updateStatus
is an instance method that is called on a credential status manager initialized by createStatusManager
. It is an asynchronous method that accepts a credential ID and desired credential status as input (options: active
| revoked
), records its new status in the caller's source control service of choice, and returns the status credential.
Here is a sample call to updateStatus
:
const statusCredential = await statusManager.updateStatus({
credentialId: credentialWithStatus.id,
credentialStatus: 'revoked'
});
console.log(statusCredential);
/*
{
'@context': [
'https://www.w3.org/2018/credentials/v1',
'https://w3id.org/vc/status-list/2021/v1'
],
id: 'https://university-xyz.github.io/credential-status/V27UAUYPNR',
type: [ 'VerifiableCredential', 'StatusList2021Credential' ],
credentialSubject: {
id: 'https://university-xyz.github.io/credential-status/V27UAUYPNR#list',
type: 'StatusList2021',
encodedList: 'H4sIAAAAAAAAA-3BMQ0AAAACIGf_0LbwAhoAAAAAAAAAAAAAAIC_AfqBUGnUMAAA',
statusPurpose: 'revocation'
},
issuer: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC',
issuanceDate: '2023-03-15T19:21:54.093Z'
}
*/
Check status of credential
The checkStatus
is an instance method that is called on a credential status manager initialized by createStatusManager
. It is an asynchronous method that accepts a credential ID as input and returns status information for the credential.
Here is a sample call to checkStatus
:
const credentialStatus = await statusManager.checkStatus(credentialWithStatus.id);
console.log(credentialStatus);
/*
{
timestamp: '2023-03-15T19:39:06.023Z',
credentialId: 'https://university-xyz.edu/credentials/3732',
credentialIssuer: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC',
credentialSubject: 'did:example:abcdef',
credentialState: 'revoked',
verificationMethod: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC#z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC',
statusListId: 'V27UAUYPNR',
statusListIndex: 1
}
*/
Check if caller has authority to update status of credentials
The hasStatusAuthority
is an instance method that is called on a credential status manager initialized by createStatusManager
. It is an asynchronous method that accepts an access token for the API of the caller's source control service of choice, and reports whether the caller has the authority to update the status of credentials.
Here is a sample call to hasStatusAuthority
in the context of Express.js middleware:
// retrieves status list manager
export async function getCredentialStatusManager(req, res, next) {
try {
req.statusManager = await getStatusManager();
next();
} catch (error) {
return res.send('Failed to retrieve credential status list manager');
}
}
// extracts access token from request header
function extractAccessToken(headers) {
if (!headers.authorization) {
return;
}
const [scheme, token] = headers.authorization.split(' ');
if (scheme === 'Bearer') {
return token;
}
}
// verifies whether caller has access to status repo
async function verifyStatusRepoAccess(req, res, next) {
const { headers } = req;
// verify that access token was included in request
const repoAccessToken = extractAccessToken(headers);
if (!repoAccessToken) {
return res.send('Failed to provide access token in request');
}
// check if caller has access to status repo
const hasAccess = await req.statusManager.hasStatusAuthority(repoAccessToken);
if (!hasAccess) {
return res.send('Caller is unauthorized to access status repo');
}
next();
}
Note: This code assumes that getStatusManager
either calls createStatusManager
or retrieves an existing status manager instance created at an earlier point in time.
Dependencies
Create credential status repositories
GitHub
- Login to GitHub
- If you are using an organization as the owner account for the credential status manager and you don't already have an organization, click the plus icon in the top-right corner of the screen, click New organization and follow the instructions for creating a new organization *
- Click the plus icon in the top-right corner of the screen and click New repository
- Configure a blank public repository for the credential status repository *, with an optional description, that is owned by your account *
- Click the plus icon in the top-right corner of the screen and click New repository
- Configure a blank private repository for the credential status metadata repository *, with an optional description, that is owned by your account *
*Note: The names you choose for the owner account, credential status repository, and credential status metadata repository should be passed in respectively as ownerAccountName
, repoName
, and metaRepoName
in invocations of createStatusManager
. When you create these repositories, be sure not to add any files (including common default files like .gitignore
, README.md
, or LICENSE
).
GitLab
- Login to GitLab
- If you are using a group as the owner account for the credential status manager and you don't already have a group, click the plus icon in the top-right corner of the screen, click New group and follow the instructions for creating a new group *
- Click the plus icon in the top-right corner of the screen and click New project/repository
- Configure a blank public repository for the credential status repository * that is owned by your account *
- Click the plus icon in the top-right corner of the screen and click New project/repository
- Configure a blank private repository for the credential status metadata repository * that is owned by your account *
*Note: The names you choose for the owner account, credential status repository, and credential status metadata repository, along with their IDs (which can be found at the main view for the repository/group), should be passed in respectively as ownerAccountName
, repoName
(ID: repoId
), and metaRepoName
(ID: metaRepoId
) in invocations of createStatusManager
. When you create these repositories, be sure not to add any files (including common default files like .gitignore
, README.md
, or LICENSE
).
Generate access tokens
GitHub
- Login to GitHub as an authorized member/representative of the owner account (
ownerAccountName
) - Click on your profile dropdown icon in the top-right corner of the screen
- Select the Settings tab
- Select the Developer settings tab toward the bottom of the left navigation panel
- Select the Personal access tokens tab
- Select the Fine-grained tokens tab
- Click the Generate new token button
- Enter a name, expiration date, and optional description for the access token
- Select the appropriate resource owner for the credential status repositories *
- Select Only select repositories and select the credential status repositories that you created earlier (
repoName
and/ormetaRepoName
) * - Select the Read and write access level for the Administration, Contents, and Pages permissions and keep the default Read-only access level for the Metadata permission
- Click the Generate token button
- Copy the generated token
- Use the token as the value for
repoAccessToken
and/ormetaRepoAccessToken
in invocations ofcreateStatusManager
andhasStatusAuthority
*
*Note: For the credential status metadata repository, you can either generate a separate access token and use that as the value for metaRepoAccessToken
or include it along with repoAccessToken
when selecting repositories. Whatever you decide, make sure to pass values for both of these values in invocations of createStatusManager
(even though the latter option will result in the same value for these properties). If you are using an organization as the owner account for the credential status manager and ownerAccountName
is not listed, follow these instructions to set a personal access token policy for it.
GitLab*
- Login to GitLab as an authorized member/representative of the owner account (
ownerAccountName
) - Select the credential status repository (
repoName
) - Select the Settings tab in the left navigation panel
- Select the Access Tokens tab within the Settings dropdown
- Enter a name and expiration date for the access token
- Select the Maintainer role
- Select the api scope
- Click the Create personal access token button
- Copy the generated token
- Use the token as the value for
repoAccessToken
in invocations ofcreateStatusManager
andhasStatusAuthority
- Repeat these steps for
metaRepoName
andmetaRepoAccessToken
*Note: At the time of this writing, group access tokens are only available in paid GitLab plans (i.e., Premium SaaS and Ultimate SaaS). Additionally, unlike other services, you cannot use the same access token for multiple repositories at this time (hence the need for repoAccessToken
and metaRepoAccessToken
). Finally, if you are unable to create access tokens, you are either on e free plan or you need to enable project access token creation.
Generate DID seeds
In order to generate a DID seed, you will need to use software that is capable of creating it in a format that corresponds to a valid DID document. Here is sample code that does this:
import { generateSecretKeySeed } from '@digitalcredentials/bnid';
// Set `didSeed` key to this value
const secretKeySeed = await generateSecretKeySeed();
If didMethod
= web
, you must also generate a DID document and host it at didWebUrl
/.well-known/did.json. Here is sample code that does this:
import { decodeSecretKeySeed } from '@digitalcredentials/bnid';
import { Ed25519VerificationKey2020 } from '@digitalcredentials/ed25519-verification-key-2020';
import { X25519KeyAgreementKey2020 } from '@digitalcredentials/x25519-key-agreement-key-2020';
import * as DidWeb from '@interop/did-web-resolver';
import { CryptoLD } from '@digitalcredentials/crypto-ld';
const cryptoLd = new CryptoLD();
cryptoLd.use(Ed25519VerificationKey2020);
cryptoLd.use(X25519KeyAgreementKey2020);
const didWebDriver = DidWeb.driver({ cryptoLd });
const decodedSeed = decodeSecretKeySeed({secretKeySeed});
// Host this document at `didWebUrl`/.well-known/did.json
const didWebUrl = 'https://university-xyz.edu';
const didDocument = didWebDriver.generate({ url: didWebUrl, seed: decodedSeed });
Contribute
PRs accepted.
If editing the Readme, please conform to the standard-readme specification.
License
MIT License © 2023 Digital Credentials Consortium.