In Google Dialogflow, How to save variable till session end - dialogflow-es

I am not able to save token as a variable till the session end. this token will be used for further APIs.
here is my code
'use strict';
const axios = require('axios');
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
const {Payload} = require('dialogflow-fulfillment');
var token = "";
function login(agent) {
const email = agent.parameters.email;
const password = agent.parameters.password;
const baseurl = "http://demo:3000/login/buyerlogin";
var data = { email: email, password: password };
return axios
.get(baseurl, { data: { email: email, password: password } })
.then((result) => {
if (result.data.status == 200) {
agent.add("Login Successfully 😊");
agent.add("Select an option to proceed with other queries 👇");
token = result.data.token; //token will be used for further APIs
agent.add(
new Payload(agent.UNSPECIFIED, payloadlog, {
rawPayload: true,
sendAsMessage: true,
})
);
}
})
.catch((error) => {
agent.add("else " + error.message);
});
}
I want to save token till the session end.
Please help me out on this.
Thank You

You can save these values in the parameters of a long-lasting Context that you set and later read in your webhook.
Setting the token value in a parameter in the Context might look something like
agent.context.set('info', 99, {
token: token
});
you can later get that Context and the token value with something like
const infoContext = agent.context.get('info');
const token = infoContext.parameters.token;

Related

Not authorized when accessing google directory api

I am using node to list all users from domain. I had created service account with domain wide delegation.
The domain admin gave access to the service account to required scopes.
Code:
const { JWT } = require('google-auth-library');
const {google, chat_v1} = require('googleapis');
const keys = require('./keys.json')
async function main() {
const client = new JWT(keys.client_email, keys, keys.private_key,
['https://www.googleapis.com/auth/admin.directory.user',
'https://www.googleapis.com/auth/admin.directory.user.readonly',
'https://www.googleapis.com/auth/cloud-platform'],
"admin#domain.com"
);
await client.authorize();
const service = google.admin("directory_v1");
service.users.list({
domain: "domain.com",
maxResults: "10",
orderBy: "email",
}, (err, res) => {
if (err) return console.error('The API returned an error:', err.message);
const users = res.data.users;
if (users.length) {
console.log('Users:');
users.forEach((user) => {
console.log(`${user.primaryEmail} (${user.name.fullName})`);
});
} else {
console.log('No users found.');
}
});
}
main();
but only thing I recive is:
The API returned an error: Login Required.
also enabled admin sdk api for this service account
any idea why this is happening?
I'm not sure if JWT is the correct way, but this is how I do it.
const google = require("googleapis").google;
const SRVC_ACCOUNT_CREDS = require('./keys.json');
const getClient = async (scopes: string[], user: string)=>{
const auth = new google.auth.GoogleAuth({
credentials: SRVC_ACCOUNT_CREDS,
scopes: scopes
});
const client = await auth.getClient();
client.subject = user;
return client;
};
const listUsers = async (query = "", limit = 500, pageToken = null, user, fields, getAll = false)=>{
const scopes = ["https://www.googleapis.com/auth/admin.directory.user"];
const client = await getClient(scopes, user);
const service = google.admin({version: "directory_v1", auth: client});
const result = {
users: [],
nextPageToken: ""
};
if(!fields) {
fields = "users(name.fullName,primaryEmail,organizations(department,primary,title),thumbnailPhotoUrl),nextPageToken";
}
do{
const request = await service.users.list({
customer: "my_customer",
fields: fields,
orderBy: "givenName",
maxResults: limit,
pageToken: pageToken,
query: query,
viewType: "admin_view"
});
pageToken = getAll ? request.data.nextPageToken : null;
const users = request.data.users;
if(users && users.length){
result.users.push(...users);
result.nextPageToken = request.data.nextPageToken;
}
} while(pageToken);
return result;
};

How can I make Jasmine wait before my expect calls, in order to wait for the code that's executed after data is returned to be run?

I just added a route in one of my node router files and would like to add the appropriate unit test for it. The route is the following:
router.post('/addtradables', checkIsAdmin, async function(req, res, next) {
const SteamApp = require('../models/steamapp.model');
const Tradable = require('../models/tradable.model');
const User = require('../models/user.model');
const postedTradables = req.body.tradables;
if(postedTradables) {
res.json({ success: true, result: constants.PROCESS_STARTED, textResult: 'The tradables are being added' });
const adminUser = await User.findOne({ steamId: ADMIN_ID }).exec();
for(let currentKey in postedTradables) {
if(postedTradables.hasOwnProperty(currentKey)) {
const currentTradable = postedTradables[currentKey];
const appFound = currentTradable.sku ? await SteamApp.findOne({ appid: currentTradable.sku }).exec() : null;
await new Tradable( { /* object data here */ } ).save();
}
}
} else {
return res.json({ success: false, result: constants.INVALID_FORMAT, textResult: 'No tradable found, check the format' });
}
} );
As you can see, it returns an object with a code and text message so that the front end can know if the process was started or not. This is because the insertion process can take a while, depending on the input data.
Here is my Jasmine spec file:
const app = require('../../app');
const request = require('supertest');
const MongoDbTest = require('../../lib/mongodb-tests.lib');
const jwt = require('jsonwebtoken');
const SteamApp = require('../../models/steamapp.model');
const Tradable = require('../../models/tradable.model');
const User = require('../../models/user.model');
const JWT_TOKEN = process.env.JTW_KEY;
const ADMIN_ID = process.env.MASTER_STEAMID;
const constants = require('../../config/constants');
const adminUser = {
role: 'admin',
steamId: ADMIN_ID
};
const testTradable = {
sku: 1234,
barterId: 83838,
extra: 2,
type: 'gift',
title: 'Les patates volent!'
};
describe("route /addtradables", () => {
const mongod = new MongoDbTest();
let testOwner;
beforeAll(async () => {
await mongod.connect();
await new SteamApp({ appid: 1234, name: 'patate' }).save();
testOwner = await new User(adminUser).save();
} );
afterAll(async () => {
await SteamApp.deleteMany({});
await User.deleteMany({});
await mongod.close();
} );
it("devrait retourner un message indiquant que le format est incorrect lorsque c'est le cas", async () => {
const userToken = jwt.sign(adminUser, JWT_TOKEN, { algorithm: 'HS256' });
const response = await request(app).post(`/api/addtradables`).send({ patate: 'pouelle' }).set('Authorization', `Bearer ${userToken}`).expect('Content-Type', /json/);
expect(response.statusCode).toBe(200);
expect(response.body.success).toBe(false);
expect(response.body.result).toBe(constants.INVALID_FORMAT);
} );
it("devrait retourner un message indiquant que le processus a débuté lorsque le format est correct", async () => {
const userToken = jwt.sign(adminUser, JWT_TOKEN, { algorithm: 'HS256' });
const response = await request(app).post(`/api/addtradables`).send({ tradables: { 9837489374: testTradable } }).set('Authorization', `Bearer ${userToken}`).expect('Content-Type', /json/);
expect(response.statusCode).toBe(200);
expect(response.body.success).toBe(true);
/*
I WANT TO WAIT HERE! If I don't wait for the router.post call to finish the data will not be available in the database
*/
const insertedTradable = await Tradable.findOne({ appid: 1234 }).exec();
expect(insertedTradable.barterId).toBe(testTradable.barterId);
expect(insertedTradable.isGift).toBe(true);
expect(insertedTradable.copies).toBe(testTradable.extra);
expect(insertedTradable.name).toBe(testTradable.title);
} );
} );
Is there a way to spy on the router.post call and check when everything is completed so that I can extract data from the database and check that the data was inserted properly? Short of that, what would be the best way to sleep / stop execution in the jasmine test routine while the the router.post call wraps up? I've looked at using a done function but since the code is inside the router I don't see how I could use done in this scenario.

xero.apiCallback is not a function

