Skip to content
Snippets Groups Projects
agent.utils.ts 10.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • import {
      Agent,
      AutoAcceptCredential,
      AutoAcceptProof,
    
    Boyan Tsolov's avatar
    Boyan Tsolov committed
      BaseEvent,
    
      ConnectionsModule,
    
      CredentialsModule,
    
      DidsModule,
    
    Boyan Tsolov's avatar
    Boyan Tsolov committed
      EncryptedMessage,
    
      Key,
    
      KeyType,
    
    Boyan Tsolov's avatar
    Boyan Tsolov committed
      ProofEventTypes,
    
      ProofsModule,
    
      TypedArrayEncoder,
      V2CredentialProtocol,
      V2ProofProtocol,
    
      WalletError,
    
      WalletKeyExistsError,
    
    } from "@aries-framework/core";
    import {
      AnonCredsCredentialFormatService,
      AnonCredsModule,
    
      AnonCredsProofFormatService,
    } from "@aries-framework/anoncreds";
    import {
      IndyVdrAnonCredsRegistry,
      IndyVdrIndyDidRegistrar,
      IndyVdrIndyDidResolver,
      IndyVdrModule,
    } from "@aries-framework/indy-vdr";
    import { AnonCredsRsModule } from "@aries-framework/anoncreds-rs";
    import { anoncreds } from "@hyperledger/anoncreds-nodejs";
    import { indyVdr } from "@hyperledger/indy-vdr-nodejs";
    import { AskarModule } from "@aries-framework/askar";
    import { ariesAskar } from "@hyperledger/aries-askar-nodejs";
    
    import { Key as C, KeyAlgs } from "@hyperledger/aries-askar-shared";
    
    import { IConfAgent } from "@ocm-engine/config";
    import axios from "axios";
    
    Boyan Tsolov's avatar
    Boyan Tsolov committed
    import {
      catchError,
      filter,
      lastValueFrom,
      map,
      Observable,
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      BehaviorSubject,
    
    Boyan Tsolov's avatar
    Boyan Tsolov committed
      Subject,
      take,
      timeout,
    } from "rxjs";
    import { SubjectInboundTransport } from "./askar/transports/agent.subject.inbound.transport";
    import { SubjectOutboundTransport } from "./askar/transports/agent.subject.outbound.transport";
    
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    export type EventBehaviorSubject = BehaviorSubject<BaseEvent>;
    
    Boyan Tsolov's avatar
    Boyan Tsolov committed
    export type SubjectMessage = {
      message: EncryptedMessage;
      replySubject?: Subject<SubjectMessage>;
    };
    
    import { Request, Response, Express } from "express";
    
    
    export const importDidsToWallet = async (
      agent: Agent,
      dids: Array<string>,
    ): Promise<void> => {
      for (const did in dids) {
        try {
          await agent.dids.import({
            did: dids[did],
            overwrite: false,
          });
        } catch (e) {
          console.log((e as Error).message);
        }
      }
    };
    
    export const generateKey = async ({
      seed,
      agent,
    }: {
      seed: string;
      agent: Agent;
    }): Promise<Key> => {
      if (!seed) {
        throw Error("No seed provided");
      }
    
      const privateKey = TypedArrayEncoder.fromString(seed);
    
    
      try {
        return await agent.wallet.createKey({
    
          seed: privateKey,
    
          keyType: KeyType.Ed25519,
        });
      } catch (e) {
        if (e instanceof WalletKeyExistsError) {
          const c = C.fromSecretBytes({
            algorithm: KeyAlgs.Ed25519,
            secretKey: TypedArrayEncoder.fromString(seed),
          });
    
          return Key.fromPublicKey(c.publicBytes, KeyType.Ed25519);
        }
    
    
        if (e instanceof WalletError) {
          throw new Error(`Failed to create key - ${e.message}`);
        }
    
        throw new Error("Unknown");
    
    };
    
    export const generateDidFromKey = (key: Key): string => {
      if (!key) {
        throw new Error("Key not provided");
      }
    
      return TypedArrayEncoder.toBase58(key.publicKey.slice(0, 16));
    };
    
    //eslint-disable-next-line
    export const getAskarAnonCredsIndyModules = (networks: any) => {
      return {
        connections: new ConnectionsModule({
          autoAcceptConnections: true,
        }),
        credentials: new CredentialsModule({
          autoAcceptCredentials: AutoAcceptCredential.ContentApproved,
          credentialProtocols: [
            new V2CredentialProtocol({
              credentialFormats: [new AnonCredsCredentialFormatService()],
            }),
          ],
        }),
        proofs: new ProofsModule({
          autoAcceptProofs: AutoAcceptProof.ContentApproved,
          proofProtocols: [
            new V2ProofProtocol({
              proofFormats: [new AnonCredsProofFormatService()],
            }),
          ],
        }),
        anoncreds: new AnonCredsModule({
          registries: [new IndyVdrAnonCredsRegistry()],
        }),
        anoncredsRs: new AnonCredsRsModule({
          anoncreds,
        }),
        indyVdr: new IndyVdrModule({
          indyVdr,
          networks,
        }),
        dids: new DidsModule({
          registrars: [new IndyVdrIndyDidRegistrar()],
    
          resolvers: [
            new IndyVdrIndyDidResolver(),
            new KeyDidResolver(),
            new PeerDidResolver(),
          ],
    
        }),
        askar: new AskarModule({
          ariesAskar,
        }),
      } as const;
    };
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    const INITIAL_EVENT: BaseEvent = {
      type: "unknown",
      payload: {},
      metadata: {
        contextCorrelationId: "-1",
      },
    };
    
    export const setupEventBehaviorSubjects = (
    
    Boyan Tsolov's avatar
    Boyan Tsolov committed
      agents: Agent[],
      eventTypes: string[],
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    ): BehaviorSubject<BaseEvent>[] => {
      const behaviorSubjects: EventBehaviorSubject[] = [];
    
    Boyan Tsolov's avatar
    Boyan Tsolov committed
    
      for (const agent of agents) {
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        const behaviorSubject = new BehaviorSubject<BaseEvent>(INITIAL_EVENT);
    
    Boyan Tsolov's avatar
    Boyan Tsolov committed
    
        for (const eventType of eventTypes) {
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          agent.events.observable(eventType).subscribe(behaviorSubject);
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        behaviorSubjects.push(behaviorSubject);
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      return behaviorSubjects;
    
    Boyan Tsolov's avatar
    Boyan Tsolov committed
    };
    
    export const setupSubjectTransports = (agents: Agent[]) => {
      const subjectMap: Record<string, Subject<SubjectMessage>> = {};
    
      for (const agent of agents) {
        const messages = new Subject<SubjectMessage>();
        subjectMap[agent.config.endpoints[0]] = messages;
        agent.registerInboundTransport(new SubjectInboundTransport(messages));
        agent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap));
      }
    };
    
    
    export const svdxProofStateChangeHandler = async (
      ev: ProofStateChangedEvent,
      agent: Agent,
      config?: IConfAgent,
    ) => {
      if (ProofState.Done !== ev.payload.proofRecord.state) {
        return;
      }
    
      const presentationMessage = await agent.proofs.findPresentationMessage(
        ev.payload.proofRecord.id,
      );
    
      console.log(JSON.stringify(presentationMessage, null, 2));
      if (!presentationMessage) {
        console.log("No presentation message found");
        return;
      }
    
      const attachmentId = presentationMessage.formats[0].attachmentId;
    
      const attachment =
        presentationMessage.getPresentationAttachmentById(attachmentId);
    
      console.log(JSON.stringify(attachment, null, 2));
      if (!attachment) {
        console.log("No attachment found");
        return;
      }
    
      const email =
        attachment.getDataAsJson<AnonCredsProof>()?.requested_proof.revealed_attrs[
          "email"
        ].raw;
    
      if (!config?.agentSVDXWebHook) {
        console.log("Agent SVDX web hook not set");
        return;
      }
    
      try {
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
        console.log(
          `sending data to svdx ${email}, ${ev.payload.proofRecord.connectionId}`,
        );
    
        await axios.post(
          config?.agentSVDXWebHook,
          {
            email,
            connectionId: ev.payload.proofRecord.connectionId,
          },
          {
            auth: {
              username: config?.agentSVDXBasicUser,
              password: config?.agentSVDXBasicPass,
            },
          },
        );
      } catch (e) {
        console.log(JSON.stringify(e, null, 2));
      }
    };
    
    export const svdxConnectionStateChangeHandler = async (
      ev: ConnectionStateChangedEvent,
      agent: Agent,
      config?: IConfAgent,
    ) => {
      if (
        ev.payload.connectionRecord.role === DidExchangeRole.Responder &&
        ev.payload.connectionRecord.state !== "completed"
      ) {
        return;
      }
    
      await agent.connections.addConnectionType(
        ev.payload.connectionRecord.id,
        ev.payload.connectionRecord.theirLabel || "svdx",
      );
    
      console.log("connection accepted", JSON.stringify(ev, null, 2));
    
      const connections = await agent.connections.findAllByConnectionTypes([
        ev.payload.connectionRecord.theirLabel || "svdx",
      ]);
    
      if (connections.length < 2) {
        return;
      }
    
      connections.sort(
        (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
      );
    
      while (connections.length > 1) {
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        const con = connections.pop()!;
    
        console.log(`deleting ${con.id}`);
        await agent.connections.deleteById(con.id);
      }
    
      try {
        await agent.proofs.requestProof({
          protocolVersion: "v2",
          connectionId: connections[0].id,
          proofFormats: {
            anoncreds: {
              name: "proof-request",
              version: "1.0",
              requested_attributes: {
                email: {
                  name: "email",
                  restrictions: [
                    {
                      schema_id: config?.agentSVDXSchemaId,
                      cred_def_id: config?.agentSVDXCredDefId,
                    },
                  ],
                },
              },
            },
          },
        });
      } catch (e) {
        console.log(JSON.stringify(e, null, 2));
        console.log("failed to issue credential");
      }
    };
    
    Boyan Tsolov's avatar
    Boyan Tsolov committed
    
    export const isProofStateChangedEvent = (
      e: BaseEvent,
    ): e is ProofStateChangedEvent => e.type === ProofEventTypes.ProofStateChanged;
    
    export const waitForProofExchangeRecordSubject = (
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      subject: BehaviorSubject<BaseEvent> | Observable<BaseEvent>,
    
    Boyan Tsolov's avatar
    Boyan Tsolov committed
      {
        threadId,
        parentThreadId,
        state,
        previousState,
        timeoutMs = 10000,
        count = 1,
      }: {
        threadId?: string;
        parentThreadId?: string;
        state?: ProofState;
        previousState?: ProofState | null;
        timeoutMs?: number;
        count?: number;
      },
    ) => {
      const observable: Observable<BaseEvent> =
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        subject instanceof BehaviorSubject ? subject.asObservable() : subject;
    
    Boyan Tsolov's avatar
    Boyan Tsolov committed
      return lastValueFrom(
        observable.pipe(
          filter(isProofStateChangedEvent),
          filter(
            (e) =>
              previousState === undefined ||
              e.payload.previousState === previousState,
          ),
          filter(
            (e) =>
              threadId === undefined || e.payload.proofRecord.threadId === threadId,
          ),
          filter(
            (e) =>
              parentThreadId === undefined ||
              e.payload.proofRecord.parentThreadId === parentThreadId,
          ),
          filter(
            (e) => state === undefined || e.payload.proofRecord.state === state,
          ),
          timeout(timeoutMs),
          catchError(() => {
            throw new Error(
              `ProofStateChangedEvent event not emitted within specified timeout: ${timeoutMs}
              previousState: ${previousState},
              threadId: ${threadId},
              parentThreadId: ${parentThreadId},
              state: ${state}
            }`,
            );
          }),
          take(count),
          map((e) => e.payload.proofRecord),
        ),
      );
    };
    
    
    export const attachShortUrlHandler = (server: Express, agent: Agent): void => {
      server.get(
        "/invitations/:invitationId",
        async (req: Request, res: Response) => {
          try {
            const { invitationId } = req.params;
    
            const outOfBandRecord = await agent.oob.findByCreatedInvitationId(
              invitationId,
            );
    
            if (
              !outOfBandRecord ||
              outOfBandRecord.state !== OutOfBandState.AwaitResponse
            ) {
              return res.status(404).send("Not found");
            }
    
            const invitationJson = outOfBandRecord.outOfBandInvitation.toJSON();
    
            return res
              .header("Content-Type", "application/json")
              .send(invitationJson);
          } catch (error) {
            console.error(error);
            return res.status(500).send("Internal Server Error");
          }
        },
      );
    };