requests.service.ts 4.19 KB
Newer Older
Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
1 2 3 4 5 6 7 8
import { Injectable } from '@nestjs/common';
import { decode, verify } from 'jsonwebtoken';

import { OrganizationsService } from 'src/organizations/organizations.service';
import { Organization } from 'src/organizations/organization.entity';
import {
  CredentialVerifyRequest,
  CredentialVerifyRequestData,
Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
9
} from './credential-verify-request.entity';
Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
10 11 12
import {
  CredentialIssueRequest,
  CredentialIssueRequestData,
Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
13
} from './credential-issue-request.entity';
14 15
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
16 17 18 19 20 21 22

export class InvalidRequestJWT extends Error {}

const JWT_MAX_AGE = '300s';

@Injectable()
export class RequestsService {
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
  constructor(
    private organizationsService: OrganizationsService,
    @InjectRepository(CredentialIssueRequest)
    private issueRequestRepository: Repository<CredentialIssueRequest>,
    @InjectRepository(CredentialVerifyRequest)
    private verifyRequestRepository: Repository<CredentialVerifyRequest>,
  ) {}

  async findVerifyRequestByIdentifier(uuid: string) {
    return this.verifyRequestRepository.findOne({ uuid });
  }

  async findIssueRequestByIdentifier(uuid: string) {
    return this.issueRequestRepository.findOne({ uuid });
  }

  async findVerifyRequestByRequestId(requestId: string) {
    const [type, uuid] = requestId.split(':');

    if (type !== CredentialVerifyRequest.requestType || !uuid) {
      return null;
    }

    return this.findVerifyRequestByIdentifier(uuid);
  }

  async findIssueRequestByRequestId(requestId: string) {
    const [type, uuid] = requestId.split(':');

    if (type !== CredentialIssueRequest.requestType || !uuid) {
      return null;
    }

    return this.findIssueRequestByIdentifier(uuid);
  }

  async findRequestByRequestId(requestId: string) {
    const [type, uuid] = requestId.split(':');

    if (!type || !uuid) {
      return null;
    }

    if (type === CredentialVerifyRequest.requestType) {
      return this.findVerifyRequestByIdentifier(uuid);
    }

    if (type === CredentialIssueRequest.requestType) {
      return this.findIssueRequestByIdentifier(uuid);
    }

    return null;
  }
Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
76 77 78 79 80 81

  async decodeVerifyRequestToken(jwt: string) {
    const { request, requestor } = await this.decodeAndVerifyJwt<
      CredentialVerifyRequestData
    >(jwt);

Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
82 83 84 85 86 87
    const verifyRequest = new CredentialVerifyRequest();

    verifyRequest.requestor = requestor;
    verifyRequest.type = request.type;
    verifyRequest.callbackUrl = request.callbackUrl;

88
    await this.verifyRequestRepository.save(verifyRequest);
Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
89
    return verifyRequest;
Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
90 91 92 93 94 95 96
  }

  async decodeIssueRequestToken(jwt: string) {
    const { request, requestor } = await this.decodeAndVerifyJwt<
      CredentialIssueRequestData
    >(jwt);

Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
97 98 99 100 101 102 103
    const issueRequest = new CredentialIssueRequest();

    issueRequest.requestor = requestor;
    issueRequest.type = request.type;
    issueRequest.callbackUrl = request.callbackUrl;
    issueRequest.data = request.data;

104
    await this.issueRequestRepository.save(issueRequest);
Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
105
    return issueRequest;
Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
106 107
  }

Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
108
  async decodeAndVerifyJwt<T = unknown>(
Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
    jwt: string,
  ): Promise<{ request: T; requestor: Organization }> {
    try {
      // First decode to extract issuer
      const decoded = decode(jwt, { json: true });

      // Check if issuer is set
      if (!decoded || !decoded.iss) {
        throw new Error('Could not decode issuer');
      }

      const requestor = await this.organizationsService.findByIdentifier(
        decoded.iss,
      );

      if (!requestor) {
        throw new Error('Could not find requestor');
      }

      // Verify that jwt is signed by specified issuer
      const request = verify(jwt, requestor.sharedSecret, {
        maxAge: JWT_MAX_AGE,
      });

      if (typeof request === 'string') {
        throw new Error(`String returned '${request}'. Expecting json object`);
      }

137 138 139 140
      // This is an unsafe casting that creates a runtime exception if the
      // casting fails. A more robust solution would be to use the
      // class-transformer and class-validator libraries to make sure the object
      // is valid.
Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
141
      return { request: (request as unknown) as T, requestor };
Hidde-Jan Jongsma's avatar
Hidde-Jan Jongsma committed
142 143 144 145 146
    } catch (e) {
      throw new InvalidRequestJWT('Could not decode request JWT');
    }
  }
}