Skip to content

Commit

Permalink
Merge branch 'main' into feat/did-rotate
Browse files Browse the repository at this point in the history
  • Loading branch information
genaris committed Jan 29, 2024
2 parents 4b6152f + 1261caa commit a83bfe4
Show file tree
Hide file tree
Showing 23 changed files with 313 additions and 45 deletions.
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,7 @@ Although Aries Framework JavaScript tries to follow the standards as described i

If you would like to contribute to the framework, please read the [Framework Developers README](/DEVREADME.md) and the [CONTRIBUTING](/CONTRIBUTING.md) guidelines. These documents will provide more information to get you started!

The Aries Framework JavaScript call takes place every week at Thursday, 6AM Pacific Time. See [World Time Buddy](https://www.worldtimebuddy.com/?qm=1&lid=5,2759794,8&h=5&date=2023-5-19&sln=9-10&hf=1) for the time in your timezone. The meeting is held on [Zoom](https://zoom.us/j/99751084865?pwd=TW1rU0FDVTBqUlhnWnY2NERkd1diZz09).
This meeting is for contributors to groom and plan the backlog, and discuss issues.
Meeting agendas and recordings can be found [here](https://wiki.hyperledger.org/display/ARIES/Framework+JS+Meetings).
Feel free to join!
There are regular community working groups to discuss ongoing efforts within the framework, showcase items you've built with Credo, or ask questions. See [Meeting Information](https://github.com/openwallet-foundation/credo-ts/wiki/Meeting-Information) for up to date information on the meeting schedule. Everyone is welcome to join!

## License

Expand Down
1 change: 1 addition & 0 deletions packages/anoncreds-rs/tests/LocalDidResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DidsApi } from '@aries-framework/core'

export class LocalDidResolver implements DidResolver {
public readonly supportedMethods = ['sov', 'indy']
public readonly allowsCaching = false

public async resolve(agentContext: AgentContext, did: string): Promise<DidResolutionResult> {
const didDocumentMetadata = {}
Expand Down
1 change: 1 addition & 0 deletions packages/cheqd/src/dids/CheqdDidResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { filterResourcesByNameAndType, getClosestResourceVersion, renderResource

export class CheqdDidResolver implements DidResolver {
public readonly supportedMethods = ['cheqd']
public readonly allowsCaching = true

public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise<DidResolutionResult> {
const didDocumentMetadata = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { DidRecord, DidDocumentRole, DidRepository } from '../../dids'
export class InMemoryDidRegistry implements DidRegistrar, DidResolver {
public readonly supportedMethods = ['inmemory']

public readonly allowsCaching = false

private dids: Record<string, DidDocument> = {}

public async create(agentContext: AgentContext, options: DidCreateOptions): Promise<DidCreateResult> {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/modules/dids/domain/DidResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { ParsedDid, DidResolutionResult, DidResolutionOptions } from '../ty

export interface DidResolver {
readonly supportedMethods: string[]
readonly allowsCaching: boolean

resolve(
agentContext: AgentContext,
did: string,
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/modules/dids/methods/jwk/JwkDidResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { DidJwk } from './DidJwk'
export class JwkDidResolver implements DidResolver {
public readonly supportedMethods = ['jwk']

/**
* No remote resolving done, did document is dynamically constructed. To not pollute the cache we don't allow caching
*/
public readonly allowsCaching = false

public async resolve(agentContext: AgentContext, did: string): Promise<DidResolutionResult> {
const didDocumentMetadata = {}

Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/modules/dids/methods/key/KeyDidResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { DidKey } from './DidKey'
export class KeyDidResolver implements DidResolver {
public readonly supportedMethods = ['key']

/**
* No remote resolving done, did document is dynamically constructed. To not pollute the cache we don't allow caching
*/
public readonly allowsCaching = false

public async resolve(agentContext: AgentContext, did: string): Promise<DidResolutionResult> {
const didDocumentMetadata = {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import { didToNumAlgo4DidDocument, isShortFormDidPeer4 } from './peerDidNumAlgo4
export class PeerDidResolver implements DidResolver {
public readonly supportedMethods = ['peer']

/**
* No remote resolving done, did document is fetched from storage. To not pollute the cache we don't allow caching
*/
public readonly allowsCaching = false

public async resolve(agentContext: AgentContext, did: string): Promise<DidResolutionResult> {
const didRepository = agentContext.dependencyManager.resolve(DidRepository)

Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/modules/dids/methods/web/WebDidResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { DidDocument } from '../../domain'
export class WebDidResolver implements DidResolver {
public readonly supportedMethods

public readonly allowsCaching = true

// FIXME: Would be nice if we don't have to provide a did resolver instance
private _resolverInstance = new Resolver()
private resolver = didWeb.getResolver()
Expand Down
65 changes: 64 additions & 1 deletion packages/core/src/modules/dids/services/DidResolverService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import { InjectionSymbols } from '../../../constants'
import { AriesFrameworkError } from '../../../error'
import { Logger } from '../../../logger'
import { injectable, inject } from '../../../plugins'
import { JsonTransformer } from '../../../utils'
import { CacheModuleConfig } from '../../cache'
import { DidsModuleConfig } from '../DidsModuleConfig'
import { DidDocument } from '../domain'
import { parseDid } from '../domain/parse'

@injectable()
Expand Down Expand Up @@ -53,9 +56,69 @@ export class DidResolverService {
}
}

return resolver.resolve(agentContext, parsed.did, parsed, options)
// extract caching options and set defaults
const { useCache = true, cacheDurationInSeconds = 300, persistInCache = true } = options
const cacheKey = `did:resolver:${parsed.did}`

if (resolver.allowsCaching && useCache) {
const cache = agentContext.dependencyManager.resolve(CacheModuleConfig).cache
// FIXME: in multi-tenancy it can be that the same cache is used for different agent contexts
// This may become a problem when resolving dids, as you can get back a cache hit for a different
// tenant. did:peer has disabled caching, and I think we should just recommend disabling caching
// for these private dids
// We could allow providing a custom cache prefix in the resolver options, so that the cache key
// can be recognized in the cache implementation
const cachedDidDocument = await cache.get<DidResolutionResult & { didDocument: Record<string, unknown> }>(
agentContext,
cacheKey
)

if (cachedDidDocument) {
return {
...cachedDidDocument,
didDocument: JsonTransformer.fromJSON(cachedDidDocument.didDocument, DidDocument),
didResolutionMetadata: {
...cachedDidDocument.didResolutionMetadata,
servedFromCache: true,
},
}
}
}

let resolutionResult = await resolver.resolve(agentContext, parsed.did, parsed, options)
// Avoid overwriting existing document
resolutionResult = {
...resolutionResult,
didResolutionMetadata: {
...resolutionResult.didResolutionMetadata,
resolutionTime: Date.now(),
// Did resolver implementation might use did method specific caching strategy
// We only set to false if not defined by the resolver
servedFromCache: resolutionResult.didResolutionMetadata.servedFromCache ?? false,
},
}

if (resolutionResult.didDocument && resolver.allowsCaching && persistInCache) {
const cache = agentContext.dependencyManager.resolve(CacheModuleConfig).cache
await cache.set(
agentContext,
cacheKey,
{
...resolutionResult,
didDocument: resolutionResult.didDocument.toJSON(),
},
// Set cache duration
cacheDurationInSeconds
)
}

return resolutionResult
}

/**
* Resolve a did document. This uses the default resolution options, and thus
* will use caching if available.
*/
public async resolveDidDocument(agentContext: AgentContext, did: string) {
const {
didDocument,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,35 @@ import type { DidResolver } from '../../domain'

import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers'
import { JsonTransformer } from '../../../../utils/JsonTransformer'
import { CacheModuleConfig, InMemoryLruCache } from '../../../cache'
import { DidsModuleConfig } from '../../DidsModuleConfig'
import didKeyEd25519Fixture from '../../__tests__/__fixtures__/didKeyEd25519.json'
import { DidDocument } from '../../domain'
import { parseDid } from '../../domain/parse'
import { DidResolverService } from '../DidResolverService'

const didResolverMock = {
allowsCaching: true,
supportedMethods: ['key'],
resolve: jest.fn(),
} as DidResolver

const cache = new InMemoryLruCache({ limit: 10 })
const agentConfig = getAgentConfig('DidResolverService')
const agentContext = getAgentContext()
const agentContext = getAgentContext({
registerInstances: [[CacheModuleConfig, new CacheModuleConfig({ cache })]],
})

describe('DidResolverService', () => {
const didResolverService = new DidResolverService(
agentConfig.logger,
new DidsModuleConfig({ resolvers: [didResolverMock] })
)

afterEach(() => {
jest.clearAllMocks()
})

it('should correctly find and call the correct resolver for a specified did', async () => {
const returnValue = {
didDocument: JsonTransformer.fromJSON(didKeyEd25519Fixture, DidDocument),
Expand All @@ -33,14 +42,72 @@ describe('DidResolverService', () => {
mockFunction(didResolverMock.resolve).mockResolvedValue(returnValue)

const result = await didResolverService.resolve(agentContext, 'did:key:xxxx', { someKey: 'string' })
expect(result).toEqual(returnValue)
expect(result).toEqual({
...returnValue,
didResolutionMetadata: {
...returnValue.didResolutionMetadata,
resolutionTime: expect.any(Number),
servedFromCache: false,
},
})

expect(didResolverMock.resolve).toHaveBeenCalledTimes(1)
expect(didResolverMock.resolve).toHaveBeenCalledWith(agentContext, 'did:key:xxxx', parseDid('did:key:xxxx'), {
someKey: 'string',
})
})

it('should return cached did document when resolved multiple times within caching duration', async () => {
const returnValue = {
didDocument: JsonTransformer.fromJSON(didKeyEd25519Fixture, DidDocument),
didDocumentMetadata: {},
didResolutionMetadata: {
contentType: 'application/did+ld+json',
},
}
mockFunction(didResolverMock.resolve).mockResolvedValue(returnValue)

const result = await didResolverService.resolve(agentContext, 'did:key:cached', { someKey: 'string' })
const cachedValue = await cache.get(agentContext, 'did:resolver:did:key:cached')

expect(result).toEqual({
...returnValue,
didResolutionMetadata: {
...returnValue.didResolutionMetadata,
resolutionTime: expect.any(Number),
servedFromCache: false,
},
})

expect(cachedValue).toEqual({
...returnValue,
didDocument: returnValue.didDocument.toJSON(),
didResolutionMetadata: {
...returnValue.didResolutionMetadata,
resolutionTime: expect.any(Number),
servedFromCache: false,
},
})

expect(didResolverMock.resolve).toHaveBeenCalledTimes(1)
expect(didResolverMock.resolve).toHaveBeenCalledWith(agentContext, 'did:key:cached', parseDid('did:key:cached'), {
someKey: 'string',
})

const resultCached = await didResolverService.resolve(agentContext, 'did:key:cached', { someKey: 'string' })
expect(resultCached).toEqual({
...returnValue,
didResolutionMetadata: {
...returnValue.didResolutionMetadata,
resolutionTime: expect.any(Number),
servedFromCache: true,
},
})

// Still called once because served from cache
expect(didResolverMock.resolve).toHaveBeenCalledTimes(1)
})

it("should return an error with 'invalidDid' if the did string couldn't be parsed", async () => {
const did = 'did:__Asd:asdfa'

Expand Down
25 changes: 24 additions & 1 deletion packages/core/src/modules/dids/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,34 @@ import type { DidDocument } from './domain'
import type { DIDDocumentMetadata, DIDResolutionMetadata, DIDResolutionOptions, ParsedDID } from 'did-resolver'

export type ParsedDid = ParsedDID
export type DidResolutionOptions = DIDResolutionOptions
export type DidDocumentMetadata = DIDDocumentMetadata

export interface DidResolutionOptions extends DIDResolutionOptions {
/**
* Whether to resolve the did document from the cache.
*
* @default true
*/
useCache?: boolean

/**
* Whether to persist the did document in the cache.
*
* @default true
*/
persistInCache?: boolean

/**
* How many seconds to persist the resolved document
*
* @default 3600
*/
cacheDurationInSeconds?: number
}

export interface DidResolutionMetadata extends DIDResolutionMetadata {
message?: string
servedFromCache?: boolean
}

export interface DidResolutionResult {
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/modules/oob/OutOfBandService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ export class OutOfBandService {
// initiating the flow
const outOfBandInvitation = new OutOfBandInvitation({
id: did,
label: '',
services: [did],
handshakeProtocols,
})
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/modules/oob/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,17 @@ export function convertToOldInvitation(newInvitation: OutOfBandInvitation) {
if (typeof service === 'string') {
options = {
id: newInvitation.id,
label: newInvitation.label,
// label is optional
label: newInvitation.label ?? '',
did: service,
imageUrl: newInvitation.imageUrl,
appendedAttachments: newInvitation.appendedAttachments,
}
} else {
options = {
id: newInvitation.id,
label: newInvitation.label,
// label is optional
label: newInvitation.label ?? '',
recipientKeys: service.recipientKeys.map(didKeyToVerkey),
routingKeys: service.routingKeys?.map(didKeyToVerkey),
serviceEndpoint: service.serviceEndpoint,
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/modules/oob/messages/OutOfBandInvitation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { OutOfBandDidCommService } from '../domain/OutOfBandDidCommService'

export interface OutOfBandInvitationOptions {
id?: string
label: string
label?: string
goalCode?: string
goal?: string
accept?: string[]
Expand Down Expand Up @@ -124,7 +124,7 @@ export class OutOfBandInvitation extends AgentMessage {
public readonly type = OutOfBandInvitation.type.messageTypeUri
public static readonly type = parseMessageType('https://didcomm.org/out-of-band/1.1/invitation')

public readonly label!: string
public readonly label?: string

@Expose({ name: 'goal_code' })
public readonly goalCode?: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,6 @@ export class PresentationExchangeProofFormatService implements ProofFormatServic
public readonly formatKey = 'presentationExchange' as const

private presentationExchangeService(agentContext: AgentContext) {
if (!agentContext.dependencyManager.isRegistered(DifPresentationExchangeService)) {
throw new AriesFrameworkError(
'DifPresentationExchangeService is not registered on the Agent. Please provide the PresentationExchangeModule as a module on the agent'
)
}

return agentContext.dependencyManager.resolve(DifPresentationExchangeService)
}

Expand Down
Loading

0 comments on commit a83bfe4

Please sign in to comment.