From a36d69103cf582d4929a37f8329b03e550a2ea32 Mon Sep 17 00:00:00 2001 From: Simonas Karuzas Date: Tue, 28 Jan 2020 15:14:47 +0200 Subject: [PATCH] feat: New Identity TS and GraphQL interfaces --- .../identity/abstract-identity-provider.ts | 16 +++ .../src/identity/abstract-identity.ts | 38 +++++ .../daf-core/src/identity/identity-manager.ts | 136 ++++++------------ .../abstract-service-controller.test.ts | 29 ++-- .../service/abstract-service-controller.ts | 4 +- .../daf-core/src/service/service-manager.ts | 8 +- 6 files changed, 125 insertions(+), 106 deletions(-) create mode 100644 packages/daf-core/src/identity/abstract-identity-provider.ts create mode 100644 packages/daf-core/src/identity/abstract-identity.ts diff --git a/packages/daf-core/src/identity/abstract-identity-provider.ts b/packages/daf-core/src/identity/abstract-identity-provider.ts new file mode 100644 index 000000000..accd6ec65 --- /dev/null +++ b/packages/daf-core/src/identity/abstract-identity-provider.ts @@ -0,0 +1,16 @@ +import { EventEmitter } from 'events' +import { AbstractIdentity } from './abstract-identity' + +export abstract class AbstractIdentityProvider extends EventEmitter { + abstract type: string + abstract description: string + abstract createIdentity: () => Promise + abstract importIdentity: (secret: string) => Promise + abstract exportIdentity: (did: string) => Promise + abstract deleteIdentity: (did: string) => Promise + abstract getIdentities: () => Promise + abstract getIdentity: (did: string) => Promise +} + +type AbstractIdentityProviderClass = typeof AbstractIdentityProvider +export interface IdentityProviderDerived extends AbstractIdentityProviderClass {} diff --git a/packages/daf-core/src/identity/abstract-identity.ts b/packages/daf-core/src/identity/abstract-identity.ts new file mode 100644 index 000000000..8a54fc506 --- /dev/null +++ b/packages/daf-core/src/identity/abstract-identity.ts @@ -0,0 +1,38 @@ +export interface DIDDocument { + '@context': 'https://w3id.org/did/v1' + id: string + publicKey: PublicKey[] + service?: ServiceEndpoint[] +} +export interface PublicKey { + id: string + type: string + owner: string + ethereumAddress?: string + publicKeyBase64?: string + publicKeyBase58?: string + publicKeyHex?: string + publicKeyPem?: string +} +export interface ServiceEndpoint { + id: string + type: string + serviceEndpoint: string + description?: string +} + +export abstract class AbstractIdentity { + abstract identityProviderType: string + abstract did: string + abstract didDoc: () => Promise + abstract sign: (data: string, keyId?: string) => Promise + abstract encrypt: (to: string, data: string | Uint8Array) => Promise + abstract decrypt: (encrypted: any) => Promise + abstract addPublicKey: (type: string, proofPurpose?: string[]) => Promise + abstract removePublicKey: (keyId: string) => Promise + abstract addService: (service: ServiceEndpoint) => Promise + abstract removeService: (service: ServiceEndpoint) => Promise +} + +type AbstractIdentityClass = typeof AbstractIdentity +export interface IdentityDerived extends AbstractIdentityClass {} diff --git a/packages/daf-core/src/identity/identity-manager.ts b/packages/daf-core/src/identity/identity-manager.ts index e0fb42df1..4b0b0d753 100644 --- a/packages/daf-core/src/identity/identity-manager.ts +++ b/packages/daf-core/src/identity/identity-manager.ts @@ -1,122 +1,80 @@ -export interface EcdsaSignature { - r: string - s: string - recoveryParam?: number -} - -export type Signer = (data: string) => Promise - -export interface Issuer { - // did-jwt-vc - type: string - did: string - signer: Signer - ethereumAddress?: string -} - -export interface IdentityController { - type: string - create: () => Promise - delete: (did: string) => Promise - listDids: () => Promise - listIssuers: () => Promise - issuer: (did: string) => Promise - export?: (did: string) => Promise - import?: (secret: string) => Promise -} +import { AbstractIdentity } from './abstract-identity' +import { AbstractIdentityProvider } from './abstract-identity-provider' interface Options { - identityControllers: IdentityController[] + identityProviders: AbstractIdentityProvider[] } export class IdentityManager { - private identityControllers: IdentityController[] + private identityProviders: AbstractIdentityProvider[] constructor(options: Options) { - this.identityControllers = options.identityControllers + this.identityProviders = options.identityProviders } - async listDids(): Promise { - let allDids: string[] = [] - - for (const identityController of this.identityControllers) { - const dids = await identityController.listDids() - allDids = allDids.concat(dids) - } - - return allDids + async getIdentityProviderTypes(): Promise<{ type: string; description: string }[]> { + return this.identityProviders.map(provider => ({ + type: provider.type, + description: provider.description, + })) } - async listIssuers(): Promise { - let allIssuers: Issuer[] = [] - - for (const identityController of this.identityControllers) { - const issuers = await identityController.listIssuers() - allIssuers = allIssuers.concat(issuers) + async createIdentity(identityProviderType: string): Promise { + for (const identityProvider of this.identityProviders) { + if (identityProvider.type === identityProviderType) { + return identityProvider.createIdentity() + } } - return allIssuers + return Promise.reject('IdentityProvider not found for type: ' + identityProviderType) } - async issuer(did: string): Promise { - const issuers = await this.listIssuers() - const issuer = issuers.find(item => item.did === did) - if (issuer) { - return issuer - } else { - return Promise.reject('No issuer for did: ' + did) + async importIdentity(identityProviderType: string, secret: string): Promise { + for (const identityProvider of this.identityProviders) { + if (identityProvider.type === identityProviderType) { + return identityProvider.importIdentity(secret) + } } - } - listTypes(): string[] { - return this.identityControllers.map(identityController => identityController.type) + return Promise.reject('IdentityProvider not found for type: ' + identityProviderType) } - create(type: string): Promise { - for (const identityController of this.identityControllers) { - if (identityController.type === type) { - return identityController.create() - } + async getIdentities(): Promise { + let allIdentities: AbstractIdentity[] = [] + for (const identityProvider of this.identityProviders) { + const identities = await identityProvider.getIdentities() + allIdentities = allIdentities.concat(identities) } - - return Promise.reject('IdentityController not found for type: ' + type) + return allIdentities } - delete(type: string, did: string): Promise { - for (const identityController of this.identityControllers) { - if (identityController.type === type) { - return identityController.delete(did) - } + async getIdentity(did: string): Promise { + const identities = await this.getIdentities() + const identity = identities.find(item => item.did === did) + if (identity) { + return identity + } else { + return Promise.reject('No identity: ' + did) } - - return Promise.reject('IdentityController not found for type: ' + type) } - import(type: string, secret: string): Promise { - for (const identityController of this.identityControllers) { - if (identityController.type === type) { - if (identityController.import) { - return identityController.import(secret) - } else { - return Promise.reject(type + ' does not support import') - } + async exportIdentity(did: string): Promise { + const identity = await this.getIdentity(did) + for (const identityProvider of this.identityProviders) { + if (identityProvider.type === identity.identityProviderType) { + return identityProvider.exportIdentity(identity.did) } } - - return Promise.reject('IdentityController not found for type: ' + type) + return Promise.reject() } - export(type: string, did: string): Promise { - for (const identityController of this.identityControllers) { - if (identityController.type === type) { - if (identityController.export) { - return identityController.export(did) - } else { - return Promise.reject(type + ' does not support export') - } + async deleteIdentity(did: string): Promise { + const identity = await this.getIdentity(did) + for (const identityProvider of this.identityProviders) { + if (identityProvider.type === identity.identityProviderType) { + return identityProvider.deleteIdentity(identity.did) } } - - return Promise.reject('IdentityController not found for type: ' + type) + return Promise.reject() } } diff --git a/packages/daf-core/src/service/__tests__/abstract-service-controller.test.ts b/packages/daf-core/src/service/__tests__/abstract-service-controller.test.ts index 9a3a4245d..8443063b2 100644 --- a/packages/daf-core/src/service/__tests__/abstract-service-controller.test.ts +++ b/packages/daf-core/src/service/__tests__/abstract-service-controller.test.ts @@ -1,6 +1,6 @@ import { AbstractServiceController } from '../abstract-service-controller' import { ServiceEventTypes } from '../service-manager' -import { Issuer } from '../../identity/identity-manager' +import { AbstractIdentity } from '../../identity/abstract-identity' import { Resolver } from '../../core' import { Message } from '../../message/message' @@ -14,8 +14,8 @@ export class MockServiceController extends AbstractServiceController { public ready: Promise - constructor(issuer: Issuer, didResolver: Resolver) { - super(issuer, didResolver) + constructor(identity: AbstractIdentity, didResolver: Resolver) { + super(identity, didResolver) this.endPointUrl = 'https://from-did-doc' this.ready = new Promise((resolve, reject) => { // do some async stuff @@ -25,7 +25,7 @@ export class MockServiceController extends AbstractServiceController { instanceId() { return { - did: this.issuer.did, + did: this.identity.did, type: this.type, id: this.endPointUrl, } @@ -41,10 +41,17 @@ export class MockServiceController extends AbstractServiceController { } } -const mockIssuer: Issuer = { +const mockIdentity: AbstractIdentity = { did: 'did:test:123', - signer: async (data: string) => data, - type: 'mock', + sign: async (data: string) => data, + identityProviderType: 'mock', + didDoc: async (): Promise => '', + encrypt: async (): Promise => '', + decrypt: async (): Promise => '', + addPublicKey: async (): Promise => '', + removePublicKey: async (): Promise => '', + addService: async (): Promise => '', + removeService: async (): Promise => '', } const mockResolver: Resolver = { @@ -58,27 +65,27 @@ it('should be possible to set configuration as a static property', async () => { }) it('resolves ready promise after finishing async logic in constructor', async () => { - const controller = new MockServiceController(mockIssuer, mockResolver) + const controller = new MockServiceController(mockIdentity, mockResolver) const ready = await controller.ready expect(ready).toEqual(true) }) it('returns and emits an event with the same message array ', async () => { - const controller = new MockServiceController(mockIssuer, mockResolver) + const controller = new MockServiceController(mockIdentity, mockResolver) spyOn(controller, 'emit') const messages = await controller.getMessagesSince(0) expect(controller.emit).toHaveBeenCalledWith(ServiceEventTypes.NewMessages, messages) }) it('emits events on listen', async () => { - const controller = new MockServiceController(mockIssuer, mockResolver) + const controller = new MockServiceController(mockIdentity, mockResolver) spyOn(controller, 'emit') await controller.listen() expect(controller.emit).toHaveBeenCalledWith(ServiceEventTypes.NewMessages, [msg1]) }) it('instanceId is generated from state', async () => { - const controller = new MockServiceController(mockIssuer, mockResolver) + const controller = new MockServiceController(mockIdentity, mockResolver) const instanceId = controller.instanceId() expect(instanceId).toEqual({ did: 'did:test:123', type: controller.type, id: 'https://from-did-doc' }) }) diff --git a/packages/daf-core/src/service/abstract-service-controller.ts b/packages/daf-core/src/service/abstract-service-controller.ts index 8da61f8a7..33d844e3d 100644 --- a/packages/daf-core/src/service/abstract-service-controller.ts +++ b/packages/daf-core/src/service/abstract-service-controller.ts @@ -1,10 +1,10 @@ import { EventEmitter } from 'events' -import { Issuer } from '../identity/identity-manager' +import { AbstractIdentity } from '../identity/abstract-identity' import { Resolver } from '../core' import { Message } from '../message/message' export abstract class AbstractServiceController extends EventEmitter { - constructor(readonly issuer: Issuer, readonly didResolver: Resolver) { + constructor(readonly identity: AbstractIdentity, readonly didResolver: Resolver) { super() } abstract ready: Promise // you cannot have an async constructor diff --git a/packages/daf-core/src/service/service-manager.ts b/packages/daf-core/src/service/service-manager.ts index 09496c6bd..9d1cbb237 100644 --- a/packages/daf-core/src/service/service-manager.ts +++ b/packages/daf-core/src/service/service-manager.ts @@ -1,7 +1,7 @@ import { EventEmitter } from 'events' import { Resolver } from '../core' import { AbstractServiceController, ServiceControllerDerived } from './abstract-service-controller' -import { Issuer } from '../identity/identity-manager' +import { AbstractIdentity } from '../identity/abstract-identity' import { Message } from '../message/message' import Debug from 'debug' const debug = Debug('daf:service-manager') @@ -34,10 +34,10 @@ export class ServiceManager extends EventEmitter { this.didResolver = options.didResolver } - async setupServices(issuers: Issuer[]) { - for (const issuer of issuers) { + async setupServices(identities: AbstractIdentity[]) { + for (const identity of identities) { for (const controller of this.controllers) { - const instance = new controller(issuer, this.didResolver) + const instance = new controller(identity, this.didResolver) await instance.ready instance.on(ServiceEventTypes.NewMessages, this.onNewMessages.bind(this)) this.controllerInstances.push(instance)