diff --git a/packages/contact-manager-rest-api/src/types.ts b/packages/contact-manager-rest-api/src/types.ts index 528389805..9c19e57f3 100644 --- a/packages/contact-manager-rest-api/src/types.ts +++ b/packages/contact-manager-rest-api/src/types.ts @@ -1,6 +1,6 @@ import { GenericAuthArgs, ISingleEndpointOpts } from '@sphereon/ssi-express-support' import { IContactManager } from '@sphereon/ssi-sdk.contact-manager' -import { IAgentContext, IDataStore, IDIDManager, IKeyManager } from '@veramo/core' +import { IAgentContext, IDIDManager, IKeyManager } from '@veramo/core' export type ContactManagerMRestApiFeatures = 'party_read' | 'party_write' | 'party_type_read' | 'identity_read' @@ -16,5 +16,5 @@ export interface IContactManagerAPIEndpointOpts { enableFeatures?: ContactManagerMRestApiFeatures[] } -export type IRequiredPlugins = IContactManager & IDataStore & IKeyManager & IDIDManager +export type IRequiredPlugins = IContactManager & IKeyManager & IDIDManager export type IRequiredContext = IAgentContext diff --git a/packages/credential-store/CHANGELOG.md b/packages/credential-store/CHANGELOG.md new file mode 100644 index 000000000..420e6f23d --- /dev/null +++ b/packages/credential-store/CHANGELOG.md @@ -0,0 +1 @@ +# Change Log diff --git a/packages/credential-store/LICENSE b/packages/credential-store/LICENSE new file mode 100644 index 000000000..aedeb278e --- /dev/null +++ b/packages/credential-store/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2024] [Sphereon International B.V.] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/credential-store/__tests__/localAgent.test.ts b/packages/credential-store/__tests__/localAgent.test.ts new file mode 100644 index 000000000..19bf074c6 --- /dev/null +++ b/packages/credential-store/__tests__/localAgent.test.ts @@ -0,0 +1,34 @@ +import { createObjects, getConfig } from '../../agent-config/dist' +import { DataSource } from 'typeorm' + +jest.setTimeout(60000) + +import credentialStoreAgentLogic from './shared/credentialStoreAgentLogic' + +let dbConnection: Promise +let agent: any + +const setup = async (): Promise => { + const config = await getConfig('packages/credential-store/agent.yml') + const { localAgent, db } = await createObjects(config, { localAgent: '/agent', db: '/dbConnection' }) + agent = localAgent + dbConnection = db + + return true +} + +const tearDown = async (): Promise => { + await (await dbConnection).close() + return true +} + +const getAgent = () => agent +const testContext = { + getAgent, + setup, + tearDown, +} + +describe('Local integration tests', (): void => { + credentialStoreAgentLogic(testContext) +}) diff --git a/packages/credential-store/__tests__/restAgent.test.ts b/packages/credential-store/__tests__/restAgent.test.ts new file mode 100644 index 000000000..3dc42d397 --- /dev/null +++ b/packages/credential-store/__tests__/restAgent.test.ts @@ -0,0 +1,70 @@ +import 'cross-fetch/polyfill' +// @ts-ignore +import express, { Router } from 'express' +import { Server } from 'http' +import { DataSource } from 'typeorm' +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 credentialStoreAgentLogic from './shared/credentialStoreAgentLogic' +import { ICredentialStore } from '../src' +jest.setTimeout(60000) + +const port = 4102 +const basePath = '/agent' + +let serverAgent: IAgent +let restServer: Server +let dbConnection: Promise + +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/credential-store/agent.yml') + const { agent, db } = await createObjects(config, { agent: '/agent', db: '/dbConnection' }) + serverAgent = agent + dbConnection = db + + const agentRouter = 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, (): void => { + resolve(true) + }) + }) +} + +const tearDown = async (): Promise => { + restServer.close() + await (await dbConnection).close() + return true +} + +const testContext = { + getAgent, + setup, + tearDown, +} + +describe('REST integration tests', (): void => { + credentialStoreAgentLogic(testContext) +}) diff --git a/packages/credential-store/__tests__/shared/credentialStoreAgentLogic.ts b/packages/credential-store/__tests__/shared/credentialStoreAgentLogic.ts new file mode 100644 index 000000000..1bcb1cc69 --- /dev/null +++ b/packages/credential-store/__tests__/shared/credentialStoreAgentLogic.ts @@ -0,0 +1,221 @@ +import { FindArgs, TAgent, TCredentialColumns } from '@veramo/core' +import * as fs from 'fs' +import { CredentialCorrelationType, CredentialRole, CredentialStateType, DigitalCredential } from '@sphereon/ssi-sdk.data-store' +import { + AddDigitalCredential, + credentialIdOrHashFilter, + DeleteCredentialsArgs, + GetCredentialsArgs, + ICredentialStore, + UniqueDigitalCredential, +} from '../../src' +import { IVerifiableCredential } from '@sphereon/ssi-types' + +type ConfiguredAgent = TAgent + +function getFile(path: string) { + return fs.readFileSync(path, 'utf-8') +} + +function getFileAsJson(path: string) { + return JSON.parse(getFile(path)) +} + +export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Promise; tearDown: () => Promise }): void => { + describe('Credential Store Agent Plugin', (): void => { + const exampleVC: IVerifiableCredential = getFileAsJson('./packages/credential-store/__tests__/vc-examples/vc_driverLicense.json') + + let agent: ConfiguredAgent + let defaultCredential: DigitalCredential + + beforeAll(async (): Promise => { + await testContext.setup() + agent = testContext.getAgent() + + const digitalCredential: AddDigitalCredential = { + credentialRole: CredentialRole.HOLDER, + tenantId: 'test-tenant', + issuerCorrelationId: 'did:example:the-issuer', + issuerCorrelationType: CredentialCorrelationType.DID, + rawDocument: JSON.stringify(exampleVC), + } + defaultCredential = await agent.crsAddCredential({ credential: digitalCredential }) + }) + + afterAll(testContext.tearDown) + + it('should get credential by id', async (): Promise => { + const result = await agent.crsGetCredential({ id: defaultCredential.id }) + expect(result.id).toEqual(defaultCredential.id) + }) + + it('should throw error when getting credential with unknown id', async (): Promise => { + const itemId = 'unknownId' + await expect(agent.crsGetCredential({ id: itemId })).rejects.toThrow(`No credential found for arg: {\"id\":\"${itemId}\"}`) + }) + + it('should get credentials by filter', async (): Promise => { + const args: GetCredentialsArgs = { + filter: [ + { + credentialRole: CredentialRole.HOLDER, + }, + ], + } + const result: Array = await agent.crsGetCredentials(args) + + expect(result.length).toBe(1) + }) + + it('should get credentials by id or hash', async (): Promise => { + const args1: GetCredentialsArgs = { + filter: [ + { + id: defaultCredential.id, + }, + ], + } + const result1: Array = await agent.crsGetCredentials(args1) + expect(result1.length).toBe(1) + + const args2: GetCredentialsArgs = { + filter: [ + { + hash: defaultCredential.hash, + }, + ], + } + const result2: Array = await agent.crsGetCredentials(args2) + expect(result2.length).toBe(1) + + const args3: GetCredentialsArgs = { + filter: [ + { + id: defaultCredential.id, + }, + { + hash: defaultCredential.hash, + }, + ], + } + const result3: Array = await agent.crsGetCredentials(args3) + expect(result3.length).toBe(1) + + const args4: GetCredentialsArgs = { + filter: [ + { + id: 'another_id', + }, + { + hash: defaultCredential.hash, + }, + ], + } + const result4: Array = await agent.crsGetCredentials(args4) + expect(result4.length).toBe(1) + }) + + it('should get unique credential by id or hash', async (): Promise => { + const result: Array = await agent.crsGetUniqueCredentials({ + filter: credentialIdOrHashFilter(defaultCredential.credentialRole, defaultCredential.hash), + }) + expect(result.length).toBe(1) + expect(result[0].hash).toEqual(defaultCredential.hash) + expect(result[0].digitalCredential.id).toEqual(defaultCredential.id) + expect(result[0].digitalCredential.hash).toEqual(defaultCredential.hash) + }) + + it('should update credential by id', async (): Promise => { + const revokeUpdate = { + id: defaultCredential.id, + verifiedState: CredentialStateType.REVOKED, + revokedAt: new Date(), + } + const result: DigitalCredential = await agent.crsUpdateCredentialState(revokeUpdate) + + expect(result.verifiedState).toEqual(revokeUpdate.verifiedState) + // expect(result.revokedAt).toEqual(revokeUpdate.revokedAt) FIXME date deserialization is broken for REST agent + }) + + it('should get credential by claims', async (): Promise => { + const claimsFilter: FindArgs = { + where: [ + { + column: 'issuanceDate', + op: 'Equal', + value: ['2010-01-01T19:23:24Z'], + }, + ], + } + const result = await agent.crsGetCredentialsByClaims({ + credentialRole: defaultCredential.credentialRole, + filter: claimsFilter, + }) + expect(result.length).toBe(1) + expect(result[0].digitalCredential.id).toEqual(defaultCredential.id) + expect(result[0].id).toEqual('https://example.com/credentials/1873') + }) + + it('should not get credential by invalid claim', async (): Promise => { + const claimsFilter: FindArgs = { + where: [ + { + column: 'issuanceDate', + op: 'Equal', + value: ['someValue'], + }, + ], + } + const result = await agent.crsGetCredentialsByClaims({ + credentialRole: defaultCredential.credentialRole, + filter: claimsFilter, + }) + expect(result.length).toBe(0) + }) + + it('should delete credential by id', async (): Promise => { + const result = await agent.crsDeleteCredential({ id: defaultCredential.id }) + + expect(result).toBe(true) + }) + + it('should throw error when deleting credential with unknown id', async (): Promise => { + const id = 'unknownId' + const result = await agent.crsDeleteCredential({ id }) + expect(result).toBe(false) + }) + + it('should delete multiple credentials by filter', async (): Promise => { + const digitalCredential1: AddDigitalCredential = { + credentialRole: CredentialRole.VERIFIER, + tenantId: 'test-tenant', + issuerCorrelationId: 'did:example:item1', + issuerCorrelationType: CredentialCorrelationType.DID, + rawDocument: JSON.stringify(exampleVC), + } + await agent.crsAddCredential({ credential: digitalCredential1 }) + + const exampleVC2: IVerifiableCredential = { ...exampleVC } + ;(exampleVC2.credentialSubject as any).extraField = 'Extra extra' + const digitalCredential2: AddDigitalCredential = { + credentialRole: CredentialRole.VERIFIER, + tenantId: 'test-tenant', + issuerCorrelationId: 'did:example:item2', + issuerCorrelationType: CredentialCorrelationType.DID, + rawDocument: JSON.stringify(exampleVC2), + } + await agent.crsAddCredential({ credential: digitalCredential2 }) + + const args: DeleteCredentialsArgs = { + filter: [ + { + credentialRole: CredentialRole.VERIFIER, + tenantId: 'test-tenant', + }, + ], + } + const deleteCount = await agent.crsDeleteCredentials(args) + expect(deleteCount).toBe(2) + }) + }) +} diff --git a/packages/credential-store/__tests__/vc-examples/vc_driverLicense.json b/packages/credential-store/__tests__/vc-examples/vc_driverLicense.json new file mode 100644 index 000000000..daa0f0f2b --- /dev/null +++ b/packages/credential-store/__tests__/vc-examples/vc_driverLicense.json @@ -0,0 +1,23 @@ +{ + "id": "https://example.com/credentials/1873", + "type": ["VerifiableCredential", "DriversLicense"], + "@context": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1/DriversLicense"], + "issuer": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21" + }, + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "given_name": "John", + "family_name": "Doe", + "birthdate": "1975-01-05" + }, + "proof": { + "type": "RsaSignature2018", + "created": "2018-09-14T21:19:10Z", + "proofPurpose": "authentication", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78" + } +} diff --git a/packages/credential-store/agent.yml b/packages/credential-store/agent.yml new file mode 100644 index 000000000..5f3d433b7 --- /dev/null +++ b/packages/credential-store/agent.yml @@ -0,0 +1,99 @@ +version: 3.0 + +constants: + baseUrl: http://localhost:3335 + port: 3335 + # please use your own X25519 key, this is only an example + secretKey: 29739248cad1bd1a0fc4d9b75cd4d2990de535baf5caadfdf8d8f86664aa830c + methods: + - crsAddCredential + - crsUpdateCredentialState + - crsGetCredential + - crsGetCredentials + - crsGetUniqueCredentials + - crsDeleteCredential + - crsDeleteCredentials + - crsGetCredentialsByClaims + - crsGetCredentialsByClaimsCount + +# Database +dbConnection: + $require: typeorm?t=function#createConnection + $args: + - type: sqlite + database: ':memory:' + synchronize: false + migrationsRun: true + migrations: + $require: './packages/data-store?t=object#DataStoreDigitalCredentialMigrations' + entities: + $require: './packages/data-store?t=object#DataStoreDigitalCredentialEntities' + +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/credential-store/dist#CredentialStore + $args: + - store: + $require: './packages/data-store/dist#DigitalCredentialStore' + $args: + - $ref: /dbConnection diff --git a/packages/credential-store/api-extractor.json b/packages/credential-store/api-extractor.json new file mode 100644 index 000000000..94c2c6a9f --- /dev/null +++ b/packages/credential-store/api-extractor.json @@ -0,0 +1,3 @@ +{ + "extends": "../include/api-extractor-base.json" +} diff --git a/packages/credential-store/package.json b/packages/credential-store/package.json new file mode 100644 index 000000000..fe245ff54 --- /dev/null +++ b/packages/credential-store/package.json @@ -0,0 +1,53 @@ +{ + "name": "@sphereon/ssi-sdk.credential-store", + "version": "0.27.0", + "source": "src/index.ts", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "veramo": { + "pluginInterfaces": { + "ICredentialStore": "./src/types/ICredentialStore.ts" + } + }, + "scripts": { + "build": "tsc", + "build:clean": "tsc --build --clean && tsc --build", + "generate-plugin-schema": "ts-node ../../packages/dev/bin/sphereon.js dev generate-plugin-schema" + }, + "dependencies": { + "@sphereon/pex": "^3.3.3", + "@sphereon/pex-models": "^2.2.4", + "@sphereon/ssi-sdk.data-store": "workspace:*", + "cross-fetch": "^3.1.8", + "debug": "^4.3.4", + "typeorm": "^0.3.20", + "uuid": "^10.0.0" + }, + "devDependencies": { + "@sphereon/ssi-sdk.agent-config": "workspace:*", + "@sphereon/ssi-types": "workspace:*", + "@veramo/remote-client": "4.2.0", + "@veramo/remote-server": "4.2.0", + "@types/uuid": "^10.0.0" + }, + "files": [ + "dist/**/*", + "src/**/*", + "README.md", + "plugin.schema.json", + "LICENSE" + ], + "private": false, + "publishConfig": { + "access": "public" + }, + "repository": "git@github.com:Sphereon-Opensource/SSI-SDK.git", + "author": "Sphereon ", + "license": "Apache-2.0", + "keywords": [ + "Sphereon", + "SSI", + "Veramo", + "Credential Manager" + ] +} diff --git a/packages/credential-store/src/agent/CredentialStore.ts b/packages/credential-store/src/agent/CredentialStore.ts new file mode 100644 index 000000000..d24053726 --- /dev/null +++ b/packages/credential-store/src/agent/CredentialStore.ts @@ -0,0 +1,256 @@ +import { IAgentPlugin } from '@veramo/core' +import { + AddCredentialArgs, + credentialIdOrHashFilter, + DeleteCredentialArgs, + DeleteCredentialsArgs, + DocumentType, + GetCredentialArgs, + GetCredentialsArgs, + GetCredentialsByClaimsArgs, + GetCredentialsByIdOrHashArgs, + ICredentialStore, + logger, + OptionalUniqueDigitalCredential, + schema, + TClaimsColumns, + UniqueDigitalCredential, +} from '../index' +import { AbstractDigitalCredentialStore, DigitalCredential, UpdateCredentialStateArgs } from '@sphereon/ssi-sdk.data-store' +import { IVerifiableCredential } from '@sphereon/ssi-types' +// Exposing the methods here for any REST implementation +export const credentialStoreMethods: Array = [ + 'crsAddCredential', + 'crsUpdateCredentialState', + 'crsGetCredential', + 'crsGetCredentials', + 'crsStoreCredential', + 'crsDeleteCredential', + 'crsDeleteCredentials', + 'crsGetUniqueCredentialByIdOrHash', + 'crsGetCredentialsByClaims', + 'crsGetCredentialsByClaimsCount', +] + +/** + * {@inheritDoc ICRManager} + */ +export class CredentialStore implements IAgentPlugin { + readonly schema = schema.ICredentialStore + readonly methods: ICredentialStore = { + crsAddCredential: this.crsAddCredential.bind(this), + crsUpdateCredentialState: this.crsUpdateCredentialState.bind(this), + crsGetCredential: this.crsGetCredential.bind(this), + crsGetCredentials: this.crsGetCredentials.bind(this), + crsGetUniqueCredentialByIdOrHash: this.crsGetUniqueCredentialByIdOrHash.bind(this), + crsGetUniqueCredentials: this.crsGetUniqueCredentials.bind(this), + crsDeleteCredential: this.crsDeleteCredential.bind(this), + crsDeleteCredentials: this.crsDeleteCredentials.bind(this), + crsGetCredentialsByClaims: this.crsGetCredentialsByClaims.bind(this), + crsGetCredentialsByClaimsCount: this.crsGetCredentialsByClaimsCount.bind(this), + } + + private readonly store: AbstractDigitalCredentialStore + + constructor(options: { store: AbstractDigitalCredentialStore }) { + this.store = options.store + } + + /** {@inheritDoc ICRManager.crmAddCredential} */ + private async crsAddCredential(args: AddCredentialArgs): Promise { + return await this.store.addCredential(args.credential) + } + + /** {@inheritDoc ICRManager.updateCredentialState} */ + private async crsUpdateCredentialState(args: UpdateCredentialStateArgs): Promise { + return await this.store.updateCredentialState(args) + } + + /** {@inheritDoc ICRManager.crmGetCredential} */ + private async crsGetCredential(args: GetCredentialArgs): Promise { + const { id } = args + const credential = await this.store.getCredential({ id }) + return credential + } + + /** {@inheritDoc ICRManager.crmGetCredentials} */ + private async crsGetCredentials(args: GetCredentialsArgs): Promise> { + const { filter } = args + const credentials = await this.store.getCredentials({ filter }) + return credentials.data + } + + /** {@inheritDoc ICRManager.crmGetUniqueCredentialByIdOrHash} */ + private async crsGetUniqueCredentialByIdOrHash(args: GetCredentialsByIdOrHashArgs): Promise { + const credentials = await this.crsGetCredentials({ filter: credentialIdOrHashFilter(args.credentialRole, args.idOrHash) }) + if (credentials.length === 0) { + return undefined + } else if (credentials.length > 1) { + logger.warning('Duplicate credentials detected in crsGetUniqueCredentialByIdOrHash', args) + } + return this.toUniqueCredentials(credentials)[0] + } + + /** {@inheritDoc ICRManager.crmGetUniqueCredentials} */ + private async crsGetUniqueCredentials(args: GetCredentialsArgs): Promise> { + const credentials = await this.crsGetCredentials(args) + return this.toUniqueCredentials(credentials) + } + + /** {@inheritDoc ICRManager.crmDeleteCredential} */ + private async crsDeleteCredential(args: DeleteCredentialArgs): Promise { + return this.store.removeCredential(args) + } + + /** {@inheritDoc ICRManager.crmDeleteCredentials} */ + private async crsDeleteCredentials(args: DeleteCredentialsArgs): Promise { + const credentials = await this.crsGetCredentials(args) + let count = 0 + for (const credential of credentials) { + const result = await this.store.removeCredential({ id: credential.id }) + if (result) { + count++ + } + } + return count + } + + /** + * Returns a list of UniqueDigitalCredentials that match the given filter based on the claims they contain. + * @param args + */ + private async crsGetCredentialsByClaims(args: GetCredentialsByClaimsArgs): Promise> { + const digitalCredentials = await this.crsGetUniqueCredentials({ + filter: [ + // TODO SDK-25 Implement param for documentType & support VP filtering below + { + documentType: DocumentType.VC, + credentialRole: args.credentialRole, + tenantId: args.tenantId, + }, + { + documentType: DocumentType.C, + credentialRole: args.credentialRole, + tenantId: args.tenantId, + }, + ], + }) + + // This a copy of how Veramo did this. TODO Use GraphQL in the future? + const claimFilteredCredentials: UniqueDigitalCredential[] = digitalCredentials.filter((uniqueVC) => { + if (!uniqueVC.uniformVerifiableCredential) { + return false + } + + const credential = uniqueVC.uniformVerifiableCredential + return ( + args.filter.where?.every((whereClause) => { + const value = this.getValueFromCredential(credential, whereClause.column) + + if (value === undefined) { + return whereClause.op === 'IsNull' + } + + switch (whereClause.op) { + case 'In': + return whereClause.value?.includes(value) + case 'Like': + return typeof value === 'string' && value.includes(whereClause.value?.[0] || '') + case 'Between': + return value >= (whereClause.value?.[0] || '') && value <= (whereClause.value?.[1] || '') + case 'LessThan': + return value < (whereClause.value?.[0] || '') + case 'LessThanOrEqual': + return value <= (whereClause.value?.[0] || '') + case 'MoreThan': + return value > (whereClause.value?.[0] || '') + case 'MoreThanOrEqual': + return value >= (whereClause.value?.[0] || '') + case 'Any': + return Array.isArray(value) && value.some((v) => whereClause.value?.includes(v)) + case 'IsNull': + return value === null || value === undefined + case 'Equal': + default: + return value === whereClause.value?.[0] + } + }) ?? true + ) + }) + + return claimFilteredCredentials + } + + private getValueFromCredential(credential: IVerifiableCredential, column: TClaimsColumns): any { + switch (column) { + case 'context': + return credential['@context'] + case 'credentialType': + return credential.type + case 'type': + return Array.isArray(credential.credentialSubject) ? credential.credentialSubject[0]?.type : credential.credentialSubject?.type + case 'value': + return JSON.stringify(credential.credentialSubject) + case 'isObj': + return typeof credential.credentialSubject === 'object' + case 'id': + return credential.id + case 'issuer': + return typeof credential.issuer === 'string' ? credential.issuer : credential.issuer.id + case 'subject': + return Array.isArray(credential.credentialSubject) ? credential.credentialSubject[0]?.id : credential.credentialSubject?.id + case 'expirationDate': + return credential.expirationDate + case 'issuanceDate': + return credential.issuanceDate + default: + return undefined + } + } + + /** + * Returns a count of UniqueDigitalCredentials that match the given filter based on the claims they contain. + * @param args + */ + private async crsGetCredentialsByClaimsCount(args: GetCredentialsByClaimsArgs): Promise { + const credentialsByClaims = await this.crsGetCredentialsByClaims(args) + return credentialsByClaims.length // FIXME ? + } + + private toUniqueCredentials(credentials: Array): Array { + return Object.values( + credentials.reduce( + (accumulator, credential) => { + const uniqueCredential: UniqueDigitalCredential = { + hash: credential.hash, + digitalCredential: credential, + } + switch (credential.documentType) { + case DocumentType.VC: + uniqueCredential.originalVerifiableCredential = JSON.parse(credential.rawDocument) + uniqueCredential.uniformVerifiableCredential = JSON.parse(credential.uniformDocument) + uniqueCredential.id = uniqueCredential.uniformVerifiableCredential?.id + break + case DocumentType.VP: + uniqueCredential.originalVerifiablePresentation = JSON.parse(credential.rawDocument) + uniqueCredential.uniformVerifiablePresentation = JSON.parse(credential.uniformDocument) + uniqueCredential.id = uniqueCredential.uniformVerifiablePresentation?.id + break + case DocumentType.P: + uniqueCredential.originalPresentation = JSON.parse(credential.rawDocument) + uniqueCredential.id = uniqueCredential.originalPresentation?.id + break + case DocumentType.C: + uniqueCredential.originalCredential = JSON.parse(credential.rawDocument) + uniqueCredential.id = uniqueCredential.originalCredential?.id + break + // TODO CBOR support + } + accumulator[credential.hash] = uniqueCredential + return accumulator + }, + {} as Record, + ), + ) + } +} diff --git a/packages/credential-store/src/index.ts b/packages/credential-store/src/index.ts new file mode 100644 index 000000000..7e5131756 --- /dev/null +++ b/packages/credential-store/src/index.ts @@ -0,0 +1,23 @@ +import { Loggers } from '@sphereon/ssi-types' + +/** + * @public + */ +const schema = require('../plugin.schema.json') +export { schema } + +export const logger = Loggers.DEFAULT.get('sphereon:credential-store') + +export { CredentialStore, credentialStoreMethods } from './agent/CredentialStore' +export { + CredentialRole, + CredentialStateType, + CredentialCorrelationType, + CredentialDocumentFormat, + DocumentType, + DigitalCredential, + FindDigitalCredentialArgs, +} from '@sphereon/ssi-sdk.data-store' +export * from './types/ICredentialStore' +export * from './types/claims' +export * from './types/filters' diff --git a/packages/credential-store/src/types/ICredentialStore.ts b/packages/credential-store/src/types/ICredentialStore.ts new file mode 100644 index 000000000..6fadeabde --- /dev/null +++ b/packages/credential-store/src/types/ICredentialStore.ts @@ -0,0 +1,122 @@ +import { IAgentContext, IPluginMethodMap } from '@veramo/core' +import { CredentialRole, DigitalCredential, UpdateCredentialStateArgs } from '@sphereon/ssi-sdk.data-store' +import { FindDigitalCredentialArgs } from '@sphereon/ssi-sdk.data-store/dist/types/digitalCredential/IAbstractDigitalCredentialStore' +import { NonPersistedDigitalCredential } from '@sphereon/ssi-sdk.data-store/dist/types/digitalCredential/digitalCredential' +import { FindClaimsArgs } from './claims' +import { ICredential, IPresentation, IVerifiableCredential, OriginalVerifiableCredential, OriginalVerifiablePresentation } from '@sphereon/ssi-types' + +export interface ICredentialStore extends IPluginMethodMap { + /** + * Add a new credential. + * @param args + */ + crsAddCredential(args: AddCredentialArgs): Promise + + /** + * Update credential the state of an existing credential. + * @param args + */ + crsUpdateCredentialState(args: UpdateCredentialStateArgs): Promise + + /** + * Get a single credentials by primary key + * @param args + */ + crsGetCredential(args: GetCredentialArgs): Promise + + /** + * Find one or more credentials using filters + * @param args + */ + crsGetCredentials(args: GetCredentialsArgs): Promise> + + /** + * Find one or more credentials using filters + * @param args + */ + crsGetUniqueCredentials(args: GetCredentialsArgs): Promise> + + /** + * Find one credential by id or hash + * @param CredentialRole + * @param idOrHash + */ + crsGetUniqueCredentialByIdOrHash(args: GetCredentialsByIdOrHashArgs): Promise + + /** + * Returns a list of UniqueDigitalCredentials that match the given filter based on the claims they contain. + * @param args + */ + crsGetCredentialsByClaims(args: GetCredentialsByClaimsArgs): Promise> + + /** + * Returns a count of UniqueDigitalCredentials that match the given filter based on the claims they contain. + * @param args + */ + crsGetCredentialsByClaimsCount(args: GetCredentialsByClaimsArgs): Promise + + /** + * Delete a single credentials by primary key + * @param args + */ + crsDeleteCredential(args: DeleteCredentialArgs): Promise + + /** + * Delete multiple credentials records using filters + * @param args + */ + crsDeleteCredentials(args: DeleteCredentialsArgs): Promise +} + +export type GetCredentialArgs = { + id: string +} + +export type GetCredentialsArgs = { + filter: FindDigitalCredentialArgs +} + +export type GetCredentialsByClaimsArgs = { + filter: FindClaimsArgs + credentialRole?: CredentialRole + tenantId?: string +} + +export type GetCredentialsByIdOrHashArgs = { + credentialRole: CredentialRole + idOrHash: string +} + +export type DeleteCredentialArgs = { + id: string +} + +export type DeleteCredentialsArgs = GetCredentialsArgs + +export type AddDigitalCredential = Omit< + NonPersistedDigitalCredential, + 'id' | 'documentType' | 'documentFormat' | 'uniformDocument' | 'hash' | 'createdAt' | 'lastUpdatedAt' | 'validFrom' | 'validUntil' +> + +export type AddCredentialArgs = { + credential: AddDigitalCredential +} + +export type { UpdateCredentialStateArgs } from '@sphereon/ssi-sdk.data-store' // TODO create a local copy? + +export interface UniqueDigitalCredential { + hash: string + id?: string + digitalCredential: DigitalCredential + + originalVerifiableCredential?: OriginalVerifiableCredential + originalVerifiablePresentation?: OriginalVerifiablePresentation + originalCredential?: ICredential + originalPresentation?: IPresentation + uniformVerifiableCredential?: IVerifiableCredential + uniformVerifiablePresentation?: IVerifiableCredential +} + +export type OptionalUniqueDigitalCredential = UniqueDigitalCredential | undefined + +export type RequiredContext = IAgentContext diff --git a/packages/credential-store/src/types/claims.ts b/packages/credential-store/src/types/claims.ts new file mode 100644 index 000000000..de94bc95b --- /dev/null +++ b/packages/credential-store/src/types/claims.ts @@ -0,0 +1,59 @@ +export type TClaimsColumns = + | 'context' + | 'credentialType' + | 'type' + | 'value' + | 'isObj' + | 'id' + | 'issuer' + | 'subject' + | 'expirationDate' + | 'issuanceDate' + +/** + * Represents the sort order of results from a {@link FindArgs} query. + * + * @beta This API may change without a BREAKING CHANGE notice. + */ +export interface Order { + column: TColumns + direction: 'ASC' | 'DESC' +} + +/** + * Represents a WHERE predicate for a {@link FindArgs} query. + * In situations where multiple WHERE predicates are present, they are combined with AND. + * + * @beta This API may change without a BREAKING CHANGE notice. + */ +export interface Where { + column: TColumns + value?: string[] + not?: boolean + op?: 'LessThan' | 'LessThanOrEqual' | 'MoreThan' | 'MoreThanOrEqual' | 'Equal' | 'Like' | 'Between' | 'In' | 'Any' | 'IsNull' +} + +export interface FindArgs { + /** + * Imposes constraints on the values of the given columns. + * WHERE clauses are combined using AND. + */ + where?: Where[] + + /** + * Sorts the results according to the given array of column priorities. + */ + order?: Order[] + + /** + * Ignores the first number of entries in a {@link IDataStoreORM} query result. + */ + skip?: number + + /** + * Returns at most this number of results from a {@link IDataStoreORM} query. + */ + take?: number +} + +export type FindClaimsArgs = FindArgs diff --git a/packages/credential-store/src/types/filters.ts b/packages/credential-store/src/types/filters.ts new file mode 100644 index 000000000..e153dd744 --- /dev/null +++ b/packages/credential-store/src/types/filters.ts @@ -0,0 +1,96 @@ +import { CredentialRole, DigitalCredential, DocumentType, FindDigitalCredentialArgs } from '@sphereon/ssi-sdk.data-store' +import { validate as uuidValidate } from 'uuid' + +/** + * Creates a filter to find a digital credential by its ID or hash. + * + * @param credentialRole - The role to filter by (e.g., ISSUER, HOLDER). + * @param idOrHash - The ID or hash of the credential to search for. + * @returns A FindDigitalCredentialArgs array for filtering by ID or hash. + */ + +export const credentialIdOrHashFilter = (credentialRole: CredentialRole, idOrHash: string): FindDigitalCredentialArgs => { + const filter: FindDigitalCredentialArgs = [ + { + hash: idOrHash, + credentialRole, + }, + { + credentialId: idOrHash, + credentialRole, + }, + ] + + if (uuidValidate(idOrHash)) { + filter.push({ + id: idOrHash, + credentialRole, + }) + } + + return filter +} + +/** + * Creates a filter for verifiable credentials with a specific role. + * + * @param credentialRole - The role to filter by (e.g., ISSUER, HOLDER). + * @param withFilter - Optional additional filter criteria. + * @returns A FindDigitalCredentialArgs array for filtering verifiable credentials by role. + */ +export const verifiableCredentialForRoleFilter = ( + credentialRole: CredentialRole, + withFilter?: FindDigitalCredentialArgs, +): FindDigitalCredentialArgs => { + const filter = [ + { + documentType: DocumentType.VC, + credentialRole: credentialRole, + }, + ] + if (withFilter !== undefined) { + return mergeFilter(withFilter, filter) + } + return filter +} + +/** + * Merges two FindDigitalCredentialArgs arrays into a single array. + * + * This function combines two filter arrays, merging objects at the same index + * and adding unique objects from both arrays. When merging objects, properties + * from filter2 overwrite those from filter1 if they exist in both. + * + * @param filter1 - The first FindDigitalCredentialArgs array to merge. + * @param filter2 - The second FindDigitalCredentialArgs array to merge. + * @returns A new FindDigitalCredentialArgs array containing the merged result. + * + * @example + * const filter1 = [{ documentType: DocumentType.VC }, { credentialRole: CredentialRole.ISSUER }]; + * const filter2 = [{ documentType: DocumentType.VP }, { hash: 'abc123' }]; + * const mergedFilter = mergeFilter(filter1, filter2); + * // Result: [{ documentType: DocumentType.VP }, { credentialRole: CredentialRole.ISSUER, hash: 'abc123' }] + */ +export const mergeFilter = (filter1: FindDigitalCredentialArgs, filter2: FindDigitalCredentialArgs): FindDigitalCredentialArgs => { + const mergedFilter: FindDigitalCredentialArgs = [] + + const mergedMap = new Map>() + + filter1.forEach((obj, index) => { + mergedMap.set(index, { ...obj }) + }) + + filter2.forEach((obj, index) => { + if (mergedMap.has(index)) { + mergedMap.set(index, { ...mergedMap.get(index), ...obj }) + } else { + mergedMap.set(index, { ...obj }) + } + }) + + mergedMap.forEach((value) => { + mergedFilter.push(value) + }) + + return mergedFilter +} diff --git a/packages/credential-store/tsconfig.json b/packages/credential-store/tsconfig.json new file mode 100644 index 000000000..f9c39f68c --- /dev/null +++ b/packages/credential-store/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig-base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "declarationDir": "dist", + "esModuleInterop": true + }, + "references": [ + { + "path": "../data-store" + }, + { + "path": "../ssi-types" + }, + { + "path": "../agent-config" + } + ] +} diff --git a/packages/data-store/src/__tests__/digitalCredential.entities.test.ts b/packages/data-store/src/__tests__/digitalCredential.entities.test.ts index 1afba4854..5c54ce8cc 100644 --- a/packages/data-store/src/__tests__/digitalCredential.entities.test.ts +++ b/packages/data-store/src/__tests__/digitalCredential.entities.test.ts @@ -1,5 +1,5 @@ import { DataSource } from 'typeorm' -import { DataStoreDigitalCredentialEntities } from '../index' +import { CredentialRole, DataStoreDigitalCredentialEntities } from '../index' import { DataStoreDigitalCredentialMigrations } from '../migrations' import { DigitalCredentialEntity } from '../entities/digitalCredential/DigitalCredentialEntity' import { @@ -43,6 +43,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', } @@ -69,6 +70,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', }) expect(digitalCredential.documentType).toEqual(DocumentType.VC) @@ -85,6 +87,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', }) expect(digitalCredential.documentType).toEqual(DocumentType.VC) @@ -128,6 +131,7 @@ describe('Database entities tests', (): void => { issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', + credentialRole: CredentialRole.VERIFIER, }) expect(digitalCredential.documentType).toEqual(DocumentType.VC) expect(digitalCredential.validFrom).toEqual(new Date('2022-01-07T11:54:12.000Z')) @@ -225,6 +229,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', }) expect(digitalCredential.documentType).toEqual(DocumentType.VP) @@ -241,6 +246,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', opts: { hasher: (data, algorithm) => createHash(algorithm).update(data).digest(), diff --git a/packages/data-store/src/__tests__/digitalCredential.store.test.ts b/packages/data-store/src/__tests__/digitalCredential.store.test.ts index ed71f9e5f..d452ef78b 100644 --- a/packages/data-store/src/__tests__/digitalCredential.store.test.ts +++ b/packages/data-store/src/__tests__/digitalCredential.store.test.ts @@ -1,6 +1,6 @@ import { DataSource } from 'typeorm' import { DataStoreDigitalCredentialMigrations } from '../migrations' -import { DataStoreDigitalCredentialEntities } from '../index' +import { CredentialRole, DataStoreDigitalCredentialEntities } from '../index' import { DigitalCredentialStore } from '../digitalCredential/DigitalCredentialStore' import { CredentialCorrelationType, @@ -45,6 +45,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', } @@ -60,6 +61,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', } @@ -76,6 +78,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', } const addCredentialArgs2: AddCredentialArgs = { @@ -85,6 +88,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', } @@ -105,6 +109,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', } @@ -148,11 +153,12 @@ describe('Database entities tests', (): void => { } const addCredentialArgs2: AddCredentialArgs = { rawDocument: JSON.stringify(sampleVP), - tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', issuerCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:example:holder', subjectCorrelationType: CredentialCorrelationType.DID, subjectCorrelationId: 'did:example:holder', + credentialRole: CredentialRole.VERIFIER, + tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', } const addCredentialArgs3: AddCredentialArgs = { rawDocument: @@ -161,6 +167,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', opts: { hasher: (data, algorithm) => createHash(algorithm).update(data).digest(), @@ -191,6 +198,7 @@ describe('Database entities tests', (): void => { const result3: GetCredentialsResponse = await digitalCredentialStore.getCredentials(args3) expect(result3.data.length).toEqual(3) expect(result3.data[1].documentFormat).toEqual(CredentialDocumentFormat.JSON_LD) + expect(result3.data[1].credentialId).toEqual('ebc6f1c2') }) it('should return no digital credentials if filter does not match', async (): Promise => { @@ -212,6 +220,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', } @@ -234,6 +243,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', } @@ -256,6 +266,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', } @@ -278,6 +289,7 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + credentialRole: CredentialRole.VERIFIER, tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', } diff --git a/packages/data-store/src/digitalCredential/AbstractDigitalCredentialStore.ts b/packages/data-store/src/digitalCredential/AbstractDigitalCredentialStore.ts index b8e946b73..dffe9fc36 100644 --- a/packages/data-store/src/digitalCredential/AbstractDigitalCredentialStore.ts +++ b/packages/data-store/src/digitalCredential/AbstractDigitalCredentialStore.ts @@ -6,12 +6,12 @@ import { RemoveCredentialArgs, UpdateCredentialStateArgs, } from '../types/digitalCredential/IAbstractDigitalCredentialStore' -import { DigitalCredentialEntity } from '../entities/digitalCredential/DigitalCredentialEntity' +import { DigitalCredential } from '../types' export abstract class AbstractDigitalCredentialStore { - abstract getCredential(args: GetCredentialArgs): Promise + abstract getCredential(args: GetCredentialArgs): Promise abstract getCredentials(args?: GetCredentialsArgs): Promise - abstract addCredential(args: AddCredentialArgs): Promise - abstract updateCredentialState(args: UpdateCredentialStateArgs): Promise + abstract addCredential(args: AddCredentialArgs): Promise + abstract updateCredentialState(args: UpdateCredentialStateArgs): Promise abstract removeCredential(args: RemoveCredentialArgs): Promise } diff --git a/packages/data-store/src/digitalCredential/DigitalCredentialStore.ts b/packages/data-store/src/digitalCredential/DigitalCredentialStore.ts index 234d98de0..b5d9c21f4 100644 --- a/packages/data-store/src/digitalCredential/DigitalCredentialStore.ts +++ b/packages/data-store/src/digitalCredential/DigitalCredentialStore.ts @@ -11,7 +11,11 @@ import { OrPromise } from '@sphereon/ssi-types' import { DataSource, FindOptionsOrder, Repository } from 'typeorm' import Debug from 'debug' import { DigitalCredentialEntity } from '../entities/digitalCredential/DigitalCredentialEntity' -import { nonPersistedDigitalCredentialEntityFromAddArgs } from '../utils/digitalCredential/MappingUtils' +import { + digitalCredentialFrom, + digitalCredentialsFrom, + nonPersistedDigitalCredentialEntityFromAddArgs, +} from '../utils/digitalCredential/MappingUtils' import { FindOptionsWhere } from 'typeorm/find-options/FindOptionsWhere' import { CredentialStateType, DigitalCredential, NonPersistedDigitalCredential } from '../types/digitalCredential/digitalCredential' import { parseAndValidateOrderOptions } from '../utils/SortingUtils' @@ -26,23 +30,23 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore { this.dbConnection = dbConnection } - addCredential = async (args: AddCredentialArgs): Promise => { + addCredential = async (args: AddCredentialArgs): Promise => { debug('Adding credential', args) const digitalCredentialEntityRepository: Repository = (await this.dbConnection).getRepository(DigitalCredentialEntity) const credentialEntity: NonPersistedDigitalCredential = nonPersistedDigitalCredentialEntityFromAddArgs(args) const createdResult: DigitalCredentialEntity = await digitalCredentialEntityRepository.save(credentialEntity) - return Promise.resolve(createdResult) + return Promise.resolve(digitalCredentialFrom(createdResult)) } - getCredential = async (args: GetCredentialArgs): Promise => { + getCredential = async (args: GetCredentialArgs): Promise => { const result: DigitalCredentialEntity | null = await (await this.dbConnection).getRepository(DigitalCredentialEntity).findOne({ where: args, }) if (!result) { - return Promise.reject(Error(`No credential found for arg: ${args.toString()}`)) + return Promise.reject(Error(`No credential found for arg: ${JSON.stringify(args)}`)) } - return result + return digitalCredentialFrom(result) } getCredentials = async (args?: GetCredentialsArgs): Promise => { @@ -58,7 +62,7 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore { order: sortOptions, }) return { - data: result, + data: digitalCredentialsFrom(result), total, } } @@ -87,7 +91,7 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore { } } - updateCredentialState = async (args: UpdateCredentialStateArgs): Promise => { + updateCredentialState = async (args: UpdateCredentialStateArgs): Promise => { const credentialRepository: Repository = (await this.dbConnection).getRepository(DigitalCredentialEntity) const whereClause: Record = {} if ('id' in args) { @@ -111,7 +115,7 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore { }) if (!credential) { - return Promise.reject(Error(`No credential found for args: ${whereClause}`)) + return Promise.reject(Error(`No credential found for args: ${JSON.stringify(whereClause)}`)) } const updatedCredential: DigitalCredential = { ...credential, @@ -122,6 +126,6 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore { } debug('Updating credential', credential) const updatedResult: DigitalCredentialEntity = await credentialRepository.save(updatedCredential, { transaction: true }) - return updatedResult + return digitalCredentialFrom(updatedResult) } } diff --git a/packages/data-store/src/entities/digitalCredential/DigitalCredentialEntity.ts b/packages/data-store/src/entities/digitalCredential/DigitalCredentialEntity.ts index 3cc249675..8a1341bbc 100644 --- a/packages/data-store/src/entities/digitalCredential/DigitalCredentialEntity.ts +++ b/packages/data-store/src/entities/digitalCredential/DigitalCredentialEntity.ts @@ -5,6 +5,7 @@ import { CredentialStateType, DocumentType, } from '../../types/digitalCredential/digitalCredential' +import { CredentialRole } from '../../types' @Entity('DigitalCredential') export class DigitalCredentialEntity extends BaseEntity { @@ -17,12 +18,18 @@ export class DigitalCredentialEntity extends BaseEntity { @Column('simple-enum', { name: 'document_format', enum: CredentialDocumentFormat, nullable: false }) documentFormat!: CredentialDocumentFormat + @Column('simple-enum', { name: 'credential_role', enum: CredentialRole, nullable: false }) + credentialRole!: CredentialRole + @Column('text', { name: 'raw_document', nullable: false }) rawDocument!: string @Column('text', { name: 'uniform_document', nullable: false }) uniformDocument!: string + @Column('text', { name: 'credential_id', nullable: true, unique: false }) + credentialId!: string + @Column('text', { name: 'hash', nullable: false, unique: true }) hash!: string diff --git a/packages/data-store/src/entities/machineState/MachineStateInfoEntity.ts b/packages/data-store/src/entities/machineState/MachineStateInfoEntity.ts index 6ed5ad644..71fe81043 100644 --- a/packages/data-store/src/entities/machineState/MachineStateInfoEntity.ts +++ b/packages/data-store/src/entities/machineState/MachineStateInfoEntity.ts @@ -38,19 +38,19 @@ export class MachineStateInfoEntity extends BaseEntity { @Column({ name: 'state', type: 'text', nullable: false }) state!: string - @CreateDateColumn({ name: 'created_at', type: 'datetime', nullable: false }) + @CreateDateColumn({ name: 'created_at', nullable: false }) createdAt!: Date - @UpdateDateColumn({ name: 'updated_at', type: 'datetime', nullable: false }) + @UpdateDateColumn({ name: 'updated_at', nullable: false }) updatedAt!: Date @Column({ name: 'updated_count', type: 'integer', nullable: false }) updatedCount!: number - @Column({ name: 'expires_at', type: 'datetime', nullable: true }) + @Column({ name: 'expires_at', nullable: true }) expiresAt?: Date - @Column({ name: 'completed_at', type: 'datetime', nullable: true }) + @Column({ name: 'completed_at', nullable: true }) completedAt?: Date @Column({ name: 'tenant_id', type: 'varchar', nullable: true }) diff --git a/packages/data-store/src/index.ts b/packages/data-store/src/index.ts index a9e972705..250c60cf5 100644 --- a/packages/data-store/src/index.ts +++ b/packages/data-store/src/index.ts @@ -28,6 +28,8 @@ import { ElectronicAddressEntity } from './entities/contact/ElectronicAddressEnt import { PhysicalAddressEntity } from './entities/contact/PhysicalAddressEntity' export { ContactStore } from './contact/ContactStore' export { AbstractContactStore } from './contact/AbstractContactStore' +export { AbstractDigitalCredentialStore } from './digitalCredential/AbstractDigitalCredentialStore' +export { DigitalCredentialStore } from './digitalCredential/DigitalCredentialStore' export { AbstractIssuanceBrandingStore } from './issuanceBranding/AbstractIssuanceBrandingStore' export { IssuanceBrandingStore } from './issuanceBranding/IssuanceBrandingStore' export { StatusListStore } from './statusList/StatusListStore' @@ -48,6 +50,7 @@ export { DataStoreMigrations, DataStoreEventLoggerMigrations, DataStoreContactMigrations, + DataStoreDigitalCredentialMigrations, DataStoreIssuanceBrandingMigrations, DataStoreStatusListMigrations, DataStoreMachineStateMigrations, @@ -55,6 +58,7 @@ export { } from './migrations' export * from './types' export * from './utils/contact/MappingUtils' +export * from './utils/digitalCredential/MappingUtils' export const DataStoreContactEntities = [ BaseConfigEntity, diff --git a/packages/data-store/src/migrations/postgres/1708525189001-CreateDigitalCredential.ts b/packages/data-store/src/migrations/postgres/1708525189001-CreateDigitalCredential.ts index 34ed5a1ce..9ca27d67e 100644 --- a/packages/data-store/src/migrations/postgres/1708525189001-CreateDigitalCredential.ts +++ b/packages/data-store/src/migrations/postgres/1708525189001-CreateDigitalCredential.ts @@ -6,6 +6,7 @@ export class CreateDigitalCredential1708525189001 implements MigrationInterface public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`CREATE TYPE "digital_document_type" AS ENUM('VC', 'VP', 'C', 'P')`) await queryRunner.query(`CREATE TYPE "digital_credential_document_format" AS ENUM('JSON_LD', 'JWT', 'SD_JWT', 'MDOC')`) + await queryRunner.query(`CREATE TYPE "digital_credential_credential_role" AS ENUM('ISSUER', 'VERIFIER', 'HOLDER')`) await queryRunner.query(`CREATE TYPE "digital_credential_correlation_type" AS ENUM('DID')`) await queryRunner.query(`CREATE TYPE "digital_credential_state_type" AS ENUM('REVOKED', 'VERIFIED', 'EXPIRED')`) @@ -14,8 +15,10 @@ export class CreateDigitalCredential1708525189001 implements MigrationInterface "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "document_type" "digital_document_type" NOT NULL, "document_format" "digital_credential_document_format" NOT NULL, + "credential_role" "digital_credential_credential_role" NOT NULL, "raw_document" text NOT NULL, "uniform_document" text NOT NULL, + "credential_id" text, "hash" text NOT NULL UNIQUE, "issuer_correlation_type" "digital_credential_correlation_type" NOT NULL, "subject_correlation_type" "digital_credential_correlation_type", @@ -39,6 +42,7 @@ export class CreateDigitalCredential1708525189001 implements MigrationInterface await queryRunner.query(`DROP TYPE "digital_credential_state_type"`) await queryRunner.query(`DROP TYPE "digital_credential_correlation_type"`) await queryRunner.query(`DROP TYPE "digital_credential_document_format"`) + await queryRunner.query(`DROP TYPE "digital_credential_credential_role"`) await queryRunner.query(`DROP TYPE "digital_document_type"`) } } diff --git a/packages/data-store/src/migrations/sqlite/1708525189002-CreateDigitalCredential.ts b/packages/data-store/src/migrations/sqlite/1708525189002-CreateDigitalCredential.ts index 3ce9f6d40..16822cf81 100644 --- a/packages/data-store/src/migrations/sqlite/1708525189002-CreateDigitalCredential.ts +++ b/packages/data-store/src/migrations/sqlite/1708525189002-CreateDigitalCredential.ts @@ -9,8 +9,10 @@ export class CreateDigitalCredential1708525189002 implements MigrationInterface "id" varchar PRIMARY KEY NOT NULL, "document_type" varchar CHECK( "document_type" IN ('VC', 'VP', 'C', 'P') ) NOT NULL, "document_format" varchar CHECK( "document_format" IN ('JSON_LD', 'JWT', 'SD_JWT', 'MDOC') ) NOT NULL, + "credential_role" varchar CHECK( "credential_role" IN ('ISSUER', 'VERIFIER', 'HOLDER') ) NOT NULL, "raw_document" text NOT NULL, "uniform_document" text NOT NULL, + "credential_id" text, "hash" text NOT NULL UNIQUE, "issuer_correlation_type" varchar CHECK( "issuer_correlation_type" IN ('DID') ) NOT NULL, "subject_correlation_type" varchar CHECK( "subject_correlation_type" IN ('DID') ), diff --git a/packages/data-store/src/types/contact/contact.ts b/packages/data-store/src/types/contact/contact.ts index 1554f83ec..2fb13f1ec 100644 --- a/packages/data-store/src/types/contact/contact.ts +++ b/packages/data-store/src/types/contact/contact.ts @@ -1,6 +1,7 @@ import { IIdentifierOpts } from '@sphereon/ssi-sdk-ext.did-utils' import { IIdentifier } from '@veramo/core' import { ILocaleBranding } from '../issuanceBranding/issuanceBranding' +import { CredentialRole } from '../digitalCredential/digitalCredential' export type MetadataTypes = string | number | Date | boolean | undefined @@ -258,12 +259,6 @@ export type ElectronicAddressType = 'email' | 'phone' export type PhysicalAddressType = 'home' | 'visit' | 'postal' -export enum CredentialRole { - ISSUER = 'issuer', - VERIFIER = 'verifier', - HOLDER = 'holder', -} - export enum ConnectionType { OPENID_CONNECT = 'OIDC', SIOPv2 = 'SIOPv2', diff --git a/packages/data-store/src/types/digitalCredential/IAbstractDigitalCredentialStore.ts b/packages/data-store/src/types/digitalCredential/IAbstractDigitalCredentialStore.ts index 903a88706..4cd8c09bb 100644 --- a/packages/data-store/src/types/digitalCredential/IAbstractDigitalCredentialStore.ts +++ b/packages/data-store/src/types/digitalCredential/IAbstractDigitalCredentialStore.ts @@ -1,4 +1,4 @@ -import { CredentialCorrelationType, CredentialStateType, DigitalCredential } from './digitalCredential' +import { CredentialCorrelationType, CredentialRole, CredentialStateType, DigitalCredential } from './digitalCredential' import { Hasher } from '@sphereon/ssi-types' import { FindOptionsOrder } from 'typeorm' import { DigitalCredentialEntity } from '../../entities/digitalCredential/DigitalCredentialEntity' @@ -25,6 +25,7 @@ export type AddCredentialArgs = { subjectCorrelationType?: CredentialCorrelationType issuerCorrelationId: string subjectCorrelationId?: string + credentialRole: CredentialRole tenantId?: string state?: CredentialStateType verifiedAt?: Date diff --git a/packages/data-store/src/types/digitalCredential/digitalCredential.ts b/packages/data-store/src/types/digitalCredential/digitalCredential.ts index e9ab2b88b..31c61995c 100644 --- a/packages/data-store/src/types/digitalCredential/digitalCredential.ts +++ b/packages/data-store/src/types/digitalCredential/digitalCredential.ts @@ -4,8 +4,10 @@ export type DigitalCredential = { id: string documentType: DocumentType documentFormat: CredentialDocumentFormat + credentialRole: CredentialRole rawDocument: string uniformDocument: string + credentialId?: string hash: string issuerCorrelationType: CredentialCorrelationType subjectCorrelationType?: CredentialCorrelationType @@ -39,6 +41,12 @@ export enum CredentialCorrelationType { DID = 'DID', } +export enum CredentialRole { + ISSUER = 'ISSUER', + VERIFIER = 'VERIFIER', + HOLDER = 'HOLDER', +} + export enum CredentialStateType { REVOKED = 'REVOKED', VERIFIED = 'VERIFIED', diff --git a/packages/data-store/src/types/index.ts b/packages/data-store/src/types/index.ts index bb55ef2ea..6353b9f1c 100644 --- a/packages/data-store/src/types/index.ts +++ b/packages/data-store/src/types/index.ts @@ -10,3 +10,5 @@ export * from './statusList/IAbstractStatusListStore' export * from './eventLogger/IAbstractEventLoggerStore' export * from './eventLogger/eventLogger' export * from './machineState/IAbstractMachineStateStore' +export * from './digitalCredential/digitalCredential' +export * from './digitalCredential/IAbstractDigitalCredentialStore' diff --git a/packages/data-store/src/utils/digitalCredential/MappingUtils.ts b/packages/data-store/src/utils/digitalCredential/MappingUtils.ts index 1acfc6e1c..c58cbdbb9 100644 --- a/packages/data-store/src/utils/digitalCredential/MappingUtils.ts +++ b/packages/data-store/src/utils/digitalCredential/MappingUtils.ts @@ -103,6 +103,7 @@ export const nonPersistedDigitalCredentialEntityFromAddArgs = (addCredentialArgs documentType, documentFormat: determineCredentialDocumentFormat(documentFormat), createdAt: new Date(), + credentialId: uniformDocument.id as string | undefined, // uniformDocument.id is being inferred as JsonValue somehow hash: computeEntryHash(addCredentialArgs.rawDocument), uniformDocument: JSON.stringify(uniformDocument), validFrom, diff --git a/packages/ebsi-support/__tests__/attestation.test.ts b/packages/ebsi-support/__tests__/attestation.test.ts index 70e70d927..473f70d19 100644 --- a/packages/ebsi-support/__tests__/attestation.test.ts +++ b/packages/ebsi-support/__tests__/attestation.test.ts @@ -17,6 +17,7 @@ import express, { Express } from 'express' import { DataSource } from 'typeorm' import { IEbsiSupport } from '../src' import { ebsiCreateDidOnLedger } from '../src/did' +import { CredentialRole } from '@sphereon/ssi-sdk.data-store' // import { AttestationAuthRequestUrlResult } from '../src/functions' let dbConnection: Promise @@ -172,6 +173,7 @@ describe.skip('attestation client should', () => { { identifier, accessTokenOpts: { + attestationToOnboardCredentialRole: CredentialRole.ISSUER, clientId, redirectUri: jwksUri, credentialIssuer: 'https://api-conformance.ebsi.eu/conformance/v3/issuer-mock', diff --git a/packages/ebsi-support/__tests__/shared/ebsiAuthorizationClientAgentLogic.ts b/packages/ebsi-support/__tests__/shared/ebsiAuthorizationClientAgentLogic.ts index b83b758ea..0b124b79d 100644 --- a/packages/ebsi-support/__tests__/shared/ebsiAuthorizationClientAgentLogic.ts +++ b/packages/ebsi-support/__tests__/shared/ebsiAuthorizationClientAgentLogic.ts @@ -9,6 +9,7 @@ import express, { Application, NextFunction, Request, Response } from 'express' import { createServer, Server } from 'http' import { importJWK, SignJWT } from 'jose' import { IEbsiSupport } from '../../src' +import { CredentialRole } from '@sphereon/ssi-sdk.data-store' type ConfiguredAgent = TAgent @@ -345,6 +346,7 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro jwksUri: 'https://exmplae.com', attestationCredential: 'eyJ0eXAiOiJKV1QiLCJraWQiOiIxODNkY2E4NDRiNzM5OGM4MTQ0ZTJiMzk5OWM3MzA2Y2I3OTYzMDJhZWQxNDdkNjY4ZmI2ZmI5YmE0OTZkNTBkIiwiYWxnIjoiRVMyNTZLIn0.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImRpZDplYnNpOnppRG5pb3hZWUxXMWEzcVVicVRGejRXIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.QWNWTWlrbUpLcFJaLVBGczQ0U3Mxb200Mk4yb3JzWndsTXp3REpHTTMxSUM2WG5ZVXJ0ZlY4RHFTbVQtaXBIMEdLSDZhclFEcGtrbXZTTy1NenYxWEE', + credentialRole: CredentialRole.ISSUER, // definitionId: ScopeByDefinition.didr_invite_presentation, idOpts: { identifier }, /* did: identifier.did, diff --git a/packages/ebsi-support/src/agent/EbsiSupport.ts b/packages/ebsi-support/src/agent/EbsiSupport.ts index dbed70ec6..f80e57c68 100644 --- a/packages/ebsi-support/src/agent/EbsiSupport.ts +++ b/packages/ebsi-support/src/agent/EbsiSupport.ts @@ -135,7 +135,7 @@ export class EbsiSupport implements IAgentPlugin { if (allVerifiableCredentials && allVerifiableCredentials.length > 0) { const pexResult = await context.agent.pexDefinitionFilterCredentials({ presentationDefinition: definitionResponse, - credentialFilterOpts: { verifiableCredentials: allVerifiableCredentials }, + credentialFilterOpts: { credentialRole: args.credentialRole, verifiableCredentials: allVerifiableCredentials }, }) if (pexResult.filteredCredentials.length > 0) { const filtered = pexResult.filteredCredentials @@ -189,7 +189,7 @@ export class EbsiSupport implements IAgentPlugin { const pexResult = hasInputDescriptors ? await context.agent.pexDefinitionFilterCredentials({ presentationDefinition: definitionResponse, - credentialFilterOpts: { verifiableCredentials: [attestationCredential!] }, + credentialFilterOpts: { credentialRole: args.credentialRole, verifiableCredentials: [attestationCredential!] }, }) : ({ // LOL, let's see whether we can trick PEX to create a VP without VCs @@ -204,6 +204,7 @@ export class EbsiSupport implements IAgentPlugin { }) const oid4vp = await opSession.getOID4VP([identifier.did]) const vp = await oid4vp.createVerifiablePresentation( + args.credentialRole, { definition, credentials: pexResult.filteredCredentials }, { proofOpts: { domain: openIDMetadata.issuer, nonce: v4(), created: new Date(Date.now() - 120_000).toString() }, diff --git a/packages/ebsi-support/src/did/functions.ts b/packages/ebsi-support/src/did/functions.ts index 388d83e0a..ff685c75d 100644 --- a/packages/ebsi-support/src/did/functions.ts +++ b/packages/ebsi-support/src/did/functions.ts @@ -300,7 +300,7 @@ export const ebsiCreateDidOnLedger = async (args: CreateEbsiDidParams, context: const { clientId, redirectUri, environment, credentialIssuer } = accessTokenOpts const controllerKey = getControllerKey({ identifier }) const secp256r1 = getKeys({ identifier, keyType: 'Secp256r1' })?.[0] - let { attestationToOnboard } = accessTokenOpts + let { attestationToOnboard, attestationToOnboardCredentialRole } = accessTokenOpts if (!controllerKey || !secp256r1) { return Promise.reject(`No secp256k1 controller key and/or secp2561r key found for identifier ${identifier}`) @@ -341,6 +341,7 @@ export const ebsiCreateDidOnLedger = async (args: CreateEbsiDidParams, context: } const insertDidAccessTokenResponse = await context.agent.ebsiAccessTokenGet({ + credentialRole: attestationToOnboardCredentialRole, attestationCredential: attestationToOnboard, jwksUri, scope: 'didr_invite', @@ -398,6 +399,7 @@ export const ebsiCreateDidOnLedger = async (args: CreateEbsiDidParams, context: idOpts.kid = calculateJwkThumbprintForKey({ key: controllerKey }) const addVMAccessTokenResponse = await context.agent.ebsiAccessTokenGet({ + credentialRole: attestationToOnboardCredentialRole, // attestationCredential: attestationToOnboard, jwksUri, scope: 'didr_write', diff --git a/packages/ebsi-support/src/did/types.ts b/packages/ebsi-support/src/did/types.ts index e57296169..dc760d722 100644 --- a/packages/ebsi-support/src/did/types.ts +++ b/packages/ebsi-support/src/did/types.ts @@ -4,6 +4,7 @@ import { IService } from '@veramo/core/build/types/IIdentifier' import { DIDDocument } from 'did-resolver' import { AccessListish, BigNumberish, BytesLike } from 'ethers' import { ApiOpts, EbsiEnvironment } from '../types/IEbsiSupport' +import { CredentialRole } from '@sphereon/ssi-sdk.data-store' export type IContext = IAgentContext @@ -382,6 +383,7 @@ export type GetDidDocumentsResponse = { export type EbsiAccessTokenOpts = { attestationToOnboard?: W3CVerifiableCredential + attestationToOnboardCredentialRole: CredentialRole jwksUri?: string redirectUri: string credentialIssuer: string diff --git a/packages/ebsi-support/src/types/IEbsiSupport.ts b/packages/ebsi-support/src/types/IEbsiSupport.ts index 896b5c262..79287f607 100644 --- a/packages/ebsi-support/src/types/IEbsiSupport.ts +++ b/packages/ebsi-support/src/types/IEbsiSupport.ts @@ -2,7 +2,7 @@ import { DiscoveryMetadataPayload, JWK } from '@sphereon/did-auth-siop' import { OID4VCICredentialFormat, RequestObjectOpts } from '@sphereon/oid4vci-common' import { Format, PresentationDefinitionV2 } from '@sphereon/pex-models' import { IIdentifierOpts } from '@sphereon/ssi-sdk-ext.did-utils' -import { IBasicCredentialLocaleBranding, Party } from '@sphereon/ssi-sdk.data-store' +import { CredentialRole, IBasicCredentialLocaleBranding, Party } from '@sphereon/ssi-sdk.data-store' import { ErrorDetails, IOID4VCIHolder, MappedCredentialToAccept } from '@sphereon/ssi-sdk.oid4vci-holder' import { IPresentationExchange } from '@sphereon/ssi-sdk.presentation-exchange' import { IDidAuthSiopOpAuthenticator } from '@sphereon/ssi-sdk.siopv2-oid4vp-op-auth' @@ -178,6 +178,7 @@ export type GetAccessTokenResult = { */ export interface EBSIAuthAccessTokenGetArgs { clientId: string + credentialRole: CredentialRole credentialIssuer?: string attestationCredential?: W3CVerifiableCredential allVerifiableCredentials?: W3CVerifiableCredential[] diff --git a/packages/ms-request-api/__tests__/restAgent.test.ts b/packages/ms-request-api/__tests__/restAgent.test.ts index 9e181e306..5b106b7de 100644 --- a/packages/ms-request-api/__tests__/restAgent.test.ts +++ b/packages/ms-request-api/__tests__/restAgent.test.ts @@ -2,10 +2,11 @@ import 'cross-fetch/polyfill' // @ts-ignore import express from 'express' import { Server } from 'http' -import { IAgent, createAgent, IAgentOptions, IDataStore, IDataStoreORM } from '@veramo/core' +import { createAgent, IAgent, IAgentOptions } from '@veramo/core' import { AgentRestClient } from '@veramo/remote-client' import { AgentRouter, RequestWithAgentRouter } from '@veramo/remote-server' -import { getConfig, createObjects } from '@sphereon/ssi-sdk.agent-config' +import { createObjects, getConfig } from '@sphereon/ssi-sdk.agent-config' +import { ICredentialStore } from '@sphereon/ssi-sdk.credential-store' import { IMsRequestApi } from '../src' import msRequestApiAgentLogic from './shared/msRequestApiAgentLogic' @@ -18,7 +19,7 @@ let serverAgent: IAgent let restServer: Server const getAgent = (options?: IAgentOptions) => - createAgent({ + createAgent({ ...options, plugins: [ new AgentRestClient({ diff --git a/packages/ms-request-api/__tests__/shared/msRequestApiAgentLogic.ts b/packages/ms-request-api/__tests__/shared/msRequestApiAgentLogic.ts index 6e08e482e..1ec933236 100644 --- a/packages/ms-request-api/__tests__/shared/msRequestApiAgentLogic.ts +++ b/packages/ms-request-api/__tests__/shared/msRequestApiAgentLogic.ts @@ -1,12 +1,13 @@ import * as MsAuthenticator from '../../../ms-authenticator/src' import { fetchIssuanceRequestMs } from '../../src/IssuerUtil' -import { IMsRequestApi, IIssueRequestResponse, IClientIssueRequest, IClientIssuanceConfig } from '../../src/types/IMsRequestApi' +import { IClientIssuanceConfig, IClientIssueRequest, IIssueRequestResponse, IMsRequestApi } from '../../src/types/IMsRequestApi' import { v4 as uuidv4 } from 'uuid' -import { createAgent, TAgent, IDataStore, IDataStoreORM, VerifiableCredential, FindArgs, TCredentialColumns } from '@veramo/core' -import { Entities, DataStore, DataStoreORM } from '@veramo/data-store' +import { createAgent, FindArgs, TAgent, TCredentialColumns, VerifiableCredential } from '@veramo/core' +import { DataStore, DataStoreORM, Entities } from '@veramo/data-store' import { DataSource } from 'typeorm' +import { CredentialRole, CredentialCorrelationType, ICredentialStore, DocumentType } from '@sphereon/ssi-sdk.credential-store' -type ConfiguredAgent = TAgent +type ConfiguredAgent = TAgent const did1 = 'did:test:111' const did2 = 'did:test:222' var requestIssuanceResponse: IIssueRequestResponse = { @@ -87,7 +88,7 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro logging: false, entities: Entities, }) - const localAgent = createAgent({ + const localAgent: TAgent = createAgent({ plugins: [new DataStore(dbConnection), new DataStoreORM(dbConnection)], }) @@ -111,9 +112,28 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro }, } - await localAgent.dataStoreSaveVerifiableCredential({ verifiableCredential: vc5 }) + await localAgent.crsAddCredential({ + credential: { + rawDocument: JSON.stringify(vc5), + credentialRole: CredentialRole.HOLDER, + issuerCorrelationType: CredentialCorrelationType.DID, + issuerCorrelationId: '', + }, + }) + + // An extra credential to make sure we can test filtering + const vc6 = { ...vc5 } + vc6.id = 'vc6' + await localAgent.crsAddCredential({ + credential: { + rawDocument: JSON.stringify(vc6), + credentialRole: CredentialRole.HOLDER, + issuerCorrelationType: CredentialCorrelationType.DID, + issuerCorrelationId: '', + }, + }) - const args: FindArgs = { + const vc5Filter: FindArgs = { where: [ { column: 'id', @@ -122,9 +142,13 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro ], } - const credentials = await localAgent.dataStoreORMGetVerifiableCredentialsByClaims({}) - expect(credentials[0].verifiableCredential.id).toEqual('vc5') - const count = await localAgent.dataStoreORMGetVerifiableCredentialsCount(args) + const credentials = await localAgent.crsGetCredentialsByClaims({ + filter: {}, + credentialRole: CredentialRole.HOLDER, + }) + expect(credentials[0].digitalCredential.documentType).toEqual(DocumentType.VC) + expect(credentials[0].digitalCredential.id).toEqual('vc5') + const count = await localAgent.crsGetCredentialsByClaimsCount({ filter: vc5Filter }) expect(count).toEqual(1) await (await dbConnection).close() }) diff --git a/packages/ms-request-api/agent.yml b/packages/ms-request-api/agent.yml index 6fa398421..2278c1208 100644 --- a/packages/ms-request-api/agent.yml +++ b/packages/ms-request-api/agent.yml @@ -7,6 +7,20 @@ constants: secretKey: 29739248cad1bd1a0fc4d9b75cd4d2990de535baf5caadfdf8d8f86664aa830c methods: - issuanceRequestMsVc + +# Database +dbConnection: + $require: typeorm?t=function#createConnection + $args: + - type: sqlite + database: ':memory:' + synchronize: false + migrationsRun: true + migrations: + $require: './packages/data-store?t=object#DataStoreDigitalCredentialMigrations' + entities: + $require: './packages/data-store?t=object#DataStoreDigitalCredentialEntities' + server: baseUrl: $ref: /constants/baseUrl @@ -63,12 +77,23 @@ server: $ref: /constants/baseUrl messagingServiceEndpoint: /messaging +# Credential Manager +credentialStore: + $require: ./packages/credential-store/dist#CredentialStore + $args: + - store: + $require: './packages/data-store/dist#DigitalCredentialStore' + $args: + - $ref: /dbConnection + + # Agent agent: $require: '@veramo/core#Agent' $args: - schemaValidation: false plugins: + - $ref: /credentialStore - $require: ./packages/ms-request-api/dist#MsRequestApi $args: - azClientId: { process.env.AZ_CLIENT_ID } diff --git a/packages/ms-request-api/package.json b/packages/ms-request-api/package.json index f62635360..28b477cb6 100644 --- a/packages/ms-request-api/package.json +++ b/packages/ms-request-api/package.json @@ -21,6 +21,7 @@ }, "devDependencies": { "@sphereon/ssi-sdk.agent-config": "workspace:*", + "@sphereon/ssi-sdk.credential-store": "workspace:*", "@types/express": "^4.17.21", "@types/express-session": "^1.18.0", "@types/jest": "^27.5.2", diff --git a/packages/ms-request-api/tsconfig.json b/packages/ms-request-api/tsconfig.json index 3f6a7e76f..2011ff153 100644 --- a/packages/ms-request-api/tsconfig.json +++ b/packages/ms-request-api/tsconfig.json @@ -13,6 +13,9 @@ }, { "path": "../agent-config" + }, + { + "path": "../credential-store" } ] } diff --git a/packages/oid4vci-holder/agent.yml b/packages/oid4vci-holder/agent.yml index cb42e53fb..a05f2a148 100644 --- a/packages/oid4vci-holder/agent.yml +++ b/packages/oid4vci-holder/agent.yml @@ -15,6 +15,19 @@ constants: - oid4vciHolderStoreCredentialBranding - oid4vciHolderStoreCredentials +# Database +dbConnection: + $require: typeorm?t=function#createConnection + $args: + - type: sqlite + database: ':memory:' + synchronize: false + migrationsRun: true + migrations: + $require: './packages/data-store?t=object#DataStoreDigitalCredentialMigrations' + entities: + $require: './packages/data-store?t=object#DataStoreDigitalCredentialEntities' + server: baseUrl: $ref: /constants/baseUrl @@ -71,10 +84,20 @@ server: $ref: /constants/baseUrl messagingServiceEndpoint: /messaging +# Credential Manager +credentialStore: + $require: ./packages/credential-store/dist#CredentialStore + $args: + - store: + $require: './packages/data-store/dist#DigitalCredentialStore' + $args: + - $ref: /dbConnection + # Agent agent: $require: '@veramo/core#Agent' $args: - schemaValidation: false plugins: + - $ref: /credentialStore - $require: ./packages/oid4vci-holder/dist#OID4VCIHolder diff --git a/packages/oid4vci-holder/package.json b/packages/oid4vci-holder/package.json index 1f42b3ba1..9604c4441 100644 --- a/packages/oid4vci-holder/package.json +++ b/packages/oid4vci-holder/package.json @@ -18,6 +18,7 @@ "@sphereon/oid4vci-common": "0.15.1-next.9", "@sphereon/ssi-sdk-ext.did-utils": "0.23.1-next.5", "@sphereon/ssi-sdk.contact-manager": "workspace:*", + "@sphereon/ssi-sdk.credential-store": "workspace:*", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-sdk.data-store": "workspace:*", "@sphereon/ssi-sdk.issuance-branding": "workspace:*", diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts index bdda84319..c3aa36d8d 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts @@ -17,6 +17,7 @@ import { import { getIdentifier, getKey, IIdentifierOpts, SupportedDidMethodEnum } from '@sphereon/ssi-sdk-ext.did-utils' import { CorrelationIdentifierType, + CredentialCorrelationType, CredentialRole, FindPartyArgs, IBasicCredentialLocaleBranding, @@ -29,6 +30,7 @@ import { } from '@sphereon/ssi-sdk.data-store' import { CredentialMapper, + ICredential, IVerifiableCredential, JwtDecodedVerifiableCredential, Loggers, @@ -73,11 +75,11 @@ import { StoreCredentialsArgs, } from '../types/IOID4VCIHolder' import { + getBasicIssuerLocaleBranding, getCredentialBranding, getCredentialConfigsSupportedMerged, getIdentifierOpts, getIssuanceOpts, - getBasicIssuerLocaleBranding, mapCredentialToAccept, selectCredentialLocaleBranding, signatureAlgorithmFromKey, @@ -506,7 +508,7 @@ export class OID4VCIHolder implements IAgentPlugin { }) } - const parties = await context.agent.cmGetContacts({ + const parties: Array = await context.agent.cmGetContacts({ filter, }) @@ -761,7 +763,6 @@ export class OID4VCIHolder implements IAgentPlugin { } const { credentialsToAccept, openID4VCIClientState, credentialsSupported, serverMetadata, selectedCredentials } = args - const credentialToAccept = credentialsToAccept[0] if (selectedCredentials && selectedCredentials.length > 1) { @@ -890,8 +891,18 @@ export class OID4VCIHolder implements IAgentPlugin { logger.log(`Will not persist credential, since we are signing as a holder and the issuer asked not to persist`) } else { logger.log(`Persisting credential`, persistCredential) - // @ts-ignore - const vcHash = await context.agent.dataStoreSaveVerifiableCredential({ verifiableCredential: persistCredential }) + + const issuer = CredentialMapper.issuerCorrelationIdFromIssuerType((persistCredential as ICredential).issuer) + const vcHash = await context.agent.crsAddCredential({ + credential: { + rawDocument: JSON.stringify(persistCredential), + credentialRole: CredentialRole.HOLDER, + issuerCorrelationType: CredentialCorrelationType.DID, + issuerCorrelationId: issuer, + subjectCorrelationType: CredentialCorrelationType.DID, + subjectCorrelationId: issuer, // FIXME get separate did for subject + }, + }) await context.agent.emit(OID4VCIHolderEvent.CREDENTIAL_STORED, { vcHash, credential: persistCredential, diff --git a/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts b/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts index a703338ba..8c8f977a1 100644 --- a/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts @@ -37,10 +37,10 @@ import { TKeyType, VerifiableCredential, } from '@veramo/core' -import { IDataStore, IDataStoreORM } from '@veramo/data-store' import { _ExtendedIKey } from '@veramo/utils' import { JWTHeader, JWTPayload } from 'did-jwt' import { BaseActionObject, Interpreter, ResolveTypegenMeta, ServiceMap, State, StateMachine, TypegenDisabled } from 'xstate' +import { ICredentialStore } from '@sphereon/ssi-sdk.credential-store' export interface IOID4VCIHolder extends IPluginMethodMap { oid4vciHolderGetIssuerMetadata(args: GetIssuerMetadataArgs, context: RequiredContext): Promise @@ -597,6 +597,6 @@ export type IdentifierOpts = { } export type RequiredContext = IAgentContext< - IIssuanceBranding & IContactManager & ICredentialVerifier & ICredentialIssuer & IDataStore & IDataStoreORM & IDIDManager & IResolver & IKeyManager + IIssuanceBranding & IContactManager & ICredentialVerifier & ICredentialIssuer & ICredentialStore & IDIDManager & IResolver & IKeyManager > export type DidAgents = TAgent diff --git a/packages/oid4vci-holder/tsconfig.json b/packages/oid4vci-holder/tsconfig.json index fb74706ec..923f31e56 100644 --- a/packages/oid4vci-holder/tsconfig.json +++ b/packages/oid4vci-holder/tsconfig.json @@ -14,6 +14,9 @@ { "path": "../contact-manager" }, + { + "path": "../credential-store" + }, { "path": "../data-store" }, diff --git a/packages/oid4vci-issuer-rest-api/src/types.ts b/packages/oid4vci-issuer-rest-api/src/types.ts index c21119c99..acea6ef1e 100644 --- a/packages/oid4vci-issuer-rest-api/src/types.ts +++ b/packages/oid4vci-issuer-rest-api/src/types.ts @@ -1,15 +1,7 @@ import { IOID4VCIIssuer } from '@sphereon/ssi-sdk.oid4vci-issuer' import { IOID4VCIStore } from '@sphereon/ssi-sdk.oid4vci-issuer-store' -import { IAgentContext, ICredentialIssuer, ICredentialVerifier, IDataStore, IDataStoreORM, IDIDManager, IKeyManager, IResolver } from '@veramo/core' +import { IAgentContext, ICredentialIssuer, ICredentialVerifier, IDIDManager, IKeyManager, IResolver } from '@veramo/core' export type IRequiredContext = IAgentContext -export type IPlugins = IDIDManager & - IKeyManager & - IDataStore & - IDataStoreORM & - IResolver & - IOID4VCIStore & - IOID4VCIIssuer & - ICredentialVerifier & - ICredentialIssuer +export type IPlugins = IDIDManager & IKeyManager & IResolver & IOID4VCIStore & IOID4VCIIssuer & ICredentialVerifier & ICredentialIssuer diff --git a/packages/oid4vci-issuer-store/src/types/IOID4VCIStore.ts b/packages/oid4vci-issuer-store/src/types/IOID4VCIStore.ts index 3dcf749fb..ddd4d2be3 100644 --- a/packages/oid4vci-issuer-store/src/types/IOID4VCIStore.ts +++ b/packages/oid4vci-issuer-store/src/types/IOID4VCIStore.ts @@ -1,7 +1,7 @@ import { IssuerMetadata, CredentialIssuerMetadataOpts } from '@sphereon/oid4vci-common' import { IDIDOptions } from '@sphereon/ssi-sdk-ext.did-utils' import { IKeyValueStore, IValueData } from '@sphereon/ssi-sdk.kv-store-temp' -import { IAgentContext, IDataStoreORM, IPluginMethodMap } from '@veramo/core' +import { IPluginMethodMap } from '@veramo/core' export interface IOID4VCIStore extends IPluginMethodMap { oid4vciStoreDefaultMetadata(): Promise> @@ -85,5 +85,3 @@ export interface Ioid4vciStoreClearArgs { storeId?: string // namespace?: string } - -export type IRequiredContext = IAgentContext diff --git a/packages/oid4vci-issuer/src/types/IOID4VCIIssuer.ts b/packages/oid4vci-issuer/src/types/IOID4VCIIssuer.ts index 33369f37e..2529273d4 100644 --- a/packages/oid4vci-issuer/src/types/IOID4VCIIssuer.ts +++ b/packages/oid4vci-issuer/src/types/IOID4VCIIssuer.ts @@ -14,7 +14,7 @@ import { CredentialDataSupplier } from '@sphereon/oid4vci-issuer' import { IDIDOptions, ResolveOpts } from '@sphereon/ssi-sdk-ext.did-utils' import { IOID4VCIStore } from '@sphereon/ssi-sdk.oid4vci-issuer-store' import { ICredential } from '@sphereon/ssi-types/dist' -import { IAgentContext, ICredentialIssuer, IDataStoreORM, IDIDManager, IKeyManager, IPluginMethodMap, IResolver } from '@veramo/core' +import { IAgentContext, ICredentialIssuer, IDIDManager, IKeyManager, IPluginMethodMap, IResolver } from '@veramo/core' import { IssuerInstance } from '../IssuerInstance' export type IssuerCredentialDefinition = JsonLdIssuerCredentialDefinition @@ -93,4 +93,4 @@ export type ICreateCredentialOfferURIResult = { //userPinRequired: boolean } -export type IRequiredContext = IAgentContext +export type IRequiredContext = IAgentContext diff --git a/packages/pd-manager-rest-api/src/types.ts b/packages/pd-manager-rest-api/src/types.ts index 128a987a5..dd4986cce 100644 --- a/packages/pd-manager-rest-api/src/types.ts +++ b/packages/pd-manager-rest-api/src/types.ts @@ -1,6 +1,6 @@ import { GenericAuthArgs, ISingleEndpointOpts } from '@sphereon/ssi-express-support' import { IPDManager } from '@sphereon/ssi-sdk.pd-manager' -import { IAgentContext, IDataStore } from '@veramo/core' +import { IAgentContext } from '@veramo/core' export type PDManagerMRestApiFeatures = 'pd_read' | 'pd_write' | 'pd_delete' @@ -15,5 +15,5 @@ export interface IPDManagerAPIEndpointOpts { enableFeatures?: PDManagerMRestApiFeatures[] } -export type IRequiredPlugins = IPDManager & IDataStore +export type IRequiredPlugins = IPDManager export type IRequiredContext = IAgentContext diff --git a/packages/presentation-exchange/agent.yml b/packages/presentation-exchange/agent.yml index ce899c327..5844e7531 100644 --- a/packages/presentation-exchange/agent.yml +++ b/packages/presentation-exchange/agent.yml @@ -112,12 +112,22 @@ universal-resolver: $args: - url: https://dev.uniresolver.io/1.0/identifiers/ +# Credential Manager +credentialStore: + $require: ./packages/credential-store/dist#CredentialStore + $args: + - store: + $require: './packages/data-store/dist#DigitalCredentialStore' + $args: + - $ref: /dbConnection + # Agent agent: $require: '@veramo/core#Agent' $args: - schemaValidation: false plugins: + - $ref: /credentialStore - $require: ./packages/pd-manager/dist#PDManager $args: - store: diff --git a/packages/presentation-exchange/package.json b/packages/presentation-exchange/package.json index ab9ca7ba9..6e521af15 100644 --- a/packages/presentation-exchange/package.json +++ b/packages/presentation-exchange/package.json @@ -18,6 +18,7 @@ "@sphereon/pex-models": "^2.2.4", "@sphereon/ssi-sdk-ext.did-utils": "0.23.1-next.5", "@sphereon/ssi-sdk.data-store": "workspace:*", + "@sphereon/ssi-sdk.credential-store": "workspace:*", "@sphereon/ssi-types": "workspace:*", "@veramo/core": "4.2.0" }, diff --git a/packages/presentation-exchange/src/agent/PresentationExchange.ts b/packages/presentation-exchange/src/agent/PresentationExchange.ts index 01d2040aa..9ecfb655a 100644 --- a/packages/presentation-exchange/src/agent/PresentationExchange.ts +++ b/packages/presentation-exchange/src/agent/PresentationExchange.ts @@ -8,13 +8,15 @@ import { schema, VersionDiscoveryResult, } from '../index' -import { FindCredentialsArgs, IAgentPlugin, ProofType, UniqueVerifiableCredential, UnsignedCredential } from '@veramo/core' +import { IAgentPlugin } from '@veramo/core' import { IPresentationExchange } from '../types/IPresentationExchange' import { Checked, IPresentationDefinition, PEX } from '@sphereon/pex' -import { CredentialMapper, JWT_PROOF_TYPE_2020, W3CVerifiableCredential } from '@sphereon/ssi-types' +import { CompactJWT, CredentialMapper, JWT_PROOF_TYPE_2020, W3CVerifiableCredential } from '@sphereon/ssi-types' import { InputDescriptorV1, InputDescriptorV2 } from '@sphereon/pex-models' import { toDIDs } from '@sphereon/ssi-sdk-ext.did-utils' +import { CredentialRole, UniqueDigitalCredential, verifiableCredentialForRoleFilter } from '@sphereon/ssi-sdk.credential-store' +import { FindDigitalCredentialArgs } from '@sphereon/ssi-sdk.data-store' export class PresentationExchange implements IAgentPlugin { readonly schema = schema.IDidAuthSiopOpAuthenticator @@ -61,7 +63,7 @@ export class PresentationExchange implements IAgentPlugin { } async pexDefinitionFilterCredentials(args: IDefinitionCredentialFilterArgs, context: IRequiredContext): Promise { - const credentials = await this.pexFilterCredentials(args.credentialFilterOpts ?? {}, context) + const credentials = await this.pexFilterCredentials(args.credentialFilterOpts, context) const holderDIDs = args.holderDIDs ? toDIDs(args.holderDIDs) : toDIDs(await context.agent.dataStoreORMGetIdentifiers()) const selectResults = this.pex.selectFrom(args.presentationDefinition, credentials ?? [], { ...args, @@ -91,11 +93,13 @@ export class PresentationExchange implements IAgentPlugin { input_descriptors: [inputDescriptor], } + const credentialRole = args.credentialFilterOpts.credentialRole + promises.set( inputDescriptor, this.pexDefinitionFilterCredentials( { - credentialFilterOpts: { verifiableCredentials: credentials }, + credentialFilterOpts: { credentialRole, verifiableCredentials: credentials }, // @ts-ignore presentationDefinition, holderDIDs, @@ -115,16 +119,24 @@ export class PresentationExchange implements IAgentPlugin { private async pexFilterCredentials( filterOpts: { + credentialRole: CredentialRole verifiableCredentials?: W3CVerifiableCredential[] - filter?: FindCredentialsArgs + filter?: FindDigitalCredentialArgs }, context: IRequiredContext, ): Promise { - if (filterOpts?.verifiableCredentials && filterOpts.verifiableCredentials.length > 0) { + if (filterOpts.verifiableCredentials && filterOpts.verifiableCredentials.length > 0) { return filterOpts.verifiableCredentials as W3CVerifiableCredential[] } - return (await context.agent.dataStoreORMGetVerifiableCredentials(filterOpts?.filter)) - .map((uniqueVC: UniqueVerifiableCredential) => uniqueVC.verifiableCredential) - .map((vc: UnsignedCredential & { proof: ProofType }) => (vc.proof && vc.proof.type === JWT_PROOF_TYPE_2020 ? vc.proof.jwt : vc)) + + const filter = verifiableCredentialForRoleFilter(filterOpts.credentialRole, filterOpts.filter) + const uniqueCredentials = await context.agent.crsGetUniqueCredentials({ filter }) + + return uniqueCredentials.map((uniqueVC: UniqueDigitalCredential) => { + const vc = uniqueVC.uniformVerifiableCredential! + const proof = Array.isArray(vc.proof) ? vc.proof : [vc.proof] + const jwtProof = proof.find((p) => p?.type === JWT_PROOF_TYPE_2020) + return jwtProof ? (jwtProof.jwt as CompactJWT) : vc + }) } } diff --git a/packages/presentation-exchange/src/types/IPresentationExchange.ts b/packages/presentation-exchange/src/types/IPresentationExchange.ts index 7de418bc2..13e3477cf 100644 --- a/packages/presentation-exchange/src/types/IPresentationExchange.ts +++ b/packages/presentation-exchange/src/types/IPresentationExchange.ts @@ -1,5 +1,4 @@ import { - FindCredentialsArgs, IAgentContext, ICredentialPlugin, IDataStoreORM, @@ -12,6 +11,8 @@ import { import { IPresentation, Optional, W3CVerifiableCredential, W3CVerifiablePresentation } from '@sphereon/ssi-types' import { IPresentationDefinition, PEVersion, SelectResults } from '@sphereon/pex' import { Format, InputDescriptorV1, InputDescriptorV2 } from '@sphereon/pex-models' +import { ICredentialStore } from '@sphereon/ssi-sdk.credential-store' +import { CredentialRole, FindDigitalCredentialArgs } from '@sphereon/ssi-sdk.data-store' export interface IPresentationExchange extends IPluginMethodMap { pexValidateDefinition(args: IDefinitionValidateArgs): Promise @@ -32,7 +33,11 @@ export interface IDefinitionValidateArgs { export interface IDefinitionCredentialFilterArgs { presentationDefinition: IPresentationDefinition - credentialFilterOpts?: { verifiableCredentials?: W3CVerifiableCredential[]; filter?: FindCredentialsArgs } + credentialFilterOpts: { + credentialRole: CredentialRole + verifiableCredentials?: W3CVerifiableCredential[] + filter?: FindDigitalCredentialArgs + } holderDIDs?: (string | IIdentifier)[] limitDisclosureSignatureSuites?: string[] restrictToFormats?: Format @@ -72,4 +77,4 @@ export interface IPEXPresentationSignCallBackParams { presentationDefinition: IPresentationDefinition } -export type IRequiredContext = IAgentContext +export type IRequiredContext = IAgentContext diff --git a/packages/presentation-exchange/tsconfig.json b/packages/presentation-exchange/tsconfig.json index c17ed4153..574f68406 100644 --- a/packages/presentation-exchange/tsconfig.json +++ b/packages/presentation-exchange/tsconfig.json @@ -18,6 +18,9 @@ }, { "path": "../agent-config" + }, + { + "path": "../credential-store" } ] } diff --git a/packages/public-key-hosting/__tests__/agent.ts b/packages/public-key-hosting/__tests__/agent.ts index b0615ef38..9f90d725e 100644 --- a/packages/public-key-hosting/__tests__/agent.ts +++ b/packages/public-key-hosting/__tests__/agent.ts @@ -21,7 +21,7 @@ const dbConnection = DataSources.singleInstance() .getDbConnection(DB_CONNECTION_NAME_SQLITE) const privateKeyStore: PrivateKeyStore = new PrivateKeyStore(dbConnection, new SecretBox(DB_ENCRYPTION_KEY)) -const agent = createAgent({ +const agent = createAgent({ plugins: [ new DataStore(dbConnection), new DataStoreORM(dbConnection), diff --git a/packages/siopv2-oid4vp-op-auth/__tests__/restAgent.test.ts b/packages/siopv2-oid4vp-op-auth/__tests__/restAgent.test.ts index 09fd40834..2f897e0a0 100644 --- a/packages/siopv2-oid4vp-op-auth/__tests__/restAgent.test.ts +++ b/packages/siopv2-oid4vp-op-auth/__tests__/restAgent.test.ts @@ -2,7 +2,7 @@ import * as fs from 'fs' import 'cross-fetch/polyfill' // @ts-ignore import express from 'express' -import { IAgent, createAgent, IAgentOptions, IDataStore } from '@veramo/core' +import { IAgent, createAgent, IAgentOptions } from '@veramo/core' import { AgentRestClient } from '@veramo/remote-client' import { Server } from 'http' import { AgentRouter, RequestWithAgentRouter } from '@veramo/remote-server' @@ -40,7 +40,7 @@ const presentationSignCallback: PresentationSignCallback = async (args) => { } const getAgent = (options?: IAgentOptions) => - createAgent({ + createAgent({ ...options, plugins: [ new DidAuthSiopOpAuthenticator(presentationSignCallback), diff --git a/packages/siopv2-oid4vp-op-auth/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts b/packages/siopv2-oid4vp-op-auth/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts index bb5adc805..73e410c57 100644 --- a/packages/siopv2-oid4vp-op-auth/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts +++ b/packages/siopv2-oid4vp-op-auth/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts @@ -1,5 +1,5 @@ import * as fs from 'fs' -import { IDataStore, TAgent, VerifiableCredential } from '@veramo/core' +import { TAgent, VerifiableCredential } from '@veramo/core' import { IAuthRequestDetails, IDidAuthSiopOpAuthenticator, IPresentationWithDefinition } from '../../src' import { AuthorizationRequest, @@ -38,7 +38,7 @@ jest.mock('@sphereon/ssi-sdk-ext.did-utils', () => ({ mapIdentifierKeysToDocWithJwkSupport: jest.fn(), })) -type ConfiguredAgent = TAgent +type ConfiguredAgent = TAgent const didMethod = 'ethr' const did = 'did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a' @@ -178,12 +178,12 @@ export default (testContext: { const idCardCredential: VerifiableCredential = getFileAsJson( './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vc/vc_idCardCredential.json', ) - await agent.dataStoreSaveVerifiableCredential({ verifiableCredential: idCardCredential }) + await agent.crsAddCredential({ verifiableCredential: idCardCredential }) const driverLicenseCredential: VerifiableCredential = getFileAsJson( './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vc/vc_driverLicense.json', ) - await agent.dataStoreSaveVerifiableCredential({ verifiableCredential: driverLicenseCredential }) + await agent.crsAddCredential({ verifiableCredential: driverLicenseCredential }) nock(redirectUrl).get(`?stateId=${stateId}`).times(5).reply(200, openIDURI) diff --git a/packages/siopv2-oid4vp-op-auth/agent.yml b/packages/siopv2-oid4vp-op-auth/agent.yml index bc176709a..799887eee 100644 --- a/packages/siopv2-oid4vp-op-auth/agent.yml +++ b/packages/siopv2-oid4vp-op-auth/agent.yml @@ -21,10 +21,12 @@ dbConnection: synchronize: false migrationsRun: true migrations: - $require: '@veramo/data-store?t=object#migrations' + - $require: '@veramo/data-store?t=object#migrations' + - $require: './packages/data-store?t=object#DataStoreDigitalCredentialMigrations' logging: false entities: - $require: '@veramo/data-store?t=object#Entities' + - $require: '@veramo/data-store?t=object#Entities' + - $require: './packages/data-store?t=object#DataStoreDigitalCredentialEntities' server: baseUrl: @@ -110,6 +112,16 @@ universal-resolver: $args: - url: https://dev.uniresolver.io/1.0/identifiers/ +# Credential Manager +credentialStore: + $require: ./packages/credential-store/dist#CredentialStore + $args: + - store: + $require: './packages/data-store/dist#DigitalCredentialStore' + $args: + - $ref: /dbConnection + + # Agent agent: $require: '@veramo/core#Agent' @@ -117,6 +129,7 @@ agent: - schemaValidation: false plugins: - $ref: /didResolver + $ref: /credentialStore - $require: ./packages/siopv2-oid4vp-op-auth/dist#DidAuthSiopOpAuthenticator $args: - presentationSignCallback: {} diff --git a/packages/siopv2-oid4vp-op-auth/package.json b/packages/siopv2-oid4vp-op-auth/package.json index 7b6d660f6..f6293e294 100644 --- a/packages/siopv2-oid4vp-op-auth/package.json +++ b/packages/siopv2-oid4vp-op-auth/package.json @@ -19,6 +19,7 @@ "@sphereon/pex-models": "2.2.4", "@sphereon/ssi-sdk-ext.did-utils": "0.23.1-next.5", "@sphereon/ssi-sdk.contact-manager": "workspace:*", + "@sphereon/ssi-sdk.credential-store": "workspace:*", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-sdk.data-store": "workspace:*", "@sphereon/ssi-sdk.issuance-branding": "workspace:*", diff --git a/packages/siopv2-oid4vp-op-auth/src/services/Siopv2MachineService.ts b/packages/siopv2-oid4vp-op-auth/src/services/Siopv2MachineService.ts index 62ffbe464..1e070cf6b 100644 --- a/packages/siopv2-oid4vp-op-auth/src/services/Siopv2MachineService.ts +++ b/packages/siopv2-oid4vp-op-auth/src/services/Siopv2MachineService.ts @@ -7,10 +7,10 @@ import { IIdentifierOpts, SupportedDidMethodEnum, } from '@sphereon/ssi-sdk-ext.did-utils' -import { ConnectionType } from '@sphereon/ssi-sdk.data-store' +import { ConnectionType, CredentialRole } from '@sphereon/ssi-sdk.data-store' import { IIdentifier } from '@veramo/core' import { DidAgents, SuitableCredentialAgents } from '../types' -import { CredentialMapper, IVerifiableCredential, Loggers, OriginalVerifiableCredential, PresentationSubmission } from '@sphereon/ssi-types' +import { CredentialMapper, Loggers, PresentationSubmission } from '@sphereon/ssi-types' import { LOGGER_NAMESPACE, RequiredContext, @@ -23,6 +23,7 @@ import { import { OID4VP, OpSession } from '../session' import { IPresentationDefinition, PEX } from '@sphereon/pex' import { InputDescriptorV1, InputDescriptorV2, PresentationDefinitionV1, PresentationDefinitionV2 } from '@sphereon/pex-models' +import { verifiableCredentialForRoleFilter } from '@sphereon/ssi-sdk.credential-store' export const logger = Loggers.DEFAULT.get(LOGGER_NAMESPACE) @@ -85,7 +86,7 @@ export const siopSendAuthorizationResponse = async ( const credentialsAndDefinitions = args.verifiableCredentialsWithDefinition ? args.verifiableCredentialsWithDefinition - : await oid4vp.filterCredentialsAgainstAllDefinitions() + : await oid4vp.filterCredentialsAgainstAllDefinitions(CredentialRole.HOLDER) const domain = ((await request.authorizationRequest.getMergedProperty('client_id')) as string) ?? request.issuer ?? @@ -104,7 +105,7 @@ export const siopSendAuthorizationResponse = async ( } } - presentationsAndDefs = await oid4vp.createVerifiablePresentations(credentialsAndDefinitions, { + presentationsAndDefs = await oid4vp.createVerifiablePresentations(CredentialRole.HOLDER, credentialsAndDefinitions, { identifierOpts: { identifier }, proofOpts: { nonce: session.nonce, @@ -168,32 +169,34 @@ export const getSelectableCredentials = async ( const { agent } = agentContext const pex = new PEX() - const uniqueVerifiableCredentials = await agent.dataStoreORMGetVerifiableCredentials() + const uniqueVerifiableCredentials = await agent.crsGetUniqueCredentials({ + filter: verifiableCredentialForRoleFilter(CredentialRole.HOLDER), + }) const credentialBranding = await agent.ibGetCredentialBranding() const selectableCredentialsMap: SelectableCredentialsMap = new Map() for (const inputDescriptor of presentationDefinition.input_descriptors) { const partialPD = buildPartialPD(inputDescriptor, presentationDefinition) - const originalCredentials = uniqueVerifiableCredentials.map((uniqueVC) => - CredentialMapper.storedCredentialToOriginalFormat(uniqueVC.verifiableCredential as OriginalVerifiableCredential), - ) + const originalCredentials = uniqueVerifiableCredentials.map((uniqueVC) => { + return CredentialMapper.storedCredentialToOriginalFormat(uniqueVC.originalVerifiableCredential!) // ( ! is valid for verifiableCredentialForRoleFilter ) + }) const selectionResults = pex.selectFrom(partialPD, originalCredentials) const selectableCredentials: Array = [] for (const selectedCredential of selectionResults.verifiableCredential || []) { const filteredUniqueVC = uniqueVerifiableCredentials.find((uniqueVC) => { - const proof = (uniqueVC.verifiableCredential as IVerifiableCredential).proof + const proof = uniqueVC.uniformVerifiableCredential!.proof return Array.isArray(proof) ? proof.some((proofItem) => proofItem.jwt === selectedCredential) : proof.jwt === selectedCredential }) if (filteredUniqueVC) { const filteredCredentialBrandings = credentialBranding.filter((cb) => cb.vcHash === filteredUniqueVC.hash) const issuerPartyIdentity = await agent.cmGetContacts({ - filter: [{ identities: { identifier: { correlationId: filteredUniqueVC.verifiableCredential.issuerDid } } }], + filter: [{ identities: { identifier: { correlationId: filteredUniqueVC.uniformVerifiableCredential!.issuerDid } } }], }) const subjectPartyIdentity = await agent.cmGetContacts({ - filter: [{ identities: { identifier: { correlationId: filteredUniqueVC.verifiableCredential.subjectDid } } }], + filter: [{ identities: { identifier: { correlationId: filteredUniqueVC.uniformVerifiableCredential!.subjectDid } } }], }) selectableCredentials.push({ diff --git a/packages/siopv2-oid4vp-op-auth/src/session/OID4VP.ts b/packages/siopv2-oid4vp-op-auth/src/session/OID4VP.ts index 314f25bfe..f46f46141 100644 --- a/packages/siopv2-oid4vp-op-auth/src/session/OID4VP.ts +++ b/packages/siopv2-oid4vp-op-auth/src/session/OID4VP.ts @@ -5,10 +5,13 @@ import { getDID, IIdentifierOpts } from '@sphereon/ssi-sdk-ext.did-utils' import { ProofOptions } from '@sphereon/ssi-sdk.core' import { CredentialMapper, W3CVerifiableCredential } from '@sphereon/ssi-types' -import { FindCredentialsArgs, IIdentifier } from '@veramo/core' +import { IIdentifier } from '@veramo/core' import { DEFAULT_JWT_PROOF_TYPE, VerifiableCredentialsWithDefinition, VerifiablePresentationWithDefinition } from '../types' import { createOID4VPPresentationSignCallback } from './functions' import { OpSession } from './OpSession' +import { UniqueDigitalCredential, verifiableCredentialForRoleFilter } from '@sphereon/ssi-sdk.credential-store' +import { CompactJWT } from '@sphereon/ssi-types/dist' +import { CredentialRole, FindDigitalCredentialArgs } from '@sphereon/ssi-sdk.data-store/dist' export class OID4VP { private readonly session: OpSession @@ -39,6 +42,7 @@ export class OID4VP { } public async createVerifiablePresentations( + credentialRole: CredentialRole, credentialsWithDefinitions: VerifiableCredentialsWithDefinition[], opts?: { forceNoCredentialsInVP?: boolean // Allow to create a VP without credentials, like EBSI is using it. Defaults to false @@ -52,10 +56,11 @@ export class OID4VP { applyFilter?: boolean }, ): Promise { - return await Promise.all(credentialsWithDefinitions.map((cred) => this.createVerifiablePresentation(cred, opts))) + return await Promise.all(credentialsWithDefinitions.map((cred) => this.createVerifiablePresentation(credentialRole, cred, opts))) } public async createVerifiablePresentation( + credentialRole: CredentialRole, selectedVerifiableCredentials: VerifiableCredentialsWithDefinition, opts?: { forceNoCredentialsInVP?: boolean // Allow to create a VP without credentials, like EBSI is using it. Defaults to false @@ -112,7 +117,7 @@ export class OID4VP { const vcs = forceNoCredentialsInVP ? selectedVerifiableCredentials : opts?.applyFilter - ? await this.filterCredentials(selectedVerifiableCredentials.definition, { + ? await this.filterCredentials(credentialRole, selectedVerifiableCredentials.definition, { restrictToFormats: opts?.restrictToFormats, restrictToDIDMethods: opts?.restrictToDIDMethods, filterOpts: { @@ -160,26 +165,33 @@ export class OID4VP { } } - public async filterCredentialsAgainstAllDefinitions(opts?: { - filterOpts?: { verifiableCredentials?: W3CVerifiableCredential[]; filter?: FindCredentialsArgs } - holderDIDs?: string[] - restrictToFormats?: Format - restrictToDIDMethods?: string[] - }): Promise { + public async filterCredentialsAgainstAllDefinitions( + credentialRole: CredentialRole, + opts?: { + filterOpts?: { + verifiableCredentials?: W3CVerifiableCredential[] + filter?: FindDigitalCredentialArgs + } + holderDIDs?: string[] + restrictToFormats?: Format + restrictToDIDMethods?: string[] + }, + ): Promise { const defs = await this.getPresentationDefinitions() const result: VerifiableCredentialsWithDefinition[] = [] if (defs) { for (const definition of defs) { - result.push(await this.filterCredentials(definition, opts)) + result.push(await this.filterCredentials(credentialRole, definition, opts)) } } return result } public async filterCredentials( + credentialRole: CredentialRole, presentationDefinition: PresentationDefinitionWithLocation, opts?: { - filterOpts?: { verifiableCredentials?: W3CVerifiableCredential[]; filter?: FindCredentialsArgs } + filterOpts?: { verifiableCredentials?: W3CVerifiableCredential[]; filter?: FindDigitalCredentialArgs } holderDIDs?: string[] restrictToFormats?: Format restrictToDIDMethods?: string[] @@ -187,21 +199,23 @@ export class OID4VP { ): Promise { return { definition: presentationDefinition, - credentials: (await this.filterCredentialsWithSelectionStatus(presentationDefinition, opts)).verifiableCredential as W3CVerifiableCredential[], + credentials: (await this.filterCredentialsWithSelectionStatus(credentialRole, presentationDefinition, opts)) + .verifiableCredential as W3CVerifiableCredential[], } } public async filterCredentialsWithSelectionStatus( + credentialRole: CredentialRole, presentationDefinition: PresentationDefinitionWithLocation, opts?: { - filterOpts?: { verifiableCredentials?: W3CVerifiableCredential[]; filter?: FindCredentialsArgs } + filterOpts?: { verifiableCredentials?: W3CVerifiableCredential[]; filter?: FindDigitalCredentialArgs } holderDIDs?: string[] restrictToFormats?: Format restrictToDIDMethods?: string[] }, ): Promise { const selectionResults: SelectResults = await this.getPresentationExchange( - await this.getCredentials(opts?.filterOpts), + await this.getCredentials(credentialRole, opts?.filterOpts), ).selectVerifiableCredentialsForSubmission(presentationDefinition.definition, opts) if (selectionResults.errors && selectionResults.errors.length > 0) { throw Error(JSON.stringify(selectionResults.errors)) @@ -216,16 +230,25 @@ export class OID4VP { return selectionResults } - private async getCredentials(filterOpts?: { - verifiableCredentials?: W3CVerifiableCredential[] - filter?: FindCredentialsArgs - }): Promise { + private async getCredentials( + credentialRole: CredentialRole, + filterOpts?: { + verifiableCredentials?: W3CVerifiableCredential[] + filter?: FindDigitalCredentialArgs + }, + ): Promise { if (filterOpts?.verifiableCredentials && filterOpts.verifiableCredentials.length > 0) { return filterOpts.verifiableCredentials } - return (await this.session.context.agent.dataStoreORMGetVerifiableCredentials(filterOpts?.filter)) - .map((uniqueVC) => uniqueVC.verifiableCredential) - .map((vc) => (vc.proof && vc.proof.type === DEFAULT_JWT_PROOF_TYPE ? vc.proof.jwt : vc)) + + const filter = verifiableCredentialForRoleFilter(credentialRole, filterOpts?.filter) + const uniqueCredentials = await this.session.context.agent.crsGetUniqueCredentials({ filter }) + return uniqueCredentials.map((uniqueVC: UniqueDigitalCredential) => { + const vc = uniqueVC.uniformVerifiableCredential! + const proof = Array.isArray(vc.proof) ? vc.proof : [vc.proof] + const jwtProof = proof.find((p) => p?.type === DEFAULT_JWT_PROOF_TYPE) + return jwtProof ? (jwtProof.jwt as CompactJWT) : vc + }) } private assertIdentifier(identifier?: IIdentifier | string): void { diff --git a/packages/siopv2-oid4vp-op-auth/src/types/IDidAuthSiopOpAuthenticator.ts b/packages/siopv2-oid4vp-op-auth/src/types/IDidAuthSiopOpAuthenticator.ts index 9f5f3d502..3055a0001 100644 --- a/packages/siopv2-oid4vp-op-auth/src/types/IDidAuthSiopOpAuthenticator.ts +++ b/packages/siopv2-oid4vp-op-auth/src/types/IDidAuthSiopOpAuthenticator.ts @@ -44,6 +44,7 @@ import { Siopv2AuthorizationRequestData, Siopv2AuthorizationResponseData, } from './siop-service' +import { ICredentialStore } from '@sphereon/ssi-sdk.credential-store' export const LOGGER_NAMESPACE = 'sphereon:siopv2-oid4vp:op-auth' @@ -117,7 +118,7 @@ export enum events { } export type IRequiredContext = IAgentContext< - IDataStoreORM & IResolver & IDIDManager & IKeyManager & ICredentialIssuer & ICredentialVerifier & IPDManager + IDataStoreORM & IResolver & IDIDManager & IKeyManager & ICredentialIssuer & ICredentialVerifier & ICredentialStore & IPDManager > export interface IOPOptions { diff --git a/packages/siopv2-oid4vp-op-auth/src/types/identifier/index.ts b/packages/siopv2-oid4vp-op-auth/src/types/identifier/index.ts index 20e6a8007..2a7aed0f0 100644 --- a/packages/siopv2-oid4vp-op-auth/src/types/identifier/index.ts +++ b/packages/siopv2-oid4vp-op-auth/src/types/identifier/index.ts @@ -1,9 +1,10 @@ -import { IDataStoreORM, IDIDManager, IIdentifier, IResolver, TAgent, TKeyType } from '@veramo/core' +import { IDIDManager, IIdentifier, IResolver, TAgent, TKeyType } from '@veramo/core' import { _ExtendedIKey } from '@veramo/utils' import { RequiredContext } from '../siop-service' import { KeyManagementSystemEnum, SupportedDidMethodEnum } from '@sphereon/ssi-sdk-ext.did-utils' import { IContactManager } from '@sphereon/ssi-sdk.contact-manager' import { IIssuanceBranding } from '@sphereon/ssi-sdk.issuance-branding' +import { ICredentialStore } from '@sphereon/ssi-sdk.credential-store' export const DID_PREFIX = 'did' @@ -59,4 +60,4 @@ export type CreateIdentifierOpts = { } export type DidAgents = TAgent -export type SuitableCredentialAgents = TAgent +export type SuitableCredentialAgents = TAgent diff --git a/packages/siopv2-oid4vp-op-auth/src/types/siop-service/index.ts b/packages/siopv2-oid4vp-op-auth/src/types/siop-service/index.ts index 6fc700743..cd5070ed8 100644 --- a/packages/siopv2-oid4vp-op-auth/src/types/siop-service/index.ts +++ b/packages/siopv2-oid4vp-op-auth/src/types/siop-service/index.ts @@ -1,11 +1,12 @@ import { IIdentifierOpts } from '@sphereon/ssi-sdk-ext.did-utils' import { IContactManager } from '@sphereon/ssi-sdk.contact-manager' -import { IAgentContext, IDataStoreORM, IDIDManager, IIdentifier, IResolver, UniqueVerifiableCredential } from '@veramo/core' +import { IAgentContext, IDIDManager, IIdentifier, IResolver } from '@veramo/core' import { PresentationDefinitionWithLocation, RPRegistrationMetadataPayload } from '@sphereon/did-auth-siop' import { DidAuthConfig, ICredentialLocaleBranding, Identity, Party } from '@sphereon/ssi-sdk.data-store' import { Siopv2MachineContext, Siopv2MachineInterpreter, Siopv2MachineState } from '../machine' import { IDidAuthSiopOpAuthenticator } from '../IDidAuthSiopOpAuthenticator' import { IIssuanceBranding } from '@sphereon/ssi-sdk.issuance-branding' +import { ICredentialStore, UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store' export type DidAuthSiopOpAuthenticatorOptions = { onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise @@ -55,7 +56,7 @@ export type Siopv2AuthorizationRequestData = { export type SelectableCredentialsMap = Map> export type SelectableCredential = { - credential: UniqueVerifiableCredential + credential: UniqueDigitalCredential credentialBranding: Array issuerParty?: Party subjectParty?: Party @@ -71,5 +72,5 @@ export type OnIdentifierCreatedArgs = { } export type RequiredContext = IAgentContext< - IContactManager & IDidAuthSiopOpAuthenticator & IDIDManager & IResolver & IDataStoreORM & IIssuanceBranding + IContactManager & IDidAuthSiopOpAuthenticator & IDIDManager & IResolver & ICredentialStore & IIssuanceBranding > diff --git a/packages/siopv2-oid4vp-op-auth/tsconfig.json b/packages/siopv2-oid4vp-op-auth/tsconfig.json index 7a3aaa7ed..484cd4416 100644 --- a/packages/siopv2-oid4vp-op-auth/tsconfig.json +++ b/packages/siopv2-oid4vp-op-auth/tsconfig.json @@ -22,6 +22,8 @@ }, { "path": "../contact-manager" + }, { + "path": "../credential-store" }, { "path": "../data-store" diff --git a/packages/siopv2-oid4vp-rp-auth/agent.yml b/packages/siopv2-oid4vp-rp-auth/agent.yml index f78f74ed5..8b749737d 100644 --- a/packages/siopv2-oid4vp-rp-auth/agent.yml +++ b/packages/siopv2-oid4vp-rp-auth/agent.yml @@ -21,10 +21,13 @@ dbConnection: synchronize: false migrationsRun: true migrations: - $require: '@veramo/data-store?t=object#migrations' + - $require: '@veramo/data-store?t=object#migrations' + - $require: './packages/data-store?t=object#DataStoreDigitalCredentialMigrations' logging: false entities: - $require: '@veramo/data-store?t=object#Entities' + - $require: '@veramo/data-store?t=object#Entities' + - $require: './packages/data-store?t=object#DataStoreDigitalCredentialEntities' + server: baseUrl: @@ -110,12 +113,23 @@ universal-resolver: $args: - url: https://dev.uniresolver.io/1.0/identifiers/ +# Credential Manager +credentialStore: + $require: ./packages/credential-store/dist#CredentialStore + $args: + - store: + $require: './packages/data-store/dist#DigitalCredentialStore' + $args: + - $ref: /dbConnection + + # Agent agent: $require: '@veramo/core#Agent' $args: - schemaValidation: false plugins: + - $ref: /credentialStore - $ref: /didResolver - $require: ./packages/siopv2-openid4vp-op-auth/dist#DidAuthSiopOpAuthenticator $args: diff --git a/packages/siopv2-oid4vp-rp-auth/src/types/ISIOPv2RP.ts b/packages/siopv2-oid4vp-rp-auth/src/types/ISIOPv2RP.ts index 41def97a5..613683331 100644 --- a/packages/siopv2-oid4vp-rp-auth/src/types/ISIOPv2RP.ts +++ b/packages/siopv2-oid4vp-rp-auth/src/types/ISIOPv2RP.ts @@ -1,14 +1,5 @@ import { ClientMetadataOpts } from '@sphereon/did-auth-siop/dist/types' -import { - IAgentContext, - ICredentialIssuer, - ICredentialVerifier, - IDataStoreORM, - IDIDManager, - IKeyManager, - IPluginMethodMap, - IResolver, -} from '@veramo/core' +import { IAgentContext, ICredentialIssuer, ICredentialVerifier, IDIDManager, IKeyManager, IPluginMethodMap, IResolver } from '@veramo/core' import { AdditionalClaims, W3CVerifiablePresentation } from '@sphereon/ssi-types' import { AuthorizationRequestPayload, @@ -184,5 +175,5 @@ export interface AuthorizationResponseStateWithVerifiedData extends Authorizatio } export type IRequiredContext = IAgentContext< - IDataStoreORM & IResolver & IDIDManager & IKeyManager & ICredentialIssuer & ICredentialVerifier & IPresentationExchange & IPDManager + IResolver & IDIDManager & IKeyManager & ICredentialIssuer & ICredentialVerifier & IPresentationExchange & IPDManager > diff --git a/packages/siopv2-oid4vp-rp-rest-api/__tests__/agent.ts b/packages/siopv2-oid4vp-rp-rest-api/__tests__/agent.ts index a662537ef..8410c6983 100644 --- a/packages/siopv2-oid4vp-rp-rest-api/__tests__/agent.ts +++ b/packages/siopv2-oid4vp-rp-rest-api/__tests__/agent.ts @@ -1,4 +1,4 @@ -import { createAgent, ICredentialVerifier, IDataStore, IDataStoreORM, IDIDManager, IKeyManager, IResolver } from '@veramo/core' +import { createAgent, ICredentialVerifier, IDIDManager, IKeyManager, IResolver } from '@veramo/core' import { IonPublicKeyPurpose } from '@decentralized-identity/ion-sdk' import { getUniResolver } from '@sphereon/did-uni-client' import { @@ -101,15 +101,7 @@ const dbConnection = getDbConnection(DB_CONNECTION_NAME) const privateKeyStore: PrivateKeyStore = new PrivateKeyStore(dbConnection, new SecretBox(DB_ENCRYPTION_KEY)) const agent = createAgent< - IDIDManager & - IKeyManager & - IDataStore & - IDataStoreORM & - IResolver & - IPresentationExchange & - ISIOPv2RP & - ICredentialVerifier & - ICredentialHandlerLDLocal + IDIDManager & IKeyManager & IResolver & IPresentationExchange & ISIOPv2RP & ICredentialVerifier & ICredentialHandlerLDLocal >({ plugins: [ new DataStore(dbConnection), diff --git a/packages/ssi-sdk-core/src/utils/index.ts b/packages/ssi-sdk-core/src/utils/index.ts index d606aeeee..f8612e694 100644 --- a/packages/ssi-sdk-core/src/utils/index.ts +++ b/packages/ssi-sdk-core/src/utils/index.ts @@ -2,4 +2,3 @@ export * from './encoding' export * from './bearer-token' export { enablePostgresUuidExtension, flattenArray, flattenMigrations } from './database' export { getImageMediaType, getImageDimensions, downloadImage } from './image' -export * from './vc' diff --git a/packages/ssi-sdk-core/src/utils/vc.ts b/packages/ssi-sdk-core/src/utils/vc.ts deleted file mode 100644 index b4562aec9..000000000 --- a/packages/ssi-sdk-core/src/utils/vc.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { IAgentContext, IDataStore, IDataStoreORM, VerifiableCredential } from '@veramo/core' - -export async function getCredentialByIdOrHash( - context: IAgentContext, - idOrHash: string, -): Promise<{ - id: string - hash?: string - vc?: VerifiableCredential -}> { - let vc: VerifiableCredential - let hash: string - const uniqueVCs = await context.agent.dataStoreORMGetVerifiableCredentials({ - where: [ - { - column: 'id', - value: [idOrHash], - op: 'Equal', - }, - ], - }) - if (uniqueVCs.length === 0) { - hash = idOrHash - vc = await context.agent.dataStoreGetVerifiableCredential({ hash }) - } else { - const uniqueVC = uniqueVCs[0] - hash = uniqueVC.hash - vc = uniqueVC.verifiableCredential - } - - return { - vc, - id: idOrHash, - hash, - } -} diff --git a/packages/ssi-types/src/mapper/credential-mapper.ts b/packages/ssi-types/src/mapper/credential-mapper.ts index 7f3f79fab..c93042872 100644 --- a/packages/ssi-types/src/mapper/credential-mapper.ts +++ b/packages/ssi-types/src/mapper/credential-mapper.ts @@ -32,6 +32,7 @@ import { AsyncHasher, } from '../types' import { ObjectUtils } from '../utils' +import { IssuerType } from '@veramo/core' export class CredentialMapper { /** @@ -53,7 +54,7 @@ export class CredentialMapper { */ static decodeVerifiablePresentation( presentation: OriginalVerifiablePresentation, - hasher?: Hasher, + hasher?: Hasher ): JwtDecodedVerifiablePresentation | IVerifiablePresentation | SdJwtDecodedVerifiableCredential { if (CredentialMapper.isJwtEncoded(presentation)) { const payload = jwt_decode(presentation as string) as JwtDecodedVerifiablePresentation @@ -94,7 +95,7 @@ export class CredentialMapper { */ static decodeVerifiableCredential( credential: OriginalVerifiableCredential, - hasher?: Hasher, + hasher?: Hasher ): JwtDecodedVerifiableCredential | IVerifiableCredential | SdJwtDecodedVerifiableCredential { if (CredentialMapper.isJwtEncoded(credential)) { const payload = jwt_decode(credential as string) as JwtDecodedVerifiableCredential @@ -134,7 +135,7 @@ export class CredentialMapper { */ static toWrappedVerifiablePresentation( originalPresentation: OriginalVerifiablePresentation, - opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }, + opts?: { maxTimeSkewInMS?: number; hasher?: Hasher } ): WrappedVerifiablePresentation { // SD-JWT if (CredentialMapper.isSdJwtDecodedCredential(originalPresentation) || CredentialMapper.isSdJwtEncoded(originalPresentation)) { @@ -164,7 +165,7 @@ export class CredentialMapper { typeof originalPresentation !== 'string' && CredentialMapper.hasJWTProofType(originalPresentation) ? proof?.jwt : originalPresentation if (!original) { throw Error( - 'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof', + 'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof' ) } const decoded = CredentialMapper.decodeVerifiablePresentation(original) as IVerifiablePresentation | JwtDecodedVerifiablePresentation @@ -192,7 +193,7 @@ export class CredentialMapper { ? [] : (CredentialMapper.toWrappedVerifiableCredentials( vp.verifiableCredential ?? [] /*.map(value => value.original)*/, - opts, + opts ) as WrappedW3CVerifiableCredential[]) const presentation = { @@ -220,7 +221,7 @@ export class CredentialMapper { */ static toWrappedVerifiableCredentials( verifiableCredentials: OriginalVerifiableCredential[], - opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }, + opts?: { maxTimeSkewInMS?: number; hasher?: Hasher } ): WrappedVerifiableCredential[] { return verifiableCredentials.map((vc) => CredentialMapper.toWrappedVerifiableCredential(vc, opts)) } @@ -236,7 +237,7 @@ export class CredentialMapper { */ static toWrappedVerifiableCredential( verifiableCredential: OriginalVerifiableCredential, - opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }, + opts?: { maxTimeSkewInMS?: number; hasher?: Hasher } ): WrappedVerifiableCredential { // SD-JWT if (CredentialMapper.isSdJwtDecodedCredential(verifiableCredential) || CredentialMapper.isSdJwtEncoded(verifiableCredential)) { @@ -264,7 +265,7 @@ export class CredentialMapper { const original = CredentialMapper.hasJWTProofType(verifiableCredential) && proof ? (proof.jwt ?? verifiableCredential) : verifiableCredential if (!original) { throw Error( - 'Could not determine original credential, probably it was a converted JWT credential, that is now missing the JWT value in the proof', + 'Could not determine original credential, probably it was a converted JWT credential, that is now missing the JWT value in the proof' ) } const decoded = CredentialMapper.decodeVerifiableCredential(original) as JwtDecodedVerifiableCredential | IVerifiableCredential @@ -359,13 +360,13 @@ export class CredentialMapper { } public static isW3cPresentation( - presentation: UniformVerifiablePresentation | IPresentation | SdJwtDecodedVerifiableCredential, + presentation: UniformVerifiablePresentation | IPresentation | SdJwtDecodedVerifiableCredential ): presentation is IPresentation { return '@context' in presentation && ((presentation as IPresentation).type?.includes('VerifiablePresentation') || false) } public static isSdJwtDecodedCredentialPayload( - credential: ICredential | SdJwtDecodedVerifiableCredentialPayload, + credential: ICredential | SdJwtDecodedVerifiableCredentialPayload ): credential is SdJwtDecodedVerifiableCredentialPayload { return 'vct' in credential } @@ -387,7 +388,7 @@ export class CredentialMapper { } public static isSdJwtDecodedCredential( - original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation, + original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation ): original is SdJwtDecodedVerifiableCredential { return (original).compactSdJwtVc !== undefined } @@ -408,7 +409,7 @@ export class CredentialMapper { static jwtEncodedPresentationToUniformPresentation( jwt: string, makeCredentialsUniform: boolean = true, - opts?: { maxTimeSkewInMS?: number }, + opts?: { maxTimeSkewInMS?: number } ): IPresentation { return CredentialMapper.jwtDecodedPresentationToUniformPresentation(jwt_decode(jwt), makeCredentialsUniform, opts) } @@ -416,7 +417,7 @@ export class CredentialMapper { static jwtDecodedPresentationToUniformPresentation( decoded: JwtDecodedVerifiablePresentation, makeCredentialsUniform: boolean = true, - opts?: { maxTimeSkewInMS?: number }, + opts?: { maxTimeSkewInMS?: number } ): IVerifiablePresentation { const { iss, aud, jti, vp, ...rest } = decoded @@ -462,7 +463,7 @@ export class CredentialMapper { verifiableCredential: OriginalVerifiableCredential, opts?: { maxTimeSkewInMS?: number - }, + } ): IVerifiableCredential { if (CredentialMapper.isSdJwtDecodedCredential(verifiableCredential)) { throw new Error('Converting SD-JWT VC to uniform VC is not supported.') @@ -473,7 +474,7 @@ export class CredentialMapper { : verifiableCredential if (!original) { throw Error( - 'Could not determine original credential from passed in credential. Probably because a JWT proof type was present, but now is not available anymore', + 'Could not determine original credential from passed in credential. Probably because a JWT proof type was present, but now is not available anymore' ) } const decoded = CredentialMapper.decodeVerifiableCredential(original) @@ -490,7 +491,7 @@ export class CredentialMapper { static toUniformPresentation( presentation: OriginalVerifiablePresentation, - opts?: { maxTimeSkewInMS?: number; addContextIfMissing?: boolean }, + opts?: { maxTimeSkewInMS?: number; addContextIfMissing?: boolean } ): IVerifiablePresentation { if (CredentialMapper.isSdJwtDecodedCredential(presentation)) { throw new Error('Converting SD-JWT VC to uniform VP is not supported.') @@ -500,7 +501,7 @@ export class CredentialMapper { const original = typeof presentation !== 'string' && CredentialMapper.hasJWTProofType(presentation) ? proof?.jwt : presentation if (!original) { throw Error( - 'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof', + 'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof' ) } const decoded = CredentialMapper.decodeVerifiablePresentation(original) @@ -517,7 +518,7 @@ export class CredentialMapper { } uniformPresentation.verifiableCredential = uniformPresentation.verifiableCredential?.map((vc) => - CredentialMapper.toUniformCredential(vc, opts), + CredentialMapper.toUniformCredential(vc, opts) ) as IVerifiableCredential[] // We cast it because we IPresentation needs a VC. The internal Credential doesn't have the required Proof anymore (that is intended) return uniformPresentation } @@ -526,14 +527,14 @@ export class CredentialMapper { jwt: string, opts?: { maxTimeSkewInMS?: number - }, + } ): IVerifiableCredential { return CredentialMapper.jwtDecodedCredentialToUniformCredential(jwt_decode(jwt), opts) } static jwtDecodedCredentialToUniformCredential( decoded: JwtDecodedVerifiableCredential, - opts?: { maxTimeSkewInMS?: number }, + opts?: { maxTimeSkewInMS?: number } ): IVerifiableCredential { const { exp, nbf, iss, vc, sub, jti, ...rest } = decoded const credential: IVerifiableCredential = { @@ -685,7 +686,7 @@ export class CredentialMapper { } static toCompactJWT( - jwtDocument: W3CVerifiableCredential | JwtDecodedVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiablePresentation | string, + jwtDocument: W3CVerifiableCredential | JwtDecodedVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiablePresentation | string ): string { if (!jwtDocument || CredentialMapper.detectDocumentType(jwtDocument) !== DocumentFormat.JWT) { throw Error('Cannot convert non JWT credential to JWT') @@ -713,7 +714,7 @@ export class CredentialMapper { | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation - | SdJwtDecodedVerifiableCredential, + | SdJwtDecodedVerifiableCredential ): DocumentFormat { if (this.isJsonLdAsString(document)) { return DocumentFormat.JSONLD @@ -735,7 +736,7 @@ export class CredentialMapper { } private static hasJWTProofType( - document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation, + document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation ): boolean { if (typeof document === 'string') { return false @@ -744,7 +745,7 @@ export class CredentialMapper { } private static getFirstProof( - document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation, + document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation ): IProof | undefined { if (!document || typeof document === 'string') { return undefined @@ -752,4 +753,20 @@ export class CredentialMapper { const proofs = 'vc' in document ? document.vc.proof : 'vp' in document ? document.vp.proof : (document).proof return Array.isArray(proofs) ? proofs[0] : proofs } + + static issuerCorrelationIdFromIssuerType(issuer: IssuerType): string { + if (issuer === undefined) { + throw new Error('Issuer type us undefined') + } else if (typeof issuer === 'string') { + return issuer + } else if (typeof issuer === 'object') { + if ('id' in issuer) { + return issuer.id + } else { + throw new Error('Encountered an invalid issuer object: missing id property') + } + } else { + throw new Error('Invalid issuer type') + } + } } diff --git a/packages/uni-resolver-registrar-api/__tests__/agent.ts b/packages/uni-resolver-registrar-api/__tests__/agent.ts index 473c5c0d7..732d4020d 100644 --- a/packages/uni-resolver-registrar-api/__tests__/agent.ts +++ b/packages/uni-resolver-registrar-api/__tests__/agent.ts @@ -79,7 +79,7 @@ export const didProviders = { const dbConnection = getDbConnection(DB_CONNECTION_NAME) const privateKeyStore: PrivateKeyStore = new PrivateKeyStore(dbConnection, new SecretBox(DB_ENCRYPTION_KEY)) -const agent = createAgent({ +const agent = createAgent({ plugins: [ new DataStore(dbConnection), new DataStoreORM(dbConnection), diff --git a/packages/uni-resolver-registrar-api/src/types.ts b/packages/uni-resolver-registrar-api/src/types.ts index 78c4f837c..3e8d3fd0f 100644 --- a/packages/uni-resolver-registrar-api/src/types.ts +++ b/packages/uni-resolver-registrar-api/src/types.ts @@ -1,9 +1,9 @@ import { DIDDocument } from '@sphereon/did-uni-client' import { GenericAuthArgs, ISingleEndpointOpts } from '@sphereon/ssi-express-support' -import { IAgentContext, IDataStore, IDataStoreORM, IDIDManager, IKeyManager, IResolver } from '@veramo/core' +import { IAgentContext, IDataStoreORM, IDIDManager, IKeyManager, IResolver } from '@veramo/core' import { VerificationMethod } from 'did-resolver' -export type IRequiredPlugins = IDataStore & IDataStoreORM & IDIDManager & IKeyManager & IResolver +export type IRequiredPlugins = IDataStoreORM & IDIDManager & IKeyManager & IResolver export type IRequiredContext = IAgentContext export interface DidRegistrationCreateRequest { diff --git a/packages/vc-status-list-issuer-drivers/src/types.ts b/packages/vc-status-list-issuer-drivers/src/types.ts index ebf576b77..1586f6896 100644 --- a/packages/vc-status-list-issuer-drivers/src/types.ts +++ b/packages/vc-status-list-issuer-drivers/src/types.ts @@ -12,7 +12,6 @@ import { ICredentialIssuer, ICredentialPlugin, ICredentialVerifier, - IDataStore, IDataStoreORM, IDIDManager, IKeyManager, @@ -20,14 +19,7 @@ import { } from '@veramo/core' import { DriverOptions } from './drivers' -export type IRequiredPlugins = IDataStore & - IDataStoreORM & - IDIDManager & - IKeyManager & - ICredentialIssuer & - ICredentialVerifier & - ICredentialPlugin & - IResolver +export type IRequiredPlugins = IDataStoreORM & IDIDManager & IKeyManager & ICredentialIssuer & ICredentialVerifier & ICredentialPlugin & IResolver export type IRequiredContext = IAgentContext export interface Driver { diff --git a/packages/vc-status-list-issuer-rest-api/__tests__/agent.ts b/packages/vc-status-list-issuer-rest-api/__tests__/agent.ts index 939645e77..d319483d9 100644 --- a/packages/vc-status-list-issuer-rest-api/__tests__/agent.ts +++ b/packages/vc-status-list-issuer-rest-api/__tests__/agent.ts @@ -66,7 +66,7 @@ const dbConnection = DataSources.singleInstance() const privateKeyStore: PrivateKeyStore = new PrivateKeyStore(dbConnection, new SecretBox(DB_ENCRYPTION_KEY)) const agent: TAgent = createAgent< - IDIDManager & IKeyManager & IDataStore & IDataStoreORM & IResolver & ICredentialHandlerLDLocal & ICredentialPlugin + IDIDManager & IKeyManager & IDataStoreORM & IResolver & ICredentialHandlerLDLocal & ICredentialPlugin >({ plugins: [ new DataStore(dbConnection), diff --git a/packages/w3c-vc-api/__tests__/agent.ts b/packages/w3c-vc-api/__tests__/agent.ts index 3c76c3d3a..ff7713a03 100644 --- a/packages/w3c-vc-api/__tests__/agent.ts +++ b/packages/w3c-vc-api/__tests__/agent.ts @@ -101,15 +101,7 @@ const dbConnection = DataSources.singleInstance() const privateKeyStore: PrivateKeyStore = new PrivateKeyStore(dbConnection, new SecretBox(DB_ENCRYPTION_KEY)) const agent = createAgent< - IDIDManager & - IKeyManager & - IDataStore & - IDataStoreORM & - IResolver & - IPresentationExchange & - ICredentialVerifier & - ICredentialHandlerLDLocal & - ICredentialPlugin + IDIDManager & IKeyManager & IDataStoreORM & IResolver & IPresentationExchange & ICredentialVerifier & ICredentialHandlerLDLocal & ICredentialPlugin >({ plugins: [ new DataStore(dbConnection), diff --git a/packages/w3c-vc-api/package.json b/packages/w3c-vc-api/package.json index 24b8d253d..2f202b61d 100644 --- a/packages/w3c-vc-api/package.json +++ b/packages/w3c-vc-api/package.json @@ -16,6 +16,7 @@ "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-sdk.kv-store-temp": "workspace:*", "@sphereon/ssi-sdk.presentation-exchange": "workspace:*", + "@sphereon/ssi-sdk.credential-store": "workspace:*", "@sphereon/ssi-types": "workspace:*", "@veramo/core": "4.2.0", "@veramo/credential-w3c": "4.2.0", diff --git a/packages/w3c-vc-api/src/api-functions.ts b/packages/w3c-vc-api/src/api-functions.ts index 229db6467..b44e095a2 100644 --- a/packages/w3c-vc-api/src/api-functions.ts +++ b/packages/w3c-vc-api/src/api-functions.ts @@ -1,4 +1,3 @@ -import { getCredentialByIdOrHash } from '@sphereon/ssi-sdk.core' import { checkAuth, ISingleEndpointOpts, sendErrorResponse } from '@sphereon/ssi-express-support' import { CredentialPayload } from '@veramo/core' import { ProofFormat } from '@veramo/core/src/types/ICredentialIssuer' @@ -7,6 +6,8 @@ import { Request, Response, Router } from 'express' import { v4 } from 'uuid' import { IIssueCredentialEndpointOpts, IRequiredContext, IVCAPIIssueOpts, IVerifyCredentialEndpointOpts } from './types' import Debug from 'debug' +import { DocumentType, FindDigitalCredentialArgs } from '@sphereon/ssi-sdk.credential-store' +import { CredentialRole } from '@sphereon/ssi-sdk.data-store' const debug = Debug('sphereon:ssi-sdk:w3c-vc-api') export function issueCredentialEndpoint(router: Router, context: IRequiredContext, opts?: IIssueCredentialEndpointOpts) { if (opts?.enabled === false) { @@ -56,9 +57,25 @@ export function getCredentialsEndpoint(router: Router, context: IRequiredContext const path = opts?.path ?? '/credentials' router.get(path, checkAuth(opts?.endpoint), async (request: Request, response: Response) => { try { - const uniqueVCs = await context.agent.dataStoreORMGetVerifiableCredentials() + const credentialRole = (request.query.credentialRole as CredentialRole) || CredentialRole.HOLDER + if (!Object.values(CredentialRole).includes(credentialRole)) { + return sendErrorResponse(response, 400, `Invalid credentialRole: ${credentialRole}`) + } + + const documentType = (request.query.documentType as DocumentType) || DocumentType.VC + if (!Object.values(DocumentType).includes(documentType)) { + return sendErrorResponse(response, 400, `Invalid documentType: ${documentType}`) + } + + const filter: FindDigitalCredentialArgs = [ + { + documentType: documentType, + credentialRole: credentialRole, + }, + ] + const uniqueVCs = await context.agent.crsGetUniqueCredentials({ filter }) response.statusCode = 202 - return response.send(uniqueVCs.map((uVC) => uVC.verifiableCredential)) + return response.send(uniqueVCs.map((uVC) => uVC.uniformVerifiableCredential)) } catch (e) { return sendErrorResponse(response, 500, e.message as string, e) } @@ -77,12 +94,20 @@ export function getCredentialEndpoint(router: Router, context: IRequiredContext, if (!id) { return sendErrorResponse(response, 400, 'no id provided') } - let vcInfo = await getCredentialByIdOrHash(context, id) - if (!vcInfo.vc) { - return sendErrorResponse(response, 404, `id ${id} not found`) + const credentialRole = (request.query.credentialRole as CredentialRole) || CredentialRole.HOLDER + if (!Object.values(CredentialRole).includes(credentialRole)) { + return sendErrorResponse(response, 400, `Invalid credentialRole: ${credentialRole}`) + } + + const vcInfo = await context.agent.crsGetUniqueCredentialByIdOrHash({ + credentialRole: credentialRole, + idOrHash: id, + }) + if (!vcInfo) { + return sendErrorResponse(response, 403, `id ${id} not found`) } response.statusCode = 200 - return response.send(vcInfo.vc) + return response.send(vcInfo.uniformVerifiableCredential) } catch (e) { return sendErrorResponse(response, 500, e.message as string, e) } @@ -129,12 +154,23 @@ export function deleteCredentialEndpoint(router: Router, context: IRequiredConte if (!id) { return sendErrorResponse(response, 400, 'no id provided') } - let vcInfo = await getCredentialByIdOrHash(context, id) - if (!vcInfo.vc || !vcInfo.hash) { + const credentialRole = request.query.credentialRole as CredentialRole + if (credentialRole === undefined) { + return sendErrorResponse(response, 400, 'credentialRole query parameter is missing') + } + if (!Object.values(CredentialRole).includes(credentialRole)) { + return sendErrorResponse(response, 400, `Invalid credentialRole: ${credentialRole}`) + } + + const vcInfo = await context.agent.crsGetUniqueCredentialByIdOrHash({ + credentialRole: credentialRole, + idOrHash: id, + }) + if (!vcInfo) { return sendErrorResponse(response, 404, `id ${id} not found`) } - const success = context.agent.dataStoreDeleteVerifiableCredential({ hash: vcInfo.hash }) - if (!success) { + const success = await context.agent.crsDeleteCredentials({ filter: [{ hash: vcInfo.hash }] }) + if (success === 0) { return sendErrorResponse(response, 400, `Could not delete Verifiable Credential with id ${id}`) } response.statusCode = 200 diff --git a/packages/w3c-vc-api/src/types.ts b/packages/w3c-vc-api/src/types.ts index 7ec06285d..9eb37c967 100644 --- a/packages/w3c-vc-api/src/types.ts +++ b/packages/w3c-vc-api/src/types.ts @@ -4,20 +4,20 @@ import { ICredentialIssuer, ICredentialPlugin, ICredentialVerifier, - IDataStore, IDataStoreORM, IDIDManager, IKeyManager, IResolver, } from '@veramo/core' import { ProofFormat } from '@veramo/core/src/types/ICredentialIssuer' +import { ICredentialStore } from '@sphereon/ssi-sdk.credential-store' -export type IRequiredPlugins = IDataStore & - IDataStoreORM & +export type IRequiredPlugins = IDataStoreORM & IDIDManager & IKeyManager & ICredentialIssuer & ICredentialVerifier & + ICredentialStore & ICredentialPlugin & IResolver export type IRequiredContext = IAgentContext diff --git a/packages/w3c-vc-api/tsconfig.json b/packages/w3c-vc-api/tsconfig.json index c8b697234..9f5c1dce8 100644 --- a/packages/w3c-vc-api/tsconfig.json +++ b/packages/w3c-vc-api/tsconfig.json @@ -22,6 +22,9 @@ { "path": "../presentation-exchange" }, + { + "path": "../credential-store" + }, { "path": "../ssi-types" }, diff --git a/packages/wellknown-did-issuer/agent.yml b/packages/wellknown-did-issuer/agent.yml index 345468994..4c7659c39 100644 --- a/packages/wellknown-did-issuer/agent.yml +++ b/packages/wellknown-did-issuer/agent.yml @@ -11,6 +11,15 @@ constants: - registerCredentialIssuance - removeCredentialIssuance - saveDidConfigurationResource + - crmAddCredential + - crmUpdateCredentialState + - crmGetCredential + - crmGetCredentials + - crmStoreCredential + - crmDeleteCredential + - crmDeleteCredentials + - crmGetCredentialsByClaims + - crmGetCredentialsByClaimsCount dbConnection: $require: typeorm?t=function#createConnection @@ -21,9 +30,11 @@ dbConnection: migrationsRun: true migrations: $require: './packages/ssi-sdk-core/dist?t=function#flattenMigrations' + $args: - migrations: - $require: '@veramo/data-store?t=object#migrations' + - $require: './packages/data-store?t=object#DataStoreDigitalCredentialMigrations' - $require: './packages/wellknown-did-issuer/dist?t=object#WellknownDidIssuerMigrations' entities: $require: './packages/ssi-sdk-core/dist?t=function#flattenArray' @@ -31,6 +42,8 @@ dbConnection: - items: - $require: '@veramo/data-store?t=object#Entities' - $require: './packages/wellknown-did-issuer/dist?t=object#WellknownDidIssuerEntities' + - $require: './packages/data-store?t=object#DataStoreDigitalCredentialEntities' + server: baseUrl: @@ -95,6 +108,15 @@ didManager: - store: $require: '@veramo/data-store#DIDStore' +# Credential Manager +credentialStore: + $require: ./packages/credential-store/dist#CredentialStore + $args: + - store: + $require: './packages/data-store/dist#DigitalCredentialStore' + $args: + - $ref: /dbConnection + # Agent agent: $require: '@veramo/core#Agent' @@ -102,6 +124,7 @@ agent: - schemaValidation: false plugins: - $ref: /didManager + - $ref: /credentialStore - $require: '@veramo/data-store#DataStore' $args: - $ref: /dbConnection diff --git a/packages/wellknown-did-issuer/package.json b/packages/wellknown-did-issuer/package.json index 67209947a..bf4e94328 100644 --- a/packages/wellknown-did-issuer/package.json +++ b/packages/wellknown-did-issuer/package.json @@ -16,6 +16,7 @@ "dependencies": { "@sphereon/ssi-types": "workspace:*", "@sphereon/wellknown-dids-client": "^0.1.3", + "@sphereon/ssi-sdk.credential-store": "workspace:*", "@veramo/data-store": "4.2.0", "@veramo/utils": "4.2.0", "debug": "^4.3.5", diff --git a/packages/wellknown-did-issuer/src/agent/WellKnownDidIssuer.ts b/packages/wellknown-did-issuer/src/agent/WellKnownDidIssuer.ts index 7dd816261..734a9f085 100644 --- a/packages/wellknown-did-issuer/src/agent/WellKnownDidIssuer.ts +++ b/packages/wellknown-did-issuer/src/agent/WellKnownDidIssuer.ts @@ -1,4 +1,4 @@ -import { parseDid } from '@sphereon/ssi-types' +import { CredentialMapper, parseDid } from '@sphereon/ssi-types' import { DomainLinkageCredential, IDidConfigurationResource, @@ -12,18 +12,19 @@ import { normalizeCredential } from 'did-jwt-vc' import { Service } from 'did-resolver/lib/resolver' import { Connection } from 'typeorm' import { v4 as uuidv4 } from 'uuid' -import { DidConfigurationResourceEntity, createCredentialEntity, didConfigurationResourceFrom } from '../entities/DidConfigurationResourceEntity' +import { createCredentialEntity, DidConfigurationResourceEntity, didConfigurationResourceFrom } from '../entities/DidConfigurationResourceEntity' +import { CredentialCorrelationType, CredentialRole, DigitalCredential } from '@sphereon/ssi-sdk.credential-store' import { IAddLinkedDomainsServiceArgs, - IWellKnownDidIssuer, - IWellKnownDidIssuerOptionsArgs, - IRegisterIssueCredentialArgs, - IRemoveCredentialIssuanceArgs, - RequiredContext, + IGetDidConfigurationResourceArgs, IIssueDidConfigurationResourceArgs, IIssueDomainLinkageCredentialArgs, - IGetDidConfigurationResourceArgs, + IRegisterIssueCredentialArgs, + IRemoveCredentialIssuanceArgs, ISaveDidConfigurationResourceArgs, + IWellKnownDidIssuer, + IWellKnownDidIssuerOptionsArgs, + RequiredContext, } from '../types/IWellKnownDidIssuer' import { schema } from '../index' @@ -220,8 +221,18 @@ export class WellKnownDidIssuer implements IAgentPlugin { return this.credentialIssuances[callbackName] } - private async saveDomainLinkageCredential(credential: DomainLinkageCredential, context: RequiredContext): Promise { - return context.agent.dataStoreSaveVerifiableCredential({ verifiableCredential: this.normalizeCredential(credential) }) + private async saveDomainLinkageCredential(credential: DomainLinkageCredential, context: RequiredContext): Promise { + const vc = this.normalizeCredential(credential) + return context.agent.crsAddCredential({ + credential: { + rawDocument: JSON.stringify(vc), + credentialRole: CredentialRole.ISSUER, + issuerCorrelationId: CredentialMapper.issuerCorrelationIdFromIssuerType(vc.issuer), + issuerCorrelationType: CredentialCorrelationType.DID, + subjectCorrelationId: CredentialMapper.issuerCorrelationIdFromIssuerType(vc.issuer), // FIXME get separate did for subject + subjectCorrelationType: CredentialCorrelationType.DID, + }, + }) } private normalizeCredential(credential: DomainLinkageCredential): VerifiableCredential { diff --git a/packages/wellknown-did-issuer/src/types/IWellKnownDidIssuer.ts b/packages/wellknown-did-issuer/src/types/IWellKnownDidIssuer.ts index 0ad8fc50c..349a1be6f 100644 --- a/packages/wellknown-did-issuer/src/types/IWellKnownDidIssuer.ts +++ b/packages/wellknown-did-issuer/src/types/IWellKnownDidIssuer.ts @@ -5,6 +5,7 @@ import { IssuanceCallback, } from '@sphereon/wellknown-dids-client' import { IAgentContext, IPluginMethodMap, IDIDManager } from '@veramo/core' +import { ICredentialStore } from '@sphereon/ssi-sdk.credential-store' export interface IWellKnownDidIssuer extends IPluginMethodMap { addLinkedDomainsService(args: IAddLinkedDomainsServiceArgs, context: RequiredContext): Promise @@ -61,4 +62,4 @@ export interface ISaveDidConfigurationResourceArgs { didConfigurationResource: IDidConfigurationResource } -export type RequiredContext = IAgentContext +export type RequiredContext = IAgentContext diff --git a/packages/wellknown-did-issuer/tsconfig.json b/packages/wellknown-did-issuer/tsconfig.json index b965f5884..94bf3db3e 100644 --- a/packages/wellknown-did-issuer/tsconfig.json +++ b/packages/wellknown-did-issuer/tsconfig.json @@ -15,6 +15,9 @@ }, { "path": "../agent-config" + }, + { + "path": "../credential-store" } ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0bff8dee..54e6c692b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -363,6 +363,46 @@ importers: specifier: ^0.3.20 version: 0.3.20(pg@8.12.0)(sqlite3@5.1.7)(ts-node@10.9.2) + packages/credential-store: + dependencies: + '@sphereon/pex': + specifier: ^4.0.1 + version: 4.0.1 + '@sphereon/pex-models': + specifier: ^2.2.4 + version: 2.2.4 + '@sphereon/ssi-sdk.data-store': + specifier: workspace:* + version: link:../data-store + cross-fetch: + specifier: ^3.1.8 + version: 3.1.8 + debug: + specifier: ^4.3.5 + version: 4.3.5 + typeorm: + specifier: ^0.3.20 + version: 0.3.20(pg@8.12.0)(sqlite3@5.1.7)(ts-node@10.9.2) + uuid: + specifier: ^10.0.0 + version: 10.0.0 + devDependencies: + '@sphereon/ssi-sdk.agent-config': + specifier: workspace:* + version: link:../agent-config + '@sphereon/ssi-types': + specifier: workspace:* + version: link:../ssi-types + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 + '@veramo/remote-client': + specifier: 4.2.0 + version: 4.2.0 + '@veramo/remote-server': + specifier: 4.2.0 + version: 4.2.0(express@4.19.2) + packages/data-store: dependencies: '@sphereon/pex': @@ -778,6 +818,9 @@ importers: '@sphereon/ssi-sdk.agent-config': specifier: workspace:* version: link:../agent-config + '@sphereon/ssi-sdk.credential-store': + specifier: workspace:* + version: link:../credential-store '@types/express': specifier: ^4.17.21 version: 4.17.21 @@ -847,6 +890,9 @@ importers: '@sphereon/ssi-sdk.core': specifier: workspace:* version: link:../ssi-sdk-core + '@sphereon/ssi-sdk.credential-store': + specifier: workspace:* + version: link:../credential-store '@sphereon/ssi-sdk.data-store': specifier: workspace:* version: link:../data-store @@ -1391,6 +1437,9 @@ importers: '@sphereon/ssi-sdk-ext.did-utils': specifier: 0.23.1-next.5 version: 0.23.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@sphereon/ssi-sdk.credential-store': + specifier: workspace:* + version: link:../credential-store '@sphereon/ssi-sdk.data-store': specifier: workspace:* version: link:../data-store @@ -1754,6 +1803,9 @@ importers: '@sphereon/ssi-sdk.core': specifier: workspace:* version: link:../ssi-sdk-core + '@sphereon/ssi-sdk.credential-store': + specifier: workspace:* + version: link:../credential-store '@sphereon/ssi-sdk.data-store': specifier: workspace:* version: link:../data-store @@ -2873,6 +2925,9 @@ importers: '@sphereon/ssi-sdk.core': specifier: workspace:* version: link:../ssi-sdk-core + '@sphereon/ssi-sdk.credential-store': + specifier: workspace:* + version: link:../credential-store '@sphereon/ssi-sdk.kv-store-temp': specifier: workspace:* version: link:../kv-store @@ -3219,6 +3274,9 @@ importers: packages/wellknown-did-issuer: dependencies: + '@sphereon/ssi-sdk.credential-store': + specifier: workspace:* + version: link:../credential-store '@sphereon/ssi-types': specifier: workspace:* version: link:../ssi-types @@ -14993,7 +15051,7 @@ packages: resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - chalk: 4.1.0 + chalk: 4.1.2 diff-sequences: 29.6.3 jest-get-type: 29.6.3 pretty-format: 29.7.0 @@ -17162,7 +17220,7 @@ packages: array-differ: 3.0.0 array-union: 2.1.0 arrify: 2.0.1 - minimatch: 3.0.5 + minimatch: 3.1.2 dev: true /mute-stream@0.0.8: @@ -17675,7 +17733,7 @@ packages: '@yarnpkg/parsers': 3.0.0-rc.46 '@zkochan/js-yaml': 0.0.7 axios: 1.7.2 - chalk: 4.1.0 + chalk: 4.1.2 cli-cursor: 3.1.0 cli-spinners: 2.6.1 cliui: 8.0.1 @@ -17905,7 +17963,7 @@ packages: engines: {node: '>=10'} dependencies: bl: 4.1.0 - chalk: 4.1.0 + chalk: 4.1.2 cli-cursor: 3.1.0 cli-spinners: 2.6.1 is-interactive: 1.0.0