I am getting an error in my xero code. It says, xero.apiCallback is not a function. The other functions in xero seems to work.
Here is my code:
require('dotenv').config();
const express = require('express');
const XeroRouter = express.Router();
const { XeroClient } = require('xero-node');
const { TokenSet } = require('openid-client');
const client_id = process.env.CLIENT_ID;
const client_secret = process.env.CLIENT_SECRET;
const redirectUrl = process.env.REDIRECT_URI;
const scopes = "openid profile email accounting.settings accounting.reports.read accounting.journals.read accounting.contacts accounting.attachments accounting.transactions offline_access";
const xero = new XeroClient({
clientId: client_id,
clientSecret: client_secret,
redirectUris: [redirectUrl],
scopes: scopes.split(" "),
});
if ( !client_secret || !redirectUrl) {
throw Error('Environment Variables not all set - please check your .env file in the project root or create one!')
}
XeroRouter.get('/', async (req, res) => {
try {
let consentUrl = await xero.buildConsentUrl();
res.redirect(consentUrl);
} catch (e) {
res.status(e.status || 500);
console.log(e);
}
});
XeroRouter.get("/callback", async (req, res) => {
let tokenSet = await xero.apiCallback(req.url);
console.log(req.url); //This has value
console.log(tokenSet); // this returns TokenSet {}
});
module.exports = XeroRouter;
I am not sure why apiCallback is not working but it says it does in the documentation.
I had the same problem and eventually got it working, i had to actually look at the code for the node module to work out what was going on.
You need to explicitly set the openIdClient on construction of XeroClient and it worked for me.
Change your constructor to look like below and it should work.
const xero = new XeroClient({
clientId: client_id,
clientSecret: client_secret,
redirectUris: [redirectUrl],
openIdClient: TokenSet,
scopes: scopes.split(" "),
});

Domain-wide delegation using default credentials in Google Cloud Run

