From 312698e42d18010e1c86ef14db7b96005043294f Mon Sep 17 00:00:00 2001 From: sksadjad Date: Thu, 22 Feb 2024 16:31:25 +0100 Subject: [PATCH] feat: added tenant aware credential store --- packages/data-store/package.json | 1 + .../uniformCredential.entities.test.ts | 60 +++++ .../__tests__/uniformCredential.store.test.ts | 214 ++++++++++++++++++ .../src/credential/AbstractCredentialStore.ts | 16 -- .../src/credential/CredentialStore.ts | 48 ---- .../UniformCredentialEntity.ts} | 24 +- packages/data-store/src/index.ts | 9 + .../generic/6-CreateUniformCredential.ts | 66 ++++++ .../src/migrations/generic/index.ts | 3 + packages/data-store/src/migrations/index.ts | 1 + .../1708525189001-CreateUniformCredential.ts | 43 ++++ .../1708525189002-CreateUniformCredential.ts | 33 +++ .../credential/IAbstractCredentialStore.ts | 21 -- .../IAbstractUniformCredentialStore.ts | 29 +++ .../uniformCredential.ts} | 8 +- .../AbstractUniformCredentialStore.ts | 16 ++ .../UniformCredentialStore.ts | 97 ++++++++ .../src/utils/credential/MappingUtils.ts | 30 --- .../utils/uniformCredential/MappingUtils.ts | 75 ++++++ pnpm-lock.yaml | 3 + 20 files changed, 671 insertions(+), 126 deletions(-) create mode 100644 packages/data-store/src/__tests__/uniformCredential.entities.test.ts create mode 100644 packages/data-store/src/__tests__/uniformCredential.store.test.ts delete mode 100644 packages/data-store/src/credential/AbstractCredentialStore.ts delete mode 100644 packages/data-store/src/credential/CredentialStore.ts rename packages/data-store/src/entities/{credential/CredentialEntity.ts => uniformCredential/UniformCredentialEntity.ts} (73%) create mode 100644 packages/data-store/src/migrations/generic/6-CreateUniformCredential.ts create mode 100644 packages/data-store/src/migrations/postgres/1708525189001-CreateUniformCredential.ts create mode 100644 packages/data-store/src/migrations/sqlite/1708525189002-CreateUniformCredential.ts delete mode 100644 packages/data-store/src/types/credential/IAbstractCredentialStore.ts create mode 100644 packages/data-store/src/types/uniformCredential/IAbstractUniformCredentialStore.ts rename packages/data-store/src/types/{credential/credential.ts => uniformCredential/uniformCredential.ts} (81%) create mode 100644 packages/data-store/src/uniformCredential/AbstractUniformCredentialStore.ts create mode 100644 packages/data-store/src/uniformCredential/UniformCredentialStore.ts delete mode 100644 packages/data-store/src/utils/credential/MappingUtils.ts create mode 100644 packages/data-store/src/utils/uniformCredential/MappingUtils.ts diff --git a/packages/data-store/package.json b/packages/data-store/package.json index cb6efa771..268483a5e 100644 --- a/packages/data-store/package.json +++ b/packages/data-store/package.json @@ -16,6 +16,7 @@ "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-types": "workspace:*", "@veramo/core": "4.2.0", + "@veramo/utils": "4.2.0", "class-validator": "^0.14.0", "debug": "^4.3.4", "typeorm": "^0.3.12" diff --git a/packages/data-store/src/__tests__/uniformCredential.entities.test.ts b/packages/data-store/src/__tests__/uniformCredential.entities.test.ts new file mode 100644 index 000000000..0791550d0 --- /dev/null +++ b/packages/data-store/src/__tests__/uniformCredential.entities.test.ts @@ -0,0 +1,60 @@ +import { DataSource } from 'typeorm' +import { DataStoreUniformCredentialEntities, uniformCredentialEntityFromAddArgs } from '../index' +import { DataStoreUniformCredentialMigrations } from '../migrations' +import { UniformCredentialEntity } from '../entities/uniformCredential/UniformCredentialEntity' +import { CredentialCorrelationType, CredentialDocumentFormat, CredentialTypeEnum } from '../types/uniformCredential/uniformCredential' +import { computeEntryHash } from '@veramo/utils' +import { AddUniformCredentialArgs } from '../../dist/types/credential/IAbstractCredentialStore' + +describe('Database entities tests', (): void => { + let dbConnection: DataSource + + beforeEach(async (): Promise => { + dbConnection = await new DataSource({ + type: 'sqlite', + database: ':memory:', + //logging: 'all', + migrationsRun: false, + migrations: DataStoreUniformCredentialMigrations, + synchronize: false, + entities: [...DataStoreUniformCredentialEntities], + }).initialize() + await dbConnection.runMigrations() + expect(await dbConnection.showMigrations()).toBeFalsy() + }) + + afterEach(async (): Promise => { + await (await dbConnection).destroy() + }) + + it('should save uniform credential to database', async (): Promise => { + console.log(`going to save the credential...`) + const rawCredential: string = + 'eyJraWQiOiJkaWQ6a2V5Ono2TWtyaGt5M3B1c20yNk1laUZhWFUzbjJuZWtyYW13RlVtZ0dyZUdHa0RWNnpRaiN6Nk1rcmhreTNwdXNtMjZNZWlGYVhVM24ybmVrcmFtd0ZVbWdHcmVHR2tEVjZ6UWoiLCJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vc3BoZXJlb24tb3BlbnNvdXJjZS5naXRodWIuaW8vc3NpLW1vYmlsZS13YWxsZXQvY29udGV4dC9zcGhlcmVvbi13YWxsZXQtaWRlbnRpdHktdjEuanNvbmxkIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJTcGhlcmVvbldhbGxldElkZW50aXR5Q3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaXJzdE5hbWUiOiJTIiwibGFzdE5hbWUiOiJLIiwiZW1haWxBZGRyZXNzIjoic0BrIn19LCJzdWIiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJqdGkiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJuYmYiOjE3MDg0NDA4MDgsImlzcyI6ImRpZDprZXk6ejZNa3Joa3kzcHVzbTI2TWVpRmFYVTNuMm5la3JhbXdGVW1nR3JlR0drRFY2elFqIn0.G0M84XVAxSmzGY-NQuB9NBofNrINSn6lvxW6761Vlq6ypvYgtc2xNdpiRmw8ryVNfnpzrr4Z5cB1RlrC05rJAw' + const uniformCredential: AddUniformCredentialArgs = { + credentialType: CredentialTypeEnum.VC, + documentFormat: CredentialDocumentFormat.JWT, + raw: rawCredential, + issuerCorrelationType: CredentialCorrelationType.DID, + subjectCorrelationType: CredentialCorrelationType.DID, + issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', + } + + const uniformCredentialEntity: UniformCredentialEntity = uniformCredentialEntityFromAddArgs(uniformCredential) + const fromDb: UniformCredentialEntity = await dbConnection.getRepository(UniformCredentialEntity).save(uniformCredentialEntity) + console.log(`saved uniformCredential: ${JSON.stringify(fromDb, null, 2)}`) + expect(fromDb).toBeDefined() + expect(fromDb?.id).not.toBeNull() + expect(fromDb?.credentialType).toEqual(CredentialTypeEnum.VC) + expect(fromDb?.documentFormat).toEqual(CredentialDocumentFormat.JWT) + expect(fromDb?.raw).toEqual(rawCredential) + expect(fromDb?.hash).toEqual(computeEntryHash(rawCredential)) + expect(fromDb?.issuerCorrelationType).toEqual(CredentialCorrelationType.DID) + expect(fromDb?.subjectCorrelationType).toEqual(CredentialCorrelationType.DID) + expect(fromDb?.issuerCorrelationId).toEqual('did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj') + expect(fromDb?.subjectCorrelationId).toEqual('did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj') + expect(fromDb?.tenantId).toEqual('urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj') + }) +}) diff --git a/packages/data-store/src/__tests__/uniformCredential.store.test.ts b/packages/data-store/src/__tests__/uniformCredential.store.test.ts new file mode 100644 index 000000000..b956b708f --- /dev/null +++ b/packages/data-store/src/__tests__/uniformCredential.store.test.ts @@ -0,0 +1,214 @@ +import { DataSource } from 'typeorm' +import { DataStoreUniformCredentialMigrations } from '../migrations' +import { DataStoreUniformCredentialEntities, UniformCredentialEntity } from '../index' +import { UniformCredentialStore } from '../uniformCredential/UniformCredentialStore' +import { + CredentialCorrelationType, + CredentialDocumentFormat, + CredentialStateType, + CredentialTypeEnum, + UniformCredential, +} from '../types/uniformCredential/uniformCredential' +import { AddUniformCredentialArgs, GetUniformCredentialsArgs } from '../types/uniformCredential/IAbstractUniformCredentialStore' +import { IVerifiablePresentation } from '@sphereon/ssi-types' + +describe('Database entities tests', (): void => { + let dbConnection: DataSource + let uniformCredentialStore: UniformCredentialStore + + beforeEach(async (): Promise => { + dbConnection = await new DataSource({ + type: 'sqlite', + database: ':memory:', + //logging: 'all', + migrationsRun: false, + migrations: DataStoreUniformCredentialMigrations, + synchronize: false, + entities: DataStoreUniformCredentialEntities, + }).initialize() + await dbConnection.runMigrations() + expect(await dbConnection.showMigrations()).toBeFalsy() + uniformCredentialStore = new UniformCredentialStore(dbConnection) + }) + + afterEach(async (): Promise => { + await (await dbConnection).destroy() + }) + + it('should store uniform credential', async (): Promise => { + const rawCredential: string = + 'eyJraWQiOiJkaWQ6a2V5Ono2TWtyaGt5M3B1c20yNk1laUZhWFUzbjJuZWtyYW13RlVtZ0dyZUdHa0RWNnpRaiN6Nk1rcmhreTNwdXNtMjZNZWlGYVhVM24ybmVrcmFtd0ZVbWdHcmVHR2tEVjZ6UWoiLCJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vc3BoZXJlb24tb3BlbnNvdXJjZS5naXRodWIuaW8vc3NpLW1vYmlsZS13YWxsZXQvY29udGV4dC9zcGhlcmVvbi13YWxsZXQtaWRlbnRpdHktdjEuanNvbmxkIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJTcGhlcmVvbldhbGxldElkZW50aXR5Q3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaXJzdE5hbWUiOiJTIiwibGFzdE5hbWUiOiJLIiwiZW1haWxBZGRyZXNzIjoic0BrIn19LCJzdWIiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJqdGkiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJuYmYiOjE3MDg0NDA4MDgsImlzcyI6ImRpZDprZXk6ejZNa3Joa3kzcHVzbTI2TWVpRmFYVTNuMm5la3JhbXdGVW1nR3JlR0drRFY2elFqIn0.G0M84XVAxSmzGY-NQuB9NBofNrINSn6lvxW6761Vlq6ypvYgtc2xNdpiRmw8ryVNfnpzrr4Z5cB1RlrC05rJAw' + const uniformCredential: AddUniformCredentialArgs = { + credentialType: CredentialTypeEnum.VC, + documentFormat: CredentialDocumentFormat.JWT, + raw: rawCredential, + issuerCorrelationType: CredentialCorrelationType.DID, + subjectCorrelationType: CredentialCorrelationType.DID, + issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', + } + + const savedUniformCredential: UniformCredential = await uniformCredentialStore.addUniformCredential(uniformCredential) + expect(savedUniformCredential).toBeDefined() + }) + + it('should get all uniform credentials', async (): Promise => { + const addCredentialArgs1: AddUniformCredentialArgs = { + credentialType: CredentialTypeEnum.VC, + documentFormat: CredentialDocumentFormat.JWT, + raw: 'eyJraWQiOiJkaWQ6a2V5Ono2TWtyaGt5M3B1c20yNk1laUZhWFUzbjJuZWtyYW13RlVtZ0dyZUdHa0RWNnpRaiN6Nk1rcmhreTNwdXNtMjZNZWlGYVhVM24ybmVrcmFtd0ZVbWdHcmVHR2tEVjZ6UWoiLCJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vc3BoZXJlb24tb3BlbnNvdXJjZS5naXRodWIuaW8vc3NpLW1vYmlsZS13YWxsZXQvY29udGV4dC9zcGhlcmVvbi13YWxsZXQtaWRlbnRpdHktdjEuanNvbmxkIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJTcGhlcmVvbldhbGxldElkZW50aXR5Q3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaXJzdE5hbWUiOiJTIiwibGFzdE5hbWUiOiJLIiwiZW1haWxBZGRyZXNzIjoic0BrIn19LCJzdWIiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJqdGkiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJuYmYiOjE3MDg0NDA4MDgsImlzcyI6ImRpZDprZXk6ejZNa3Joa3kzcHVzbTI2TWVpRmFYVTNuMm5la3JhbXdGVW1nR3JlR0drRFY2elFqIn0.G0M84XVAxSmzGY-NQuB9NBofNrINSn6lvxW6761Vlq6ypvYgtc2xNdpiRmw8ryVNfnpzrr4Z5cB1RlrC05rJAw', + issuerCorrelationType: CredentialCorrelationType.DID, + subjectCorrelationType: CredentialCorrelationType.DID, + issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', + } + const addCredentialArgs2: AddUniformCredentialArgs = { + credentialType: CredentialTypeEnum.VC, + documentFormat: CredentialDocumentFormat.JWT, + raw: 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MDkyMTQxNzgsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJHdWVzdENyZWRlbnRpYWwiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiZmlyc3ROYW1lIjoiUyIsImxhc3ROYW1lIjoiSyIsIkUtbWFpbCI6IiIsInR5cGUiOiJTcGhlcmVvbiBHdWVzdCIsImlkIjoiZGlkOmp3azpleUpoYkdjaU9pSkZVekkxTmtzaUxDSjFjMlVpT2lKemFXY2lMQ0pyZEhraU9pSkZReUlzSW1OeWRpSTZJbk5sWTNBeU5UWnJNU0lzSW5naU9pSldjWGhIZVhWUk5WUTBXVEpzZGpKSFkybE9TaTFEYURCVWFGVm1kVk5RWm0wdFJYVlNZbGRNWlVOM0lpd2llU0k2SW01T1FWQnBiR0V5VDBRNGRXOXBXbk5LVm1aUmFrbDJTMUZUZWxBelFqVlBXbVZSYkVoQ1VUbHliVFFpZlEifX0sIkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJHdWVzdENyZWRlbnRpYWwiXSwiZXhwaXJhdGlvbkRhdGUiOiIyMDI0LTAyLTI5VDEzOjQyOjU4LjgzNVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaXJzdE5hbWUiOiJTIiwibGFzdE5hbWUiOiJLIiwiRS1tYWlsIjoiIiwidHlwZSI6IlNwaGVyZW9uIEd1ZXN0IiwiaWQiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOa3NpTENKMWMyVWlPaUp6YVdjaUxDSnJkSGtpT2lKRlF5SXNJbU55ZGlJNkluTmxZM0F5TlRack1TSXNJbmdpT2lKV2NYaEhlWFZSTlZRMFdUSnNkakpIWTJsT1NpMURhREJVYUZWbWRWTlFabTB0UlhWU1lsZE1aVU4zSWl3aWVTSTZJbTVPUVZCcGJHRXlUMFE0ZFc5cFduTktWbVpSYWtsMlMxRlRlbEF6UWpWUFdtVlJiRWhDVVRseWJUUWlmUSJ9LCJpc3N1ZXIiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOaUlzSW5WelpTSTZJbk5wWnlJc0ltdDBlU0k2SWtWRElpd2lZM0oySWpvaVVDMHlOVFlpTENKNElqb2lWRWN5U0RKNE1tUlhXRTR6ZFVOeFduQnhSakY1YzBGUVVWWkVTa1ZPWDBndFEwMTBZbWRxWWkxT1p5SXNJbmtpT2lJNVRUaE9lR1F3VUU0eU1rMDViRkJFZUdSd1JIQnZWRXg2TVRWM1pubGFTbk0yV21oTFNWVktNek00SW4wIiwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wMi0yMlQxMzo0Mjo1OC44MzVaIiwic3ViIjoiZGlkOmp3azpleUpoYkdjaU9pSkZVekkxTmtzaUxDSjFjMlVpT2lKemFXY2lMQ0pyZEhraU9pSkZReUlzSW1OeWRpSTZJbk5sWTNBeU5UWnJNU0lzSW5naU9pSldjWGhIZVhWUk5WUTBXVEpzZGpKSFkybE9TaTFEYURCVWFGVm1kVk5RWm0wdFJYVlNZbGRNWlVOM0lpd2llU0k2SW01T1FWQnBiR0V5VDBRNGRXOXBXbk5LVm1aUmFrbDJTMUZUZWxBelFqVlBXbVZSYkVoQ1VUbHliVFFpZlEiLCJuYmYiOjE3MDg2MDkzNzgsImlzcyI6ImRpZDpqd2s6ZXlKaGJHY2lPaUpGVXpJMU5pSXNJblZ6WlNJNkluTnBaeUlzSW10MGVTSTZJa1ZESWl3aVkzSjJJam9pVUMweU5UWWlMQ0o0SWpvaVZFY3lTREo0TW1SWFdFNHpkVU54V25CeFJqRjVjMEZRVVZaRVNrVk9YMGd0UTAxMFltZHFZaTFPWnlJc0lua2lPaUk1VFRoT2VHUXdVRTR5TWswNWJGQkVlR1J3UkhCdlZFeDZNVFYzWm5sYVNuTTJXbWhMU1ZWS016TTRJbjAifQ.GgLRWHO674wu6QF_xUGbCi_2zDD8jNf_xoWNvH5K605xvBoz6qKx0ndmxLeGQWWUA-4VuAkznf3ROcp9wpgbEw', + issuerCorrelationType: CredentialCorrelationType.DID, + subjectCorrelationType: CredentialCorrelationType.DID, + issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', + } + + const uniformCredential1: UniformCredential = await uniformCredentialStore.addUniformCredential(addCredentialArgs1) + expect(uniformCredential1).toBeDefined() + const uniformCredential2: UniformCredential = await uniformCredentialStore.addUniformCredential(addCredentialArgs2) + expect(uniformCredential2).toBeDefined() + + const result: Array = await uniformCredentialStore.getUniformCredentials() + expect(result.length).toEqual(2) + }) + + it('should get uniform credentials by filter', async (): Promise => { + const addCredentialArgs1: AddUniformCredentialArgs = { + credentialType: CredentialTypeEnum.VC, + documentFormat: CredentialDocumentFormat.JWT, + raw: 'eyJraWQiOiJkaWQ6a2V5Ono2TWtyaGt5M3B1c20yNk1laUZhWFUzbjJuZWtyYW13RlVtZ0dyZUdHa0RWNnpRaiN6Nk1rcmhreTNwdXNtMjZNZWlGYVhVM24ybmVrcmFtd0ZVbWdHcmVHR2tEVjZ6UWoiLCJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vc3BoZXJlb24tb3BlbnNvdXJjZS5naXRodWIuaW8vc3NpLW1vYmlsZS13YWxsZXQvY29udGV4dC9zcGhlcmVvbi13YWxsZXQtaWRlbnRpdHktdjEuanNvbmxkIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJTcGhlcmVvbldhbGxldElkZW50aXR5Q3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaXJzdE5hbWUiOiJTIiwibGFzdE5hbWUiOiJLIiwiZW1haWxBZGRyZXNzIjoic0BrIn19LCJzdWIiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJqdGkiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJuYmYiOjE3MDg0NDA4MDgsImlzcyI6ImRpZDprZXk6ejZNa3Joa3kzcHVzbTI2TWVpRmFYVTNuMm5la3JhbXdGVW1nR3JlR0drRFY2elFqIn0.G0M84XVAxSmzGY-NQuB9NBofNrINSn6lvxW6761Vlq6ypvYgtc2xNdpiRmw8ryVNfnpzrr4Z5cB1RlrC05rJAw', + issuerCorrelationType: CredentialCorrelationType.DID, + subjectCorrelationType: CredentialCorrelationType.DID, + issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', + } + + const sampleVP: IVerifiablePresentation = { + '@context': ['https://www.w3.org/2018/credentials/v1'], + type: ['VerifiablePresentation'], + verifiableCredential: [ + { + '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'], + id: 'https://example.com/credentials/1872', + type: ['VerifiableCredential', 'IDCardCredential'], + issuer: { + id: 'did:example:issuer', + }, + issuanceDate: '2010-01-01T19:23:24Z', + credentialSubject: { + given_name: 'Fredrik', + family_name: 'Strömberg', + birthdate: '1949-01-22', + }, + proof: { + type: 'Ed25519Signature2018', + created: '2021-03-19T15:30:15Z', + jws: 'eyJhb...IAoDA', + proofPurpose: 'assertionMethod', + verificationMethod: 'did:example:issuer#keys-1', + }, + }, + ], + id: 'ebc6f1c2', + holder: 'did:example:holder', + proof: { + type: 'Ed25519Signature2018', + created: '2021-03-19T15:30:15Z', + challenge: 'n-0S6_WzA2Mj', + domain: 'https://client.example.org/cb', + jws: 'eyJhb...JQdBw', + proofPurpose: 'authentication', + verificationMethod: 'did:example:holder#key-1', + }, + } + const addCredentialArgs2: AddUniformCredentialArgs = { + credentialType: CredentialTypeEnum.VP, + raw: JSON.stringify(sampleVP), + tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', + documentFormat: CredentialDocumentFormat.JSON_LD, + issuerCorrelationType: CredentialCorrelationType.DID, + issuerCorrelationId: 'did:example:holder', + subjectCorrelationType: CredentialCorrelationType.DID, + subjectCorrelationId: 'did:example:holder', + } + const savedUniformCredential1: UniformCredential = await uniformCredentialStore.addUniformCredential(addCredentialArgs1) + expect(savedUniformCredential1).toBeDefined() + const savedUniformCredential2: UniformCredential = await uniformCredentialStore.addUniformCredential(addCredentialArgs2) + expect(savedUniformCredential2).toBeDefined() + const args: GetUniformCredentialsArgs = { + filter: [{ credentialType: CredentialTypeEnum.VC }], + } + const result: Array = await uniformCredentialStore.getUniformCredentials(args) + + expect(result.length).toEqual(1) + }) + + it('should return no uniform credentials if filter does not match', async (): Promise => { + const args: GetUniformCredentialsArgs = { + filter: [{ issuerCorrelationId: 'unknown_id' }], + } + const result: Array = await uniformCredentialStore.getUniformCredentials(args) + + expect(result.length).toEqual(0) + }) + + it('should delete stored uniform credential', async (): Promise => { + const rawCredential: string = + 'eyJraWQiOiJkaWQ6a2V5Ono2TWtyaGt5M3B1c20yNk1laUZhWFUzbjJuZWtyYW13RlVtZ0dyZUdHa0RWNnpRaiN6Nk1rcmhreTNwdXNtMjZNZWlGYVhVM24ybmVrcmFtd0ZVbWdHcmVHR2tEVjZ6UWoiLCJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vc3BoZXJlb24tb3BlbnNvdXJjZS5naXRodWIuaW8vc3NpLW1vYmlsZS13YWxsZXQvY29udGV4dC9zcGhlcmVvbi13YWxsZXQtaWRlbnRpdHktdjEuanNvbmxkIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJTcGhlcmVvbldhbGxldElkZW50aXR5Q3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaXJzdE5hbWUiOiJTIiwibGFzdE5hbWUiOiJLIiwiZW1haWxBZGRyZXNzIjoic0BrIn19LCJzdWIiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJqdGkiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJuYmYiOjE3MDg0NDA4MDgsImlzcyI6ImRpZDprZXk6ejZNa3Joa3kzcHVzbTI2TWVpRmFYVTNuMm5la3JhbXdGVW1nR3JlR0drRFY2elFqIn0.G0M84XVAxSmzGY-NQuB9NBofNrINSn6lvxW6761Vlq6ypvYgtc2xNdpiRmw8ryVNfnpzrr4Z5cB1RlrC05rJAw' + const uniformCredential: AddUniformCredentialArgs = { + credentialType: CredentialTypeEnum.VC, + documentFormat: CredentialDocumentFormat.JWT, + raw: rawCredential, + issuerCorrelationType: CredentialCorrelationType.DID, + subjectCorrelationType: CredentialCorrelationType.DID, + issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', + } + + const savedUniformCredential: UniformCredential = await uniformCredentialStore.addUniformCredential(uniformCredential) + let result = await uniformCredentialStore.removeUniformCredential({ id: savedUniformCredential.id }) + expect(result).toEqual(true) + }) + + it('should not delete stored uniform credential if id not found', async (): Promise => { + const result = await uniformCredentialStore.removeUniformCredential({ id: 'unknown_id' }) + expect(result).toEqual(false) + }) + + it('should update stored uniform credential', async (): Promise => { + const rawCredential: string = + 'eyJraWQiOiJkaWQ6a2V5Ono2TWtyaGt5M3B1c20yNk1laUZhWFUzbjJuZWtyYW13RlVtZ0dyZUdHa0RWNnpRaiN6Nk1rcmhreTNwdXNtMjZNZWlGYVhVM24ybmVrcmFtd0ZVbWdHcmVHR2tEVjZ6UWoiLCJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vc3BoZXJlb24tb3BlbnNvdXJjZS5naXRodWIuaW8vc3NpLW1vYmlsZS13YWxsZXQvY29udGV4dC9zcGhlcmVvbi13YWxsZXQtaWRlbnRpdHktdjEuanNvbmxkIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJTcGhlcmVvbldhbGxldElkZW50aXR5Q3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaXJzdE5hbWUiOiJTIiwibGFzdE5hbWUiOiJLIiwiZW1haWxBZGRyZXNzIjoic0BrIn19LCJzdWIiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJqdGkiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJuYmYiOjE3MDg0NDA4MDgsImlzcyI6ImRpZDprZXk6ejZNa3Joa3kzcHVzbTI2TWVpRmFYVTNuMm5la3JhbXdGVW1nR3JlR0drRFY2elFqIn0.G0M84XVAxSmzGY-NQuB9NBofNrINSn6lvxW6761Vlq6ypvYgtc2xNdpiRmw8ryVNfnpzrr4Z5cB1RlrC05rJAw' + const uniformCredential: AddUniformCredentialArgs = { + credentialType: CredentialTypeEnum.VC, + documentFormat: CredentialDocumentFormat.JWT, + raw: rawCredential, + issuerCorrelationType: CredentialCorrelationType.DID, + subjectCorrelationType: CredentialCorrelationType.DID, + issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', + } + + const savedUniformCredential: UniformCredential = await uniformCredentialStore.addUniformCredential(uniformCredential) + + const result = await uniformCredentialStore.updateUniformCredentialState({ + id: savedUniformCredential.id, + verifiedState: CredentialStateType.REVOKED, + }) + expect(result.lastVerifiedState).toEqual(CredentialStateType.REVOKED) + }) +}) diff --git a/packages/data-store/src/credential/AbstractCredentialStore.ts b/packages/data-store/src/credential/AbstractCredentialStore.ts deleted file mode 100644 index 08e17ad00..000000000 --- a/packages/data-store/src/credential/AbstractCredentialStore.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { - AddCredentialArgs, - GetCredentialArgs, - GetCredentialsArgs, - RemoveCredentialArgs, - UpdateCredentialStateArgs, -} from '../types/credential/IAbstractCredentialStore' -import { UniformCredential } from '../types/credential/credential' - -export abstract class AbstractCredentialStore { - abstract getCredential(args: GetCredentialArgs): Promise - abstract getCredentials(args?: GetCredentialsArgs): Promise> - abstract addCredential(args: AddCredentialArgs): Promise - abstract updateCredentialState(args: UpdateCredentialStateArgs): Promise - abstract removeCredential(args: RemoveCredentialArgs): Promise -} diff --git a/packages/data-store/src/credential/CredentialStore.ts b/packages/data-store/src/credential/CredentialStore.ts deleted file mode 100644 index 3e7192e9f..000000000 --- a/packages/data-store/src/credential/CredentialStore.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { AbstractCredentialStore } from './AbstractCredentialStore' -import { AddCredentialArgs, GetCredentialArgs, GetCredentialsArgs, RemoveCredentialArgs } from '../types/credential/IAbstractCredentialStore' -import { UniformCredential } from '../types/credential/credential' -import { OrPromise } from '@sphereon/ssi-types' -import { DataSource, Repository } from 'typeorm' -import Debug from 'debug' -import { UniformCredentialEntity } from '../entities/credential/CredentialEntity' -import { PartyEntity } from '../entities/contact/PartyEntity' -import { credentialEntityFrom } from '../utils/credential/MappingUtils' - -const debug: Debug.Debugger = Debug('sphereon:ssi-sdk:credential-store') -export class CredentialStore extends AbstractCredentialStore { - private readonly dbConnection: OrPromise - - constructor(dbConnection: OrPromise) { - super() - this.dbConnection = dbConnection - } - - addCredential = async (args: AddCredentialArgs): Promise => { - debug('Adding credential', args) - const uniformCredentialRepository: Repository = (await this.dbConnection).getRepository(UniformCredentialEntity) - const credentialEntity: UniformCredentialEntity = credentialEntityFrom(args) - const createdResult: UniformCredentialEntity = await uniformCredentialRepository.save(credentialEntity) - return Promise.resolve(undefined) - } - - getCredential = async (args: GetCredentialArgs): Promise => { - const result: UniformCredentialEntity | null = await (await this.dbConnection).getRepository(UniformCredentialEntity).findOne({ - where: args, - }) - - if (!result) { - return Promise.reject(Error(`No party found for arg: ${args.toString()}`)) - } - - //todo: cast to UniformCredential - return result - } - - getCredentials = async (args?: GetCredentialsArgs): Promise> => { - return Promise.resolve(undefined) - } - - removeCredential = async (args: RemoveCredentialArgs): Promise => { - return Promise.resolve(undefined) - } -} diff --git a/packages/data-store/src/entities/credential/CredentialEntity.ts b/packages/data-store/src/entities/uniformCredential/UniformCredentialEntity.ts similarity index 73% rename from packages/data-store/src/entities/credential/CredentialEntity.ts rename to packages/data-store/src/entities/uniformCredential/UniformCredentialEntity.ts index 5d846ba8d..b4c4e86c1 100644 --- a/packages/data-store/src/entities/credential/CredentialEntity.ts +++ b/packages/data-store/src/entities/uniformCredential/UniformCredentialEntity.ts @@ -1,5 +1,10 @@ import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm' -import { CredentialCorrelationType, CredentialDocumentFormat, CredentialStateType, CredentialTypeEnum } from '../../types/credential/credential' +import { + CredentialCorrelationType, + CredentialDocumentFormat, + CredentialStateType, + CredentialTypeEnum, +} from '../../types/uniformCredential/uniformCredential' @Entity('UniformCredential') export class UniformCredentialEntity extends BaseEntity { @@ -21,19 +26,16 @@ export class UniformCredentialEntity extends BaseEntity { @Column('text', { name: 'hash', nullable: false, unique: true }) hash!: string - @Column('string', { name: 'type', nullable: false }) - type!: string - @Column('simple-enum', { name: 'issuer_correlation_type', enum: CredentialCorrelationType, nullable: false }) issuerCorrelationType!: CredentialCorrelationType @Column('simple-enum', { name: 'subject_correlation_type', enum: CredentialCorrelationType, nullable: true }) subjectCorrelationType?: CredentialCorrelationType - @Column('text', { name: 'issuer_correlation_type', nullable: false }) + @Column('text', { name: 'issuer_correlation_id', nullable: false }) issuerCorrelationId!: string - @Column('text', { name: 'subject_correlation_type', nullable: true }) + @Column('text', { name: 'subject_correlation_id', nullable: true }) subjectCorrelationId?: string @Column('simple-enum', { name: 'last_verified_state', enum: CredentialStateType, nullable: true }) @@ -48,6 +50,12 @@ export class UniformCredentialEntity extends BaseEntity { @UpdateDateColumn({ name: 'last_updated_at', nullable: false }) lastUpdatedAt!: Date - @Column('date', { name: 'expires_at', nullable: false }) - expiresAt!: Date + @Column('date', { name: 'expires_at', nullable: true }) + expiresAt?: Date + + @Column('date', { name: 'verification_date', nullable: true }) + verificationDate?: Date + + @Column('date', { name: 'revocation_date', nullable: true }) + revocationDate?: Date } diff --git a/packages/data-store/src/index.ts b/packages/data-store/src/index.ts index 06cd9a23d..decbcd4c1 100644 --- a/packages/data-store/src/index.ts +++ b/packages/data-store/src/index.ts @@ -31,6 +31,8 @@ export { AbstractIssuanceBrandingStore } from './issuanceBranding/AbstractIssuan export { IssuanceBrandingStore } from './issuanceBranding/IssuanceBrandingStore' export { StatusListStore } from './statusList/StatusListStore' import { AuditEventEntity, auditEventEntityFrom } from './entities/eventLogger/AuditEventEntity' +import { UniformCredentialEntity } from './entities/uniformCredential/UniformCredentialEntity' +import { uniformCredentialFrom, uniformCredentialsFrom, uniformCredentialEntityFromAddArgs } from './utils/uniformCredential/MappingUtils' export { AbstractEventLoggerStore } from './eventLogger/AbstractEventLoggerStore' export { EventLoggerStore } from './eventLogger/EventLoggerStore' export { @@ -77,12 +79,15 @@ export const DataStoreStatusListEntities = [StatusListEntity, StatusListEntryEnt export const DataStoreEventLoggerEntities = [AuditEventEntity] +export const DataStoreUniformCredentialEntities = [UniformCredentialEntity] + // All entities combined if a party wants to enable them all at once export const DataStoreEntities = [ ...DataStoreContactEntities, ...DataStoreIssuanceBrandingEntities, ...DataStoreStatusListEntities, ...DataStoreEventLoggerEntities, + ...DataStoreUniformCredentialEntities, ] export { @@ -119,4 +124,8 @@ export { StatusListEntryEntity, AuditEventEntity, auditEventEntityFrom, + UniformCredentialEntity, + uniformCredentialFrom, + uniformCredentialsFrom, + uniformCredentialEntityFromAddArgs, } diff --git a/packages/data-store/src/migrations/generic/6-CreateUniformCredential.ts b/packages/data-store/src/migrations/generic/6-CreateUniformCredential.ts new file mode 100644 index 000000000..b5857e94f --- /dev/null +++ b/packages/data-store/src/migrations/generic/6-CreateUniformCredential.ts @@ -0,0 +1,66 @@ +import { DatabaseType, MigrationInterface, QueryRunner } from 'typeorm' +import Debug, { Debugger } from 'debug' +import { CreateUniformCredential1708525189001 } from '../postgres/1708525189001-CreateUniformCredential' +import { CreateUniformCredential1708525189002 } from '../sqlite/1708525189002-CreateUniformCredential' + +const debug: Debugger = Debug('sphereon:ssi-sdk:migrations') + +export class CreateUniformCredential1708525189000 implements MigrationInterface { + name: string = 'CreateUniformCredential1708525189000' + + public async up(queryRunner: QueryRunner): Promise { + debug('migration: creating UniformCredential tables') + const dbType: DatabaseType = queryRunner.connection.driver.options.type + + switch (dbType) { + case 'postgres': { + debug('using postgres migration file for UniformCredential') + const mig: CreateUniformCredential1708525189001 = new CreateUniformCredential1708525189001() + await mig.up(queryRunner) + debug('Postgres Migration statements for UniformCredential executed') + return + } + case 'sqlite': + case 'expo': + case 'react-native': { + debug('using sqlite/react-native migration file for UniformCredential') + const mig: CreateUniformCredential1708525189002 = new CreateUniformCredential1708525189002() + await mig.up(queryRunner) + debug('SQLite Migration statements for UniformCredential executed') + return + } + default: + return Promise.reject( + `Migrations are currently only supported for sqlite, react-native, expo, and postgres for UniformCredential. Was ${dbType}. Please run your database without migrations and with 'migrationsRun: false' and 'synchronize: true' for now` + ) + } + } + + public async down(queryRunner: QueryRunner): Promise { + debug('migration: reverting UniformCredential tables') + const dbType: DatabaseType = queryRunner.connection.driver.options.type + + switch (dbType) { + case 'postgres': { + debug('using postgres migration file for UniformCredential') + const mig: CreateUniformCredential1708525189001 = new CreateUniformCredential1708525189001() + await mig.down(queryRunner) + debug('Postgres Migration statements for UniformCredential reverted') + return + } + case 'sqlite': + case 'expo': + case 'react-native': { + debug('using sqlite/react-native migration file for UniformCredential') + const mig: CreateUniformCredential1708525189002 = new CreateUniformCredential1708525189002() + await mig.down(queryRunner) + debug('SQLite Migration statements for UniformCredential reverted') + return + } + default: + return Promise.reject( + `Migrations are currently only supported for sqlite, react-native, expo, and postgres for UniformCredential. Was ${dbType}. Please run your database without migrations and with 'migrationsRun: false' and 'synchronize: true' for now` + ) + } + } +} diff --git a/packages/data-store/src/migrations/generic/index.ts b/packages/data-store/src/migrations/generic/index.ts index 27cc68d40..ecba54b7d 100644 --- a/packages/data-store/src/migrations/generic/index.ts +++ b/packages/data-store/src/migrations/generic/index.ts @@ -3,6 +3,7 @@ import { CreateIssuanceBranding1659463079429 } from './2-CreateIssuanceBranding' import { CreateContacts1690925872318 } from './3-CreateContacts' import { CreateStatusList1693866470000 } from './4-CreateStatusList' import { CreateAuditEvents1701635835330 } from './5-CreateAuditEvents' +import { CreateUniformCredential1708525189000 } from './6-CreateUniformCredential' /** * The migrations array that SHOULD be used when initializing a TypeORM database connection. @@ -17,6 +18,7 @@ export const DataStoreContactMigrations = [CreateContacts1659463079429, CreateCo export const DataStoreIssuanceBrandingMigrations = [CreateIssuanceBranding1659463079429] export const DataStoreStatusListMigrations = [CreateStatusList1693866470000] export const DataStoreEventLoggerMigrations = [CreateAuditEvents1701635835330] +export const DataStoreUniformCredentialMigrations = [CreateUniformCredential1708525189000] // All migrations together export const DataStoreMigrations = [ @@ -24,4 +26,5 @@ export const DataStoreMigrations = [ ...DataStoreIssuanceBrandingMigrations, ...DataStoreStatusListMigrations, ...DataStoreEventLoggerMigrations, + ...DataStoreUniformCredentialMigrations, ] diff --git a/packages/data-store/src/migrations/index.ts b/packages/data-store/src/migrations/index.ts index 9088bea28..eee09e3a1 100644 --- a/packages/data-store/src/migrations/index.ts +++ b/packages/data-store/src/migrations/index.ts @@ -4,4 +4,5 @@ export { DataStoreContactMigrations, DataStoreIssuanceBrandingMigrations, DataStoreStatusListMigrations, + DataStoreUniformCredentialMigrations, } from './generic' diff --git a/packages/data-store/src/migrations/postgres/1708525189001-CreateUniformCredential.ts b/packages/data-store/src/migrations/postgres/1708525189001-CreateUniformCredential.ts new file mode 100644 index 000000000..8229e1d76 --- /dev/null +++ b/packages/data-store/src/migrations/postgres/1708525189001-CreateUniformCredential.ts @@ -0,0 +1,43 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class CreateUniformCredential1708525189001 implements MigrationInterface { + name = 'CreateUniformCredential1708525189001' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TYPE "uniform_credential_credential_type_enum" AS ENUM('vc', 'vp')`) + await queryRunner.query(`CREATE TYPE "uniform_credential_document_format_enum" AS ENUM('JSON-LD', 'JWT', 'SD-JWT', 'MDOC')`) + await queryRunner.query(`CREATE TYPE "uniform_credential_correlation_type_enum" AS ENUM('did')`) + await queryRunner.query(`CREATE TYPE "uniform_credential_state_type_enum" AS ENUM('revoked', 'verified', 'expired')`) + + await queryRunner.query(` + CREATE TABLE "UniformCredential" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "credential_type" "uniform_credential_credential_type_enum" NOT NULL, + "document_format" "uniform_credential_document_format_enum" NOT NULL, + "raw" text NOT NULL, + "uniform_document" text NOT NULL, + "hash" text NOT NULL UNIQUE, + "issuer_correlation_type" "uniform_credential_correlation_type_enum" NOT NULL, + "subject_correlation_type" "uniform_credential_correlation_type_enum", + "issuer_correlation_id" text NOT NULL, + "subject_correlation_id" text, + "last_verified_state" "uniform_credential_state_type_enum", + "tenant_id" text, + "created_at" TIMESTAMP NOT NULL DEFAULT now(), + "last_updated_at" TIMESTAMP NOT NULL DEFAULT now(), + "expires_at" DATE, + "verification_date" DATE, + "revocation_date" DATE, + PRIMARY KEY ("id") + ) + `) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "UniformCredential"`) + await queryRunner.query(`DROP TYPE "uniform_credential_state_type_enum"`) + await queryRunner.query(`DROP TYPE "uniform_credential_correlation_type_enum"`) + await queryRunner.query(`DROP TYPE "uniform_credential_document_format_enum"`) + await queryRunner.query(`DROP TYPE "uniform_credential_credential_type_enum"`) + } +} diff --git a/packages/data-store/src/migrations/sqlite/1708525189002-CreateUniformCredential.ts b/packages/data-store/src/migrations/sqlite/1708525189002-CreateUniformCredential.ts new file mode 100644 index 000000000..3dd6da0d9 --- /dev/null +++ b/packages/data-store/src/migrations/sqlite/1708525189002-CreateUniformCredential.ts @@ -0,0 +1,33 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CreateUniformCredential1708525189002 implements MigrationInterface { + name = 'CreateUniformCredential1708525189002'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE TABLE "UniformCredential" ( + "id" varchar PRIMARY KEY NOT NULL, + "credential_type" varchar CHECK( "credential_type" IN ('vc', 'vp') ) NOT NULL, + "document_format" varchar CHECK( "document_format" IN ('JSON-LD', 'JWT', 'SD-JWT', 'MDOC') ) NOT NULL, + "raw" text NOT NULL, + "uniform_document" text NOT NULL, + "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') ), + "issuer_correlation_id" text NOT NULL, + "subject_correlation_id" text, + "last_verified_state" varchar CHECK( "last_verified_state" IN ('revoked', 'verified', 'expired') ), + "tenant_id" text, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "last_updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "expires_at" datetime, + "verification_date" datetime, + "revocation_date" datetime + ) + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "UniformCredential"`); + } +} diff --git a/packages/data-store/src/types/credential/IAbstractCredentialStore.ts b/packages/data-store/src/types/credential/IAbstractCredentialStore.ts deleted file mode 100644 index 4f4a82bf7..000000000 --- a/packages/data-store/src/types/credential/IAbstractCredentialStore.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { CredentialCorrelationType, CredentialDocumentFormat, CredentialStateType, CredentialTypeEnum } from './credential' - -export type GetCredentialArgs = { id: string } | { hash: string } - -// TODO: discuss about what args we want here -export type GetCredentialsArgs = {} - -export type AddCredentialArgs = { - credentialType: CredentialTypeEnum - documentFormat: CredentialDocumentFormat - raw: string - issuerCorrelationType: CredentialCorrelationType - subjectCorrelationType?: CredentialCorrelationType - issuerCorrelationId: string - subjectCorrelationId?: string - tenantId?: string -} - -export type UpdateCredentialStateArgs = GetCredentialArgs & { verified_state: CredentialStateType } - -export type RemoveCredentialArgs = { id: string } | { hash: string } diff --git a/packages/data-store/src/types/uniformCredential/IAbstractUniformCredentialStore.ts b/packages/data-store/src/types/uniformCredential/IAbstractUniformCredentialStore.ts new file mode 100644 index 000000000..4f4f4387f --- /dev/null +++ b/packages/data-store/src/types/uniformCredential/IAbstractUniformCredentialStore.ts @@ -0,0 +1,29 @@ +import { CredentialCorrelationType, CredentialDocumentFormat, CredentialStateType, CredentialTypeEnum, UniformCredential } from './uniformCredential' + +export type GetUniformCredentialArgs = { id: string } | { hash: string } + +export type FindUniformCredentialArgs = Array> + +// TODO: discuss about what args we want here +export type GetUniformCredentialsArgs = { + filter?: FindUniformCredentialArgs +} + +export type AddUniformCredentialArgs = { + credentialType: CredentialTypeEnum + documentFormat: CredentialDocumentFormat + raw: string + issuerCorrelationType: CredentialCorrelationType + subjectCorrelationType?: CredentialCorrelationType + issuerCorrelationId: string + subjectCorrelationId?: string + tenantId?: string + expiresAt?: Date + state?: CredentialStateType + verificationDate?: Date + revocationDate?: Date +} + +export type UpdateUniformCredentialStateArgs = GetUniformCredentialArgs & { verifiedState: CredentialStateType } + +export type RemoveUniformCredentialArgs = { id: string } diff --git a/packages/data-store/src/types/credential/credential.ts b/packages/data-store/src/types/uniformCredential/uniformCredential.ts similarity index 81% rename from packages/data-store/src/types/credential/credential.ts rename to packages/data-store/src/types/uniformCredential/uniformCredential.ts index 04b18e7b9..fa46d38e7 100644 --- a/packages/data-store/src/types/credential/credential.ts +++ b/packages/data-store/src/types/uniformCredential/uniformCredential.ts @@ -1,19 +1,21 @@ +export type NonPersistedUniformCredential = Omit + export type UniformCredential = { id: string credentialType: CredentialTypeEnum documentFormat: CredentialDocumentFormat raw: string hash: string - type: string issuerCorrelationType: CredentialCorrelationType subjectCorrelationType?: CredentialCorrelationType issuerCorrelationId: string subjectCorrelationId?: string - last_verified_state?: CredentialStateType + uniformDocument: string + lastVerifiedState?: CredentialStateType tenantId?: string createdAt: Date lastUpdatedAt: Date - expiresAt: Date + expiresAt?: Date } export enum CredentialTypeEnum { diff --git a/packages/data-store/src/uniformCredential/AbstractUniformCredentialStore.ts b/packages/data-store/src/uniformCredential/AbstractUniformCredentialStore.ts new file mode 100644 index 000000000..fa76c3df6 --- /dev/null +++ b/packages/data-store/src/uniformCredential/AbstractUniformCredentialStore.ts @@ -0,0 +1,16 @@ +import { + AddUniformCredentialArgs, + GetUniformCredentialArgs, + GetUniformCredentialsArgs, + RemoveUniformCredentialArgs, + UpdateUniformCredentialStateArgs, +} from '../types/uniformCredential/IAbstractUniformCredentialStore' +import { UniformCredentialEntity } from '../entities/uniformCredential/UniformCredentialEntity' + +export abstract class AbstractUniformCredentialStore { + abstract getUniformCredential(args: GetUniformCredentialArgs): Promise + abstract getUniformCredentials(args?: GetUniformCredentialsArgs): Promise> + abstract addUniformCredential(args: AddUniformCredentialArgs): Promise + abstract updateUniformCredentialState(args: UpdateUniformCredentialStateArgs): Promise + abstract removeUniformCredential(args: RemoveUniformCredentialArgs): Promise +} diff --git a/packages/data-store/src/uniformCredential/UniformCredentialStore.ts b/packages/data-store/src/uniformCredential/UniformCredentialStore.ts new file mode 100644 index 000000000..6e9584d4a --- /dev/null +++ b/packages/data-store/src/uniformCredential/UniformCredentialStore.ts @@ -0,0 +1,97 @@ +import { AbstractUniformCredentialStore } from './AbstractUniformCredentialStore' +import { + AddUniformCredentialArgs, + GetUniformCredentialArgs, + GetUniformCredentialsArgs, + RemoveUniformCredentialArgs, + UpdateUniformCredentialStateArgs, +} from '../types/uniformCredential/IAbstractUniformCredentialStore' +import { OrPromise } from '@sphereon/ssi-types' +import { DataSource, Repository } from 'typeorm' +import Debug from 'debug' +import { UniformCredentialEntity } from '../entities/uniformCredential/UniformCredentialEntity' +import { uniformCredentialEntityFromAddArgs } from '../utils/uniformCredential/MappingUtils' + +const debug: Debug.Debugger = Debug('sphereon:ssi-sdk:credential-store') +export class UniformCredentialStore extends AbstractUniformCredentialStore { + private readonly dbConnection: OrPromise + + constructor(dbConnection: OrPromise) { + super() + this.dbConnection = dbConnection + } + + addUniformCredential = async (args: AddUniformCredentialArgs): Promise => { + debug('Adding credential', args) + const uniformCredentialRepository: Repository = (await this.dbConnection).getRepository(UniformCredentialEntity) + const credentialEntity: UniformCredentialEntity = uniformCredentialEntityFromAddArgs(args) + const createdResult: UniformCredentialEntity = await uniformCredentialRepository.save(credentialEntity) + return Promise.resolve(createdResult) + } + + getUniformCredential = async (args: GetUniformCredentialArgs): Promise => { + const result: UniformCredentialEntity | null = await (await this.dbConnection).getRepository(UniformCredentialEntity).findOne({ + where: args, + }) + + if (!result) { + return Promise.reject(Error(`No credential found for arg: ${args.toString()}`)) + } + return result + } + + getUniformCredentials = async (args?: GetUniformCredentialsArgs): Promise> => { + const result: Array = await (await this.dbConnection).getRepository(UniformCredentialEntity).find({ + ...(args?.filter && { where: args?.filter }), + }) + if (!result) { + return Promise.reject(Error(`No credential found for arg: ${args?.toString() ?? undefined}`)) + } + return result + } + + removeUniformCredential = async (args: RemoveUniformCredentialArgs): Promise => { + let error = false + const result = await ( + await this.dbConnection + ) + .getRepository(UniformCredentialEntity) + .delete({ + id: args.id, + }) + .catch((error) => { + error = true + }) + error = !result?.affected || result.affected !== 1 + return !error + } + + updateUniformCredentialState = async (args: UpdateUniformCredentialStateArgs): Promise => { + const credentialRepository: Repository = (await this.dbConnection).getRepository(UniformCredentialEntity) + const whereClause: Record = {} + if ('id' in args) { + whereClause.id = args.id + } + if ('hash' in args) { + whereClause.hash = args.hash + } + const credential: UniformCredentialEntity | null = await credentialRepository.findOne({ + where: whereClause, + }) + + if (!credential) { + return Promise.reject(Error(`No credential found for args: ${whereClause}`)) + } + + const updatedCredential = { + ...credential, + lastUpdatedAt: new Date(), + lastVerifiedState: args.verifiedState, + } + + debug('Updating credential', credential) + const updatedResult: UniformCredentialEntity = await credentialRepository.save(updatedCredential, { transaction: true }) + + return updatedResult + } +} diff --git a/packages/data-store/src/utils/credential/MappingUtils.ts b/packages/data-store/src/utils/credential/MappingUtils.ts deleted file mode 100644 index 61d449856..000000000 --- a/packages/data-store/src/utils/credential/MappingUtils.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { AddCredentialArgs } from '../../types/credential/IAbstractCredentialStore' -import { UniformCredentialEntity } from '../../entities/credential/CredentialEntity' -import { CredentialMapper, WrappedVerifiableCredential, WrappedVerifiablePresentation } from '@sphereon/ssi-types' -import { CredentialTypeEnum } from '../../types/credential/credential' -import { computeEntryHash } from '@veramo/utils' - -export const credentialEntityFrom = async (addCredentialArgs: AddCredentialArgs): Promise => { - const wrappedCredential: WrappedVerifiableCredential | WrappedVerifiablePresentation = - addCredentialArgs.credentialType === CredentialTypeEnum.VC - ? CredentialMapper.toWrappedVerifiableCredential(JSON.stringify(addCredentialArgs.raw)) - : CredentialMapper.toWrappedVerifiablePresentation(addCredentialArgs.raw) - const uniformCredentialEntity: UniformCredentialEntity = new UniformCredentialEntity() - - uniformCredentialEntity.credentialType = addCredentialArgs.credentialType - uniformCredentialEntity.createdAt = new Date(wrappedCredential.decoded.nbf) - console.log(`We have createdAt:`, uniformCredentialEntity.createdAt) - uniformCredentialEntity.documentFormat = addCredentialArgs.documentFormat - uniformCredentialEntity.lastUpdatedAt = new Date() - uniformCredentialEntity.tenantId = addCredentialArgs.tenantId - uniformCredentialEntity.raw = addCredentialArgs.raw - uniformCredentialEntity.subjectCorrelationId = addCredentialArgs.subjectCorrelationId - uniformCredentialEntity.subjectCorrelationType = addCredentialArgs.subjectCorrelationType - uniformCredentialEntity.type = wrappedCredential.type - uniformCredentialEntity.hash = computeEntryHash(addCredentialArgs.raw) - uniformCredentialEntity.uniformDocument = - addCredentialArgs.credentialType === CredentialTypeEnum.VC - ? JSON.stringify((wrappedCredential as WrappedVerifiableCredential).credential) - : JSON.stringify((wrappedCredential as WrappedVerifiablePresentation).presentation) - return {} as UniformCredentialEntity -} diff --git a/packages/data-store/src/utils/uniformCredential/MappingUtils.ts b/packages/data-store/src/utils/uniformCredential/MappingUtils.ts new file mode 100644 index 000000000..94e95133a --- /dev/null +++ b/packages/data-store/src/utils/uniformCredential/MappingUtils.ts @@ -0,0 +1,75 @@ +import { AddUniformCredentialArgs } from '../../types/uniformCredential/IAbstractUniformCredentialStore' +import { UniformCredentialEntity } from '../../entities/uniformCredential/UniformCredentialEntity' +import { CredentialMapper, WrappedVerifiableCredential, WrappedVerifiablePresentation } from '@sphereon/ssi-types' +import { CredentialTypeEnum, NonPersistedUniformCredential, UniformCredential } from '../../types/uniformCredential/uniformCredential' +import { computeEntryHash } from '@veramo/utils' + +export const uniformCredentialEntityFromAddArgs = (addCredentialArgs: AddUniformCredentialArgs): UniformCredentialEntity => { + const wrappedCredential: WrappedVerifiableCredential | WrappedVerifiablePresentation = + addCredentialArgs.credentialType === CredentialTypeEnum.VC + ? CredentialMapper.toWrappedVerifiableCredential(JSON.stringify(addCredentialArgs.raw)) + : CredentialMapper.toWrappedVerifiablePresentation(addCredentialArgs.raw) + const uniformCredentialEntity: UniformCredentialEntity = new UniformCredentialEntity() + + uniformCredentialEntity.credentialType = addCredentialArgs.credentialType + uniformCredentialEntity.createdAt = new Date() + uniformCredentialEntity.documentFormat = addCredentialArgs.documentFormat + uniformCredentialEntity.lastUpdatedAt = new Date() + uniformCredentialEntity.tenantId = addCredentialArgs.tenantId + uniformCredentialEntity.raw = addCredentialArgs.raw + uniformCredentialEntity.issuerCorrelationId = addCredentialArgs.issuerCorrelationId + uniformCredentialEntity.issuerCorrelationType = addCredentialArgs.issuerCorrelationType + uniformCredentialEntity.subjectCorrelationId = addCredentialArgs.subjectCorrelationId + uniformCredentialEntity.subjectCorrelationType = addCredentialArgs.subjectCorrelationType + uniformCredentialEntity.expiresAt = addCredentialArgs.expiresAt + uniformCredentialEntity.lastVerifiedState = addCredentialArgs.state + uniformCredentialEntity.verificationDate = addCredentialArgs.verificationDate + uniformCredentialEntity.revocationDate = addCredentialArgs.revocationDate + uniformCredentialEntity.hash = computeEntryHash(addCredentialArgs.raw) + uniformCredentialEntity.uniformDocument = + addCredentialArgs.credentialType === CredentialTypeEnum.VC + ? JSON.stringify((wrappedCredential as WrappedVerifiableCredential).credential) + : JSON.stringify((wrappedCredential as WrappedVerifiablePresentation).presentation) + return uniformCredentialEntity +} + +export const uniformCredentialEntityFromNonPersisted = (uniformCredential: NonPersistedUniformCredential): UniformCredentialEntity => { + const uniformCredentialEntity: UniformCredentialEntity = new UniformCredentialEntity() + uniformCredentialEntity.credentialType = uniformCredential.credentialType + uniformCredentialEntity.createdAt = uniformCredential.createdAt + uniformCredentialEntity.documentFormat = uniformCredential.documentFormat + uniformCredentialEntity.lastUpdatedAt = new Date() + uniformCredentialEntity.tenantId = uniformCredential.tenantId + uniformCredentialEntity.raw = uniformCredential.raw + uniformCredentialEntity.issuerCorrelationId = uniformCredential.issuerCorrelationId + uniformCredentialEntity.issuerCorrelationType = uniformCredential.issuerCorrelationType + uniformCredentialEntity.subjectCorrelationId = uniformCredential.subjectCorrelationId + uniformCredentialEntity.subjectCorrelationType = uniformCredential.subjectCorrelationType + uniformCredentialEntity.hash = uniformCredential.hash + uniformCredentialEntity.uniformDocument = uniformCredential.uniformDocument + return uniformCredentialEntity +} + +export const uniformCredentialFrom = (credentialEntity: UniformCredentialEntity): UniformCredential => { + return { + id: credentialEntity.id, + credentialType: credentialEntity.credentialType, + documentFormat: credentialEntity.documentFormat, + raw: credentialEntity.raw, + uniformDocument: credentialEntity.uniformDocument, + hash: credentialEntity.hash, + issuerCorrelationType: credentialEntity.issuerCorrelationType, + subjectCorrelationType: credentialEntity.subjectCorrelationType, + issuerCorrelationId: credentialEntity.issuerCorrelationId, + subjectCorrelationId: credentialEntity.subjectCorrelationId, + lastVerifiedState: credentialEntity.lastVerifiedState, + tenantId: credentialEntity.tenantId, + createdAt: credentialEntity.createdAt, + lastUpdatedAt: credentialEntity.lastUpdatedAt, + expiresAt: credentialEntity.expiresAt, + } +} + +export const uniformCredentialsFrom = (credentialEntities: Array): UniformCredential[] => { + return credentialEntities.map((credentialEntity) => uniformCredentialFrom(credentialEntity)) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c5cbafed0..019959ea2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -366,6 +366,9 @@ importers: '@veramo/core': specifier: 4.2.0 version: 4.2.0(patch_hash=c5oempznsz4br5w3tcuk2i2mau) + '@veramo/utils': + specifier: 4.2.0 + version: 4.2.0 class-validator: specifier: ^0.14.0 version: 0.14.0