diff --git a/packages/oid4vci-rest-client/CHANGELOG.md b/packages/oid4vci-issuer-rest-client/CHANGELOG.md similarity index 100% rename from packages/oid4vci-rest-client/CHANGELOG.md rename to packages/oid4vci-issuer-rest-client/CHANGELOG.md diff --git a/packages/oid4vci-rest-client/LICENSE b/packages/oid4vci-issuer-rest-client/LICENSE similarity index 100% rename from packages/oid4vci-rest-client/LICENSE rename to packages/oid4vci-issuer-rest-client/LICENSE diff --git a/packages/oid4vci-rest-client/README.md b/packages/oid4vci-issuer-rest-client/README.md similarity index 100% rename from packages/oid4vci-rest-client/README.md rename to packages/oid4vci-issuer-rest-client/README.md diff --git a/packages/oid4vci-issuer-rest-client/__tests__/localAgent.test.ts b/packages/oid4vci-issuer-rest-client/__tests__/localAgent.test.ts new file mode 100644 index 000000000..9dd73e0b6 --- /dev/null +++ b/packages/oid4vci-issuer-rest-client/__tests__/localAgent.test.ts @@ -0,0 +1,44 @@ +import { createObjects, getConfig } from '../../agent-config/dist' + +jest.setTimeout(30000) + +import issuanceRestClientAgentLogic from './shared/issuanceRestClientAgentLogic' +import nock from 'nock' + +let agent: any + +const setup = async (): Promise => { + const config = await getConfig('packages/oid4vci-issuer-rest-client/agent.yml') + const { localAgent } = await createObjects(config, { localAgent: '/agent' }) + agent = localAgent + nock('https://ssi-backend.sphereon.com/webapp/') + .post(`/credential-offers`, { + grants: { + 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { + 'pre-authorized_code': '1234', + user_pin_required: false, + }, + }, + credentials: ['dbc2023'], + }) + .times(4) + .reply(200, { + uri: 'openid-credential-offer://?credential_offer=%7B%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%221234%22%2C%22user_pin_required%22%3Afalse%7D%7D%2C%22credentials%22%3A%5B%22dbc2023%22%5D%2C%22credential_issuer%22%3A%22https%3A%2F%2Fdbc2023.test.sphereon.com%2Fissuer%2Fdbc2023%22%7D', + }) + return true +} + +const tearDown = async (): Promise => { + return true +} + +const getAgent = () => agent +const testContext = { + getAgent, + setup, + tearDown, +} + +describe('Local integration tests', () => { + issuanceRestClientAgentLogic(testContext) +}) diff --git a/packages/oid4vci-issuer-rest-client/__tests__/restAgent.test.ts b/packages/oid4vci-issuer-rest-client/__tests__/restAgent.test.ts new file mode 100644 index 000000000..7ce5f979e --- /dev/null +++ b/packages/oid4vci-issuer-rest-client/__tests__/restAgent.test.ts @@ -0,0 +1,67 @@ +import 'cross-fetch/polyfill' +// @ts-ignore +import express, { Router } from 'express' +import { Server } from 'http' +import { IAgent, createAgent, IAgentOptions } from '@veramo/core' +import { AgentRestClient } from '@veramo/remote-client' +import { AgentRouter, RequestWithAgentRouter } from '@veramo/remote-server' +import { createObjects, getConfig } from '../../agent-config/dist' +import { IOID4VCIRestClient } from '../src' +import issuanceRestClientAgentLogic from './shared/issuanceRestClientAgentLogic' + +jest.setTimeout(30000) + +const port = 3002 +const basePath = '/agent' + +let serverAgent: IAgent +let restServer: Server + +const getAgent = (options?: IAgentOptions) => + createAgent({ + ...options, + plugins: [ + new AgentRestClient({ + url: 'http://localhost:' + port + basePath, + enabledMethods: serverAgent.availableMethods(), + schema: serverAgent.getSchema(), + }), + ], + }) + +const setup = async (): Promise => { + const config = await getConfig('packages/oid4vci-issuer-rest-client/agent.yml') + const { agent } = await createObjects(config, { agent: '/agent' }) + serverAgent = agent + + const agentRouter: Router = AgentRouter({ + exposedMethods: serverAgent.availableMethods(), + }) + + const requestWithAgent: Router = RequestWithAgentRouter({ + agent: serverAgent, + }) + + return new Promise((resolve): void => { + const app = express() + app.use(basePath, requestWithAgent, agentRouter) + restServer = app.listen(port, () => { + resolve(true) + }) + }) +} + +const tearDown = async (): Promise => { + restServer.close() + return true +} + +const testContext = { + getAgent, + setup, + tearDown, +} + +describe('REST integration tests', () => { + issuanceRestClientAgentLogic(testContext) +}) diff --git a/packages/oid4vci-issuer-rest-client/__tests__/shared/issuanceRestClientAgentLogic.ts b/packages/oid4vci-issuer-rest-client/__tests__/shared/issuanceRestClientAgentLogic.ts new file mode 100644 index 000000000..5b531e468 --- /dev/null +++ b/packages/oid4vci-issuer-rest-client/__tests__/shared/issuanceRestClientAgentLogic.ts @@ -0,0 +1,49 @@ +import { TAgent } from '@veramo/core' +import { IOID4VCIRestClient } from '../../src' + +type ConfiguredAgent = TAgent + +export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Promise; tearDown: () => Promise }) => { + describe('ssi-sdk.oid4vci-issuer-rest-client', () => { + let agent: ConfiguredAgent + + beforeAll(async () => { + await testContext.setup() + agent = testContext.getAgent() + }) + afterAll(async () => { + await testContext.tearDown() + }) + + it('should create the url Offer Url with baseUrl', async () => { + const result = await agent.vciClientCreateOfferUri({ + baseUrl: 'https://ssi-backend.sphereon.com', + grants: { + 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { + 'pre-authorized_code': '1234', + user_pin_required: false, + }, + }, + credentials: ['dbc2023'], + }) + expect(result).toEqual({ + uri: 'openid-credential-offer://?credential_offer=%7B%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%221234%22%2C%22user_pin_required%22%3Afalse%7D%7D%2C%22credentials%22%3A%5B%22dbc2023%22%5D%2C%22credential_issuer%22%3A%22https%3A%2F%2Fdbc2023.test.sphereon.com%2Fissuer%2Fdbc2023%22%7D', + }) + }) + + it('should create the url Offer Url without baseUrl', async () => { + const result = await agent.vciClientCreateOfferUri({ + grants: { + 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { + 'pre-authorized_code': '1234', + user_pin_required: false, + }, + }, + credentials: ['dbc2023'], + }) + expect(result).toEqual({ + uri: 'openid-credential-offer://?credential_offer=%7B%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%221234%22%2C%22user_pin_required%22%3Afalse%7D%7D%2C%22credentials%22%3A%5B%22dbc2023%22%5D%2C%22credential_issuer%22%3A%22https%3A%2F%2Fdbc2023.test.sphereon.com%2Fissuer%2Fdbc2023%22%7D', + }) + }) + }) +} diff --git a/packages/oid4vci-issuer-rest-client/agent.yml b/packages/oid4vci-issuer-rest-client/agent.yml new file mode 100644 index 000000000..8df7785ae --- /dev/null +++ b/packages/oid4vci-issuer-rest-client/agent.yml @@ -0,0 +1,73 @@ +version: 3.0 + +constants: + baseUrl: http://localhost:3335 + port: 3335 + methods: + - vciClientCreateOfferUri + +server: + baseUrl: + $ref: /constants/baseUrl + port: + $ref: /constants/port + use: + # CORS + - - $require: 'cors' + + # Add agent to the request object + - - $require: '@veramo/remote-server?t=function#RequestWithAgentRouter' + $args: + - agent: + $ref: /agent + + # API base path + - - /agent + - $require: '@veramo/remote-server?t=function#apiKeyAuth' + $args: + # Please configure your own API key. This is used when executing agent methods through ${baseUrl}/agent or ${baseUrl}/api-docs + - apiKey: test123 + - $require: '@veramo/remote-server?t=function#AgentRouter' + $args: + - exposedMethods: + $ref: /constants/methods + + # Open API schema + - - /open-api.json + - $require: '@veramo/remote-server?t=function#ApiSchemaRouter' + $args: + - basePath: :3335/agent + securityScheme: bearer + apiName: Agent + apiVersion: '1.0.0' + exposedMethods: + $ref: /constants/methods + + # Swagger docs + - - /api-docs + - $require: swagger-ui-express?t=object#serve + - $require: swagger-ui-express?t=function#setup + $args: + - null + - swaggerOptions: + url: '/open-api.json' + + # Execute during server initialization + init: + - $require: '@veramo/remote-server?t=function#createDefaultDid' + $args: + - agent: + $ref: /agent + baseUrl: + $ref: /constants/baseUrl + messagingServiceEndpoint: /messaging + +# Agent +agent: + $require: '@veramo/core#Agent' + $args: + - schemaValidation: false + plugins: + - $require: ./packages/oid4vci-issuer-rest-client/dist#OID4VCIRestClient + $args: + - baseUrl: 'https://ssi-backend.sphereon.com' diff --git a/packages/oid4vci-rest-client/api-extractor.json b/packages/oid4vci-issuer-rest-client/api-extractor.json similarity index 100% rename from packages/oid4vci-rest-client/api-extractor.json rename to packages/oid4vci-issuer-rest-client/api-extractor.json diff --git a/packages/oid4vci-rest-client/package.json b/packages/oid4vci-issuer-rest-client/package.json similarity index 95% rename from packages/oid4vci-rest-client/package.json rename to packages/oid4vci-issuer-rest-client/package.json index 967f06247..de015418e 100644 --- a/packages/oid4vci-rest-client/package.json +++ b/packages/oid4vci-issuer-rest-client/package.json @@ -1,5 +1,5 @@ { - "name": "@sphereon/ssi-sdk.oid4vci-rest-client", + "name": "@sphereon/ssi-sdk.oid4vci-issuer-rest-client", "description": "contains the client side to call REST endpoints of a Verifiable Credential Issuer", "version": "0.11.0", "source": "src/index.ts", diff --git a/packages/oid4vci-rest-client/src/agent/OID4VCIRestClient.ts b/packages/oid4vci-issuer-rest-client/src/agent/OID4VCIRestClient.ts similarity index 66% rename from packages/oid4vci-rest-client/src/agent/OID4VCIRestClient.ts rename to packages/oid4vci-issuer-rest-client/src/agent/OID4VCIRestClient.ts index 2963b8580..f0fa667e9 100644 --- a/packages/oid4vci-rest-client/src/agent/OID4VCIRestClient.ts +++ b/packages/oid4vci-issuer-rest-client/src/agent/OID4VCIRestClient.ts @@ -8,7 +8,7 @@ import { import Debug from 'debug' import { IAgentPlugin } from '@veramo/core' -const debug = Debug('sphereon:ssi-sdk.oid4vci-rest-client') +const debug = Debug('sphereon:ssi-sdk.oid4vci-issuer-rest-client') /** * @beta @@ -30,36 +30,42 @@ export class OID4VCIRestClient implements IAgentPlugin { if (!args.credentials || !args.grants) { throw new Error("Can't generate the credential offer url without credentials and grants params present.") } - const baseUrl = this.checkBaseUrlParameter(args.baseUri) + this.assertBaseUrl(args.baseUrl) const request: IVCIClientCreateOfferUriRequest = { credentials: args.credentials, + grants: args.grants, } - if (args.grants) { - request['grants'] = args.grants - } + const baseUrl = args.baseUrl || this.baseUrl const url = this.urlWithBase(`webapp/credential-offers`, baseUrl) debug(`OID4VCIRestClient is going to send request: ${JSON.stringify(request)} to ${url}`) - const origResponse = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(request), - }) - return await origResponse.json() + try { + const origResponse = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(request), + }) + if (origResponse.status >= 400) { + return Promise.reject(Error(`request to ${url} returned ${origResponse.status}`)); + } + return await origResponse.json() + } catch (e) { + debug(`Error on posting to url ${url}: ${e}`) + throw e + } } private urlWithBase(path: string, baseUrl?: string): string { if (!this.baseUrl && !baseUrl) { throw new Error('You have to provide baseUrl') } - return baseUrl ? `${baseUrl}${path.startsWith('/') ? path : '/' + path}` : `${this.baseUrl}${path.startsWith('/') ? path : '/' + path}` + return baseUrl ? `${baseUrl}${path.startsWith('/') ? path : `/${path}`}` : `${this.baseUrl}${path.startsWith('/') ? path : `/${path}`}` } - private checkBaseUrlParameter(baseUrl?: string): string { + private assertBaseUrl(baseUrl?: string) { if (!baseUrl && !this.baseUrl) { throw new Error('No base url has been provided') } - return baseUrl ? baseUrl : (this.baseUrl as string) } } diff --git a/packages/oid4vci-rest-client/src/index.ts b/packages/oid4vci-issuer-rest-client/src/index.ts similarity index 100% rename from packages/oid4vci-rest-client/src/index.ts rename to packages/oid4vci-issuer-rest-client/src/index.ts diff --git a/packages/oid4vci-rest-client/src/types/IOID4VCIRestClient.ts b/packages/oid4vci-issuer-rest-client/src/types/IOID4VCIRestClient.ts similarity index 97% rename from packages/oid4vci-rest-client/src/types/IOID4VCIRestClient.ts rename to packages/oid4vci-issuer-rest-client/src/types/IOID4VCIRestClient.ts index 503018adf..deff30e56 100644 --- a/packages/oid4vci-rest-client/src/types/IOID4VCIRestClient.ts +++ b/packages/oid4vci-issuer-rest-client/src/types/IOID4VCIRestClient.ts @@ -8,7 +8,7 @@ export interface IOID4VCIRestClient extends IPluginMethodMap { export interface IVCIClientCreateOfferUriRequestArgs { grants: Grant credentials: (CredentialOfferFormat | string)[] - baseUri?: string + baseUrl?: string } export interface IVCIClientCreateOfferUriResponse { diff --git a/packages/oid4vci-rest-client/tsconfig.json b/packages/oid4vci-issuer-rest-client/tsconfig.json similarity index 100% rename from packages/oid4vci-rest-client/tsconfig.json rename to packages/oid4vci-issuer-rest-client/tsconfig.json diff --git a/packages/oid4vci-rest-client/__tests__/integration.test.ts b/packages/oid4vci-rest-client/__tests__/integration.test.ts deleted file mode 100644 index 2e8a317d8..000000000 --- a/packages/oid4vci-rest-client/__tests__/integration.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { createAgent, IResolver } from '@veramo/core' -import { IOID4VCIRestClient, OID4VCIRestClient } from '../src' - -const baseUrl = 'https://ssi-backend.sphereon.com' - -const agent = createAgent({ - plugins: [new OID4VCIRestClient({ baseUrl })], -}) - -describe('@sphereon/ssi-sdk.oid4vci-rest-client', () => { - xit('should mock the call endpoint vciClientCreateOfferUri', async () => { - const result = await agent.vciClientCreateOfferUri({ - baseUri: 'https://dbc2023.test.sphereon.com/issuer/dbc2023', - grants: { - 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { - 'pre-authorized_code': '1234', - user_pin_required: false, - }, - }, - credentials: ['dbc2023'], - }) - expect(result.uri).toEqual( - 'openid-credential-offer://?credential_offer=%7B%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%221234%22%2C%22user_pin_required%22%3Afalse%7D%7D%2C%22credentials%22%3A%5B%22dbc2023%22%5D%2C%22credential_issuer%22%3A%22https%3A%2F%2Fdbc2023.test.sphereon.com%2Fissuer%2Fdbc2023%22%7D' - ) - }) -}) diff --git a/packages/oid4vci-rest-client/__tests__/mockedEndpoints.test.ts b/packages/oid4vci-rest-client/__tests__/mockedEndpoints.test.ts deleted file mode 100644 index 4bf5fc275..000000000 --- a/packages/oid4vci-rest-client/__tests__/mockedEndpoints.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { createAgent, IResolver } from '@veramo/core' -// @ts-ignore -import nock from 'nock' -import { IOID4VCIRestClient, OID4VCIRestClient } from '../src' - -const baseUrl = 'https://my-vci-endpoint' - -const agent = createAgent({ - plugins: [new OID4VCIRestClient({ baseUrl })], -}) -afterAll(() => { - nock.cleanAll() -}) - -describe('@sphereon/ssi-sdk.oid4vci-rest-client', () => { - it('should call the mock endpoint for vciClientCreateOfferUri', async () => { - nock(`${baseUrl}/webapp/`).post(`/credential-offers`, { - grants: { - 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { - 'pre-authorized_code': '1234', - user_pin_required: false, - }, - }, - credentials: ['dbc2023'], - }).times(1).reply(200, { - uri: 'openid-credential-offer://?credential_offer=%7B%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%221234%22%2C%22user_pin_required%22%3Afalse%7D%7D%2C%22credentials%22%3A%5B%22dbc2023%22%5D%2C%22credential_issuer%22%3A%22https%3A%2F%2Fmy-vci-endpoint%2Fissuer%2Fdbc2023%22%7D', - }) - await expect( - agent.vciClientCreateOfferUri({ - grants: { - 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { - 'pre-authorized_code': '1234', - user_pin_required: false, - }, - }, - credentials: ['dbc2023'], - }) - ).resolves.toBeDefined() - }) -}) diff --git a/packages/tsconfig.json b/packages/tsconfig.json index c3575c53f..6b92c20d2 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -13,6 +13,7 @@ { "path": "oid4vci-issuer-store" }, { "path": "oid4vci-issuer" }, { "path": "oid4vci-issuer-rest-api" }, + { "path": "oid4vci-issuer-rest-client" }, { "path": "siopv2-oid4vp-common" }, { "path": "siopv2-oid4vp-op-auth" }, { "path": "siopv2-oid4vp-rp-auth" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index acd30dc49..2bb905f14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -663,6 +663,34 @@ importers: specifier: ^2.0.24 version: 2.0.24 + packages/oid4vci-issuer-rest-client: + dependencies: + '@sphereon/oid4vci-common': + specifier: 0.4.1-unstable.286 + version: 0.4.1-unstable.286 + '@veramo/core': + specifier: 4.2.0 + version: 4.2.0(patch_hash=c5oempznsz4br5w3tcuk2i2mau) + cross-fetch: + specifier: ^3.1.5 + version: 3.1.5 + devDependencies: + '@types/node': + specifier: ^18.16.3 + version: 18.16.3 + did-resolver: + specifier: ^4.1.0 + version: 4.1.0 + nock: + specifier: ^13.3.0 + version: 13.3.0 + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@18.16.3)(typescript@4.9.5) + typescript: + specifier: 4.9.5 + version: 4.9.5 + packages/oid4vci-issuer-store: dependencies: '@sphereon/oid4vci-common': @@ -709,34 +737,6 @@ importers: specifier: ^13.2.1 version: 13.3.0 - packages/oid4vci-rest-client: - dependencies: - '@sphereon/oid4vci-common': - specifier: 0.4.1-unstable.286 - version: 0.4.1-unstable.286 - '@veramo/core': - specifier: 4.2.0 - version: 4.2.0(patch_hash=c5oempznsz4br5w3tcuk2i2mau) - cross-fetch: - specifier: ^3.1.5 - version: 3.1.5 - devDependencies: - '@types/node': - specifier: ^18.16.3 - version: 18.16.3 - did-resolver: - specifier: ^4.1.0 - version: 4.1.0 - nock: - specifier: ^13.3.0 - version: 13.3.0 - ts-node: - specifier: ^10.9.1 - version: 10.9.1(@types/node@18.16.3)(typescript@4.9.5) - typescript: - specifier: 4.9.5 - version: 4.9.5 - packages/presentation-exchange: dependencies: '@sphereon/pex':