# All files / src/profiles profileTokens.js

79.59% Statements 39/49
60.53% Branches 23/38
100% Functions 4/4
79.59% Lines 39/49

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
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159

    8x

    8x

    8x
    8x

    8x
    8x

    8x

    8x

    8x

    7x

    20x
    20x

    20x
    20x

    20x
    20x

    20x

    20x
    20x

    20x
    20x
    20x
    20x

    20x

    8x

    4x

    20x
    20x

    20x
    20x

    20x

    11x
    11x

    11x
    11x
    11x
    11x
    11x

    11x

     import { ECPair } from 'bitcoinjs-lib'
    import {
      decodeToken, SECP256K1Client, TokenSigner, TokenVerifier
    } from 'jsontokens'

    import { nextYear, makeUUID4, ecPairToAddress } from '../utils'

    /**
      * Signs a profile token
      * @param {Object} profile - the JSON of the profile to be signed
      * @param {String} privateKey - the signing private key
      * @param {Object} subject - the entity that the information is about
      * @param {Object} issuer - the entity that is issuing the token
      * @param {String} signingAlgorithm - the signing algorithm to use
      * @param {Date} issuedAt - the time of issuance of the token
      * @param {Date} expiresAt - the time of expiration of the token
      * @returns {Object} - the signed profile token
      */
    export function signProfileToken(profile,
                                     privateKey,
                                     subject = null,
                                     issuer = null,
                                     signingAlgorithm = 'ES256K',
                                     issuedAt = new Date(),
                                     expiresAt = nextYear()) {
      Iif (signingAlgorithm !== 'ES256K') {
        throw new Error('Signing algorithm not supported')
      }

      const publicKey = SECP256K1Client.derivePublicKey(privateKey)

      Eif (subject === null) {
        subject = { publicKey }
      }

      Eif (issuer === null) {
        issuer = { publicKey }
      }

      const tokenSigner = new TokenSigner(signingAlgorithm, privateKey)

      const payload = {
        jti: makeUUID4(),
        iat: issuedAt.toISOString(),
        exp: expiresAt.toISOString(),
        subject,
        issuer,
        claim: profile
      }

      return tokenSigner.sign(payload)
    }

    /**
      * Wraps a token for a profile token file
      * @param {String} token - the token to be wrapped
      * @returns {Object} - including `token` and `decodedToken`
      */
    export function wrapProfileToken(token) {
      return {
        token,
        decodedToken: decodeToken(token)
      }
    }

    /**
      * 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, publicKeyOrAddress) {
      const decodedToken = decodeToken(token)
      const payload = decodedToken.payload

      // Inspect and verify the subject
      Eif (payload.hasOwnProperty('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
      Eif (payload.hasOwnProperty('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.publicKey
      const publicKeyBuffer = new Buffer(issuerPublicKey, 'hex')

      const compressedKeyPair =  ECPair.fromPublicKey(publicKeyBuffer, { compressed: true })
      const compressedAddress = ecPairToAddress(compressedKeyPair)
      const uncompressedKeyPair = ECPair.fromPublicKey(publicKeyBuffer, { compressed: false })
      const uncompressedAddress = ecPairToAddress(uncompressedKeyPair)

      if (publicKeyOrAddress === issuerPublicKey) {
        // pass
      } else if (publicKeyOrAddress === compressedAddress) {
        // pass
      } else Eif (publicKeyOrAddress === uncompressedAddress) {
        // pass
      } else {
        throw new Error('Token issuer public key does not match the verifying value')
      }

      const tokenVerifier = new TokenVerifier(decodedToken.header.alg, 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, publicKeyOrAddress = null) {
      let decodedToken
      Eif (publicKeyOrAddress) {
        decodedToken = verifyProfileToken(token, publicKeyOrAddress)
      } else {
        decodedToken = decodeToken(token)
      }

      let profile = {}
      Eif (decodedToken.hasOwnProperty('payload')) {
        const payload = decodedToken.payload
        Eif (payload.hasOwnProperty('claim')) {
          profile = decodedToken.payload.claim
        }
      }

      return profile
    }