Skip to content

Commit

Permalink
feat: implement vc api verifier router
Browse files Browse the repository at this point in the history
Signed-off-by: toanNgo <toan.ngo@gosource.com.au>
  • Loading branch information
toanNgo committed Jun 23, 2023
1 parent 100b35d commit c0617b9
Show file tree
Hide file tree
Showing 12 changed files with 494 additions and 147 deletions.
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@vckit/example-documents": "^1.0.0-beta.4",
"@vckit/remote-client": "^1.0.0-beta.4",
"@vckit/remote-server": "^1.0.0-beta.4",
"@vckit/vc-api": "^1.0.0-beta.2",
"@vckit/renderer": "workspace:1.0.0-beta.2",
"@veramo/credential-eip712": "^5.1.2",
"@veramo/credential-ld": "^5.1.2",
Expand Down
97 changes: 74 additions & 23 deletions packages/vc-api/README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,90 @@
# vc-api agent router
# vc-api

- This agent router conform to vc-api standard that to achieve interoperability's goal between various parties.
- This agent router conform to vc-api standard that to achieve interoperbaility’s goal between various parties.

## Usage

- This plugin follow the `veramo` architecture , so you can configure it with `agent.yml`
- This plugin follow the `veramo` architecture , so you can configure it with `agent.yml` . For example:

```jsx
# API base path
- - /issuer
- $require: '@vckit/vc-api-issuer?t=function#AgentRouter'

- - /vc-api
- $require: '@vckit/vc-api?t=function#IssuerRouter'
$args:
- createCredential: createVerifiableCredential
updateCredentialStatus: updateVerifiableCredentialStatus
config:
proofFormat: OpenAttestationMerkleProofSignature2018
save: false
- $require: '@vckit/vc-api?t=function#VerifierRouter'
$args:
- verifyCredential: verifyCredential
verifyPresentation: verifyPresentation
```

## Test with test-suite

- Clone the test suite: https://github.com/w3c-ccg/vc-api-issuer-test-suite
- Go to `node_modules/vc-api-test-suite-implementations/implementations` , create files except the index file
- Clone the test suite: https://github.com/w3c-ccg/vc-api-issuer-test-suite and https://github.com/w3c-ccg/vc-api-verifier-test-suite

```jsx
{
"name": "GoSource",
"implementation": "GoSource Verifiable Credentials",
"issuers": [{
"id": "YOUR_DID_MANAGED_BY_YOUR_MKS",
"endpoint": "http://localhost:3332/issuer/credentials/issue",
"tags": ["vc-api", "Ed25519Signature2020"]
}],
"verifiers": [{
"id": "YOUR_DID_MANAGED_BY_YOUR_MKS",
"endpoint": "http://localhost:3332/verifier/credentials/verify",
"method": "POST",
"tags": ["vc-api"]
}]
}
```

- Create new implementation file:

```jsx
{
"name": "GoSource",
"implementation": "GoSource Verifiable Credentials",
"issuers": [{
"id": "YOUR_DID_MANAGED_BY_YOUR_KMS",
"endpoint": "http://localhost:3332/agent/credentials/issue",
"options": {
"type": "Ed25519Signature2020"
},
"tags": ["vc-api", "Ed25519Signature2020"]
}]
}
```

- Run the test command `npm run test`
- Test result
- Issuer test result

```jsx
✓ MUST successfully issue a credential.
✓ Request body MUST have property "credential".
✓ credential MUST have property "@context".
✓ credential "@context" MUST be an array.
✓ credential "@context" items MUST be strings.
✓ credential MUST have property "type"
✓ "credential.type" MUST be an array.
✓ "credential.type" items MUST be strings
✓ credential MUST have property "issuer"
✓ "credential.issuer" MUST be a string or an object
✓ credential MUST have property "credentialSubject"
✓ "credential.credentialSubject" MUST be an object
✓ credential MAY have property "expirationDate"
```

- Verifier test result

