• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

alkem-io / server / #8050

16 Aug 2024 11:21AM UTC coverage: 13.92%. First build
#8050

Pull #4411

travis-ci

Pull Request #4411: Type added to authorization policy entity

80 of 4158 branches covered (1.92%)

Branch coverage included in aggregate %.

61 of 116 new or added lines in 50 files covered. (52.59%)

1945 of 10389 relevant lines covered (18.72%)

3.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

9.83
/src/domain/agent/agent/agent.service.ts
1
import { PubSubEngine } from 'graphql-subscriptions';
29✔
2
import { SUBSCRIPTION_PROFILE_VERIFIED_CREDENTIAL } from '@common/constants';
29✔
3
import { Profiling } from '@common/decorators/profiling.decorator';
29✔
4
import { LogContext } from '@common/enums';
29✔
5
import {
29✔
6
  AuthenticationException,
7
  EntityNotFoundException,
8
  EntityNotInitializedException,
9
  ValidationException,
10
} from '@common/exceptions';
11
import { SsiException } from '@common/exceptions/ssi.exception';
29✔
12
import { SubscriptionType } from '@common/enums/subscription.type';
29✔
13
import { ProfileCredentialVerified } from '@domain/agent/agent/dto/agent.dto.profile.credential.verified';
14
import { Agent, CreateAgentInput, IAgent } from '@domain/agent/agent';
29✔
15
import { CredentialsSearchInput, ICredential } from '@domain/agent/credential';
16
import { AuthorizationPolicy } from '@domain/common/authorization-policy/authorization.policy.entity';
29✔
17
import { AuthorizationPolicyService } from '@domain/common/authorization-policy/authorization.policy.service';
29✔
18
import { Inject, Injectable, LoggerService } from '@nestjs/common';
29✔
19
import { CACHE_MANAGER } from '@nestjs/cache-manager';
29✔
20
import { ConfigService } from '@nestjs/config';
29✔
21
import { InjectRepository } from '@nestjs/typeorm';
29✔
22
import { Cache } from 'cache-manager';
23
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
29✔
24
import { FindOneOptions, Repository } from 'typeorm';
29✔
25
import { CredentialService } from '../credential/credential.service';
29✔
26
import { WalletManagerCommand } from '@common/enums/wallet.manager.command';
29✔
27
import { CredentialMetadataOutput } from '../verified-credential/dto/verified.credential.dto.metadata';
28
import { IClaim } from '@services/external/trust-registry/trust.registry.claim/claim.interface';
29
import { TrustRegistryAdapter } from '@services/external/trust-registry/trust.registry.adapter/trust.registry.adapter';
29✔
30
import { RevokeCredentialInput } from './dto/agent.dto.credential.revoke';
31
import { AgentBeginVerifiedCredentialRequestOutput } from './dto/agent.dto.verified.credential.request.begin.output';
32
import { AgentBeginVerifiedCredentialOfferOutput } from './dto/agent.dto.verified.credential.offer.begin.output';
33
import { VerifiedCredentialService } from '../verified-credential/verified.credential.service';
29✔
34
import { IVerifiedCredential } from '../verified-credential/verified.credential.interface';
35
import { AgentInteractionVerifiedCredentialRequestJolocom } from './dto/agent.dto.interaction.verified.credential.request.jolocom';
36
import { SsiIssuerType } from '@common/enums/ssi.issuer.type';
29✔
37
import { SsiInteractionNotFound } from '@common/exceptions/ssi.interaction.not.found';
29✔
38
import { AgentInteractionVerifiedCredentialOffer } from './dto/agent.dto.interaction.verified.credential.offer';
39
import { SsiSovrhdAdapter } from '@services/adapters/ssi-sovrhd/ssi.sovrhd.adapter';
29✔
40
import { WalletManagerAdapter } from '@services/adapters/wallet-manager-adapter/wallet.manager.adapter';
29✔
41
import { VerifiedCredential } from '../verified-credential/dto/verified.credential.dto.result';
29✔
42
import { SsiSovrhdRegisterCallbackSession } from '@services/adapters/ssi-sovrhd/dto/ssi.sovrhd.dto.register.callback.session';
43
import { AgentInteractionVerifiedCredentialRequestSovrhd } from './dto/agent.dto.interaction.verified.credential.request.sovrhd';
44
import { SsiSovrhdRegisterCallbackCredential } from '@services/adapters/ssi-sovrhd/dto/ssi.sovrhd.dto.register.callback.credential';
45
import { getRandomId } from '@src/common/utils';
29✔
46
import { AgentInfoCacheService } from '../../../core/authentication.agent.info/agent.info.cache.service';
29✔
47
import { GrantCredentialToAgentInput } from './dto/agent.dto.credential.grant';
48
import { AlkemioConfig } from '@src/types';
49
import { AuthorizationPolicyType } from '@common/enums/authorization.policy.type';
29✔
50

