Skip to content

Commit

Permalink
Merge branch 'main' into fix/support-all-minor-versions-handshake-pro…
Browse files Browse the repository at this point in the history
…tocol
  • Loading branch information
TimoGlastra authored Jan 29, 2024
2 parents daedaae + 1261caa commit a58c007
Show file tree
Hide file tree
Showing 18 changed files with 306 additions and 27 deletions.
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
Loading

0 comments on commit a58c007

Please sign in to comment.