intuit / oauth-jsclient

Intuit's NodeJS OAuth client provides a set of methods to make it easier to work with OAuth2.0 and Open ID
https://developer.intuit.com/
Apache License 2.0
121 stars 156 forks source link

Error when calling `makeApiCall` #72

Closed daytonlowell closed 4 years ago

daytonlowell commented 4 years ago

I wrote a wrapper that looks like this

const OAuthClient = require('intuit-oauth')
const Tokens = require('csrf')
const csrf = new Tokens()

module.exports = class {
    constructor({
        clientId,
        clientSecret,
        redirectUri,
        storeToken,
        loadToken,
    } = {}) {
        if (typeof (storeToken) !== 'function' || typeof (loadToken) !== 'function') {
            throw new Error('storeToken and loadToken functions must be provided!')
        }

        this.storeToken = storeToken
        this.loadToken = loadToken

        this.oauthClientDefaults = {
            clientId,
            clientSecret,
            environment: `sandbox`,
            redirectUri: redirectUri || `http://localhost:3000/callback`,
        }

        Object.freeze(this.oauthClientDefaults)

        this.oauthClient = new OAuthClient(this.oauthClientDefaults)

        this.loadToken().then(token => {
            console.log(`loaded token: `, token)
            if (token) {
                this.oauthClient.setToken(token)
                console.log(`token from getToken:`, this.oauthClient.token.getToken())
            }
        }).catch(err => {
            console.error(err)
        })

        this.connectToQuickbooks = async(req, res) => {
            const csrfSecret = await csrf.secret()
            const csrfToken = csrf.create(csrfSecret)

            const state = Buffer.from(JSON.stringify({ csrf: csrfToken })).toString('base64')

            const OAuthUri = this.oauthClient.authorizeUri({
                scope: [
                    OAuthClient.scopes.Accounting,
                    OAuthClient.scopes.Payment,
                ],
                state,
            })

            res.redirect(OAuthUri)
        }

        this.connectCallback = async(req, res) => {
            try {
                await this.oauthClient.createToken(req.url)

                res.redirect('connected.html')

                await this.storeToken(JSON.stringify(this.oauthClient.token.getToken(), null, 2))
            } catch (err) {
                console.error(err)
                res.send(err)
            }
        }

        this.makeApiCall = async(body, method = 'POST') => {
            console.log(`makeApiCall getToken: `, this.oauthClient.token.getToken())
            try {
                const token = this.oauthClient.token.getToken()
                console.log(token.realmId)
                const response = await this.oauthClient.makeApiCall({
                    url: `https://sandbox-quickbooks.api.intuit.com/v3/company/${token.realmId}`,
                    method,
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body,
                })

                JSON.parse(response.text())
            } catch (err) {
                console.error(err)
                throw err
            }
        }
    }
}

The return of that getToken() call within my makeApiCall doesn't look valid. It looks like

{
  token_type: '',
  access_token: '',
  expires_in: 0,
  refresh_token: '',
  x_refresh_token_expires_in: 0,
  realmId: '',
  id_token: '',
  createdAt: 1580580218952
}

The token that got stored in my auth callback and later loaded and used in a setToken call, looks reasonable?

{
  "token_type": "bearer",
  "access_token": "eyJlbmMiOiJBMTI4Q0xxxxxxxxxxxxxxxxxxx5QJau070uy61g21h6bg",
  "expires_in": 3600,
  "refresh_token": "AB11589306707ExxxxxxxxxxxxxxxxxxxxdzIL4HFzfa",
  "x_refresh_token_expires_in": 8726400,
  "realmId": "4625319964618376098",
  "id_token": "",
  "createdAt": 1580580307470
}

Not sure what I'm doing wrong here.

daytonlowell commented 4 years ago

Here's my example app using my wrapper:

const express = require('express')
const webServer = express()
const fs = require('fs')
const qbAuth = require('./auth.js')

const qbAuthSession = new qbAuth({
    clientId: '<secret>',
    clientSecret: '<also secret>',
    storeToken: sessionData => {
        return new Promise((resolve, reject) => {
            fs.writeFile('./qb_auth_session.json', JSON.stringify(sessionData, null, 2), err => {
                err ? reject(err) : resolve()
            })
        })
    },
    loadToken: () => {
        return new Promise((resolve, reject) => {
            fs.readFile('./qb_auth_session.json', 'utf8', (err, tokenData) => {
                err ? reject(err) : resolve(JSON.parse(tokenData))
            })
        })
    },
})

webServer.use(express.static('./static'))

webServer.get('/connect_to_quickbooks', qbAuthSession.connectToQuickbooks)
webServer.get('/callback', qbAuthSession.connectCallback)
webServer.get('/testApiCall', async(req, res) => {
    const response = await qbAuthSession.makeApiCall({
        body: `Select * from Account STARTPOSITION 1 MAXRESULTS 5\n`,
    })

    console.log(response)

    res.send(response)
})

webServer.listen(3000, () => console.log('Example app listening on port 3000!'))
abisalehalliprasan commented 4 years ago

@daytonlowell : Went through your code. There are two places where the String to Object and Object to String conversion has caused errors in your code.

  1. this.loadToken() in constructor
this.loadToken().then(token => {
      console.log(`loaded token: `, token );
      if (token) {
        // setToken method accepts only JSON object 
        this.oauthClient.setToken(JSON.parse(token));
      }
    }).catch(err => {
      console.error(err)
    })
  1. this.makeApiCall in constructor
 this.makeApiCall = async(req,res) => {
      console.log(`makeApiCall getToken: `, this.oauthClient.token.getToken())
      try {
        // set the token first from qb_auth_session.json file
        const token = await this.loadToken();
       // parse the token 
        const parseToken = JSON.parse(token);

        const response = await this.oauthClient.makeApiCall({
          url: `https://sandbox-quickbooks.api.intuit.com/v3/company/${parseToken.realmId}/companyinfo/${parseToken.realmId}`
        })
        console.log("The response is : "+ JSON.stringify(response.getJson(),null,2));
      } catch (err) {
        console.error(err)
        throw err
      }
    }
daytonlowell commented 4 years ago

Thanks, I was double stringifying my JSON. 🤦