This is a library for credential issuers integrated with GlobaliD.
npm install @globalid/issuer-toolkit
The GidIssuerClient
class is the primary component of the toolkit, providing several methods for issuing a credential.
The function to create a GidIssuerClient
requires the client ID and secret of a developer app created in GlobaliD's developer portal.
const clientId = '...';
const clientSecret = '...';
const client = createGidIssuerClient(clientId, clientSecret);
The GidIssuerClient
supports the typical flow for issuing a credential:
- Receive and validate a credential request.
- Encrypt and upload file claims (optional).
- Build and send a credential offer.
If anything goes wrong in that process, issuers can report an error, which notifies the prospective holder of a problem in the credential issuance.
The validateRequest
method will check the validity of a CredentialRequest
, which consists of the following properties:
data
(optional) - Information about the credential being requestedgidUuid
- UUID of the holder's GlobaliD identitysignature
- Result of digitally signing the concatenation of thetimestamp
,threadId
, and (if present)data
, using the holder's private keythreadId
- ID correlating interactions related to this credential requesttimestamp
- Time of the request as the number of milliseconds since the Unix epoch
Of those, the signature
and timestamp
are validated. The signature
is verified using the public key corresponding to the holder's identity (identified by gidUuid
). The timestamp
must be no more than 5 minutes in the past or 1 minute in the future. If the credential request is invalid, an error is thrown.
This method also handles boilerplate error reporting. An InvalidSignatureError
, StaleRequestError
, or EagerRequestError
is reported as a 600-16
. All other errors are reported as a 600-7
.
const threadId = '...';
const gidUuid = '...';
const credentialRequest = {
threadId,
gidUuid,
timestamp: 1640995200000,
signature: 'abcdefghijklmnopqrstuvwxyz',
data: {
givenName: 'Neville',
birthDate: '1980-07-30'
}
};
try {
await client.validateRequest(credentialRequest);
} catch (error) {
if (error instanceof IdentityNotFoundError) {
// invalid identity (i.e., `gidUuid` does not exist)
} else if (error instanceof PublicKeyNotFoundError) {
// user has no public key
} else if (error instanceof InvalidSignatureError) {
// `signature` is invalid
} else if (error instanceof StaleRequestError || error instanceof EagerRequestError) {
// `timestamp` is outside acceptable range
}
}
The uploadFile
method allows for encrypting and uploading a file to GlobaliD's S3 instance. The file is encrypted using AES and a randomly-generated 256-bit key, which is itself encrypted using the holder's public key.
const fileClaim = await client.uploadFile(gidUuid, {
name: '8bfd3afe-8f0b-4583-836e-97cde534e304.foo.jpg',
type: 'image/jpeg',
content: Buffer.from(/* ... */)
});
The result of uploadFile
is a FileClaimValue
intended for use in a CredentialOffer
(see Sending a Credential Offer). A FileClaimValue
has the following properties:
decryptionKey
- Symmetric key used to decrypt (via AES) the payload received by dereferencing theurl
. The key is encrypted using RSA and the holder's public key.sha512sum
- Checksum of the file's contenttype
- Media type of the file's contenturl
- Location of the encrypted file
The sendOffer
method allows sending an offer for a credential following a credential request. The method accepts a CredentialOffer
, which has the following properties:
claims
- Claims about the credential subjectcontextUri
- URI of a JSON-LD context describing the credential subjectdescription
(optional) - Descriptive text about the credential being offeredname
- Name of the credential being offeredschemaUri
- URI of a JSON Schema describing the data schema of the credential subject's claimssubjectType
- JSON-LD@type
of the credential subjectthreadId
- ID correlating interactions related to this credential request
const claims = ;
const credentialOffer = {
threadId,
name: 'Government ID',
description: 'Lorem ipsum dolor sit amet',
contextUri: 'https://example.com/contexts/Person',
schemaUri: 'https://example.com/schemas/Person',
subjectType: 'Person',
claims: {
givenName: 'Neville',
birthDate: '1980-07-30',
avatar: fileClaim
}
};
await client.sendOffer(credentialOffer);
If something goes wrong while fulfilling a credential request, you can report the error using the reportError
method.
await client.reportError(threadId, '600-1');
Code | Description |
---|---|
300-8 |
Document unsupported |
600-1 |
General credential request failure |
600-3 |
Verification process was cancelled |
600-7 |
GlobaliD erred or is unavailable |
600-8 |
Issuer is unavailable |
600-16 |
Request validation failed |
The toolkit offers the downloadFile
utility function for downloading and optionally decrypting a file from a URL, presumably sent in the initial credential request. This function is essentially the inverse of GidIssuerClient
's uploadFile
.
In addition to a URL string, downloadFile
accepts the following options:
decryptionKey
- Symmetric key used to decrypt the downloaded file via AES. The file is assumed to be in plaintext if this option is absent.privateKey
- Asymmetric private key (typically the issuer's) used to decrypt thedecryptionKey
via RSA. ThedecryptionKey
is assumed to be plaintext if this option is absent.sha512sum
- Checksum used to validate the integrity of the downloaded (and possibly decrypted) file
import { downloadFile } from '@globalid/issuer-toolkit';
const buffer1 = await downloadFile('http://example.com/unencrypted-file');
const buffer2 = await downloadFile('https://example.com/encrypted-file', {
decryptionKey: request.data.avatar.key,
privateKey: process.env.PRIVATE_KEY,
sha512sum: request.data.avatar.checksum
});
The @globalid/issuer-toolkit/testing
module provides functions for mocking the HTTP requests (using nock
) made by GidIssuerClient
. There are mock*
functions for each GidIssuerClient
method, as well as a clearMocks
function for cleanup.
import * as GidIssuerClient from '@globalid/issuer-toolkit/testing';
afterEach(() => {
GidIssuerClient.clearMocks();
});
test('request validation', async () => {
GidIssuerClient.mockValidateRequest(gidUuid, publicKey);
// call your code that uses GidIssuerClient#validateRequest...
// assertions...
});
test('sending an offer', async () => {
GidIssuerClient.mockSendOffer();
// ...
});
The @globalid/issuer-toolkit/testing/sinon
allows Sinon users to create a GidIssuerClient
stub.
import stubGidIssuerClient from '@globalid/issuer-toolkit/testing/sinon';
import sinon from 'sinon';
const GidIssuerClientStub = stubGidIssuerClient();
afterEach(() => {
sinon.restore();
});
test('request validation', async () => {
GidIssuerClientStub.validateRequest.withArgs(/* ... */).resolves();
// call your code that uses GidIssuerClient#validateRequest...
// assertions...
});
test('sending an offer', async () => {
GidIssuerClientStub.sendOffer.withArgs(/* ... */).resolves();
// ...
});
The issuer toolkit is written in TypeScript, so type declarations are bundled with the package.
The following NPM scripts are available for development:
build
– Runs theclean
,genver
,compile
,lint
, andformat:check
scripts to build the projectclean
– Removes the output directory for a clean buildcompile
– Compiles TypeScript files withtsc
format
– Formats the files with Prettierformat:check
– Checks the formatting of the files with Prettiergenver
- Generates a version module withgenversion
lint
– Lints the code with ESLintlint:fix
– Attempts to fix problems found by the lintertest
– Tests the code with Jesttest:watch
– Tests the code in watch mode