From 7cb7ed3cffc2f0f5a1ecf4ad9bc5db3f5d412abc Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Mon, 11 Jul 2022 17:51:18 +0200 Subject: [PATCH] feat(credentials): credential module config Signed-off-by: Timo Glastra --- packages/core/src/agent/Agent.ts | 4 +- packages/core/src/agent/BaseAgent.ts | 6 +- .../core/src/agent/__tests__/Agent.test.ts | 6 +- .../src/modules/credentials/CredentialsApi.ts | 626 +++++++++++++++++ .../modules/credentials/CredentialsModule.ts | 635 +----------------- .../credentials/CredentialsModuleConfig.ts | 21 + .../core/src/modules/credentials/index.ts | 2 +- packages/core/src/plugins/Module.ts | 5 +- 8 files changed, 672 insertions(+), 633 deletions(-) create mode 100644 packages/core/src/modules/credentials/CredentialsApi.ts create mode 100644 packages/core/src/modules/credentials/CredentialsModuleConfig.ts diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 7e820f7a19..8756748993 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -183,7 +183,9 @@ export class Agent extends BaseAgent { // Register all modules dependencyManager.registerModules( ConnectionsModule, - CredentialsModule, + new CredentialsModule({ + autoAcceptCredentials: this.agentConfig.autoAcceptCredentials, + }), ProofsModule, MediatorModule, RecipientModule, diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index 34115dc2a5..bb23bb7c9f 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -6,7 +6,7 @@ import type { TransportSession } from './TransportService' import { AriesFrameworkError } from '../error' import { BasicMessagesModule } from '../modules/basic-messages/BasicMessagesModule' import { ConnectionsModule } from '../modules/connections/ConnectionsModule' -import { CredentialsModule } from '../modules/credentials/CredentialsModule' +import { CredentialsApi } from '../modules/credentials/CredentialsApi' import { DidsModule } from '../modules/dids/DidsModule' import { DiscoverFeaturesModule } from '../modules/discover-features' import { GenericRecordsModule } from '../modules/generic-records/GenericRecordsModule' @@ -45,7 +45,7 @@ export abstract class BaseAgent { public readonly genericRecords: GenericRecordsModule public readonly ledger: LedgerModule public readonly questionAnswer!: QuestionAnswerModule - public readonly credentials: CredentialsModule + public readonly credentials: CredentialsApi public readonly mediationRecipient: RecipientModule public readonly mediator: MediatorModule public readonly discovery: DiscoverFeaturesModule @@ -82,7 +82,7 @@ export abstract class BaseAgent { // We set the modules in the constructor because that allows to set them as read-only this.connections = this.dependencyManager.resolve(ConnectionsModule) - this.credentials = this.dependencyManager.resolve(CredentialsModule) as CredentialsModule + this.credentials = this.dependencyManager.resolve(CredentialsApi) as CredentialsApi this.proofs = this.dependencyManager.resolve(ProofsModule) this.mediator = this.dependencyManager.resolve(MediatorModule) this.mediationRecipient = this.dependencyManager.resolve(RecipientModule) diff --git a/packages/core/src/agent/__tests__/Agent.test.ts b/packages/core/src/agent/__tests__/Agent.test.ts index 558f267e14..00c9c9a880 100644 --- a/packages/core/src/agent/__tests__/Agent.test.ts +++ b/packages/core/src/agent/__tests__/Agent.test.ts @@ -7,7 +7,7 @@ import { ConnectionRepository } from '../../modules/connections/repository/Conne import { ConnectionService } from '../../modules/connections/services/ConnectionService' import { TrustPingService } from '../../modules/connections/services/TrustPingService' import { CredentialRepository } from '../../modules/credentials' -import { CredentialsModule } from '../../modules/credentials/CredentialsModule' +import { CredentialsApi } from '../../modules/credentials/CredentialsApi' import { IndyLedgerService } from '../../modules/ledger' import { LedgerModule } from '../../modules/ledger/LedgerModule' import { ProofRepository, ProofService } from '../../modules/proofs' @@ -119,7 +119,7 @@ describe('Agent', () => { expect(container.resolve(ProofService)).toBeInstanceOf(ProofService) expect(container.resolve(ProofRepository)).toBeInstanceOf(ProofRepository) - expect(container.resolve(CredentialsModule)).toBeInstanceOf(CredentialsModule) + expect(container.resolve(CredentialsApi)).toBeInstanceOf(CredentialsApi) expect(container.resolve(CredentialRepository)).toBeInstanceOf(CredentialRepository) expect(container.resolve(BasicMessagesModule)).toBeInstanceOf(BasicMessagesModule) @@ -161,7 +161,7 @@ describe('Agent', () => { expect(container.resolve(ProofService)).toBe(container.resolve(ProofService)) expect(container.resolve(ProofRepository)).toBe(container.resolve(ProofRepository)) - expect(container.resolve(CredentialsModule)).toBe(container.resolve(CredentialsModule)) + expect(container.resolve(CredentialsApi)).toBe(container.resolve(CredentialsApi)) expect(container.resolve(CredentialRepository)).toBe(container.resolve(CredentialRepository)) expect(container.resolve(BasicMessagesModule)).toBe(container.resolve(BasicMessagesModule)) diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts new file mode 100644 index 0000000000..177f0c3bc3 --- /dev/null +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -0,0 +1,626 @@ +import type { AgentMessage } from '../../agent/AgentMessage' +import type { DeleteCredentialOptions } from './CredentialServiceOptions' +import type { + AcceptCredentialOptions, + AcceptOfferOptions, + AcceptProposalOptions, + AcceptRequestOptions, + CreateOfferOptions, + FindCredentialMessageReturn, + FindOfferMessageReturn, + FindProposalMessageReturn, + FindRequestMessageReturn, + GetFormatDataReturn, + NegotiateOfferOptions, + NegotiateProposalOptions, + OfferCredentialOptions, + ProposeCredentialOptions, + SendProblemReportOptions, + ServiceMap, +} from './CredentialsModuleOptions' +import type { CredentialFormat } from './formats' +import type { IndyCredentialFormat } from './formats/indy/IndyCredentialFormat' +import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' +import type { CredentialService } from './services/CredentialService' + +import { AgentContext } from '../../agent' +import { MessageSender } from '../../agent/MessageSender' +import { createOutboundMessage } from '../../agent/helpers' +import { InjectionSymbols } from '../../constants' +import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' +import { AriesFrameworkError } from '../../error' +import { Logger } from '../../logger' +import { inject, injectable, module } from '../../plugins' +import { DidCommMessageRole } from '../../storage' +import { DidCommMessageRepository } from '../../storage/didcomm/DidCommMessageRepository' +import { ConnectionService } from '../connections/services' +import { RoutingService } from '../routing/services/RoutingService' + +import { CredentialState } from './models/CredentialState' +import { RevocationNotificationService } from './protocol/revocation-notification/services' +import { V1CredentialService } from './protocol/v1/V1CredentialService' +import { V2CredentialService } from './protocol/v2/V2CredentialService' +import { CredentialRepository } from './repository/CredentialRepository' + +export interface CredentialsApi[]> { + // Proposal methods + proposeCredential(options: ProposeCredentialOptions): Promise + acceptProposal(options: AcceptProposalOptions): Promise + negotiateProposal(options: NegotiateProposalOptions): Promise + + // Offer methods + offerCredential(options: OfferCredentialOptions): Promise + acceptOffer(options: AcceptOfferOptions): Promise + declineOffer(credentialRecordId: string): Promise + negotiateOffer(options: NegotiateOfferOptions): Promise + // out of band + createOffer(options: CreateOfferOptions): Promise<{ + message: AgentMessage + credentialRecord: CredentialExchangeRecord + }> + // Request + // This is for beginning the exchange with a request (no proposal or offer). Only possible + // (currently) with W3C. We will not implement this in phase I + // requestCredential(credentialOptions: RequestCredentialOptions): Promise + + // when the issuer accepts the request he issues the credential to the holder + acceptRequest(options: AcceptRequestOptions): Promise + + // Credential + acceptCredential(options: AcceptCredentialOptions): Promise + sendProblemReport(options: SendProblemReportOptions): Promise + + // Record Methods + getAll(): Promise + getById(credentialRecordId: string): Promise + findById(credentialRecordId: string): Promise + deleteById(credentialRecordId: string, options?: DeleteCredentialOptions): Promise + getFormatData(credentialRecordId: string): Promise> + + // DidComm Message Records + findProposalMessage(credentialExchangeId: string): Promise> + findOfferMessage(credentialExchangeId: string): Promise> + findRequestMessage(credentialExchangeId: string): Promise> + findCredentialMessage(credentialExchangeId: string): Promise> +} + +@injectable() +export class CredentialsApi< + CFs extends CredentialFormat[] = [IndyCredentialFormat], + CSs extends CredentialService[] = [V1CredentialService, V2CredentialService] +> implements CredentialsApi +{ + private connectionService: ConnectionService + private messageSender: MessageSender + private credentialRepository: CredentialRepository + private agentContext: AgentContext + private didCommMessageRepo: DidCommMessageRepository + private routingService: RoutingService + private logger: Logger + private serviceMap: ServiceMap + + public constructor( + messageSender: MessageSender, + connectionService: ConnectionService, + agentContext: AgentContext, + @inject(InjectionSymbols.Logger) logger: Logger, + credentialRepository: CredentialRepository, + mediationRecipientService: RoutingService, + didCommMessageRepository: DidCommMessageRepository, + v1Service: V1CredentialService, + v2Service: V2CredentialService, + // only injected so the handlers will be registered + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _revocationNotificationService: RevocationNotificationService + ) { + this.messageSender = messageSender + this.connectionService = connectionService + this.credentialRepository = credentialRepository + this.routingService = mediationRecipientService + this.agentContext = agentContext + this.didCommMessageRepo = didCommMessageRepository + this.logger = logger + + // Dynamically build service map. This will be extracted once services are registered dynamically + this.serviceMap = [v1Service, v2Service].reduce( + (serviceMap, service) => ({ + ...serviceMap, + [service.version]: service, + }), + {} + ) as ServiceMap + + this.logger.debug(`Initializing Credentials Module for agent ${this.agentContext.config.label}`) + } + + public getService(protocolVersion: PVT): CredentialService { + if (!this.serviceMap[protocolVersion]) { + throw new AriesFrameworkError(`No credential service registered for protocol version ${protocolVersion}`) + } + + return this.serviceMap[protocolVersion] + } + + /** + * Initiate a new credential exchange as holder by sending a credential proposal message + * to the connection with the specified credential options + * + * @param options configuration to use for the proposal + * @returns Credential exchange record associated with the sent proposal message + */ + + public async proposeCredential(options: ProposeCredentialOptions): Promise { + const service = this.getService(options.protocolVersion) + + this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) + + const connection = await this.connectionService.getById(this.agentContext, options.connectionId) + + // will get back a credential record -> map to Credential Exchange Record + const { credentialRecord, message } = await service.createProposal(this.agentContext, { + connection, + credentialFormats: options.credentialFormats, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + + this.logger.debug('We have a message (sending outbound): ', message) + + // send the message here + const outbound = createOutboundMessage(connection, message) + + this.logger.debug('In proposeCredential: Send Proposal to Issuer') + await this.messageSender.sendMessage(this.agentContext, outbound) + return credentialRecord + } + + /** + * Accept a credential proposal as issuer (by sending a credential offer message) to the connection + * associated with the credential record. + * + * @param options config object for accepting the proposal + * @returns Credential exchange record associated with the credential offer + * + */ + public async acceptProposal(options: AcceptProposalOptions): Promise { + const credentialRecord = await this.getById(options.credentialRecordId) + + if (!credentialRecord.connectionId) { + throw new AriesFrameworkError( + `No connectionId found for credential record '${credentialRecord.id}'. Connection-less issuance does not support credential proposal or negotiation.` + ) + } + + // with version we can get the Service + const service = this.getService(credentialRecord.protocolVersion) + + // will get back a credential record -> map to Credential Exchange Record + const { message } = await service.acceptProposal(this.agentContext, { + credentialRecord, + credentialFormats: options.credentialFormats, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + + // send the message + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + const outbound = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outbound) + + return credentialRecord + } + + /** + * Negotiate a credential proposal as issuer (by sending a credential offer message) to the connection + * associated with the credential record. + * + * @param options configuration for the offer see {@link NegotiateProposalOptions} + * @returns Credential exchange record associated with the credential offer + * + */ + public async negotiateProposal(options: NegotiateProposalOptions): Promise { + const credentialRecord = await this.getById(options.credentialRecordId) + + if (!credentialRecord.connectionId) { + throw new AriesFrameworkError( + `No connection id for credential record ${credentialRecord.id} not found. Connection-less issuance does not support negotiation` + ) + } + + // with version we can get the Service + const service = this.getService(credentialRecord.protocolVersion) + + const { message } = await service.negotiateProposal(this.agentContext, { + credentialRecord, + credentialFormats: options.credentialFormats, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return credentialRecord + } + + /** + * Initiate a new credential exchange as issuer by sending a credential offer message + * to the connection with the specified connection id. + * + * @param options config options for the credential offer + * @returns Credential exchange record associated with the sent credential offer message + */ + public async offerCredential(options: OfferCredentialOptions): Promise { + const connection = await this.connectionService.getById(this.agentContext, options.connectionId) + const service = this.getService(options.protocolVersion) + + this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) + + const { message, credentialRecord } = await service.createOffer(this.agentContext, { + credentialFormats: options.credentialFormats, + autoAcceptCredential: options.autoAcceptCredential, + comment: options.comment, + connection, + }) + + this.logger.debug('Offer Message successfully created; message= ', message) + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return credentialRecord + } + + /** + * Accept a credential offer as holder (by sending a credential request message) to the connection + * associated with the credential record. + * + * @param options The object containing config options of the offer to be accepted + * @returns Object containing offer associated credential record + */ + public async acceptOffer(options: AcceptOfferOptions): Promise { + const credentialRecord = await this.getById(options.credentialRecordId) + + const service = this.getService(credentialRecord.protocolVersion) + + this.logger.debug(`Got a CredentialService object for this version; version = ${service.version}`) + const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) + + // Use connection if present + if (credentialRecord.connectionId) { + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + + const { message } = await service.acceptOffer(this.agentContext, { + credentialRecord, + credentialFormats: options.credentialFormats, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return credentialRecord + } + // Use ~service decorator otherwise + else if (offerMessage?.service) { + // Create ~service decorator + const routing = await this.routingService.getRouting(this.agentContext) + const ourService = new ServiceDecorator({ + serviceEndpoint: routing.endpoints[0], + recipientKeys: [routing.recipientKey.publicKeyBase58], + routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), + }) + const recipientService = offerMessage.service + + const { message } = await service.acceptOffer(this.agentContext, { + credentialRecord, + credentialFormats: options.credentialFormats, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + + // Set and save ~service decorator to record (to remember our verkey) + message.service = ourService + await this.didCommMessageRepo.saveOrUpdateAgentMessage(this.agentContext, { + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + + await this.messageSender.sendMessageToService(this.agentContext, { + message, + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }) + + return credentialRecord + } + // Cannot send message without connectionId or ~service decorator + else { + throw new AriesFrameworkError( + `Cannot accept offer for credential record without connectionId or ~service decorator on credential offer.` + ) + } + } + + public async declineOffer(credentialRecordId: string): Promise { + const credentialRecord = await this.getById(credentialRecordId) + credentialRecord.assertState(CredentialState.OfferReceived) + + // with version we can get the Service + const service = this.getService(credentialRecord.protocolVersion) + await service.updateState(this.agentContext, credentialRecord, CredentialState.Declined) + + return credentialRecord + } + + public async negotiateOffer(options: NegotiateOfferOptions): Promise { + const credentialRecord = await this.getById(options.credentialRecordId) + + const service = this.getService(credentialRecord.protocolVersion) + const { message } = await service.negotiateOffer(this.agentContext, { + credentialFormats: options.credentialFormats, + credentialRecord, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + + if (!credentialRecord.connectionId) { + throw new AriesFrameworkError( + `No connection id for credential record ${credentialRecord.id} not found. Connection-less issuance does not support negotiation` + ) + } + + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return credentialRecord + } + + /** + * Initiate a new credential exchange as issuer by creating a credential offer + * not bound to any connection. The offer must be delivered out-of-band to the holder + * @param options The credential options to use for the offer + * @returns The credential record and credential offer message + */ + public async createOffer(options: CreateOfferOptions): Promise<{ + message: AgentMessage + credentialRecord: CredentialExchangeRecord + }> { + const service = this.getService(options.protocolVersion) + + this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) + const { message, credentialRecord } = await service.createOffer(this.agentContext, { + credentialFormats: options.credentialFormats, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + + this.logger.debug('Offer Message successfully created; message= ', message) + + return { message, credentialRecord } + } + + /** + * Accept a credential request as holder (by sending a credential request message) to the connection + * associated with the credential record. + * + * @param options The object containing config options of the request + * @returns CredentialExchangeRecord updated with information pertaining to this request + */ + public async acceptRequest(options: AcceptRequestOptions): Promise { + const credentialRecord = await this.getById(options.credentialRecordId) + + // with version we can get the Service + const service = this.getService(credentialRecord.protocolVersion) + + this.logger.debug(`Got a CredentialService object for version ${credentialRecord.protocolVersion}`) + + const { message } = await service.acceptRequest(this.agentContext, { + credentialRecord, + credentialFormats: options.credentialFormats, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + this.logger.debug('We have a credential message (sending outbound): ', message) + + const requestMessage = await service.findRequestMessage(this.agentContext, credentialRecord.id) + const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) + + // Use connection if present + if (credentialRecord.connectionId) { + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return credentialRecord + } + // Use ~service decorator otherwise + else if (requestMessage?.service && offerMessage?.service) { + const recipientService = requestMessage.service + const ourService = offerMessage.service + + message.service = ourService + await this.didCommMessageRepo.saveOrUpdateAgentMessage(this.agentContext, { + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + + await this.messageSender.sendMessageToService(this.agentContext, { + message, + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }) + + return credentialRecord + } + // Cannot send message without connectionId or ~service decorator + else { + throw new AriesFrameworkError( + `Cannot accept request for credential record without connectionId or ~service decorator on credential offer / request.` + ) + } + } + + /** + * Accept a credential as holder (by sending a credential acknowledgement message) to the connection + * associated with the credential record. + * + * @param credentialRecordId The id of the credential record for which to accept the credential + * @returns credential exchange record associated with the sent credential acknowledgement message + * + */ + public async acceptCredential(options: AcceptCredentialOptions): Promise { + const credentialRecord = await this.getById(options.credentialRecordId) + + // with version we can get the Service + const service = this.getService(credentialRecord.protocolVersion) + + this.logger.debug(`Got a CredentialService object for version ${credentialRecord.protocolVersion}`) + + const { message } = await service.acceptCredential(this.agentContext, { + credentialRecord, + }) + + const requestMessage = await service.findRequestMessage(this.agentContext, credentialRecord.id) + const credentialMessage = await service.findCredentialMessage(this.agentContext, credentialRecord.id) + + if (credentialRecord.connectionId) { + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + const outboundMessage = createOutboundMessage(connection, message) + + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return credentialRecord + } + // Use ~service decorator otherwise + else if (credentialMessage?.service && requestMessage?.service) { + const recipientService = credentialMessage.service + const ourService = requestMessage.service + + await this.messageSender.sendMessageToService(this.agentContext, { + message, + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }) + + return credentialRecord + } + // Cannot send message without connectionId or ~service decorator + else { + throw new AriesFrameworkError( + `Cannot accept credential without connectionId or ~service decorator on credential message.` + ) + } + } + + /** + * Send problem report message for a credential record + * @param credentialRecordId The id of the credential record for which to send problem report + * @param message message to send + * @returns credential record associated with the credential problem report message + */ + public async sendProblemReport(options: SendProblemReportOptions) { + const credentialRecord = await this.getById(options.credentialRecordId) + if (!credentialRecord.connectionId) { + throw new AriesFrameworkError(`No connectionId found for credential record '${credentialRecord.id}'.`) + } + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + + const service = this.getService(credentialRecord.protocolVersion) + const problemReportMessage = service.createProblemReport(this.agentContext, { message: options.message }) + problemReportMessage.setThread({ + threadId: credentialRecord.threadId, + }) + const outboundMessage = createOutboundMessage(connection, problemReportMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return credentialRecord + } + + public async getFormatData(credentialRecordId: string): Promise> { + const credentialRecord = await this.getById(credentialRecordId) + const service = this.getService(credentialRecord.protocolVersion) + + return service.getFormatData(this.agentContext, credentialRecordId) + } + + /** + * Retrieve a credential record by id + * + * @param credentialRecordId The credential record id + * @throws {RecordNotFoundError} If no record is found + * @return The credential record + * + */ + public getById(credentialRecordId: string): Promise { + return this.credentialRepository.getById(this.agentContext, credentialRecordId) + } + + /** + * Retrieve all credential records + * + * @returns List containing all credential records + */ + public getAll(): Promise { + return this.credentialRepository.getAll(this.agentContext) + } + + /** + * Find a credential record by id + * + * @param credentialRecordId the credential record id + * @returns The credential record or null if not found + */ + public findById(credentialRecordId: string): Promise { + return this.credentialRepository.findById(this.agentContext, credentialRecordId) + } + + /** + * Delete a credential record by id, also calls service to delete from wallet + * + * @param credentialId the credential record id + * @param options the delete credential options for the delete operation + */ + public async deleteById(credentialId: string, options?: DeleteCredentialOptions) { + const credentialRecord = await this.getById(credentialId) + const service = this.getService(credentialRecord.protocolVersion) + return service.delete(this.agentContext, credentialRecord, options) + } + + public async findProposalMessage(credentialExchangeId: string): Promise> { + const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + + return service.findProposalMessage(this.agentContext, credentialExchangeId) + } + + public async findOfferMessage(credentialExchangeId: string): Promise> { + const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + + return service.findOfferMessage(this.agentContext, credentialExchangeId) + } + + public async findRequestMessage(credentialExchangeId: string): Promise> { + const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + + return service.findRequestMessage(this.agentContext, credentialExchangeId) + } + + public async findCredentialMessage(credentialExchangeId: string): Promise> { + const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + + return service.findCredentialMessage(this.agentContext, credentialExchangeId) + } + + private async getServiceForCredentialExchangeId(credentialExchangeId: string) { + const credentialExchangeRecord = await this.getById(credentialExchangeId) + + return this.getService(credentialExchangeRecord.protocolVersion) + } +} diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index 5aefd21ee2..241a7fd6c8 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -1,638 +1,29 @@ -import type { AgentMessage } from '../../agent/AgentMessage' import type { DependencyManager } from '../../plugins' -import type { DeleteCredentialOptions } from './CredentialServiceOptions' -import type { - AcceptCredentialOptions, - AcceptOfferOptions, - AcceptProposalOptions, - AcceptRequestOptions, - CreateOfferOptions, - FindCredentialMessageReturn, - FindOfferMessageReturn, - FindProposalMessageReturn, - FindRequestMessageReturn, - GetFormatDataReturn, - NegotiateOfferOptions, - NegotiateProposalOptions, - OfferCredentialOptions, - ProposeCredentialOptions, - SendProblemReportOptions, - ServiceMap, -} from './CredentialsModuleOptions' -import type { CredentialFormat } from './formats' -import type { IndyCredentialFormat } from './formats/indy/IndyCredentialFormat' -import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' -import type { CredentialService } from './services/CredentialService' -import { AgentContext } from '../../agent' -import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' -import { InjectionSymbols } from '../../constants' -import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' -import { AriesFrameworkError } from '../../error' -import { Logger } from '../../logger' -import { inject, injectable, module } from '../../plugins' -import { DidCommMessageRole } from '../../storage' -import { DidCommMessageRepository } from '../../storage/didcomm/DidCommMessageRepository' -import { ConnectionService } from '../connections/services' -import { RoutingService } from '../routing/services/RoutingService' +import { module } from '../../plugins' -import { IndyCredentialFormatService } from './formats' -import { CredentialState } from './models/CredentialState' +import { CredentialsApi } from './CredentialsApi' +import { CredentialsModuleConfig, CredentialsModuleConfigOptions } from './CredentialsModuleConfig' +import { IndyCredentialFormatService } from './formats/indy' import { RevocationNotificationService } from './protocol/revocation-notification/services' -import { V1CredentialService } from './protocol/v1/V1CredentialService' -import { V2CredentialService } from './protocol/v2/V2CredentialService' -import { CredentialRepository } from './repository/CredentialRepository' - -export interface CredentialsModule[]> { - // Proposal methods - proposeCredential(options: ProposeCredentialOptions): Promise - acceptProposal(options: AcceptProposalOptions): Promise - negotiateProposal(options: NegotiateProposalOptions): Promise - - // Offer methods - offerCredential(options: OfferCredentialOptions): Promise - acceptOffer(options: AcceptOfferOptions): Promise - declineOffer(credentialRecordId: string): Promise - negotiateOffer(options: NegotiateOfferOptions): Promise - // out of band - createOffer(options: CreateOfferOptions): Promise<{ - message: AgentMessage - credentialRecord: CredentialExchangeRecord - }> - // Request - // This is for beginning the exchange with a request (no proposal or offer). Only possible - // (currently) with W3C. We will not implement this in phase I - // requestCredential(credentialOptions: RequestCredentialOptions): Promise - - // when the issuer accepts the request he issues the credential to the holder - acceptRequest(options: AcceptRequestOptions): Promise - - // Credential - acceptCredential(options: AcceptCredentialOptions): Promise - sendProblemReport(options: SendProblemReportOptions): Promise - - // Record Methods - getAll(): Promise - getById(credentialRecordId: string): Promise - findById(credentialRecordId: string): Promise - deleteById(credentialRecordId: string, options?: DeleteCredentialOptions): Promise - getFormatData(credentialRecordId: string): Promise> - - // DidComm Message Records - findProposalMessage(credentialExchangeId: string): Promise> - findOfferMessage(credentialExchangeId: string): Promise> - findRequestMessage(credentialExchangeId: string): Promise> - findCredentialMessage(credentialExchangeId: string): Promise> -} +import { V1CredentialService } from './protocol/v1' +import { V2CredentialService } from './protocol/v2' +import { CredentialRepository } from './repository' @module() -@injectable() -export class CredentialsModule< - CFs extends CredentialFormat[] = [IndyCredentialFormat], - CSs extends CredentialService[] = [V1CredentialService, V2CredentialService] -> implements CredentialsModule -{ - private connectionService: ConnectionService - private messageSender: MessageSender - private credentialRepository: CredentialRepository - private agentContext: AgentContext - private didCommMessageRepo: DidCommMessageRepository - private routingService: RoutingService - private logger: Logger - private serviceMap: ServiceMap - - public constructor( - messageSender: MessageSender, - connectionService: ConnectionService, - agentContext: AgentContext, - @inject(InjectionSymbols.Logger) logger: Logger, - credentialRepository: CredentialRepository, - mediationRecipientService: RoutingService, - didCommMessageRepository: DidCommMessageRepository, - v1Service: V1CredentialService, - v2Service: V2CredentialService, - // only injected so the handlers will be registered - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _revocationNotificationService: RevocationNotificationService - ) { - this.messageSender = messageSender - this.connectionService = connectionService - this.credentialRepository = credentialRepository - this.routingService = mediationRecipientService - this.agentContext = agentContext - this.didCommMessageRepo = didCommMessageRepository - this.logger = logger - - // Dynamically build service map. This will be extracted once services are registered dynamically - this.serviceMap = [v1Service, v2Service].reduce( - (serviceMap, service) => ({ - ...serviceMap, - [service.version]: service, - }), - {} - ) as ServiceMap - - this.logger.debug(`Initializing Credentials Module for agent ${this.agentContext.config.label}`) - } - - public getService(protocolVersion: PVT): CredentialService { - if (!this.serviceMap[protocolVersion]) { - throw new AriesFrameworkError(`No credential service registered for protocol version ${protocolVersion}`) - } - - return this.serviceMap[protocolVersion] - } - - /** - * Initiate a new credential exchange as holder by sending a credential proposal message - * to the connection with the specified credential options - * - * @param options configuration to use for the proposal - * @returns Credential exchange record associated with the sent proposal message - */ - - public async proposeCredential(options: ProposeCredentialOptions): Promise { - const service = this.getService(options.protocolVersion) - - this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) - - const connection = await this.connectionService.getById(this.agentContext, options.connectionId) - - // will get back a credential record -> map to Credential Exchange Record - const { credentialRecord, message } = await service.createProposal(this.agentContext, { - connection, - credentialFormats: options.credentialFormats, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - - this.logger.debug('We have a message (sending outbound): ', message) - - // send the message here - const outbound = createOutboundMessage(connection, message) - - this.logger.debug('In proposeCredential: Send Proposal to Issuer') - await this.messageSender.sendMessage(this.agentContext, outbound) - return credentialRecord - } - - /** - * Accept a credential proposal as issuer (by sending a credential offer message) to the connection - * associated with the credential record. - * - * @param options config object for accepting the proposal - * @returns Credential exchange record associated with the credential offer - * - */ - public async acceptProposal(options: AcceptProposalOptions): Promise { - const credentialRecord = await this.getById(options.credentialRecordId) - - if (!credentialRecord.connectionId) { - throw new AriesFrameworkError( - `No connectionId found for credential record '${credentialRecord.id}'. Connection-less issuance does not support credential proposal or negotiation.` - ) - } - - // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) - - // will get back a credential record -> map to Credential Exchange Record - const { message } = await service.acceptProposal(this.agentContext, { - credentialRecord, - credentialFormats: options.credentialFormats, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - - // send the message - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outbound = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outbound) - - return credentialRecord - } - - /** - * Negotiate a credential proposal as issuer (by sending a credential offer message) to the connection - * associated with the credential record. - * - * @param options configuration for the offer see {@link NegotiateProposalOptions} - * @returns Credential exchange record associated with the credential offer - * - */ - public async negotiateProposal(options: NegotiateProposalOptions): Promise { - const credentialRecord = await this.getById(options.credentialRecordId) - - if (!credentialRecord.connectionId) { - throw new AriesFrameworkError( - `No connection id for credential record ${credentialRecord.id} not found. Connection-less issuance does not support negotiation` - ) - } - - // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) - - const { message } = await service.negotiateProposal(this.agentContext, { - credentialRecord, - credentialFormats: options.credentialFormats, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return credentialRecord - } - - /** - * Initiate a new credential exchange as issuer by sending a credential offer message - * to the connection with the specified connection id. - * - * @param options config options for the credential offer - * @returns Credential exchange record associated with the sent credential offer message - */ - public async offerCredential(options: OfferCredentialOptions): Promise { - const connection = await this.connectionService.getById(this.agentContext, options.connectionId) - const service = this.getService(options.protocolVersion) - - this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) - - const { message, credentialRecord } = await service.createOffer(this.agentContext, { - credentialFormats: options.credentialFormats, - autoAcceptCredential: options.autoAcceptCredential, - comment: options.comment, - connection, - }) - - this.logger.debug('Offer Message successfully created; message= ', message) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return credentialRecord - } - - /** - * Accept a credential offer as holder (by sending a credential request message) to the connection - * associated with the credential record. - * - * @param options The object containing config options of the offer to be accepted - * @returns Object containing offer associated credential record - */ - public async acceptOffer(options: AcceptOfferOptions): Promise { - const credentialRecord = await this.getById(options.credentialRecordId) - - const service = this.getService(credentialRecord.protocolVersion) - - this.logger.debug(`Got a CredentialService object for this version; version = ${service.version}`) - const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) - - // Use connection if present - if (credentialRecord.connectionId) { - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - - const { message } = await service.acceptOffer(this.agentContext, { - credentialRecord, - credentialFormats: options.credentialFormats, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return credentialRecord - } - // Use ~service decorator otherwise - else if (offerMessage?.service) { - // Create ~service decorator - const routing = await this.routingService.getRouting(this.agentContext) - const ourService = new ServiceDecorator({ - serviceEndpoint: routing.endpoints[0], - recipientKeys: [routing.recipientKey.publicKeyBase58], - routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), - }) - const recipientService = offerMessage.service - - const { message } = await service.acceptOffer(this.agentContext, { - credentialRecord, - credentialFormats: options.credentialFormats, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - - // Set and save ~service decorator to record (to remember our verkey) - message.service = ourService - await this.didCommMessageRepo.saveOrUpdateAgentMessage(this.agentContext, { - agentMessage: message, - role: DidCommMessageRole.Sender, - associatedRecordId: credentialRecord.id, - }) - - await this.messageSender.sendMessageToService(this.agentContext, { - message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - returnRoute: true, - }) - - return credentialRecord - } - // Cannot send message without connectionId or ~service decorator - else { - throw new AriesFrameworkError( - `Cannot accept offer for credential record without connectionId or ~service decorator on credential offer.` - ) - } - } - - public async declineOffer(credentialRecordId: string): Promise { - const credentialRecord = await this.getById(credentialRecordId) - credentialRecord.assertState(CredentialState.OfferReceived) - - // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) - await service.updateState(this.agentContext, credentialRecord, CredentialState.Declined) - - return credentialRecord - } - - public async negotiateOffer(options: NegotiateOfferOptions): Promise { - const credentialRecord = await this.getById(options.credentialRecordId) - - const service = this.getService(credentialRecord.protocolVersion) - const { message } = await service.negotiateOffer(this.agentContext, { - credentialFormats: options.credentialFormats, - credentialRecord, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - - if (!credentialRecord.connectionId) { - throw new AriesFrameworkError( - `No connection id for credential record ${credentialRecord.id} not found. Connection-less issuance does not support negotiation` - ) - } - - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return credentialRecord - } - - /** - * Initiate a new credential exchange as issuer by creating a credential offer - * not bound to any connection. The offer must be delivered out-of-band to the holder - * @param options The credential options to use for the offer - * @returns The credential record and credential offer message - */ - public async createOffer(options: CreateOfferOptions): Promise<{ - message: AgentMessage - credentialRecord: CredentialExchangeRecord - }> { - const service = this.getService(options.protocolVersion) - - this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) - const { message, credentialRecord } = await service.createOffer(this.agentContext, { - credentialFormats: options.credentialFormats, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - - this.logger.debug('Offer Message successfully created; message= ', message) - - return { message, credentialRecord } - } - - /** - * Accept a credential request as holder (by sending a credential request message) to the connection - * associated with the credential record. - * - * @param options The object containing config options of the request - * @returns CredentialExchangeRecord updated with information pertaining to this request - */ - public async acceptRequest(options: AcceptRequestOptions): Promise { - const credentialRecord = await this.getById(options.credentialRecordId) - - // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) - - this.logger.debug(`Got a CredentialService object for version ${credentialRecord.protocolVersion}`) - - const { message } = await service.acceptRequest(this.agentContext, { - credentialRecord, - credentialFormats: options.credentialFormats, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - this.logger.debug('We have a credential message (sending outbound): ', message) - - const requestMessage = await service.findRequestMessage(this.agentContext, credentialRecord.id) - const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) - - // Use connection if present - if (credentialRecord.connectionId) { - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return credentialRecord - } - // Use ~service decorator otherwise - else if (requestMessage?.service && offerMessage?.service) { - const recipientService = requestMessage.service - const ourService = offerMessage.service - - message.service = ourService - await this.didCommMessageRepo.saveOrUpdateAgentMessage(this.agentContext, { - agentMessage: message, - role: DidCommMessageRole.Sender, - associatedRecordId: credentialRecord.id, - }) - - await this.messageSender.sendMessageToService(this.agentContext, { - message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - returnRoute: true, - }) - - return credentialRecord - } - // Cannot send message without connectionId or ~service decorator - else { - throw new AriesFrameworkError( - `Cannot accept request for credential record without connectionId or ~service decorator on credential offer / request.` - ) - } - } - - /** - * Accept a credential as holder (by sending a credential acknowledgement message) to the connection - * associated with the credential record. - * - * @param credentialRecordId The id of the credential record for which to accept the credential - * @returns credential exchange record associated with the sent credential acknowledgement message - * - */ - public async acceptCredential(options: AcceptCredentialOptions): Promise { - const credentialRecord = await this.getById(options.credentialRecordId) - - // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) - - this.logger.debug(`Got a CredentialService object for version ${credentialRecord.protocolVersion}`) - - const { message } = await service.acceptCredential(this.agentContext, { - credentialRecord, - }) - - const requestMessage = await service.findRequestMessage(this.agentContext, credentialRecord.id) - const credentialMessage = await service.findCredentialMessage(this.agentContext, credentialRecord.id) - - if (credentialRecord.connectionId) { - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outboundMessage = createOutboundMessage(connection, message) - - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return credentialRecord - } - // Use ~service decorator otherwise - else if (credentialMessage?.service && requestMessage?.service) { - const recipientService = credentialMessage.service - const ourService = requestMessage.service - - await this.messageSender.sendMessageToService(this.agentContext, { - message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - returnRoute: true, - }) - - return credentialRecord - } - // Cannot send message without connectionId or ~service decorator - else { - throw new AriesFrameworkError( - `Cannot accept credential without connectionId or ~service decorator on credential message.` - ) - } - } - - /** - * Send problem report message for a credential record - * @param credentialRecordId The id of the credential record for which to send problem report - * @param message message to send - * @returns credential record associated with the credential problem report message - */ - public async sendProblemReport(options: SendProblemReportOptions) { - const credentialRecord = await this.getById(options.credentialRecordId) - if (!credentialRecord.connectionId) { - throw new AriesFrameworkError(`No connectionId found for credential record '${credentialRecord.id}'.`) - } - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - - const service = this.getService(credentialRecord.protocolVersion) - const problemReportMessage = service.createProblemReport(this.agentContext, { message: options.message }) - problemReportMessage.setThread({ - threadId: credentialRecord.threadId, - }) - const outboundMessage = createOutboundMessage(connection, problemReportMessage) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return credentialRecord - } - - public async getFormatData(credentialRecordId: string): Promise> { - const credentialRecord = await this.getById(credentialRecordId) - const service = this.getService(credentialRecord.protocolVersion) - - return service.getFormatData(this.agentContext, credentialRecordId) - } - - /** - * Retrieve a credential record by id - * - * @param credentialRecordId The credential record id - * @throws {RecordNotFoundError} If no record is found - * @return The credential record - * - */ - public getById(credentialRecordId: string): Promise { - return this.credentialRepository.getById(this.agentContext, credentialRecordId) - } - - /** - * Retrieve all credential records - * - * @returns List containing all credential records - */ - public getAll(): Promise { - return this.credentialRepository.getAll(this.agentContext) - } - - /** - * Find a credential record by id - * - * @param credentialRecordId the credential record id - * @returns The credential record or null if not found - */ - public findById(credentialRecordId: string): Promise { - return this.credentialRepository.findById(this.agentContext, credentialRecordId) - } - - /** - * Delete a credential record by id, also calls service to delete from wallet - * - * @param credentialId the credential record id - * @param options the delete credential options for the delete operation - */ - public async deleteById(credentialId: string, options?: DeleteCredentialOptions) { - const credentialRecord = await this.getById(credentialId) - const service = this.getService(credentialRecord.protocolVersion) - return service.delete(this.agentContext, credentialRecord, options) - } - - public async findProposalMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - - return service.findProposalMessage(this.agentContext, credentialExchangeId) - } - - public async findOfferMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - - return service.findOfferMessage(this.agentContext, credentialExchangeId) - } - - public async findRequestMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - - return service.findRequestMessage(this.agentContext, credentialExchangeId) - } - - public async findCredentialMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - - return service.findCredentialMessage(this.agentContext, credentialExchangeId) - } - - private async getServiceForCredentialExchangeId(credentialExchangeId: string) { - const credentialExchangeRecord = await this.getById(credentialExchangeId) +export class CredentialsModule { + public readonly config: CredentialsModuleConfig - return this.getService(credentialExchangeRecord.protocolVersion) + public constructor(config: CredentialsModuleConfigOptions) { + this.config = new CredentialsModuleConfig(config) } /** * Registers the dependencies of the credentials module on the dependency manager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api - dependencyManager.registerContextScoped(CredentialsModule) + dependencyManager.registerContextScoped(CredentialsApi) // Services dependencyManager.registerSingleton(V1CredentialService) diff --git a/packages/core/src/modules/credentials/CredentialsModuleConfig.ts b/packages/core/src/modules/credentials/CredentialsModuleConfig.ts new file mode 100644 index 0000000000..a1f5359653 --- /dev/null +++ b/packages/core/src/modules/credentials/CredentialsModuleConfig.ts @@ -0,0 +1,21 @@ +import { AutoAcceptCredential } from './models' + +/** + * CredentialsModuleConfigOptions defines the interface for the options of the CredentialsModuleConfig class. + * This can contain optional parameters that have default values in the config class itself. + */ +export interface CredentialsModuleConfigOptions { + autoAcceptCredentials: AutoAcceptCredential +} + +export class CredentialsModuleConfig { + private options: CredentialsModuleConfigOptions + + public constructor(options: CredentialsModuleConfigOptions) { + this.options = options + } + + public get autoAcceptCredentials() { + return this.options.autoAcceptCredentials ?? AutoAcceptCredential.Never + } +} diff --git a/packages/core/src/modules/credentials/index.ts b/packages/core/src/modules/credentials/index.ts index 8b24da2371..b2668f4e3d 100644 --- a/packages/core/src/modules/credentials/index.ts +++ b/packages/core/src/modules/credentials/index.ts @@ -1,4 +1,4 @@ -export * from './CredentialsModule' +export * from './CredentialsApi' export * from './repository' export * from './CredentialEvents' diff --git a/packages/core/src/plugins/Module.ts b/packages/core/src/plugins/Module.ts index c209e26021..5210e2d9c4 100644 --- a/packages/core/src/plugins/Module.ts +++ b/packages/core/src/plugins/Module.ts @@ -1,8 +1,7 @@ +import type { Constructor } from '../utils/mixins' import type { DependencyManager } from './DependencyManager' export interface Module { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - new (...args: any[]): any register(dependencyManager: DependencyManager): void } @@ -11,5 +10,5 @@ export interface Module { * on the class declaration. */ export function module() { - return (constructor: U) => constructor + return >(constructor: U) => constructor }