All files / src/domain/verifier/useCases getIssuerProfile.ts

90.32% Statements 28/31
83.33% Branches 5/6
100% Functions 2/2
90% Lines 27/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114                    130x 130x       194x       109x       2x               1x     1x       127x 127x   1x       72x     55x       19x                             184x   1x       56x               53x   19x   34x   34x     53x                 1x     129x 2x 2x       1x     126x    
import { request } from '@blockcerts/explorer-lookup';
import { VerifierError } from '../../../models';
import { getText } from '../../i18n/useCases';
import type { Issuer } from '../../../models/Issuer';
import domain from '../../../domain';
import type { IDidDocument } from '../../../models/DidDocument';
 
// TODO: move these functions to url helper
function isValidUrl (url: string): boolean {
  // https://stackoverflow.com/a/15734347/4064775
  const regex = /^(ftp|http|https):\/\/[^ "]+$/;
  return regex.test(url);
}
 
export function isDidUri (url: string): boolean {
  return url.startsWith('did:', 0);
}
 
export function isDidKey (url: string): boolean {
  return url.startsWith('did:key:', 0);
}
 
function isValidV1Profile (profile: Issuer): boolean {
  const { issuer_key, revocation_key, issuerKeys, revocationKeys } = profile;
  if (!!issuer_key && !!revocation_key) {
    // https://github.com/blockchain-certificates/cert-schema/blob/master/cert_schema/1.1/issuer-schema-v1-1.json
    return true;
  }
 
  if (issuerKeys && revocationKeys) {
    // https://github.com/blockchain-certificates/cert-schema/blob/master/cert_schema/1.2/issuer-id-1.2.json
    return true;
  }
 
  return false;
}
 
function isValidProfile (profile: Issuer): boolean {
  const validTypes: string[] = ['issuer', 'profile', 'blockcertsissuerprofile']; // https://w3id.org/openbadges#Profile
  const { type } = profile;
  if (!type) {
    return false;
  }
 
  if (Array.isArray(type)) {
    return type.some(type => validTypes.includes(type.toLowerCase()));
  }
 
  return validTypes.includes(type.toLowerCase());
}
 
function createIssuerProfileFromDidKey (didDocument: IDidDocument): Issuer {
  return {
    '@context': [
      'https://w3id.org/openbadges/v2',
      'https://w3id.org/blockcerts/3.0'
    ],
    publicKey: [
      {
        created: new Date().toISOString(),
        id: didDocument.id.split(':').pop()
      }
    ]
  };
}
 
export default async function getIssuerProfile (issuerAddress: Issuer | string): Promise<Issuer> {
  const errorMessage = getText('errors', 'getIssuerProfile');
  if (!issuerAddress) {
    throw new VerifierError('getIssuerProfile', `${errorMessage} - ${getText('errors', 'issuerProfileNotSet')}`);
  }
 
  if (typeof issuerAddress === 'object') {
    issuerAddress = issuerAddress.id;
  }
 
  let issuerProfile: Issuer;
  if (isDidUri(issuerAddress)) {
    // TODO: it could be that the issuer profile is embedded, or that it is distant,
    //  but we found a did document so the rest of the function does not apply
    try {
      const didDocument: IDidDocument = await domain.did.resolve(issuerAddress);
      if (isDidKey(issuerAddress)) {
        issuerProfile = createIssuerProfileFromDidKey(didDocument);
      } else {
        const issuerProfileUrl = domain.did.getIssuerProfileUrl(didDocument);
        if (issuerProfileUrl) {
          issuerProfile = await getIssuerProfile(issuerProfileUrl);
        }
      }
      return {
        didDocument,
        ...issuerProfile
      };
    } catch (e) {
      console.error(e);
      throw new VerifierError('getIssuerProfile', `${errorMessage} - ${e as string}`);
    }
  } else if (!isValidUrl(issuerAddress)) {
    throw new VerifierError('getIssuerProfile', `${errorMessage} - ${getText('errors', 'issuerProfileNotSet')}`);
  }
 
  issuerProfile = JSON.parse(await request({ url: issuerAddress }).catch((error) => {
    console.error(error);
    throw new VerifierError('getIssuerProfile', errorMessage);
  }));
 
  if (!isValidProfile(issuerProfile) && !isValidV1Profile(issuerProfile)) {
    throw new VerifierError('getIssuerProfile', `${errorMessage} - ${getText('errors', 'issuerProfileInvalid')}`);
  }
 
  return issuerProfile;
}