51
@Injectable()
52
export class AgentService {
29✔
53
  private readonly cache_ttl: number;
54

55
  constructor(
56
    private agentInfoCacheService: AgentInfoCacheService,
×
57
    private authorizationPolicyService: AuthorizationPolicyService,
×
58
    private configService: ConfigService<AlkemioConfig, true>,
×
59
    private credentialService: CredentialService,
×
60
    @InjectRepository(Agent)
61
    private agentRepository: Repository<Agent>,
×
62
    private trustRegistryAdapter: TrustRegistryAdapter,
×
63
    private ssiSovrhdAdapter: SsiSovrhdAdapter,
×
64
    private walletManagerAdapter: WalletManagerAdapter,
×
65
    private verifiedCredentialService: VerifiedCredentialService,
×
66
    @Inject(WINSTON_MODULE_NEST_PROVIDER)
67
    private readonly logger: LoggerService,
×
68
    @Inject(CACHE_MANAGER)
69
    private readonly cacheManager: Cache,
×
70
    @Inject(SUBSCRIPTION_PROFILE_VERIFIED_CREDENTIAL)
71
    private subscriptionVerifiedCredentials: PubSubEngine
×
72
  ) {
73
    this.cache_ttl = this.configService.get(
×
74
      'identity.authentication.cache_ttl',
75
      { infer: true }
76
    );
77
  }
78

79
  public async createAgent(inputData: CreateAgentInput): Promise<IAgent> {
80
    // a very weird type error is resolved by spreading the input
×
81
    const agent: IAgent = Agent.create({ ...inputData });
×
NEW
82
    agent.credentials = [];
×
83
    agent.authorization = new AuthorizationPolicy(
84
      AuthorizationPolicyType.AGENT
85
    );
×
86
    agent.type = inputData.type;
87

×
88
    const ssiEnabled = this.configService.get('ssi.enabled', { infer: true });
89

×
90
    if (ssiEnabled) {
×
91
      return this.createDidOnAgent(agent);
92
    }
93

×
94
    return agent;
95
  }
96

97
  async getAgentOrFail(
98
    agentID: string,
99
    options?: FindOneOptions<Agent>
100
  ): Promise<IAgent | never> {
×
101
    const agent = await this.agentRepository.findOne({
102
      where: { id: agentID },
103
      ...options,
104
    });
×
105
    if (!agent)
×
106
      throw new EntityNotFoundException(
107
        `No Agent found with the given id: ${agentID}`,
108
        LogContext.AGENT
109
      );
×
110
    return agent;
111
  }
112

113
  async deleteAgent(agentID: string): Promise<IAgent> {
114
    // Note need to load it in with all contained entities so can remove fully
×
115
    const agent = await this.getAgentOrFail(agentID);
116
    // Remove all credentials
×
117
    if (agent.credentials) {
×
118
      for (const credential of agent.credentials) {
×
119
        await this.credentialService.deleteCredential(credential.id);
120
      }
121
    }
×
122
    if (agent.authorization)
×
123
      await this.authorizationPolicyService.delete(agent.authorization);
124

×
125
    return await this.agentRepository.remove(agent as Agent);
126
  }
127

128
  async saveAgent(agent: IAgent): Promise<IAgent> {
×
129
    return await this.agentRepository.save(agent);
130
  }
131

132
  async findAgentsWithMatchingCredentials(
133
    credentialCriteria: CredentialsSearchInput
134
  ): Promise<IAgent[]> {
135
    const matchingCredentials =
×
136
      await this.credentialService.findMatchingCredentials(credentialCriteria);
137

×
138
    const agents: IAgent[] = [];
×
139
    for (const match of matchingCredentials) {
×
140
      const agent = match.agent;
×
141
      if (agent) {
×
142
        agents.push(agent);
143
      }
144
    }
×
145
    return agents;
146
  }
147

148
  private getAgentCacheKey(agentId: string): string {
×
149
    return `@agent:id:${agentId}`;
150
  }
151

152
  private async getAgentFromCache(id: string): Promise<IAgent | undefined> {
×
153
    return await this.cacheManager.get<IAgent>(this.getAgentCacheKey(id));
154
  }
155

156
  private async setAgentCache(agent: IAgent): Promise<IAgent> {
×
157
    const cacheKey = this.getAgentCacheKey(agent.id);
×
158
    return await this.cacheManager.set<IAgent>(cacheKey, agent, {
159
      ttl: this.cache_ttl,
160
    });
161
  }
162

163
  async getAgentCredentials(
164
    agentID: string
165
  ): Promise<{ agent: IAgent; credentials: ICredential[] }> {
×
166
    let agent: IAgent | undefined = await this.getAgentFromCache(agentID);
×
167
    if (!agent || !agent.credentials) {
×
168
      agent = await this.getAgentOrFail(agentID, {
169
        relations: { credentials: true },
170
      });
171

×
172
      if (agent) {
×
173
        await this.setAgentCache(agent);
174
      }
×
175
      if (!agent.credentials) {
×
176
        throw new EntityNotInitializedException(
177
          `Agent not initialized: ${agentID}`,
178
          LogContext.AGENT
179
        );
180
      }
181
    }
×
182
    return { agent: agent, credentials: agent.credentials };
183
  }
184

185
  /**
186
   *
187
   * @param grantCredentialData
×
188
   * @throws ValidationException If the agent already has a credential of the same type AND resourceID
189
   */
190
  async grantCredentialOrFail(
191
    grantCredentialData: GrantCredentialToAgentInput
×
192
  ): Promise<IAgent> {
193
    const { agent, credentials } = await this.getAgentCredentials(
194
      grantCredentialData.agentID
×
195
    );
×
196

×
197
    if (!grantCredentialData.resourceID) grantCredentialData.resourceID = '';
198

199
    // Check if the agent already has this credential type + Value
×
200
    for (const credential of credentials) {
201
      if (
202
        credential.type === grantCredentialData.type &&
203
        credential.resourceID === grantCredentialData.resourceID
204
      ) {
205
        throw new ValidationException(
206
          'Agent already has credential of this type on this resource',
207
          LogContext.AUTH,
×
208
          {
209
            agentId: agent.id,
×
210
            credentialType: grantCredentialData.type,
×
211
            resourceID: grantCredentialData.resourceID,
×
212
          }
213
        );
×
214
      }
215
    }
216

217
    const credential =
218
      await this.credentialService.createCredential(grantCredentialData);
219
    credential.agent = agent;
×
220
    await this.credentialService.save(credential);
221
    const agentWithCredential = await this.getAgentOrFail(agent.id);
222
    await this.agentInfoCacheService.updateAgentInfoCache(agentWithCredential);
223
    await this.setAgentCache(agentWithCredential);
×
224

225
    return agentWithCredential;
×
226
  }
×
227

×
228
  async revokeCredential(
×
229
    revokeCredentialData: RevokeCredentialInput
230
  ): Promise<IAgent> {
231
    const { agent, credentials } = await this.getAgentCredentials(
×
232
      revokeCredentialData.agentID
233
    );
×
234

235
    if (!revokeCredentialData.resourceID) revokeCredentialData.resourceID = '';
236

×
237
    const newCredentials: ICredential[] = [];
×
238
    for (const credential of credentials) {
×
239
      if (
240
        credential.type === revokeCredentialData.type &&
×
241
        credential.resourceID === revokeCredentialData.resourceID
242
      ) {
243
        await this.credentialService.deleteCredential(credential.id);
244
      } else {
245
        newCredentials.push(credential);
246
      }
247
    }
×
248
    agent.credentials = newCredentials;
249
    await this.agentInfoCacheService.updateAgentInfoCache(agent);
×
250
    await this.setAgentCache(agent);
×
251

×
252
    return await this.saveAgent(agent);
×
253
  }
×
254

255
  async hasValidCredential(
256
    agentID: string,
257
    credentialCriteria: CredentialsSearchInput
×
258
  ): Promise<boolean> {
259
    const { credentials } = await this.getAgentCredentials(agentID);
260

261
    for (const credential of credentials) {
29✔
262
      if (credential.type === credentialCriteria.type) {
×
263
        if (!credentialCriteria.resourceID) return true;
264
        if (credentialCriteria.resourceID === credential.resourceID)
×
265
          return true;
266
      }
×
267
    }
268

269
    return false;
270
  }
271

272
  @Profiling.api
×
273
  async createDidOnAgent(agent: IAgent): Promise<IAgent> {
274
    agent.password = Math.random().toString(36).substr(2, 10);
×
275

276
    agent.did = await this.walletManagerAdapter.createIdentity(agent.password);
277

278
    return agent;
×
279
  }
×
280

×
281
  async getVerifiedCredentials(
×
282
    agentID: string
×
283
  ): Promise<IVerifiedCredential[]> {
×
284
    const agent = await this.getAgentOrFail(agentID);
×
285
    const verifiedCredentialsWalletMgr =
×
286
      await this.walletManagerAdapter.getVerifiedCredentials(
×
287
        agent.did,
×
288
        agent.password
289
      );
×
290
    const verifiedCredentials: IVerifiedCredential[] = [];
291
    for (const vcWalletMgr of verifiedCredentialsWalletMgr) {
292
      const verifiedCredential = new VerifiedCredential();
×
293
      verifiedCredential.name = vcWalletMgr.name;
294
      verifiedCredential.type = vcWalletMgr.type;
295
      verifiedCredential.issued = vcWalletMgr.issued;
296
      verifiedCredential.issuer = vcWalletMgr.issuer;
29✔
297
      verifiedCredential.expires = vcWalletMgr.expires;
298
      verifiedCredential.context = vcWalletMgr.context || '';
299
      verifiedCredential.claims =
300
        await this.verifiedCredentialService.getClaims(vcWalletMgr.claim);
×
301
      verifiedCredentials.push(verifiedCredential);
302
    }
×
303

×
304
    return verifiedCredentials;
305
  }
306

307
  @Profiling.api
308
  async beginCredentialRequestInteraction(
309
    issuerAgentID: string,
×
310
    credentialTypes: string[]
311
  ): Promise<AgentBeginVerifiedCredentialRequestOutput> {
312
    const issuerAgent = await this.getAgentOrFail(issuerAgentID);
×
313
    // todo: for now only support a single type per request; may want to enforce this.
314
    if (credentialTypes.length !== 1) {
×
315
      throw new SsiException(
316
        `[beginCredentialRequestInteraction]: Only a single credential must be requested: ${credentialTypes.length}`
×
317
      );
318
    }
×
319

×
320
    const requestedCredentialMetadata =
×
321
      this.trustRegistryAdapter.getVerifiedCredentialMetadata(
322
        credentialTypes[0]
323
      );
324
    const vcIssuer = requestedCredentialMetadata.issuer;
325
    const vcIssuerType =
×
326
      this.trustRegistryAdapter.getVcIssuerTypeOrFail(vcIssuer);
327

328
    const nonce = this.trustRegistryAdapter.generateNonceForInteraction();
329
    let uniqueCallbackURL =
330
      this.trustRegistryAdapter.generateCredentialRequestUrlJolocom(nonce);
331
    if (vcIssuerType === SsiIssuerType.SOVRHD) {
×
332
      uniqueCallbackURL =
333
        this.trustRegistryAdapter.generateCredentialRequestUrlSovrhd(nonce);
334
    }
335

336
    const agentWalletResponse =
337
      await this.walletManagerAdapter.beginCredentialRequestInteraction(
338
        issuerAgent.did,
×
339
        issuerAgent.password,
×
340
        uniqueCallbackURL,
341
        requestedCredentialMetadata
×
342
      );
×
343
    const clientResponse: AgentBeginVerifiedCredentialRequestOutput = {
344
      qrCodeImg: '',
345
      jwt: '',
346
    };
347

348
    // Adapt behaviour based on IssuerType
349
    const requestExpirationTtl =
×
350
      agentWalletResponse.expiresOn - new Date().getTime();
351
    if (vcIssuerType === SsiIssuerType.SOVRHD) {
352
      const sovrhdRegisterResponse =
353
        await this.ssiSovrhdAdapter.establishSession(uniqueCallbackURL);
354
      const interactionInfo: AgentInteractionVerifiedCredentialRequestSovrhd = {
355
        nonce: nonce,
356
        interactionId: agentWalletResponse.interactionId,
×
357
        agent: issuerAgent,
×
358
        sovrhdSessionId: sovrhdRegisterResponse.session,
×
359
        credentialType: requestedCredentialMetadata.uniqueType,
360
      };
×
361
      this.cacheManager.set<AgentInteractionVerifiedCredentialRequestSovrhd>(
362
        nonce,
363
        interactionInfo,
364
        {
365
          ttl: requestExpirationTtl,
×
366
        }
367
      );
368
      clientResponse.qrCodeImg = sovrhdRegisterResponse.qr;
369
    } else if (vcIssuerType === SsiIssuerType.JOLOCOM) {
370
      clientResponse.jwt = agentWalletResponse.jwt;
371
      const interactionInfo: AgentInteractionVerifiedCredentialRequestJolocom =
372
        {
373
          nonce: nonce,
374
          interactionId: agentWalletResponse.interactionId,
×
375
          agent: issuerAgent,
376
        };
377
      this.cacheManager.set<AgentInteractionVerifiedCredentialRequestJolocom>(
378
        nonce,
379
        interactionInfo,
380
        {
×
381
          ttl: requestExpirationTtl,
382
        }
383
      );
384
    }
385

386
    this.walletManagerAdapter.logVerifiedCredentialInteraction(
387
      agentWalletResponse.jwt,
×
388
      WalletManagerCommand.BEGIN_CREDENTIAL_REQUEST_INTERACTION,
389
      'begin'
390
    );
×
391

×
392
    return clientResponse;
393
  }
394

395
  private async getRequestInteractionJolocomInfoFromCache(
396
    nonce: string
×
397
  ): Promise<AgentInteractionVerifiedCredentialRequestJolocom> {
×
398
    const interactionInfo =
×
399
      await this.cacheManager.get<AgentInteractionVerifiedCredentialRequestJolocom>(
400
        nonce
401
      );
×
402
    if (!interactionInfo) {
403
      throw new SsiInteractionNotFound(
404
        `Unable to find interaction for nonce: ${nonce}`,
405
        LogContext.SSI
×
406
      );
407
    }
408
    const agent = interactionInfo.agent;
409
    if (!agent) {
410
      throw new Error('An agent could not be found for the interactionId');
411
    }
412

×
413
    this.logger.verbose?.(
414
      `Interaction with agent ${agent.did} retrieved`,
415
      LogContext.SSI
×
416
    );
×
417
    return interactionInfo;
418
  }
419

420
  private async getRequestInteractionSovrhdInfoFromCache(
421
    nonce: string
×
422
  ): Promise<AgentInteractionVerifiedCredentialRequestSovrhd> {
×
423
    const interactionInfo =
×
424
      await this.cacheManager.get<AgentInteractionVerifiedCredentialRequestSovrhd>(
425
        nonce
426
      );
×
427
    if (!interactionInfo) {
428
      throw new SsiInteractionNotFound(
429
        `Unable to find interaction for nonce: ${nonce}`,
430
        LogContext.SSI
×
431
      );
432
    }
433
    const agent = interactionInfo.agent;
434
    if (!agent) {
435
      throw new Error('An agent could not be found for the interactionId');
436
    }
437

438
    this.logger.verbose?.(
×
439
      `InteractionId with agent ${agent.did} retrieved`,
440
      LogContext.SSI
×
441
    );
442
    return interactionInfo;
443
  }
444

445
  async completeCredentialRequestInteractionJolocom(
446
    nonce: string,
447
    token: string
×
448
  ): Promise<void> {
×
449
    const interactionInfo =
×
450
      await this.getRequestInteractionJolocomInfoFromCache(nonce);
×
451

452
    this.walletManagerAdapter.logVerifiedCredentialInteraction(
453
      token,
454
      WalletManagerCommand.COMPLETE_CREDENTIAL_REQUEST_INTERACTION_JOLOCOM,
455
      'response'
×
456
    );
457

×
458
    // Retrieve the credential to store
×
459
    const tokenDecoded: any = this.walletManagerAdapter.decodeJwt(token);
460
    const vcToBeStored = tokenDecoded.interactionToken.suppliedCredentials[0];
461
    const vcName = vcToBeStored.name;
×
462
    this.logger.verbose?.(
463
      `[completeCredentialRequestInteraction]: received VC with name '${vcName}' to be stored`,
464
      LogContext.SSI
465
    );
×
466

×
467
    this.validateTrustedIssuerOrFail(vcName, vcToBeStored);
468

469
    const agent = interactionInfo.agent;
×
470
    await this.walletManagerAdapter.completeCredentialRequestInteractionJolocom(
471
      agent.did,
472
      agent.password,
×
473
      interactionInfo?.interactionId,
474
      token
475
    );
476

477
    const eventID = `credentials-${getRandomId()}`;
478
    const payload: ProfileCredentialVerified = {
479
      eventID,
480
      vc: 'something something vc',
481
      userEmail: agent.id ?? '',
482
    };
483

×
484
    await this.subscriptionVerifiedCredentials.publish(
485
      SubscriptionType.PROFILE_VERIFIED_CREDENTIAL,
×
486
      payload
487
    );
488
  }
489

×
490
  async completeCredentialRequestInteractionSovrhd(
491
    nonce: string,
×
492
    data: any
×
493
  ): Promise<void> {
494
    const interactionInfo =
×
495
      await this.getRequestInteractionSovrhdInfoFromCache(nonce);
496

497
    this.logger.verbose?.(
498
      `sovhrd callback data: ${JSON.stringify(data)}`,
×
499
      LogContext.SSI_SOVRHD
500
    );
501
    if (data.id) {
502
      // assume the callback to establish the session
503
      await this.callbackCredentialRequestSovrhdSession(data, interactionInfo);
504
      return;
505
    } else {
506
      await this.callbackCredentialRequestSovrhdCredential(
507
        data,
×
508
        interactionInfo
509
      );
510
      return;
511
    }
512
  }
×
513

514
  async callbackCredentialRequestSovrhdSession(
×
515
    data: SsiSovrhdRegisterCallbackSession,
516
    interactionInfo: AgentInteractionVerifiedCredentialRequestSovrhd
517
  ): Promise<void> {
518
    const requestCredentialsResponse =
519
      await this.ssiSovrhdAdapter.requestCredentials(
520
        data.session,
521
        data.id,
522
        interactionInfo.credentialType
×
523
      );
524
    if (requestCredentialsResponse.result === 'ok') {
525
      // request has been made, await now the second call back
526
      return;
527
    }
×
528
  }
×
529

×
530
  async callbackCredentialRequestSovrhdCredential(
531
    data: SsiSovrhdRegisterCallbackCredential,
532
    interactionInfo: AgentInteractionVerifiedCredentialRequestSovrhd
×
533
  ): Promise<void> {
×
534
    this.logger.verbose?.(
535
      `Sovhrd credential callback: ${interactionInfo.credentialType}`,
536
      LogContext.SSI_SOVRHD
537
    );
538
    const validateCredential =
×
539
      this.ssiSovrhdAdapter.validateSovrhdCredentialResponse(data);
×
540
    if (!validateCredential) {
541
      return;
542
    }
×
543

544
    const credentials = data.content.verifiableCredential;
545
    this.logger.verbose?.(
546
      `Sovhrd credentials returned: ${credentials.length}`,
547
      LogContext.SSI_SOVRHD
×
548
    );
×
549

550
    const agent = interactionInfo.agent;
551
    await this.walletManagerAdapter.completeCredentialRequestInteractionSovrhd(
×
552
      agent.did,
553
      agent.password,
554
      interactionInfo?.interactionId,
×
555
      JSON.stringify(credentials[0]),
556
      interactionInfo.credentialType
557
    );
558

559
    const eventID = `credentials-${getRandomId()}`;
560
    const payload: ProfileCredentialVerified = {
561
      eventID,
×
562
      vc: 'something something vc',
563
      userEmail: agent.id ?? '', // TODO: not a flow we are maintaining at the moment
564
    };
565

×
566
    await this.subscriptionVerifiedCredentials.publish(
567
      SubscriptionType.PROFILE_VERIFIED_CREDENTIAL,
568
      payload
×
569
    );
570
  }
571

×
572
  validateTrustedIssuerOrFail(vcName: string, vcToBeStored: any) {
573
    const trustedIssuerValidationEnabled = this.configService.get(
574
      'ssi.issuer_validation_enabled',
575
      { infer: true }
×
576
    );
×
577
    if (!trustedIssuerValidationEnabled) return;
578

579
    const trustedIssuers =
580
      this.trustRegistryAdapter.getTrustedIssuersForCredentialNameOrFail(
29✔
581
        vcName
582
      );
583
    this.logger.verbose?.(
584
      `[completeCredentialRequestInteraction]: retrieved trusted issuers for VC with name '${vcName}': ${trustedIssuers}`,
×
585
      LogContext.SSI
×
586
    );
587
    const issuer = vcToBeStored.issuer;
588
    this.trustRegistryAdapter.validateIssuerOrFail(vcName, issuer);
589
  }
590

×
591
  @Profiling.api
592
  async beginCredentialOfferInteraction(
593
    issuerAgentID: string,
×
594
    credentials: { type: string; claims: IClaim[] }[]
595
  ): Promise<AgentBeginVerifiedCredentialOfferOutput> {
596
    if (!issuerAgentID || issuerAgentID.length == 0) {
×
597
      throw new AuthenticationException(
598
        'Unable to retrieve authenticated agent; no identifier',
599
        LogContext.AGENT
×
600
      );
601
    }
602
    const issuerAgent = await this.getAgentOrFail(issuerAgentID);
603

604
    const { nonce, uniqueCallbackURL } =
605
      this.trustRegistryAdapter.generateCredentialOfferUrl();
606

607
    const offeredCredentials =
×
608
      this.trustRegistryAdapter.getCredentialOffers(credentials);
×
609

610
    const credentialOfferResponse =
611
      await this.walletManagerAdapter.beingCredentialOfferInteraction(
612
        issuerAgent.did,
613
        issuerAgent.password,
614
        uniqueCallbackURL,
615
        offeredCredentials
×
616
      );
617

618
    const requestExpirationTtl =
619
      credentialOfferResponse.expiresOn - new Date().getTime();
620
    const interactionInfo: AgentInteractionVerifiedCredentialOffer = {
621
      nonce: nonce,
622
      issuer: SsiIssuerType.JOLOCOM,
623
      agent: issuerAgent,
×
624
      interactionId: credentialOfferResponse.interactionId,
625
      offeredCredentials: offeredCredentials,
626
    };
627
    this.cacheManager.set<AgentInteractionVerifiedCredentialOffer>(
628
      nonce,
629
      interactionInfo,
×
630
      {
631
        ttl: requestExpirationTtl,
632
      }
633
    );
29✔
634

635
    this.walletManagerAdapter.logVerifiedCredentialInteraction(
636
      credentialOfferResponse.jwt,
637
      WalletManagerCommand.BEGIN_CREDENTIAL_OFFER_INTERACTION,
638
      'begin'
×
639
    );
640

641
    return { jwt: credentialOfferResponse.jwt, qrCodeImg: '' };
×
642
  }
×
643

644
  @Profiling.api
645
  async completeCredentialOfferInteraction(
646
    nonce: string,
647
    token: string
×
648
  ): Promise<any> {
×
649
    const interactionInfo =
×
650
      await this.cacheManager.get<AgentInteractionVerifiedCredentialOffer>(
651
        nonce
×
652
      );
×
653
    if (!interactionInfo) {
654
      throw new SsiInteractionNotFound(
655
        `Unable to locate intereaction: ${nonce}`,
×
656
        LogContext.SSI
657
      );
658
    }
659
    const interactionId = interactionInfo.interactionId;
660
    const agent = interactionInfo.agent;
661
    const offeredCredentials = interactionInfo.offeredCredentials;
662

×
663
    if (!agent || !offeredCredentials) {
664
      throw new Error('An agent could not be found for the interaction');
665
    }
666

667
    this.logger.verbose?.(
668
      `InteractionId with agent: ${interactionId} - ${
×
669
        agent.did
×
670
      } received ${token.substring(0, 25)}......`,
×
671
      LogContext.SSI
672
    );
673

×
674
    this.walletManagerAdapter.logVerifiedCredentialInteraction(
675
      token,
676
      WalletManagerCommand.COMPLETE_CREDENTIAL_OFFER_INTERACTION,
677
      '2-received'
678
    );
29✔
679

×
680
    return await this.walletManagerAdapter.completeCredentialOfferInteraction(
×
681
      agent.did,
682
      agent.password,
×
683
      interactionId,
684
      token,
685
      offeredCredentials.map(c => c.metadata)
686
    );
687
  }
×
688

689
  @Profiling.api
690
  async getSupportedCredentialMetadata(): Promise<CredentialMetadataOutput[]> {
691
    try {
692
      return this.trustRegistryAdapter
693
        .getSupportedCredentialMetadata()
694
        .map(x => ({
×
695
          ...x,
×
696
          context: JSON.stringify(x.context),
×
697
        }));
698
    } catch (err: any) {
699
      throw new SsiException(
700
        `[${WalletManagerCommand.COMPLETE_CREDENTIAL_OFFER_INTERACTION}]: Failed to offer credential: ${err.message}`
701
      );
702
    }
703
  }
×
704

705
  async ensureDidsCreated() {
706
    const agentsWithoutDids = await this.agentRepository.findBy({ did: '' });
707
    for (const agent of agentsWithoutDids) {
708
      await this.createDidOnAgent(agent);
709
    }
710
  }
711

712
  async countAgentsWithMatchingCredentials(
713
    credentialCriteria: CredentialsSearchInput
714
  ): Promise<number> {
715
    return await this.credentialService.countMatchingCredentials(
716
      credentialCriteria
717
    );
718
  }
719
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc