diff --git a/packages/cli/package.json b/packages/cli/package.json index 8250e873..56f097b1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -76,6 +76,7 @@ "@veramo/url-handler": "5.5.3", "@veramo/utils": "5.5.3", "blessed": "^0.1.81", + "body-parser": "^1.20.3", "commander": "^11.1.0", "console-table-printer": "^2.12.0", "cors": "^2.8.5", @@ -110,6 +111,7 @@ "yaml": "^2.3.4" }, "devDependencies": { + "@types/body-parser": "^1.19.5", "@types/debug": "4.1.12", "@types/express": "4.17.21", "@types/inquirer": "9.0.7", diff --git a/packages/cli/src/server.ts b/packages/cli/src/server.ts index 12b1c5f5..a18c11bf 100644 --- a/packages/cli/src/server.ts +++ b/packages/cli/src/server.ts @@ -1,5 +1,6 @@ import express from 'express' import { Command } from 'commander' +import bodyParser from 'body-parser' import { getConfig } from './setup.js' import { createObjects } from './lib/objectCreator.js' @@ -8,6 +9,8 @@ const server = new Command('server') .option('-p, --port ', 'Optionally set port to override config') .action(async (opts: { port: number }, cmd: Command) => { const app = express() + app.use(bodyParser.json({ limit: '50mb' })); + app.use(bodyParser.urlencoded({ limit: '50mb', extended: true })); let server: any diff --git a/packages/core-types/src/plugin.schema.json b/packages/core-types/src/plugin.schema.json index 5ed9fd96..09089c1c 100644 --- a/packages/core-types/src/plugin.schema.json +++ b/packages/core-types/src/plugin.schema.json @@ -6020,7 +6020,7 @@ "credential": { "anyOf": [ { - "$ref": "#/components/schemas/VerifiableCredential" + "$ref": "#/components/schemas/W3CVerifiableCredential" }, { "$ref": "#/components/schemas/UnsignedCredential" @@ -6033,6 +6033,17 @@ ], "description": "Arguments for rendering a verifiable credential." }, + "W3CVerifiableCredential": { + "anyOf": [ + { + "$ref": "#/components/schemas/VerifiableCredential" + }, + { + "$ref": "#/components/schemas/CompactJWT" + } + ], + "description": "Represents a signed Verifiable Credential (includes proof), in either JSON or compact JWT format. See {@link https://www.w3.org/TR/vc-data-model/#credentials | VC data model } See {@link https://www.w3.org/TR/vc-data-model/#proof-formats | proof formats }" + }, "VerifiableCredential": { "type": "object", "properties": { @@ -6160,6 +6171,10 @@ ], "description": "Used for the discovery of information about the current status of a verifiable credential, such as whether it is suspended or revoked. The precise contents of the credential status information is determined by the specific `credentialStatus` type definition, and varies depending on factors such as whether it is simple to implement or if it is privacy-enhancing.\n\nSee {@link https://www.w3.org/TR/vc-data-model/#status | Credential Status }" }, + "CompactJWT": { + "type": "string", + "description": "Represents a Json Web Token in compact form. \"header.payload.signature\"" + }, "UnsignedCredential": { "type": "object", "properties": { diff --git a/packages/core-types/src/types/IRender.ts b/packages/core-types/src/types/IRender.ts index 8e07dcc1..016a650c 100644 --- a/packages/core-types/src/types/IRender.ts +++ b/packages/core-types/src/types/IRender.ts @@ -3,14 +3,14 @@ import { IDIDManager } from './IDIDManager'; import { IDataStore } from './IDataStore'; import { IKeyManager } from './IKeyManager'; import { IResolver } from './IResolver'; -import { UnsignedCredential, VerifiableCredential } from './vc-data-model'; +import { UnsignedCredential, W3CVerifiableCredential } from './vc-data-model'; /** * Arguments for rendering a verifiable credential. * @public */ export interface IRenderCredentialArgs { - credential: VerifiableCredential | UnsignedCredential; + credential: W3CVerifiableCredential | UnsignedCredential; } /** @@ -32,7 +32,7 @@ export type IRendererContext = IAgentContext< * Result of rendering a verifiable credential. * @beta */ -export interface IRenderDocument{ +export interface IRenderDocument { type: string; renderedTemplate: string; name?: string; diff --git a/packages/renderer/__tests__/renderer.test.ts b/packages/renderer/__tests__/renderer.test.ts index 416d84e6..d8797d1f 100644 --- a/packages/renderer/__tests__/renderer.test.ts +++ b/packages/renderer/__tests__/renderer.test.ts @@ -335,4 +335,29 @@ describe('Renderer', () => { }, ]); }); + + it('should render a verifiable credential with JWT - Enveloping Proof Jose', async () => { + // Mock data + const args: IRenderCredentialArgs = { + credential: + 'eyJhbGciOiJFZERTQSIsImlzcyI6ImRpZDp3ZWI6ZDVmNi0xMTYtMTA2LTE5Mi0yMTUubmdyb2stZnJlZS5hcHAiLCJ0eXAiOiJ2Yy1sZCtqd3QifQ.eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvbnMvY3JlZGVudGlhbHMvdjIiLCJodHRwczovL3d3dy53My5vcmcvbnMvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjIiLCJodHRwczovL2Rldi1yZW5kZXItbWV0aG9kLWNvbnRleHQuczMuYXAtc291dGhlYXN0LTEuYW1hem9uYXdzLmNvbS9kZXYtcmVuZGVyLW1ldGhvZC1jb250ZXh0Lmpzb24iXSwiaWQiOiJodHRwOi8vdW5pdmVyc2l0eS5leGFtcGxlL2NyZWRlbnRpYWxzLzE4NzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiRXhhbXBsZUFsdW1uaUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOndlYjpkNWY2LTExNi0xMDYtMTkyLTIxNS5uZ3Jvay1mcmVlLmFwcCIsInZhbGlkRnJvbSI6IjIwMTAtMDEtMDFUMTk6MjM6MjRaIiwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvZXhhbXBsZXMvZGVncmVlLmpzb24iLCJ0eXBlIjoiSnNvblNjaGVtYSJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDpleGFtcGxlOjEyMyIsImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjaGVsb3Igb2YgU2NpZW5jZSBhbmQgQXJ0cyJ9fSwicmVuZGVyIjpbeyJ0ZW1wbGF0ZSI6IlBHUnBkaUJ6ZEhsc1pUMGlkMmxrZEdnNk16QXdjSGc3SUdobGFXZG9kRG94TURCd2VEc2dZbTl5WkdWeU9pQXljSGdnYzI5c2FXUWdZbXhoWTJzN0lIUmxlSFF0WVd4cFoyNDZZMlZ1ZEdWeUlqNEtJQ0E4WkdsMlBnb2dJQ0FnVkdocGN5QkNZV05vWld4dmNpQnZaaUJUWTJsbGJtTmxJR0Z1WkNCQmNuUnpJR2x6SUdOdmJtWmxjbkpsWkNCMGJ3b2dJRHd2WkdsMlBnb2dJRHh6ZEhKdmJtY2djM1I1YkdVOUltWnZiblF0YzJsNlpUb2dNVFp3ZUNJK0NpQWdJQ0JLWVc1bElGTnRhWFJvQ2lBZ1BDOXpkSEp2Ym1jK0NpQWdQR1JwZGo0S0lDQWdJR0o1SUVWNFlXMXdiR1VnVlc1cGRtVnljMmwwZVM0S0lDQThMMlJwZGo0S1BDOWthWFkrIiwiQHR5cGUiOiJXZWJSZW5kZXJpbmdUZW1wbGF0ZTIwMjIifV0sImlzc3VhbmNlRGF0ZSI6IjIwMjQtMDktMzBUMDg6MzE6MTYuODg4WiJ9.UFC7Zxk3sAqef5Rs7oJxDwv304TTNPQHV1xBr2sgWg-xpZBAfmFpXcxoYvxEf80I__c3TwI95GnFH9C7qJzIAg', + }; + + const context = {}; + + // Call the renderCredential method + const result: IRenderResult = await renderer.renderCredential( + args, + context as IRendererContext, + ); + + // Verify the result + expect(result.documents).toEqual([ + { + renderedTemplate: + 'UEdScGRpQnpkSGxzWlQwaWQybGtkR2c2TXpBd2NIZzdJR2hsYVdkb2REb3hNREJ3ZURzZ1ltOXlaR1Z5T2lBeWNIZ2djMjlzYVdRZ1lteGhZMnM3SUhSbGVIUXRZV3hwWjI0NlkyVnVkR1Z5SWo0S0lDQThaR2wyUGdvZ0lDQWdWR2hwY3lCQ1lXTm9aV3h2Y2lCdlppQlRZMmxsYm1ObElHRnVaQ0JCY25SeklHbHpJR052Ym1abGNuSmxaQ0IwYndvZ0lEd3ZaR2wyUGdvZ0lEeHpkSEp2Ym1jZ2MzUjViR1U5SW1admJuUXRjMmw2WlRvZ01UWndlQ0krQ2lBZ0lDQktZVzVsSUZOdGFYUm9DaUFnUEM5emRISnZibWMrQ2lBZ1BHUnBkajRLSUNBZ0lHSjVJRVY0WVcxd2JHVWdWVzVwZG1WeWMybDBlUzRLSUNBOEwyUnBkajRLUEM5a2FYWSs=', + type: 'WebRenderingTemplate2022', + }, + ]); + }); }); diff --git a/packages/renderer/package.json b/packages/renderer/package.json index 525132db..c0f85d31 100644 --- a/packages/renderer/package.json +++ b/packages/renderer/package.json @@ -34,7 +34,8 @@ "dependencies": { "@digitalcredentials/jsonld": "5.2.1", "@vckit/core-types": "workspace:^", - "handlebars": "^4.7.8" + "handlebars": "^4.7.8", + "jose": "^5.9.3" }, "type": "module", "moduleDirectories": [ diff --git a/packages/renderer/src/renderer.ts b/packages/renderer/src/renderer.ts index b960fa8a..924f4aaf 100644 --- a/packages/renderer/src/renderer.ts +++ b/packages/renderer/src/renderer.ts @@ -1,9 +1,7 @@ import jsonld from '@digitalcredentials/jsonld'; import { JsonLdObj } from 'jsonld/jsonld-spec'; import { Buffer } from 'buffer'; - import { - VerifiableCredential, RenderMethodPayload, IRendererProvider, IAgentPlugin, @@ -12,9 +10,11 @@ import { IRenderResult, UnsignedCredential, IRenderer, + VerifiableCredential, } from '@vckit/core-types'; import { RenderDefaultContexts } from './render-default-contexts.js'; import schema from '@vckit/core-types/build/plugin.schema.json' assert { type: 'json' }; +import * as jose from 'jose'; export const RENDER_METHOD = 'https://www.w3.org/2018/credentials#renderMethod'; @@ -73,9 +73,14 @@ export class Renderer implements IAgentPlugin { context?: IRendererContext, ): Promise { try { - const [expandedDocument] = await expandVerifiableCredential( - args.credential, - ); + let credential: VerifiableCredential | UnsignedCredential; + if (typeof args.credential === 'string') { + credential = decodeJWTJose(args.credential); + } else { + credential = { ...args.credential }; + } + + const [expandedDocument] = await expandVerifiableCredential(credential); if (!expandedDocument) { throw new Error('Error expanding the verifiable credential'); @@ -94,7 +99,7 @@ export class Renderer implements IAgentPlugin { const document = await rendererProvider.renderCredential({ data: renderMethod.data, context, - document: args.credential, + document: credential, }); const responseDocument = { type: renderMethod.type, @@ -193,3 +198,7 @@ function documentLoader(url: string, options: any) { } return documentLoaderFn(url); } + +function decodeJWTJose(jwt: string): UnsignedCredential { + return jose.decodeJwt(jwt); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1cbb4ab6..0f7ca98b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -272,7 +272,7 @@ importers: specifier: workspace:^ version: link:../utils '@vckit/vc-api': - specifier: workspace:1.0.0-beta.8 + specifier: workspace:1.0.0-beta.9 version: link:../vc-api '@veramo/core': specifier: link:../../.tmp_npm/veramo/packages/core @@ -343,6 +343,9 @@ importers: blessed: specifier: ^0.1.81 version: 0.1.81 + body-parser: + specifier: ^1.20.3 + version: 1.20.3 commander: specifier: ^11.1.0 version: 11.1.0 @@ -440,6 +443,9 @@ importers: specifier: ^2.3.4 version: 2.4.2 devDependencies: + '@types/body-parser': + specifier: ^1.19.5 + version: 1.19.5 '@types/debug': specifier: 4.1.12 version: 4.1.12 @@ -1281,6 +1287,9 @@ importers: handlebars: specifier: ^4.7.8 version: 4.7.8 + jose: + specifier: ^5.9.3 + version: 5.9.3 devDependencies: '@types/jsonld': specifier: ^1.5.13 @@ -16012,6 +16021,26 @@ packages: transitivePeerDependencies: - supports-color + /body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9(supports-color@6.1.0) + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + /bonjour-service@1.2.1: resolution: {integrity: sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==} dependencies: @@ -25698,6 +25727,10 @@ packages: resolution: {integrity: sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==} dev: false + /jose@5.9.3: + resolution: {integrity: sha512-egLIoYSpcd+QUF+UHgobt5YzI2Pkw/H39ou9suW687MY6PmCwPmkNV/4TNjn1p2tX5xO3j0d0sq5hiYE24bSlg==} + dev: false + /js-base64@3.7.7: resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} dev: false @@ -31457,6 +31490,13 @@ packages: dependencies: side-channel: 1.0.6 + /qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.6 + dev: false + /qs@6.5.3: resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} engines: {node: '>=0.6'}