All files / src/storage/profile tokens.ts

23.07% Statements 9/39
19.23% Branches 5/26
50% Functions 1/2
21.62% Lines 8/37

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          3x                   3x                                                                                                                                     3x 3x       3x 3x 3x   3x        
import {
  decodeToken,
  TokenInterface,
  TokenVerifier,
  publicKeyToStxAddress,
} from 'micro-stacks/crypto';
 
/**
 * Verifies a profile token
 * @param {String} token - the token to be verified
 * @param {String} publicKeyOrAddress - the public key or address of the
 *   keypair that is thought to have signed the token
 * @returns {Object} - the verified, decoded profile token
 * @throws {Error} - throws an error if token verification fails
 */
export function verifyProfileToken(token: string, publicKeyOrAddress: string): TokenInterface {
  const decodedToken = decodeToken(token);
  Iif (!decodedToken) throw Error('no decoded token');
 
  const payload = decodedToken.payload;
  Iif (typeof payload === 'string') {
    throw new Error('Unexpected token payload type of string');
  }
 
  // Inspect and verify the subject
  if (payload.hasOwnProperty('subject') && payload.subject) {
    Iif (!payload.subject.hasOwnProperty('publicKey')) {
      throw new Error("Token doesn't have a subject public key");
    }
  } else {
    throw new Error("Token doesn't have a subject");
  }
 
  // Inspect and verify the issuer
  if (payload.hasOwnProperty('issuer') && payload.issuer) {
    Iif (!payload.issuer.hasOwnProperty('publicKey')) {
      throw new Error("Token doesn't have an issuer public key");
    }
  } else {
    throw new Error("Token doesn't have an issuer");
  }
 
  // Inspect and verify the claim
  Iif (!payload.hasOwnProperty('claim')) {
    throw new Error("Token doesn't have a claim");
  }
 
  const issuerPublicKey = (payload.issuer as Record<string, string>).publicKey;
  const address = publicKeyToStxAddress(issuerPublicKey);
 
  if (publicKeyOrAddress === issuerPublicKey) {
    // pass
  } else if (publicKeyOrAddress === address) {
    // pass
  } else {
    throw new Error('Token issuer public key does not match the verifying value');
  }
 
  const tokenVerifier = new TokenVerifier(decodedToken.header.alg as string, issuerPublicKey);
  Iif (!tokenVerifier) {
    throw new Error('Invalid token verifier');
  }
 
  const tokenVerified = tokenVerifier.verify(token);
  Iif (!tokenVerified) {
    throw new Error('Token verification failed');
  }
 
  return decodedToken;
}
 
/**
 * Extracts a profile from an encoded token and optionally verifies it,
 * if `publicKeyOrAddress` is provided.
 *
 * @param {String} token - the token to be extracted
 * @param {String} publicKeyOrAddress - the public key or address of the
 *   keypair that is thought to have signed the token
 * @returns {Object} - the profile extracted from the encoded token
 * @throws {Error} - if the token isn't signed by the provided `publicKeyOrAddress`
 */
 
export function extractProfile(token: string, publicKeyOrAddress?: string): Record<string, any> {
  const decodedToken = publicKeyOrAddress
    ? verifyProfileToken(token, publicKeyOrAddress)
    : decodeToken(token);
 
  if (decodedToken && decodedToken.hasOwnProperty('payload')) {
    const payload = decodedToken.payload;
    Iif (typeof payload === 'string')
      throw new Error('[micro-stacks] extractProfile: Unexpected token payload type of string');
    if (payload.hasOwnProperty('claim')) return payload.claim as object;
  }
  return {};
}