diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 67fcad0d6..b171d5a88 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -10,6 +10,7 @@ export * from './types/IAgent.js' export * from './types/ICredentialStatus.js' export * from './types/ICredentialStatusManager.js' export * from './types/ICredentialStatusVerifier.js' +export * from './types/ICredentialStatusRouter.js' export * from './types/IDataStore.js' export * from './types/IDataStoreORM.js' export * from './types/IIdentifier.js' diff --git a/packages/core/src/types/ICredentialStatusManager.ts b/packages/core/src/types/ICredentialStatusManager.ts index 5d51ec8f2..4cf8f0ff9 100644 --- a/packages/core/src/types/ICredentialStatusManager.ts +++ b/packages/core/src/types/ICredentialStatusManager.ts @@ -1,5 +1,5 @@ export { DIDDocument, DIDResolutionOptions, DIDResolutionResult } from 'did-resolver' -import { IPluginMethodMap } from './IAgent.js' +import { IAgentContext, IPluginMethodMap } from './IAgent.js' import { CredentialStatusReference, VerifiableCredential } from './vc-data-model.js' /** @@ -54,11 +54,30 @@ export interface CredentialStatusGenerateArgs { [x: string]: any } +/** + * Arguments to request the verifiable credential status value. + * + * @beta This API may change without a BREAKING CHANGE notice. + */ +export interface CredentialStatusRequestArgs { + /** + * The credential with a defined credential status + * + * @beta This API may change without a BREAKING CHANGE notice. + */ + credential: VerifiableCredential & { credentialStatus: CredentialStatusReference } +} + /** * Credential status manager interface * @beta */ export interface ICredentialStatusManager extends IPluginMethodMap { + /** + * + */ + credentialStatusRead(args: CredentialStatusRequestArgs, context: IAgentContext): Promise + /** * Changes the status of an existing {@link VerifiableCredential}. * Commonly used to revoke an existing credential. diff --git a/packages/core/src/types/ICredentialStatusRouter.ts b/packages/core/src/types/ICredentialStatusRouter.ts new file mode 100644 index 000000000..5057c2d15 --- /dev/null +++ b/packages/core/src/types/ICredentialStatusRouter.ts @@ -0,0 +1,42 @@ +import { IAgentContext, IPluginMethodMap } from "./IAgent" +import { CredentialStatusGenerateArgs, CredentialStatusRequestArgs, CredentialStatusUpdateArgs } from "./ICredentialStatusManager" +import { ICheckCredentialStatusArgs } from "./ICredentialStatusVerifier" +import { IResolver } from "./IResolver" +import { CredentialStatus, CredentialStatusReference, VerifiableCredential } from "./vc-data-model" + + +export interface ICredentialStatusRouter extends IPluginMethodMap { + /** + * Returns a list of available credential status methods + */ + statusRouterGetStatusMethods(): Promise + + /** + * Returns the revocation status of a credential from a given managed method + */ + statusRouterCheckStatus(args: ICheckCredentialStatusArgs, context: IAgentContext): Promise + + /** + * Generates a `credentialStatus` property for a future credential, not yet signed. + * + * @param args - Input arguments for generating a `credentialStatus` property for a {@link VerifiableCredential} + * @returns A {@link CredentialStatusReference} object + */ + statusRouterGenerateStatus(args: CredentialStatusGenerateArgs): Promise + + /** + * Reads a credential with a `credentialStatus` property and returns the parsed credential. + * + * @param args - Input arguments to request the verifiable credential status value + * @returns A {@link VerifiableCredential} object + */ + statusRouterParseStatus(args: CredentialStatusRequestArgs, context: IAgentContext): Promise + + /** + * Changes the status of an existing {@link VerifiableCredential}. + * Commonly used to revoke an existing credential. + * + * @param args - Input arguments for updating the status(revoking) a credential + */ + statusRouterUpdateStatus(args: CredentialStatusUpdateArgs): Promise +} \ No newline at end of file diff --git a/packages/credential-status-list-2021/src/credential-status-list-2021.ts b/packages/credential-status-list-2021/src/credential-status-list-2021.ts index 6d5666631..f95263e7c 100644 --- a/packages/credential-status-list-2021/src/credential-status-list-2021.ts +++ b/packages/credential-status-list-2021/src/credential-status-list-2021.ts @@ -3,7 +3,17 @@ import { CredentialStatus, CredentialStatusGenerateArgs, CredentialStatusReference, - CredentialStatusUpdateArgs, IAgentContext, IAgentPlugin, ICheckCredentialStatusArgs, ICredentialStatus, IResolver, IssuerType, ProofType, UnsignedCredential, VerifiableCredential + CredentialStatusUpdateArgs, + CredentialStatusRequestArgs, + IAgentContext, + IAgentPlugin, + ICheckCredentialStatusArgs, + ICredentialStatus, + IResolver, + IssuerType, + ProofType, + UnsignedCredential, + VerifiableCredential, } from '@veramo/core' import { ICredentialIssuer } from '@veramo/credential-w3c' @@ -90,20 +100,6 @@ export interface StatusList2021UpdateArgs extends CredentialStatusUpdateArgs { } } -/** - * Arguments to request the verifiable credential status value. - * - * @beta This API may change without a BREAKING CHANGE notice. - */ -export interface CredentialStatusRequestArgs { - /** - * The credential with a defined credential status - * - * @beta This API may change without a BREAKING CHANGE notice. - */ - credential: VerifiableCredential & { credentialStatus: CredentialStatusReference } -} - /** * Used to store the credntials status list by an ID. * diff --git a/packages/credential-status/jest.config.cjs b/packages/credential-status/jest.config.cjs new file mode 100644 index 000000000..9fbd9537c --- /dev/null +++ b/packages/credential-status/jest.config.cjs @@ -0,0 +1,6 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + modulePathIgnorePatterns: [""], +}; \ No newline at end of file diff --git a/packages/credential-status/src/__tests__/credential-status.test.ts b/packages/credential-status/src/__tests__/credential-status.test.ts index 56e4f4b97..034d172d2 100644 --- a/packages/credential-status/src/__tests__/credential-status.test.ts +++ b/packages/credential-status/src/__tests__/credential-status.test.ts @@ -1,12 +1,14 @@ -import { createAgent, ICredentialStatusVerifier, VerifiableCredential } from '../../../core/build/index.js' -import { DIDResolverPlugin } from '../../../did-resolver/build/index.js' -import { CredentialStatusPlugin } from '../../build/credential-status.js' +import { createAgent, VerifiableCredential } from '@veramo/core' +import { ICredentialStatusVerifier } from '@veramo/core/src/types/ICredentialStatusVerifier.js' +import { DIDResolverPlugin } from '@veramo/did-resolver' +import { CredentialStatusPlugin } from '../credential-status' import { DIDDocument, DIDResolutionOptions, DIDResolutionResult, Resolvable } from 'did-resolver' import { jest } from '@jest/globals' +import { CredentialStatusReference } from '@veramo/core/src/types/vc-data-model.js' describe('@veramo/credential-status', () => { const referenceDoc: DIDDocument = { id: 'did:example:1234' } - const referenceCredential: VerifiableCredential = { + const referenceCredential: VerifiableCredential & { credentialStatus: CredentialStatusReference } = { '@context': [], issuanceDate: new Date().toISOString(), proof: {}, diff --git a/packages/credential-status/src/__tests__/status-router.test.ts b/packages/credential-status/src/__tests__/status-router.test.ts new file mode 100644 index 000000000..40836f19d --- /dev/null +++ b/packages/credential-status/src/__tests__/status-router.test.ts @@ -0,0 +1,58 @@ +import { createAgent, CredentialStatus, VerifiableCredential } from "@veramo/core" +import { DIDDocument } from "did-resolver" +import { AbstractStatusMethod } from '../abstract-status-method' +import { CredentialStatusRouter } from '../status-router' +import { CredentialStatusReference } from '@veramo/core/src/types/vc-data-model' +import { ICredentialStatusRouter } from '@veramo/core/src/types/ICredentialStatusRouter' +import { ICredentialIssuer } from "@veramo/credential-w3c" + +const referenceDidDoc: DIDDocument = { id: 'did:example:1234' } +const referenceCredentialStatus = { + type: 'ExoticStatusMethod2022', + id: 'some-exotic-id', +} +const referenceCredential: VerifiableCredential & { credentialStatus: CredentialStatusReference } = { + '@context': [], + issuanceDate: new Date().toISOString(), + proof: {}, + issuer: referenceDidDoc.id, + credentialSubject: {}, + credentialStatus: referenceCredentialStatus +} + +class ExoticStatusMethod2022 extends AbstractStatusMethod { + async checkCredentialStatus(args: any, context: any): Promise { + return { verified: true } + } + async credentialStatusRead(args: any, context: any): Promise { + return referenceCredential + } + async credentialStatusGenerate(args: any): Promise { + return referenceCredential.credentialStatus + } + async credentialStatusUpdate(args: any): Promise { + throw new Error("Method not implemented.") + } +} + +describe('@veramo/credential-status/status-router', () => { + it('should route to the correct status method instance', async () => { + const statusMethod = new ExoticStatusMethod2022() + const agent = createAgent({ + plugins: [ + new CredentialStatusRouter({ + statusMethods: { + ExoticStatusMethod2022: statusMethod, + }, + defaultStatusMethod: '', + }) + ] + }) + + const result = await agent.statusRouterCheckStatus({ + credential: referenceCredential, + }) + + expect(result).toStrictEqual({ verified: true }) + }) +}) \ No newline at end of file diff --git a/packages/credential-status/src/abstract-status-method.ts b/packages/credential-status/src/abstract-status-method.ts new file mode 100644 index 000000000..f8475ad93 --- /dev/null +++ b/packages/credential-status/src/abstract-status-method.ts @@ -0,0 +1,37 @@ +import { + CredentialStatus, + CredentialStatusGenerateArgs, + CredentialStatusReference, + CredentialStatusRequestArgs, + CredentialStatusUpdateArgs, + IAgentContext, + ICheckCredentialStatusArgs, + IResolver, + VerifiableCredential +} from "@veramo/core"; + +import { ICredentialIssuer } from '@veramo/credential-w3c' + + +/** + * An abstract class for the {@link @veramo/credential-status#CredentialStatusRouter} status method + */ +export abstract class AbstractStatusMethod { + abstract checkCredentialStatus( + args: ICheckCredentialStatusArgs, + context: IAgentContext + ): Promise + + abstract credentialStatusRead( + args: CredentialStatusRequestArgs, + context: IAgentContext + ): Promise + + abstract credentialStatusGenerate( + args: CredentialStatusGenerateArgs + ): Promise + + abstract credentialStatusUpdate( + args: CredentialStatusUpdateArgs + ): Promise +} \ No newline at end of file diff --git a/packages/credential-status/src/abstract-status-storage.ts b/packages/credential-status/src/abstract-status-storage.ts new file mode 100644 index 000000000..ac1aec025 --- /dev/null +++ b/packages/credential-status/src/abstract-status-storage.ts @@ -0,0 +1,5 @@ +export abstract class AbstractStatusStorage { + abstract get(key: string): Promise + abstract set(key: string, value: string): Promise + abstract keys(): Promise +} \ No newline at end of file diff --git a/packages/credential-status/src/status-router.ts b/packages/credential-status/src/status-router.ts new file mode 100644 index 000000000..35ec4dcce --- /dev/null +++ b/packages/credential-status/src/status-router.ts @@ -0,0 +1,90 @@ +import { + CredentialStatus, + CredentialStatusGenerateArgs, + CredentialStatusReference, + CredentialStatusRequestArgs, + CredentialStatusUpdateArgs, + IAgentContext, + IAgentPlugin, + IAgentPluginSchema, + ICheckCredentialStatusArgs, + ICredentialStatusRouter, + IResolver, + VerifiableCredential, +} from "@veramo/core"; +import { ICredentialIssuer } from "@veramo/credential-w3c"; +import { AbstractStatusMethod } from "./abstract-status-method"; +import { AbstractStatusStorage } from "./abstract-status-storage"; + +/** + * Agent plugin that implements {@link @veramo/core#ICredentialStatusRouter} interface + * @public + */ +export class CredentialStatusRouter implements IAgentPlugin { + /** + * Plugin methods + * @public + */ + readonly methods: ICredentialStatusRouter + readonly schema?: IAgentPluginSchema | undefined + + private statusMethods: Record + // Beta: Default status method that bypasses the router + private defaultStatusMethod: string + // Beta: Instantiate a default storage method + private storage?: AbstractStatusStorage + + constructor(options: { + statusMethods: Record + defaultStatusMethod: string + storage?: AbstractStatusStorage + }) { + this.statusMethods = options.statusMethods + this.defaultStatusMethod = options.defaultStatusMethod + this.storage = options.storage + this.methods = { + statusRouterGetStatusMethods: this.statusRouterGetStatusMethods.bind(this), + statusRouterCheckStatus: this.statusRouterCheckCredentialStatus.bind(this), + statusRouterGenerateStatus: this.statusRouterGenerateStatus.bind(this), + statusRouterParseStatus: this.statusRouterParseStatus.bind(this), + statusRouterUpdateStatus: this.statusRouterUpdateStatus.bind(this), + } + } + + private getStatusMethod(statusReference: CredentialStatusReference): AbstractStatusMethod { + let statusMethod: AbstractStatusMethod | undefined = this.statusMethods[statusReference.type] + if (!statusMethod) { + throw new Error(`invalid_argument: unrecognized method ${statusReference.type}`) + } + return statusMethod + } + + /** {@inheritDoc @veramo/core#ICredentialStatusRouter.statusRouterGetStatusMethods} */ + async statusRouterGetStatusMethods(): Promise { + return Object.keys(this.statusMethods) + } + + /** {@inheritDoc @veramo/core#ICredentialStatusRouter.statusRouterCheckCredentialStatus} */ + async statusRouterCheckCredentialStatus(args: ICheckCredentialStatusArgs, context: IAgentContext): Promise { + const statusMethod = this.getStatusMethod(args.credential?.credentialStatus!) + return statusMethod.checkCredentialStatus(args, context) + } + + /** {@inheritDoc @veramo/core#ICredentialStatusRouter.statusRouterGenerateStatus} */ + async statusRouterGenerateStatus(args: CredentialStatusGenerateArgs): Promise { + const statusMethod = this.getStatusMethod({ id: '', type: args.type } as CredentialStatusReference) + return statusMethod.credentialStatusGenerate(args) + } + + /** {@inheritDoc @veramo/core#ICredentialStatusRouter.statusRouterParseStatus} */ + async statusRouterParseStatus(args: CredentialStatusRequestArgs, context: IAgentContext): Promise { + const statusMethod = this.getStatusMethod(args.credential.credentialStatus) + return statusMethod.credentialStatusRead(args, context) + } + + /** {@inheritDoc @veramo/core#ICredentialStatusRouter.statusRouterUpdateStatus} */ + async statusRouterUpdateStatus(args: CredentialStatusUpdateArgs): Promise { + const statusMethod = this.getStatusMethod(args.vc.credentialStatus!) + return statusMethod.credentialStatusUpdate(args) + } +} \ No newline at end of file