-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(mrtd): return disclosures for sync requests (#66)
Signed-off-by: Ariel Gentile <[email protected]>
- Loading branch information
Showing
7 changed files
with
383 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import type { ConnectionRecord, EncryptedMessage } from '@credo-ts/core' | ||
|
||
import { AskarModule } from '@credo-ts/askar' | ||
import { Agent, ConsoleLogger, DidExchangeState, LogLevel, utils } from '@credo-ts/core' | ||
import { agentDependencies } from '@credo-ts/node' | ||
import { ariesAskar } from '@hyperledger/aries-askar-nodejs' | ||
import { Subject } from 'rxjs' | ||
|
||
import { DidCommMrtdModule } from '../src/DidCommMrtdModule' | ||
|
||
import { SubjectInboundTransport } from './transport/SubjectInboundTransport' | ||
import { SubjectOutboundTransport } from './transport/SubjectOutboundTransport' | ||
|
||
const logger = new ConsoleLogger(LogLevel.off) | ||
|
||
export type SubjectMessage = { message: EncryptedMessage; replySubject?: Subject<SubjectMessage> } | ||
|
||
describe('profile test', () => { | ||
let aliceAgent: Agent<{ askar: AskarModule; mrtd: DidCommMrtdModule }> | ||
let bobAgent: Agent<{ askar: AskarModule; mrtd: DidCommMrtdModule }> | ||
let aliceWalletId: string | ||
let aliceWalletKey: string | ||
let bobWalletId: string | ||
let bobWalletKey: string | ||
let aliceConnectionRecord: ConnectionRecord | ||
let bobConnectionRecord: ConnectionRecord | ||
|
||
beforeEach(async () => { | ||
aliceWalletId = utils.uuid() | ||
aliceWalletKey = utils.uuid() | ||
bobWalletId = utils.uuid() | ||
bobWalletKey = utils.uuid() | ||
|
||
const aliceMessages = new Subject<SubjectMessage>() | ||
const bobMessages = new Subject<SubjectMessage>() | ||
|
||
const subjectMap = { | ||
'rxjs:alice': aliceMessages, | ||
'rxjs:bob': bobMessages, | ||
} | ||
|
||
// Initialize alice | ||
aliceAgent = new Agent({ | ||
config: { | ||
label: 'alice', | ||
endpoints: ['rxjs:alice'], | ||
walletConfig: { id: aliceWalletId, key: aliceWalletKey }, | ||
logger, | ||
}, | ||
dependencies: agentDependencies, | ||
modules: { askar: new AskarModule({ ariesAskar }), mrtd: new DidCommMrtdModule() }, | ||
}) | ||
|
||
aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) | ||
aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) | ||
await aliceAgent.initialize() | ||
|
||
// Initialize bob | ||
bobAgent = new Agent({ | ||
config: { | ||
endpoints: ['rxjs:bob'], | ||
label: 'bob', | ||
walletConfig: { id: bobWalletId, key: bobWalletKey }, | ||
logger, | ||
}, | ||
dependencies: agentDependencies, | ||
modules: { askar: new AskarModule({ ariesAskar }), mrtd: new DidCommMrtdModule() }, | ||
}) | ||
|
||
bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) | ||
bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) | ||
await bobAgent.initialize() | ||
|
||
const outOfBandRecord = await aliceAgent.oob.createInvitation({ | ||
autoAcceptConnection: true, | ||
}) | ||
|
||
const { connectionRecord } = await bobAgent.oob.receiveInvitationFromUrl( | ||
outOfBandRecord.outOfBandInvitation.toUrl({ domain: 'https://example.com/ssi' }), | ||
{ autoAcceptConnection: true }, | ||
) | ||
|
||
bobConnectionRecord = await bobAgent.connections.returnWhenIsConnected(connectionRecord!.id) | ||
expect(bobConnectionRecord.state).toBe(DidExchangeState.Completed) | ||
|
||
aliceConnectionRecord = (await aliceAgent.connections.findAllByOutOfBandId(outOfBandRecord.id))[0] | ||
aliceConnectionRecord = await aliceAgent.connections.returnWhenIsConnected(aliceConnectionRecord!.id) | ||
expect(aliceConnectionRecord.state).toBe(DidExchangeState.Completed) | ||
}) | ||
|
||
afterEach(async () => { | ||
// Wait for messages to flush out | ||
await new Promise((r) => setTimeout(r, 1000)) | ||
|
||
if (aliceAgent) { | ||
await aliceAgent.shutdown() | ||
|
||
if (aliceAgent.wallet.isInitialized && aliceAgent.wallet.isProvisioned) { | ||
await aliceAgent.wallet.delete() | ||
} | ||
} | ||
|
||
if (bobAgent) { | ||
await bobAgent.shutdown() | ||
|
||
if (bobAgent.wallet.isInitialized && bobAgent.wallet.isProvisioned) { | ||
await bobAgent.wallet.delete() | ||
} | ||
} | ||
}) | ||
|
||
test('Set and query an NFC support', async () => { | ||
// Bob requests MRTD capabilities. eMRTD read support is false, since it is not set | ||
let response = await bobAgent.modules.mrtd.requestMrtdCapabilities({ | ||
connectionId: bobConnectionRecord.id, | ||
awaitDisclosure: true, | ||
}) | ||
expect(response.eMrtdReadSupported).toBeFalsy() | ||
|
||
// Alice sets eMRTD read support. When Bob queries for it, he should get a true value | ||
await aliceAgent.modules.mrtd.setMrtdCapabilities({ eMrtdReadSupported: true }) | ||
|
||
response = await bobAgent.modules.mrtd.requestMrtdCapabilities({ | ||
connectionId: bobConnectionRecord.id, | ||
awaitDisclosure: true, | ||
}) | ||
expect(response.eMrtdReadSupported).toBeTruthy() | ||
|
||
// Alice now sets eMRTD read support to false. When Bob queries for it, he should get a false value | ||
|
||
await aliceAgent.modules.mrtd.setMrtdCapabilities({ eMrtdReadSupported: false }) | ||
|
||
response = await bobAgent.modules.mrtd.requestMrtdCapabilities({ | ||
connectionId: bobConnectionRecord.id, | ||
awaitDisclosure: true, | ||
}) | ||
expect(response.eMrtdReadSupported).toBeFalsy() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import type { | ||
BaseRecord, | ||
RecordSavedEvent, | ||
RecordDeletedEvent, | ||
RecordUpdatedEvent, | ||
Agent, | ||
BaseEvent, | ||
} from '@credo-ts/core' | ||
import type { Constructor } from '@credo-ts/core/build/utils/mixins' | ||
|
||
import { RepositoryEventTypes } from '@credo-ts/core' | ||
import { map, filter, pipe } from 'rxjs' | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
type BaseRecordAny = BaseRecord<any, any, any> | ||
type RecordClass<R extends BaseRecordAny> = Constructor<R> & { type: string } | ||
export interface RecordsState<R extends BaseRecordAny> { | ||
loading: boolean | ||
records: R[] | ||
} | ||
|
||
export const addRecord = <R extends BaseRecordAny>(record: R, state: RecordsState<R>): RecordsState<R> => { | ||
const newRecordsState = [...state.records] | ||
newRecordsState.unshift(record) | ||
return { | ||
loading: state.loading, | ||
records: newRecordsState, | ||
} | ||
} | ||
|
||
export const updateRecord = <R extends BaseRecordAny>(record: R, state: RecordsState<R>): RecordsState<R> => { | ||
const newRecordsState = [...state.records] | ||
const index = newRecordsState.findIndex((r) => r.id === record.id) | ||
if (index > -1) { | ||
newRecordsState[index] = record | ||
} | ||
return { | ||
loading: state.loading, | ||
records: newRecordsState, | ||
} | ||
} | ||
|
||
export const removeRecord = <R extends BaseRecordAny>(record: R, state: RecordsState<R>): RecordsState<R> => { | ||
const newRecordsState = state.records.filter((r) => r.id !== record.id) | ||
return { | ||
loading: state.loading, | ||
records: newRecordsState, | ||
} | ||
} | ||
|
||
const filterByType = <R extends BaseRecordAny>(recordClass: RecordClass<R>) => { | ||
return pipe( | ||
map((event: BaseEvent) => (event.payload as Record<string, R>).record), | ||
filter((record: R) => record.type === recordClass.type), | ||
) | ||
} | ||
|
||
export const recordsAddedByType = <R extends BaseRecordAny>(agent: Agent | undefined, recordClass: RecordClass<R>) => { | ||
if (!agent) { | ||
throw new Error('Agent is required to subscribe to events') | ||
} | ||
return agent?.events.observable<RecordSavedEvent<R>>(RepositoryEventTypes.RecordSaved).pipe(filterByType(recordClass)) | ||
} | ||
|
||
export const recordsUpdatedByType = <R extends BaseRecordAny>( | ||
agent: Agent | undefined, | ||
recordClass: RecordClass<R>, | ||
) => { | ||
if (!agent) { | ||
throw new Error('Agent is required to subscribe to events') | ||
} | ||
return agent?.events | ||
.observable<RecordUpdatedEvent<R>>(RepositoryEventTypes.RecordUpdated) | ||
.pipe(filterByType(recordClass)) | ||
} | ||
|
||
export const recordsRemovedByType = <R extends BaseRecordAny>( | ||
agent: Agent | undefined, | ||
recordClass: RecordClass<R>, | ||
) => { | ||
if (!agent) { | ||
throw new Error('Agent is required to subscribe to events') | ||
} | ||
return agent?.events | ||
.observable<RecordDeletedEvent<R>>(RepositoryEventTypes.RecordDeleted) | ||
.pipe(filterByType(recordClass)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
import 'reflect-metadata' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import type { Agent, AgentContext, EncryptedMessage, InboundTransport, TransportSession } from '@credo-ts/core' | ||
import type { Subscription } from 'rxjs' | ||
|
||
import { MessageReceiver, TransportService, utils } from '@credo-ts/core' | ||
import { Subject } from 'rxjs' | ||
|
||
export type SubjectMessage = { message: EncryptedMessage; replySubject?: Subject<SubjectMessage> } | ||
|
||
export class SubjectInboundTransport implements InboundTransport { | ||
public readonly ourSubject: Subject<SubjectMessage> | ||
private subscription?: Subscription | ||
|
||
public constructor(ourSubject = new Subject<SubjectMessage>()) { | ||
this.ourSubject = ourSubject | ||
} | ||
|
||
public async start(agent: Agent) { | ||
this.subscribe(agent) | ||
} | ||
|
||
public async stop() { | ||
this.subscription?.unsubscribe() | ||
} | ||
|
||
private subscribe(agent: Agent) { | ||
const logger = agent.config.logger | ||
const transportService = agent.dependencyManager.resolve(TransportService) | ||
const messageReceiver = agent.dependencyManager.resolve(MessageReceiver) | ||
|
||
this.subscription = this.ourSubject.subscribe({ | ||
next: async ({ message, replySubject }: SubjectMessage) => { | ||
logger.test('Received message') | ||
|
||
let session: SubjectTransportSession | undefined | ||
if (replySubject) { | ||
session = new SubjectTransportSession(`subject-session-${utils.uuid()}`, replySubject) | ||
|
||
// When the subject is completed (e.g. when the session is closed), we need to | ||
// remove the session from the transport service so it won't be used for sending messages | ||
// in the future. | ||
replySubject.subscribe({ | ||
complete: () => session && transportService.removeSession(session), | ||
}) | ||
} | ||
|
||
await messageReceiver.receiveMessage(message, { session }) | ||
}, | ||
}) | ||
} | ||
} | ||
|
||
export class SubjectTransportSession implements TransportSession { | ||
public id: string | ||
public readonly type = 'subject' | ||
private replySubject: Subject<SubjectMessage> | ||
|
||
public constructor(id: string, replySubject: Subject<SubjectMessage>) { | ||
this.id = id | ||
this.replySubject = replySubject | ||
} | ||
|
||
public async send(agentContext: AgentContext, encryptedMessage: EncryptedMessage): Promise<void> { | ||
this.replySubject.next({ message: encryptedMessage }) | ||
} | ||
|
||
public async close(): Promise<void> { | ||
this.replySubject.complete() | ||
} | ||
} |
Oops, something went wrong.