Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
AkyunaAkish opened this issue Dec 23, 2021 · 1 comment

Comments

@AkyunaAkish
Copy link

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
@ghdna
Copy link
Owner

ghdna commented Jan 17, 2022

Thanks for sharing. I'll look into integrating this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants