cognito-express icon indicating copy to clipboard operation
cognito-express copied to clipboard

Curl Method Option needed to get around issues running cognito-express in Ubuntu on windows + Windows Powershell

Open AkyunaAkish opened this issue 2 years ago • 1 comments

Hi, I implemented cognito-express in my mac osx environment. Then the devs on my team pulled in the changes to ubuntu on windows and windows PowerShell and kept getting "could not get certificate" errors from the request-promise call in the init method.

Their laptops have more security features than my mac and for some reason, this library was not working for us on all operating systems/setups. I modified the source code to use shelljs + a curl command to get the certificates instead, and now it works on all of our operating systems.

I just wanted to mention this in case anyone else runs into this issue. Also, I think it could be useful to add this logic to the library as an optional strategy in case others also have trouble using this library across multiple systems.

'use strict'

const jwkToPem = require('jwk-to-pem'),
  jwt = require('jsonwebtoken'),
  shelljs = require('shelljs')

class CognitoExpress {
  constructor(config) {
    if (!config)
      throw new TypeError(
        'Options not found. Please refer to README for usage example at https://github.com/ghdna/cognito-express'
      )

    if (configurationIsCorrect(config)) {
      this.userPoolId = config.cognitoUserPoolId
      this.tokenUse = config.tokenUse
      this.tokenExpiration = config.tokenExpiration || 3600000
      this.iss = `https://cognito-idp.${config.region}.amazonaws.com/${this.userPoolId}`
      this.promise = this.init((callback) => {})
    }
  }

  init(callback) {
    return new Promise((resolve, reject) => {
      const child = shelljs.exec(
        `curl ${`${this.iss}/.well-known/jwks.json`}`,
        {
          async: true,
          silent: true,
        }
      )

      child.stdout.on('data', (data) => {
        try {
          const response = JSON.parse(data)

          if (response) {
            this.pems = {}
            let keys = response['keys']
            for (let i = 0; i < keys.length; i++) {
              let key_id = keys[i].kid
              let modulus = keys[i].n
              let exponent = keys[i].e
              let key_type = keys[i].kty
              let jwk = { kty: key_type, n: modulus, e: exponent }
              let pem = jwkToPem(jwk)
              this.pems[key_id] = pem
            }

            callback(true)
            resolve(true)
          } else {
            callback(false)
            reject(false)
          }
        } catch (error) {
          callback(false)
          reject(false)
        }

        child.kill()
      })
    })
  }

  validate(token, callback) {
    const p = this.promise
      .then(() => {
        let decodedJwt = jwt.decode(token, { complete: true })

        try {
          if (!decodedJwt) throw new TypeError('Not a valid JWT token')

          if (decodedJwt.payload.iss !== this.iss)
            throw new TypeError('token is not from your User Pool')

          if (decodedJwt.payload.token_use !== this.tokenUse)
            throw new TypeError(`Not an ${this.tokenUse} token`)

          let kid = decodedJwt.header.kid
          let pem = this.pems[kid]

          if (!pem) throw new TypeError(`Invalid ${this.tokenUse} token`)

          let params = {
            token: token,
            pem: pem,
            iss: this.iss,
            maxAge: this.tokenExpiration,
          }
          if (callback) {
            jwtVerify(params, callback)
          } else {
            return new Promise((resolve, reject) => {
              jwtVerify(params, (err, result) => {
                if (err) {
                  reject(err)
                } else {
                  resolve(result)
                }
              })
            })
          }
        } catch (err) {
          if (!callback) throw err

          callback(err.message, null)
        }
      })
      .catch((e) => callback(e?.message || e, null))

    if (!callback) {
      return p
    }
  }
}

function configurationIsCorrect(config) {
  let configurationPassed = false
  switch (true) {
    case !config.region:
      throw new TypeError('AWS Region not specified in constructor')
      break
    case !config.cognitoUserPoolId:
      throw new TypeError(
        'Cognito User Pool ID is not specified in constructor'
      )
      break
    case !config.tokenUse:
      throw new TypeError(
        "Token use not specified in constructor. Possible values 'access' | 'id'"
      )
      break
    case !(config.tokenUse == 'access' || config.tokenUse == 'id'):
      throw new TypeError(
        "Token use values not accurate in the constructor. Possible values 'access' | 'id'"
      )
      break
    default:
      configurationPassed = true
  }
  return configurationPassed
}

function jwtVerify(params, callback) {
  jwt.verify(
    params.token,
    params.pem,
    {
      issuer: params.iss,
      maxAge: params.maxAge,
    },
    function (err, payload) {
      if (err) return callback(err, null)
      return callback(null, payload)
    }
  )
}

module.exports = CognitoExpress

AkyunaAkish avatar Dec 23 '21 15:12 AkyunaAkish

Thanks for sharing. I'll look into integrating this

ghdna avatar Jan 17 '22 16:01 ghdna