```jsx
✓ MUST verify a valid VC.
✓ MUST not verify if "@context" property is missing.
✓ MUST not verify if "type" property is missing.
✓ MUST not verify if "issuer" property is missing.
✓ MUST not verify if "credentialSubject" property is missing.
✓ MUST not verify if "proof" property is missing.
✓ MUST not verify if "proof.type" property is missing.
✓ MUST not verify if "proof.created" property is missing.
✓ MUST not verify if "proof.verificationMethod" property is missing.
✓ MUST not verify if "proof.proofValue" property is missing.
✓ MUST not verify if "proof.proofPurpose" property is missing.
✓ MUST not verify if "@context" is not an array.
✓ MUST not verify if "@context" items are not strings.
✓ MUST not verify if "type" is not an array.
✓ MUST not verify if "type" items are not strings.
✓ MUST not verify if "issuer" is not an object or a string.
✓ MUST not verify if "credentialSubject" is not an object.
✓ MUST not verify if "proof" is not an object.
```
4 changes: 3 additions & 1 deletion packages/vc-api/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { ProofFormat } from '@vckit/core-types';
export type IssuerConfiguration = {
proofFormat: ProofFormat;
removeOriginalFields: boolean;
save: boolean
save: boolean;

[x: string]: any;
};

export const configuration: IssuerConfiguration = {
Expand Down
1 change: 1 addition & 0 deletions packages/vc-api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { IssuerRouter, IssuerRouterOptions } from './issuer-router.js'
export { VerifierRouter, VerifierRouterOptions } from './verifier-router.js'
26 changes: 19 additions & 7 deletions packages/vc-api/src/issuer-router.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { IAgent, VerifiableCredential } from '@vckit/core-types';
import { Request, Response, NextFunction, Router, json } from 'express';
import { validateCredentialPayload, validateUpdateStatusCredentialPayload } from './middlewares/index.js';
import { IssueCredentialRequestPayload, UpdateCredentialStatusRequestPayload } from './types/index.js';
import {
validateCredentialPayload,
validateUpdateStatusCredentialPayload,
} from './middlewares/index.js';
import {
IssueCredentialRequestPayload,
UpdateCredentialStatusRequestPayload,
} from './types/index.js';
import { mapCredentialPayload, mapCredentialResponse } from './utils/index.js';
import { configuration } from './config/index.js';
import {
IssuerConfiguration,
configuration as DEFAULT_CONFIG,
} from './config/index.js';
import { validationResult } from 'express-validator';

interface RequestWithAgent extends Request {
Expand All @@ -15,14 +24,16 @@ interface RequestWithAgent extends Request {
*/
export interface IssuerRouterOptions {
/**
* Agaent method to create credential
* Agent method to create credential
*/
createCredential: string;

/**
* Agent method to update credential status (revocation)
*/
updateCredentialStatus: string;

config?: IssuerConfiguration;
}

/**
Expand All @@ -37,6 +48,7 @@ export interface IssuerRouterOptions {
export const IssuerRouter = ({
createCredential,
updateCredentialStatus,
config,
}: IssuerRouterOptions): Router => {
const router = Router();
router.use(json({ limit: '10mb' }));
Expand All @@ -50,23 +62,23 @@ export const IssuerRouter = ({
if (!errors.isEmpty()) {
return res.status(400).json({ description: errors.array() });
}

if (!req.agent) {
throw Error('Agent not available');
}

try {
const payload = mapCredentialPayload(
req.body as IssueCredentialRequestPayload,
configuration
{ ...DEFAULT_CONFIG, ...config }
);
const result = (await req.agent.execute(
createCredential,
payload
)) as VerifiableCredential;
res.status(201).json(mapCredentialResponse(result));
} catch (e: any) {
return res.status(400).json({ error: e.message });
return res.status(500).json({ error: e.message });
}
}
);
Expand Down
1 change: 1 addition & 0 deletions packages/vc-api/src/middlewares/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './issuer.js'
export * from './verifier.js'
160 changes: 160 additions & 0 deletions packages/vc-api/src/middlewares/verifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { check, oneOf } from 'express-validator';

/**
* Validate the input for verifying credential that use vc-kit.
*/

export const validateVerifyCredentialRequest = () => {
return [
check('verifiableCredential').isObject().notEmpty(),
...validateContext(),
...validateIssuer(),
...validateCredentialSubject(),
...validateType(),
...validateProof(),
...validateOptions(),
];
};

export const validateVerifyPresentationRequest = () => {
return [
check('verifiablePresentation').isObject().notEmpty(),
...validateOptions(),
check('options.verificationMethod', 'options.domain must be a string')
.isString()
.notEmpty(),
check('options.proofPurpose', 'options.domain must be a string')
.isString()
.notEmpty(),
];
};

const validateProof = () => {
return [
check('verifiableCredential.proof').isObject().notEmpty(),
check(
'verifiableCredential.proof.type',
'verifiableCredential.proof.type must be a string'
)
.isString()
.notEmpty(),
check(
'verifiableCredential.proof.created',
'verifiableCredential.proof.created must be a string'
)
.isString()
.notEmpty(),
check(
'verifiableCredential.proof.challenge',
'verifiableCredential.proof.challenge must be a string'
)
.optional()
.isString()
.notEmpty(),
check(
'verifiableCredential.proof.domain',
'verifiableCredential.proof.domain must be a string'
)
.optional()
.isString()
.notEmpty(),
check(
'verifiableCredential.proof.nonce',
'verifiableCredential.proof.nonce must be a string'
)
.optional()
.isString()
.notEmpty(),
check(
'verifiableCredential.proof.verificationMethod',
'verifiableCredential.proof.verificationMethod must be a string'
)
.isString()
.notEmpty(),
check(
'verifiableCredential.proof.proofPurpose',
'verifiableCredential.proof.proofPurpose must be a string'
)
.isString()
.notEmpty(),
check(
'verifiableCredential.proof.jws',
'verifiableCredential.proof.jws must be a string'
)
.optional()
.isString()
.notEmpty(),
check(
'verifiableCredential.proof.proofValue',
'verifiableCredential.proof.proofValue must be a string'
)
.isString()
.notEmpty(),
];
};

const validateContext = () => {
return [
check(
'verifiableCredential.@context',
'verifiableCredential.@context must be an array'
).isArray({ min: 1 }),
check(
'verifiableCredential.@context.*',
'verifiableCredential.@context item must be string'
)
.isString()
.notEmpty(),
];
};

const validateIssuer = () => {
return [
oneOf(
[
check('verifiableCredential.issuer').isObject().notEmpty(),
check('verifiableCredential.issuer').isString().notEmpty(),
],
{ message: 'verifiableCredential.issuer must be string or an object' }
),
];
};

const validateCredentialSubject = () => {
return [
check(
'verifiableCredential.credentialSubject',
'verifiableCredential.credentialSubject must be an object'
)
.isObject()
.notEmpty(),
];
};

const validateType = () => {
return [
check(
'verifiableCredential.type',
'verifiableCredential.@type must be an array'
).isArray({ min: 1 }),
check(
'verifiableCredential.type.*',
'verifiableCredential.@type item must be string'
)
.isString()
.notEmpty(),
];
};

const validateOptions = () => {
return [
check('options.domain', 'options.domain must be a string')
.optional()
.isString()
.notEmpty(),
check('options.challenge', 'options.created must be a string')
.optional()
.isString()
.notEmpty(),
];
};
2 changes: 1 addition & 1 deletion packages/vc-api/src/types/issuer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export type IssueCredentialRequestPayload = {
options?: IssueCredentialOptions; // Options for specifying how the LinkedDataProof is created.
};

type IssueCredentialOptions = {
export type IssueCredentialOptions = {
created?: string; // The date and time of the proof. Default current system time.
credentialStatus?: { type: string }; // The method of credential status to issue the credential including. If omitted credential status will be included.
challenge?: string; // A challenge provided by the requesting party of the proof. For example 6e62f66e-67de-11eb-b490-ef3eeefa55f2
Expand Down
Loading

0 comments on commit c0617b9

Please sign in to comment.