0
votes

I have an Express App ( hosted on Heroku ) which i'm using to handle intents from Dialogflow and make callouts to APEX REST Webservice classes (to get data from Salesforce) and then show the results back on Google Assistant.

For authentication, i'm trying to implement OAuth, and hence I've created Connected App on Salesforce. On Google Actions under Account Linking i've mentioned the 'Authorization URL' as Express App URL (something like https://testBot.herokuapp.com/authorization) and 'Client Id issued by your Actions to Google' as Consumer Key from Salesforce Connected App and lastly 'Client Secret' as Salesforce Connected App Consumer Secret. Also, my Token URL is like https://testBot.herokuapp.com/token.

On Express i've created routes, first to handle the request coming in for authorization (to get authorization code) and then secondly on the callback route (this is the callback URL on Salesforce Connected App) as mentioned on Implement OAuth account linking i've redirected to redirect_uri (of the form https://oauth-redirect.googleusercontent.com/r/MY_PROJECT_ID) with authorization code and state as parameters. This is how the uri looks https://oauth-redirect.googleusercontent.com/r/MY_PROJECT_ID?code=AUTHORIZATION_CODE&state=STATE_STRING. Now on the 3rd route (https://testBot.herokuapp.com/token), logic is written to exchange authorization code for an access token and a refresh token. Note that the token exchange endpoint responds to POST requests.

Now as per official documentation , Google stores the access token and the refresh token for the user. So, what this means is that Conversation or conv object should hold the access token values however when I try to access the same and then make a callout to the APEX Webservice I could see that conv.user.accessToken gives undefined and hence the callout is also unsuccessful (error : INVALID_SESSION_ID: Session expired or invalid) even after successful authentication.

My question is why i'm not getting the access token from CONV and if this is expected (am I reading the documentation incorrectly) how am I supposed to get the access token ?

Here is the express code:

const express = require('express');
const bodyParser = require('body-parser');
const jsforce = require('jsforce');
const { dialogflow } = require('actions-on-google');
const {
  SimpleResponse,
  BasicCard,
  SignIn,
  Image,
  Suggestions,
  Button
} = require('actions-on-google');

var options;
var timeOut = 3600;

var port = process.env.PORT || 3000;
var conn = {};

const expApp = express().use(bodyParser.json());
expApp.use(bodyParser.urlencoded());
//app instance
const app = dialogflow({
  debug: true
});

const oauth2 = new jsforce.OAuth2({
    clientId: process.env.SALESFORCE_CONSUMER_KEY,
    clientSecret: process.env.SALESFORCE_CONSUMER_SECRET,
    redirectUri: 'https://testbot.herokuapp.com/callback'
});

expApp.get('/authorize', function(req, res) {
    var queryParams = req.query;
    console.log('this is the first request: '+req);
    res.redirect(oauth2.getAuthorizationUrl({ state: queryParams.state }));

});

expApp.get('/callback', function(req,res) {
    var queryParams = req.query;
    console.log('Request came for access callback');
    console.log('Query params in callback uri is ', req.query);
    let redirectUri = `${process.env.GOOGLE_REDIRECT_URI}?code=${queryParams.code}&state=${queryParams.state}`;
    console.log('Google redirecturi is ', redirectUri);
    res.redirect(redirectUri);
});


expApp.post('/token', function(req, res) {
    console.log('Request came for accesstoken');

    console.log('query params are-->', req.body);
    console.log('req query-->', req.query);

    res.setHeader('Content-Type', 'application/json');
    if (req.body.client_id != process.env.SALESFORCE_CONSUMER_KEY) {
        console.log('Invalid Client ID');
        return res.status(400).send('Invalid Client ID');
    }
    if (req.body.client_secret != process.env.SALESFORCE_CONSUMER_SECRET) {
        console.log('Invalid Client Ksecret');
        return res.status(400).send('Invalid Client ID');
    }
    if (req.body.grant_type) {
        if (req.body.grant_type == 'authorization_code') {
            console.log('Fetching token from salesforce');
            oauth2.requestToken(req.body.code, (err, tokenResponse) => {
                if (err) {
                    console.log(err.message);
                    return res.status(400).json({ "error": "invalid_grant" });
                }
                console.log('Token respons: ',tokenResponse);

                var googleToken = {
                    token_type: tokenResponse.token_type,
                    access_token: tokenResponse.access_token,
                    refresh_token: tokenResponse.refresh_token,
                    expires_in: timeOut
                };
                console.log('Token response for auth code', googleToken);

                res.status(200).json(googleToken);

            });
        } 
        else if (req.body.grant_type == 'refresh_token') {
            console.log('Fetching refresh token from salesforce');
            oauth2.refreshToken(req.body.refresh_token, (err, tokenResponse) => {
                if (err) {
                    console.log(err.message);
                    return res.status(400).json({ "error": "invalid_grant" });
                }
                console.log('Token response in refresh token: ',tokenResponse);
                var googleToken = { token_type: tokenResponse.token_type, access_token: tokenResponse.access_token, expires_in: timeOut };

                console.log('Token response for auth code', googleToken);

                res.status(200).json(googleToken);
            });
        }
    } else {
        res.send('Invalid parameter');
    }
});

var createTask = function(oppName,taskSubject,taskPriority,conFName,conn){
    return new Promise((resolve,reject)=>{
        conn.apex.get("/createTask?oppName="+oppName+"&taskSubject="+taskSubject+"&taskPriority="+taskPriority+"&contactFirstName="+conFName,function(err, res){
            if (err) {
                console.log('error is --> ',err);
                reject(err);
            }
            else{
                console.log('res is --> ',res);
                resolve(res);
            }
        });
    });
};

app.intent('Default Welcome Intent', (conv) => {

    console.log('Request came for account link flow start');    
        if(!conv.user.accessToken){
            conv.ask(new SignIn());
        }
        else{
            conv.ask('You are already signed in ');
        }

});

app.intent('Get SignIn Info', (conv, params, signin) => {    
    console.log('Sign in info Intent');    
    console.log('Sign in content-->',signin);       
    if (signin.status === 'OK') {         
        conv.ask('Hola, thanks for signing in! What do you want to do next?')       ;
    } 
    else {         
        conv.ask('Something went wrong in the sign in process');       
    }     
}); 

app.intent('Create Task on Opportunity', (conv, {oppName,taskSubject,taskPriority,contactFirstName} ) => {
    console.log('conv: ',conv);
    //this logs undefined
    console.log('Access token from conv inside intent: ',conv.user.accessToken);
    const opName = conv.parameters['oppName'];
    const tskSbj = conv.parameters['taskSubject'];
    const tskPr = conv.parameters['taskPriority'];
    const conFName = conv.parameters['contactFirstName'];
    console.log('Instance URL as stored in heroku process variable: ',process.env.INSTANCE_URL);
    conn = new jsforce.Connection({
      instanceUrl : process.env.INSTANCE_URL,
      accessToken : conv.user.accessToken
    });
    return createTask(opName,tskSbj,tskPr,conFName,conn).then((resp) => {
        conv.ask(new SimpleResponse({
            speech:resp,
            text:resp,
        }));
    });
});

expApp.get('/', function (req, res) {
    res.send('Hello World!');
});
expApp.listen(port, function () {
    expApp.post('/fulfillment', app);
    console.log('Example app listening on port !');
});
1

1 Answers

0
votes

So, on logging conversation.user I understood that conv.user.access.token is correct and not conv.user.accessToken. Hence, now the connection instance would look like:

conn = new jsforce.Connection({
  instanceUrl : process.env.INSTANCE_URL,
  accessToken : conv.user.acces.token
});

Now, get request on apex web service does send expected response !