Skip to content

Commit

Permalink
feat: New Identity TS and GraphQL interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
simonas-notcat committed Jan 28, 2020
1 parent e43335e commit a36d691
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 106 deletions.
16 changes: 16 additions & 0 deletions packages/daf-core/src/identity/abstract-identity-provider.ts
Original file line number Diff line number Diff line change
@@ -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<AbstractIdentity>
abstract importIdentity: (secret: string) => Promise<AbstractIdentity>
abstract exportIdentity: (did: string) => Promise<string>
abstract deleteIdentity: (did: string) => Promise<boolean>
abstract getIdentities: () => Promise<AbstractIdentity[]>
abstract getIdentity: (did: string) => Promise<AbstractIdentity>
}

type AbstractIdentityProviderClass = typeof AbstractIdentityProvider
export interface IdentityProviderDerived extends AbstractIdentityProviderClass {}
38 changes: 38 additions & 0 deletions packages/daf-core/src/identity/abstract-identity.ts
Original file line number Diff line number Diff line change
@@ -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<DIDDocument>
abstract sign: (data: string, keyId?: string) => Promise<any>
abstract encrypt: (to: string, data: string | Uint8Array) => Promise<any>
abstract decrypt: (encrypted: any) => Promise<string>
abstract addPublicKey: (type: string, proofPurpose?: string[]) => Promise<PublicKey>
abstract removePublicKey: (keyId: string) => Promise<boolean>
abstract addService: (service: ServiceEndpoint) => Promise<boolean>
abstract removeService: (service: ServiceEndpoint) => Promise<boolean>
}

type AbstractIdentityClass = typeof AbstractIdentity
export interface IdentityDerived extends AbstractIdentityClass {}
136 changes: 47 additions & 89 deletions packages/daf-core/src/identity/identity-manager.ts
Original file line number Diff line number Diff line change
@@ -1,122 +1,80 @@
export interface EcdsaSignature {
r: string
s: string
recoveryParam?: number
}

export type Signer = (data: string) => Promise<EcdsaSignature | string>

export interface Issuer {
// did-jwt-vc
type: string
did: string
signer: Signer
ethereumAddress?: string
}

export interface IdentityController {
type: string
create: () => Promise<string>
delete: (did: string) => Promise<boolean>
listDids: () => Promise<string[]>
listIssuers: () => Promise<Issuer[]>
issuer: (did: string) => Promise<Issuer>
export?: (did: string) => Promise<string>
import?: (secret: string) => Promise<Issuer>
}
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<string[]> {
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<Issuer[]> {
let allIssuers: Issuer[] = []

for (const identityController of this.identityControllers) {
const issuers = await identityController.listIssuers()
allIssuers = allIssuers.concat(issuers)
async createIdentity(identityProviderType: string): Promise<AbstractIdentity> {
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<Issuer> {
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<AbstractIdentity> {
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<string> {
for (const identityController of this.identityControllers) {
if (identityController.type === type) {
return identityController.create()
}
async getIdentities(): Promise<AbstractIdentity[]> {
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<boolean> {
for (const identityController of this.identityControllers) {
if (identityController.type === type) {
return identityController.delete(did)
}
async getIdentity(did: string): Promise<AbstractIdentity> {
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<Issuer> {
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<string> {
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<string> {
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<boolean> {
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()
}
}
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -14,8 +14,8 @@ export class MockServiceController extends AbstractServiceController {

public ready: Promise<boolean>

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
Expand All @@ -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,
}
Expand All @@ -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<any> => '',
encrypt: async (): Promise<any> => '',
decrypt: async (): Promise<any> => '',
addPublicKey: async (): Promise<any> => '',
removePublicKey: async (): Promise<any> => '',
addService: async (): Promise<any> => '',
removeService: async (): Promise<any> => '',
}

const mockResolver: Resolver = {
Expand All @@ -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' })
})
4 changes: 2 additions & 2 deletions packages/daf-core/src/service/abstract-service-controller.ts
Original file line number Diff line number Diff line change
@@ -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<boolean> // you cannot have an async constructor
Expand Down
8 changes: 4 additions & 4 deletions packages/daf-core/src/service/service-manager.ts
Original file line number Diff line number Diff line change
@@ -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')
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit a36d691

Please sign in to comment.