Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(credential-status-router): Added basic router implementation & verifier/ manager encapsulation #1022

Open
wants to merge 1 commit into
base: feat/credentialStatusList2021-ESM
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
21 changes: 20 additions & 1 deletion packages/core/src/types/ICredentialStatusManager.ts
Original file line number Diff line number Diff line change
@@ -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'

/**
Expand Down Expand Up @@ -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<any>): Promise<VerifiableCredential>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this method?
I think that statusRead/statusCheck operations should be reserved for the ICredentialStatusVerifier interface, but perhaps I'm misunderstanding how this fits in.

Can you add some description to it?


/**
* Changes the status of an existing {@link VerifiableCredential}.
* Commonly used to revoke an existing credential.
Expand Down
42 changes: 42 additions & 0 deletions packages/core/src/types/ICredentialStatusRouter.ts
Original file line number Diff line number Diff line change
@@ -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<string[]>

/**
* Returns the revocation status of a credential from a given managed method
*/
statusRouterCheckStatus(args: ICheckCredentialStatusArgs, context: IAgentContext<IResolver>): Promise<CredentialStatus>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method should be described by the ICredentialStatusVerifier interface.

If the ICredentialStatusRouter interface is supposed to be a merge between ICredentialStatusVerifier and ICredentialStatusManager, then we can declare that merger directly instead of redefining the methods.


/**
* 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<CredentialStatusReference>

/**
* 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<any>): Promise<VerifiableCredential>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand the use of this method.

Can you help me figure out how it fits into the greater model?


/**
* 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<any>
}
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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.
*
Expand Down
6 changes: 6 additions & 0 deletions packages/credential-status/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
modulePathIgnorePatterns: [""],
};
Original file line number Diff line number Diff line change
@@ -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: {},
Expand Down
58 changes: 58 additions & 0 deletions packages/credential-status/src/__tests__/status-router.test.ts
Original file line number Diff line number Diff line change
@@ -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 = <CredentialStatusReference>{
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<CredentialStatus> {
return { verified: true }
}
async credentialStatusRead(args: any, context: any): Promise<VerifiableCredential> {
return referenceCredential
}
async credentialStatusGenerate(args: any): Promise<CredentialStatusReference> {
return <CredentialStatusReference>referenceCredential.credentialStatus
}
async credentialStatusUpdate(args: any): Promise<any> {
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<ICredentialIssuer | ICredentialStatusRouter>({
plugins: [
new CredentialStatusRouter({
statusMethods: {
ExoticStatusMethod2022: statusMethod,
},
defaultStatusMethod: '',
})
]
})

const result = await agent.statusRouterCheckStatus({
credential: referenceCredential,
})

expect(result).toStrictEqual({ verified: true })
})
})
37 changes: 37 additions & 0 deletions packages/credential-status/src/abstract-status-method.ts
Original file line number Diff line number Diff line change
@@ -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<IResolver>
): Promise<CredentialStatus>

abstract credentialStatusRead(
args: CredentialStatusRequestArgs,
context: IAgentContext<ICredentialIssuer>
): Promise<VerifiableCredential>

abstract credentialStatusGenerate(
args: CredentialStatusGenerateArgs
): Promise<CredentialStatusReference>

abstract credentialStatusUpdate(
args: CredentialStatusUpdateArgs
): Promise<any>
}
5 changes: 5 additions & 0 deletions packages/credential-status/src/abstract-status-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export abstract class AbstractStatusStorage {
abstract get(key: string): Promise<string | undefined>
abstract set(key: string, value: string): Promise<void>
abstract keys(): Promise<string[]>
}
90 changes: 90 additions & 0 deletions packages/credential-status/src/status-router.ts
Original file line number Diff line number Diff line change
@@ -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<string, AbstractStatusMethod>
// Beta: Default status method that bypasses the router
private defaultStatusMethod: string
// Beta: Instantiate a default storage method
private storage?: AbstractStatusStorage

constructor(options: {
statusMethods: Record<string, AbstractStatusMethod>
defaultStatusMethod: string
storage?: AbstractStatusStorage
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the storage parameter is not actually used by this class. It looks safe to remove.

}) {
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<string[]> {
return Object.keys(this.statusMethods)
}

/** {@inheritDoc @veramo/core#ICredentialStatusRouter.statusRouterCheckCredentialStatus} */
async statusRouterCheckCredentialStatus(args: ICheckCredentialStatusArgs, context: IAgentContext<IResolver>): Promise<CredentialStatus> {
const statusMethod = this.getStatusMethod(args.credential?.credentialStatus!)
return statusMethod.checkCredentialStatus(args, context)
}

/** {@inheritDoc @veramo/core#ICredentialStatusRouter.statusRouterGenerateStatus} */
async statusRouterGenerateStatus(args: CredentialStatusGenerateArgs): Promise<CredentialStatusReference> {
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<ICredentialIssuer>): Promise<VerifiableCredential> {
const statusMethod = this.getStatusMethod(args.credential.credentialStatus)
return statusMethod.credentialStatusRead(args, context)
}

/** {@inheritDoc @veramo/core#ICredentialStatusRouter.statusRouterUpdateStatus} */
async statusRouterUpdateStatus(args: CredentialStatusUpdateArgs): Promise<any> {
const statusMethod = this.getStatusMethod(args.vc.credentialStatus!)
return statusMethod.credentialStatusUpdate(args)
}
}