I'm using a custom service account (using --service-account parameter in the deploy command). That service account has domain-wide delegation enabled and it's installed in the G Apps Admin panel.
I tried this code:
app.get('/test', async (req, res) => {
const auth = new google.auth.GoogleAuth()
const gmailClient = google.gmail({ version: 'v1' })
const { data } = await gmailClient.users.labels.list({ auth, userId: 'user#domain.com' })
return res.json(data).end()
})
It works if I run it on my machine (having the GOOGLE_APPLICATION_CREDENTIALS env var setted to the path of the same service account that is assigned to the Cloud Run service) but when it's running in Cloud Run, I get this response:
{
"code" : 400,
"errors" : [ {
"domain" : "global",
"message" : "Bad Request",
"reason" : "failedPrecondition"
} ],
"message" : "Bad Request"
}
I saw this solution for this same issue, but it's for Python and I don't know how to replicate that behaviour with the Node library.
After some days of research, I finally got a working solution (porting the Python implementation):
async function getGoogleCredentials(subject: string, scopes: string[]): Promise<JWT | OAuth2Client> {
const auth = new google.auth.GoogleAuth({
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
})
const authClient = await auth.getClient()
if (authClient instanceof JWT) {
return (await new google.auth.GoogleAuth({ scopes, clientOptions: { subject } }).getClient()) as JWT
} else if (authClient instanceof Compute) {
const serviceAccountEmail = (await auth.getCredentials()).client_email
const unpaddedB64encode = (input: string) =>
Buffer.from(input)
.toString('base64')
.replace(/=*$/, '')
const now = Math.floor(new Date().getTime() / 1000)
const expiry = now + 3600
const payload = JSON.stringify({
aud: 'https://accounts.google.com/o/oauth2/token',
exp: expiry,
iat: now,
iss: serviceAccountEmail,
scope: scopes.join(' '),
sub: subject,
})
const header = JSON.stringify({
alg: 'RS256',
typ: 'JWT',
})
const iamPayload = `${unpaddedB64encode(header)}.${unpaddedB64encode(payload)}`
const iam = google.iam('v1')
const { data } = await iam.projects.serviceAccounts.signBlob({
auth: authClient,
name: `projects/-/serviceAccounts/${serviceAccountEmail}`,
requestBody: {
bytesToSign: unpaddedB64encode(iamPayload),
},
})
const assertion = `${iamPayload}.${data.signature!.replace(/=*$/, '')}`
const headers = { 'content-type': 'application/x-www-form-urlencoded' }
const body = querystring.encode({ assertion, grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer' })
const response = await fetch('https://accounts.google.com/o/oauth2/token', { method: 'POST', headers, body }).then(r => r.json())
const newCredentials = new OAuth2Client()
newCredentials.setCredentials({ access_token: response.access_token })
return newCredentials
} else {
throw new Error('Unexpected authentication type')
}
}
What you can do here is define ENV variables in your yaml file as described in this documentation to set the GOOGLE_APPLICATION_CREDENTIALS to the path of the JSON key.
Then use a code such as the one mentioned here.
const authCloudExplicit = async ({projectId, keyFilename}) => {
// [START auth_cloud_explicit]
// Imports the Google Cloud client library.
const {Storage} = require('#google-cloud/storage');
// Instantiates a client. Explicitly use service account credentials by
// specifying the private key file. All clients in google-cloud-node have this
// helper, see https://github.com/GoogleCloudPlatform/google-cloud-node/blob/master/docs/authentication.md
// const projectId = 'project-id'
// const keyFilename = '/path/to/keyfile.json'
const storage = new Storage({projectId, keyFilename});
// Makes an authenticated API request.
try {
const [buckets] = await storage.getBuckets();
console.log('Buckets:');
buckets.forEach(bucket => {
console.log(bucket.name);
});
} catch (err) {
console.error('ERROR:', err);
}
// [END auth_cloud_explicit]
};
Or follow an approach similar to the one mentioned here.
'use strict';
const {auth, Compute} = require('google-auth-library');
async function main() {
const client = new Compute({
serviceAccountEmail: 'some-service-account#example.com',
});
const projectId = await auth.getProjectId();
const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`;
const res = await client.request({url});
console.log(res.data);
}
main().catch(console.error);

How to make dialogflow session

I want to set different session for different users in dialogflow.
https://www.npmjs.com/package/dialogflow
I referred to the following site for this, but the program is not returning any output.
const dialogflow = require('dialogflow');
const uuid = require('uuid');
/**
* Send a query to the dialogflow agent, and return the query result.
* #param {string} projectId The project to be used
*/
async function runSample(projectId = 'your-project-id') {
// A unique identifier for the given session
const sessionId = uuid.v4();
// Create a new session
const sessionClient = new dialogflow.SessionsClient();
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
text: {
// The query to send to the dialogflow agent
text: 'hello',
// The language used by the client (en-US)
languageCode: 'en-US',
},
},
};
// Send request and log result
const responses = await sessionClient.detectIntent(request);
console.log('Detected intent');
const result = responses[0].queryResult;
console.log(` Query: ${result.queryText}`);
console.log(` Response: ${result.fulfillmentText}`);
if (result.intent) {
console.log(` Intent: ${result.intent.displayName}`);
} else {
console.log(` No intent matched.`);
}
}
const functions = require('firebase-functions');
const dialogflow = require('dialogflow');
const cors = require('cors')({origin: true});
const admin = require('firebase-admin');
admin.initializeApp();
// Create and Deploy Your First Cloud Functions
// https://firebase.google.com/docs/functions/write-firebase-functions
exports.connectChat = functions.https.onRequest((req, response) => {
cors(req, response, () => {
const projectId = '******';
const query = req.query.text;
const session= req.query.session;
const languageCode = 'en-US';
var result="";
let privateKey = "-----BEGIN PRIVATE KEY-----******\n-----END PRIVATE KEY-----\n";
// as per goolgle json
let clientEmail = "*****#.gserviceaccount.com";
let config = {
credentials: {
private_key: privateKey,
client_email: clientEmail
}
}
const sessionClient = new dialogflow.SessionsClient(config);
// Define session path
const sessionPath = sessionClient.sessionPath(projectId, session);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
text: {
text: query,
languageCode: languageCode,
},
},
};
// Send request and log result
sessionClient
.detectIntent(request)
.then(responses => {
result = responses[0].queryResult;
response.send(result);
return null;
})
.catch(err => {
console.error('ERROR:', err);
response.send('ERROR');
return null;
});
});
});
projectId,clientEmail,privateKey are get from JSON file downloaded from https://console.cloud.google.com/iam-admin
create your session id (req.query.session) from client side in javascript

Resources