I have created a webhook for WhatsApp Chatbot using NodeJS following this online article: https://dev.to/newtonmunene_yg/creating-a-whatsapp-chatbot-using-node-js-dialogflow-and-twilio-31km
The webhook is linked to Twilio Sandbox for WhatsApp.
I have also provided the DialogFlow Admin API permission to service account on Google Cloud Platform.
When I send a new message from WhatsApp, it's received on Twilio and the webhook is triggered, but I am getting the following error on the console on my local machine.
"Error: 7 PERMISSION_DENIED: IAM permission 'dialogflow.sessions.detectIntent' on 'projects/xxxx-xxx-xxxx/agent' denied."
I am using Ngrok to tunnel the localhost build to the web and using that URL as the webhook URL in Twilio.
We have a client demo for this feature, so any quick help is appreciated. I am placing my dialog flow code and controller code below
dialogflow.ts
const dialogflow = require("dialogflow");
const credentials = require("../../credential-new.json");
const sessionClient = new dialogflow.SessionsClient({
credentials: credentials
});
const projectId: string = process.env.DIALOGFLOW_PROJECT_ID!;
export const runQuery = (query: string, number: string) => {
return new Promise(async (resolve, reject) => {
try {
// A unique identifier for the given session
//const sessionId = uuid.v4();
const sessionId = number;
// Create a new session
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: query,
// The language used by the client (en-US)
languageCode: "en-US"
}
}
};
// Send request and log result
const responses = await sessionClient.detectIntent(request);
const result = responses[0].queryResult;
resolve(result);
} catch (error) {
reject(error);
}
});
};
This issue got resolved by creating a new account on DialogFlow and providing new API key.
I think the problem is with the service account. Make sure you use the same email which is registered with Dialogflow and GCP and then create a service account and also make sure the service account email is present in the credential-new.json file is the same service account which has the Dialog Flow Admin Role and also check you have given the valid path to access credential-new.json file in the code.
You can safely do this by going to the settings menu on Dialogflow and then clicking on the project id, it will take you to the correct place.
Also, there may be a possibility that you forget to enable the Dialogflow API from the API section on GCP.
Related
I'm having issues getting a cloud function to access a secret from the secrets manager. Basically I want to have my front-end access secrets by sending a request to the backend and then the backend getting the secret from secrets manager.
My cloud function endpoint looks like this:
Endpoint.get("/get-key", authMiddleware, async (req: any, res: Response) => {
try {
const auth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/cloud-platform']
});
const project = req.params.projectId;
const secret = req.params.secret;
const name = `projects/${project}/secrets/${secret}/versions/latest`;
const credentials = await auth.getCredentials();
const ver = new SecretManagerServiceClient({credentials,projectId:project});
const request = {
name,
};
const response = await ver.accessSecretVersion(request);
const payload = response.payload.data.toString();
console.log(`Payload: ${payload}`);
return res.status(200).send({ payload });
} catch (error:any) {
console.log("get key error: ", error);
return res.status(500).send(error.message);
}
});
When I try to access that endpoint I get this error:
PERMISSION_DENIED: Permission denied: Consumer 'project:undefined' has been suspended
I tried explicitly setting the projectId there and it still gives me that error. Not sure what else I can change. perhaps the "latest" is not a valid endpoint for the secrets manager.
PERMISSION_DENIED: Permission denied: Consumer 'project:undefined' has been suspended
The above error occurs when you exceed usage quota as discussed in this github thread or due to ToS violations. You may need to submit the appeal. You can contact Google support for the same.
Also make sure you have given secretAccessor role to your service account.
I created the service account and i provided to my env following this guide
https://cloud.google.com/dialogflow/cx/docs/quick/setup#windows
I tried to run my code using firebase serve, but i got the following error:
Error: 7 PERMISSION_DENIED: IAM permission 'dialogflow.sessions.detectIntent' on 'projects/botDialogflowCX/locations/us-central1/agents/chat' denied
I'm sure that the service account is correct. I already tried to create a dialogflow admin account, client and project owner account.
Here is my code
const functions = require("firebase-functions");
const { SessionsClient } = require("#google-cloud/dialogflow-cx");
const crededentials = require("../../.env/botdialogflowcx-5e936a89c163.json");
exports.teste = functions.https.onRequest((request, response) => {
functions.logger.info("Hello logs!", { structuredData: true });
const client = new SessionsClient({
apiEndpoint: "us-central1-dialogflow.googleapis.com",
});
const sessionId = Math.random().toString(36).substring(7);
const sessionPath = client.projectLocationAgentSessionPath(
"botDialogflowCX",
"us-central1",
"chat",
sessionId);
console.info(sessionPath);
const requestDialogflow = {
session: sessionPath,
queryInput: {
text: {
text: "Oi",
},
languageCode: "pt-br",
},
};
client.detectIntent(requestDialogflow).then((snapshot) => {
const webhookResponse = {
fulfillment_response: {
messages: [{
text: {
text: ["testandoooo", snapshot],
},
},
],
},
};
response.send(webhookResponse);
}).catch((error) => {
console.log(error);
response.status(500).send(error);
});
});
I really don't know what is going on.
Running the command
gcloud projects get-iam-policy botdialogflowcx --flatten="bindings[].members" --format="table(bindings.role)" --filter="bindings.members:teste-889#botdialogflowcx.iam.gserviceaccount.com"
The output was roles/dialogflow.admin.
I add the email to the service account in the dialogflow CX - agent - share.
email in the dialogflow CX - agent - share
email in the account service
But still having the same error, that the IAM does not have permission.
The IAM Permission denied error usually occurs because the service account you are using has not been granted sufficient permission to perform the requested action on the GCP Project connected to the Dialogflow Agent, you have used the incorrect credentials in your request, or you have queried the incorrect agent.
Looking at the following code and error encountered, it seems that the Project Name and Agent Name were used instead of the Project ID and Agent ID value respectively.
const sessionPath = client.projectLocationAgentSessionPath(
"botDialogflowCX", // update to Project ID
"us-central1",
"Chat", // update to Agent ID
sessionId);
Please note that Project ID and Agent ID are different from the Project Name and Agent Name, you can refer to the following documentation on how to collect IDs.
I got it. I just had to change the
client.projectLocationAgentSessionPath(
"botDialogflowCX",
"us-central1",
"chat",
sessionId);
to
const sessionPath = client.projectLocationAgentSessionPath(
"botdialogflowcx",
"us-central1",
"e55b9ef5-d1f2-4e5c-9e95-974501233d50",
sessionId);
and it worked.
if you're running this code in cloud functions i don't believe you need to provide the credentials. If you're running this locally you would set your credentials like this :
$env:GOOGLE_APPLICATION_CREDENTIALS="KEY_PATH"
That way you wouldn't need to provide the credentials in your code.
I'm trying to work with the google API's for the first time, and when I attempt to make a request to the gmail API I'm getting a "precondition check failed" error. I am using a service account authorization, not Oauth2 user consent. Things I've tried:
Authorized "domain wide delegation" for the service account.
Ensured the APP is trusted in the G suite account.
Ensured service account role is "owner"
Enabled domain wide delegation for the client ID of the service account in the g suite admin panel.
This is an adapted sample from the Node client library, but the sample did not use service account auth so I wasn't able to use the sample directly.
const path = require('path');
const {google} = require('googleapis');
const gmail = google.gmail('v1');
async function runSample() {
// Obtain user credentials to use for the request
const auth = new google.auth.GoogleAuth({
keyFile: path.resolve(__dirname, 'google-key.json'),
scopes: ['https://www.googleapis.com/auth/gmail.readonly'],
});
google.options({auth});
const res = await gmail.users.messages.list({userId: 'me'}); // have tried with my gsuite email address as well
console.log(res.data);
return res.data;
}
if (module === require.main) {
runSample().catch(console.error);
}
module.exports = runSample;
Returning error with message: Error: Precondition check failed.
After searching the dark web for eternity, I found a link to a github issue that described how to authenticate as a service using JWT auth.
This is a working version of what I was trying to accomplish:
const path = require('path');
const {google} = require('googleapis');
async getMessageList(userId, qty) {
const JWT = google.auth.JWT;
const authClient = new JWT({
keyFile: path.resolve(__dirname, 'google-key.json'),
scopes: ['https://www.googleapis.com/auth/gmail.readonly'],
subject: 'admin#example.com' // google admin email address to impersonate
});
await authClient.authorize(); // once authorized, can do whatever you want
const gmail = google.gmail({
auth: authClient,
version: 'v1'
});
const response = await gmail.users.messages.list({
includeSpamTrash: false,
maxResults: qty,
q: "",
userId: userId
});
// the data object includes a "messages" array of message data
return response.data;
}
I am trying to verify members of groups using the Google Directory API and cannot get past a 403 error every time I make the request.
I am using a service account, which I have enabled the "Enable G Suite Domain-wide Delegation" option for. I have also added the "https://www.googleapis.com/auth/admin.directory.user, https://www.googleapis.com/auth/admin.directory.group" Scopes using the Client ID within Suite under, "Manage API Client Access"
Code wise, I am using Node for this, and the google supplied googleapis package from NPM.
The external JSON file is the JSON credentials file downloaded when I created the service user.
Here's the code of me trying to get the request.
import { google } from 'googleapis';
async function getGroupUsers(){
const auth = await google.auth.getClient({
keyFile: './src/jwt.keys.json',
scopes: [
'https://www.googleapis.com/auth/admin.directory.group',
'https://www.googleapis.com/auth/admin.directory.group.member',
],
});
const admin = google.admin({
version: 'directory_v1',
auth,
});
const res = await admin.groups.get({
groupKey: 'redacted#domain.redacted',
});
console.log(res)
}
I can't see any obvious reason this isn't working, as I can't see how the user doesn't have permission to the resource?
Obviously missing something obvious here, as the google documentation for this is all over the shop sadly.
Help greatly appreciated!
Thanks
Gareth
Ok after much banging of head and googling I finally for there with this, final working code is as follows, not the inclusion of the client.subject value, which has to be an administrator for the domain in question.
async function validateToken(idToken) {
const keys = JSON.parse(GOOGLE_CREDS);
const client = auth.fromJSON(keys);
client.scopes = [
'https://www.googleapis.com/auth/admin.directory.user',
'https://www.googleapis.com/auth/admin.directory.group',
];
client.subject = 'admin#gsuite.domain';
const admin = google.admin({
version: 'directory_v1',
// auth,
auth: client,
});
const res = await admin.groups.list({
domain: 'redacted',
userKey: email,
});
const { groups } = res.data;
let role = '';
// Check for user role
if (containsGroup(USER_GROUP, groups)) {
role = USER_GROUP;
}
// Check for admin role
if (containsGroup(ADMIN_GROUP, groups)) {
role = ADMIN_GROUP;
}
// Not an admin or user so return unathenticated
if (role === '') {
return authResponse();
}
return successResponse({
'X-Hasura-User-Id': userid,
'X-Hasura-Email': email,
'X-Hasura-Role': role,
'X-Hasura-Groups': groups.map(group => group.id),
'Cache-Control': 'max-age=600',
});
}
I am developing my first app for Google Cloud Platform.
In particular, I am using Node.js as base-framework. Google itself provides Node.js client libraries to interact with their services.
For instance, this code is able to create a new bucket within Cloud Storage:
var storage = require('#google-cloud/storage')();
var bucket = storage.bucket('albums');
bucket.create(function(err, bucket, apiResponse) {
if (!err) {
// The bucket was created successfully.
}
});
//-
// If the callback is omitted, we'll return a Promise.
//-
bucket.create().then(function(data) {
var bucket = data[0];
var apiResponse = data[1];
});
However, if I deploy this code on Google Application Engine, the action above is done using a service account (I suppose, at least) and not as end-user, even after an OAuth authentication, thus ignoring the IAM policies in place.
How could I avoid this problem, and use an user-centric flow for my requested? Can I use the Identiy-Aware Proxy? If yes, how?
Update
To make my question more understandable:
Consider this code:
router.get('/test2', oauth2.required, (req, res, next) => {
const Storage = require('#google-cloud/storage');
// Creates a client
const storage = new Storage();
// Lists all buckets in the current project
storage
.getBuckets()
.then(results => {
const buckets = results[0];
console.log('Buckets:');
buckets.forEach(bucket => {
console.log(bucket.name);
});
res.send("There are " + buckets.length + " buckets.");
})
.catch(err => {
res.send(err);
});
});
This route can be invoked if a given user has already signed in via OAuth2.
I would like to invoke the getBuckets() method passing the OAuth accessToken to perform this operation impersonating the user itself.
In this way, the action cannot skip the IAM rules in place in GCP for that given user currently logged.
I didi try:
const storage = new Storage({
auth: {
'bearer': req.user.accessToken
}});
But it does not work. The application still uses my default account.
You have two options to make sure your requests are allowed:
Grant the necessary permissions on the bucket and/or objects to your service account. This works if you control the data, but not if your application has to function with buckets/objects the user controls.
Do the "three legged Oauth" flow to get permission to make calls on behalf of the user. Unfortunately the client library you are calling doesn't support this. I don't know where you got the auth:{'bearer':...} from, but even if that did work the token you are passing wouldn't have the required scopes to access the bucket.
This autogenerated library does support three-legged auth. You'd use it soemthing like:
var google = require('googleapis');
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
function handle_requests_to_redirect_url() {
// extract code query string parameter
token = await oauth2Client.getToken(code);
// Save token
}
if (no_known_auth_token_for_user()) {
// redirect user to
oauth2Client.generateAuthUrl({access_type:'offline', scope: ['https://www.googleapis.com/auth/devstorage.read_write']});
// after clicking OK, user is redirected to YOUR_REDIRECT_URL
}
var token = get_saved_token_for_this_user();
oauth2Client.setCredentials(token);
var storage = google.storage({version: 'v1', auth: oauth2Client});
storage.buckets.list(function (e,res) {
console.log(res);
});