I have been trying to connect an assistant action to my backend server
I am using my own Oauth server and followed the instructions on
https://developers.google.com/actions/identity/oauth2?oauth=code
I am using actions_intent_Sign_in for my dialogflow event intent (like https://actions-on-google.github.io/actions-on-google-nodejs/classes/conversation_helper.signin.html)
when i use my action to sign in, i get the login window to my server, i do the account linking and i can see that i generated the tokens on my server but i cant find the token in (conv.user.access.token)
and this is the code for my intent using "actions on google sdk "
'use strict';
var _ = require('lodash');
var path = require('path')
var express = require('express')
var http = require('http')
const bodyParser = require('body-parser');
var expressApp = express().use(bodyParser.json());
var server = http.createServer(expressApp).listen(3000)
const {
dialogflow,
SignIn
} = require('actions-on-google');
const app = dialogflow({
debug: true,
clientId: '7b4a6dfc-4b35-11e9-8646-d663bd873d93'
});
app.intent('Start Sign-in', conv => {
conv.ask(new SignIn());
});
app.intent('Get Sign-in', (conv, params, signin) => {
console.log("get sign in ");
console.log(JSON.stringify(signin));
if (signin.status === 'OK') {
const access = conv.user.access.token
console.log("the access token is " + access);
conv.ask('Great, thanks for signing in! What do you want to do next?');
} else {
conv.ask('I wont be able to save your data, but what do you want to do next?.');
}
});
and the response comes back as
{"#type":"type.googleapis.com/google.actions.v2.SignInValue","status":"OK"}
the access token is undefined
Response {
"status": 200,
"headers": {
"content-type": "application/json;charset=utf-8"
},
"body": {
"payload": {
"google": {
"expectUserResponse": true,
"richResponse": {
"items": [
{
"simpleResponse": {
"textToSpeech": "Great, thanks for signing in! What do you want to do next?"
}
}
]
}
}
}
}
}
the user object of conv has only this data
"user": {
"raw": {
"lastSeen": "2019-03-20T12:46:23Z",
"locale": "en-US",
"userId": "okdhyeGSk5tofgLjEepIUrA6mmewCESY8MjklZRPvQJgv6-uybfPobwdfgtrGZJ3bE2sM9ninhst"
},
"storage": {},
"_id": "okdhyeGSk5tofgLjEepIUrA6mmewCESY8MjklZRPvQJgv6-uybfPobwdfgtrGZJ3bE2sM9ninhst",
"locale": "en-US",
"permissions": [],
"last": {
"seen": "2019-03-20T12:46:23.000Z"
},
"name": {},
"entitlements": [],
"access": {},
"profile": {}
}
i dont know where the access/refresh token can be found or if there is any requirement for the post to send from my oauth server that i missed
so finally i managed to get it working with the help of Actions on Google Support Team
the problem was me having another google account logged-in in another tab, even though i had the AoG and dialogflow agent connected with the same account
tried all using incognito window and it works
Related
Trying to get this to work and GET / Patch work just fine, but POST gives me HTTP STATUS 400 and 403. Must be something with scopes. In Azure AD I have set the following scopes:
Mail.ReadWrite (Delegated)
Mail.ReadWrite (Application)
Mail.Send Delegated)
Mail.Send (Application)
So, signing in works just fine, getting / patching messages as well. Only POST doesnt seem to work.
See code for exact error messages.
Angular10
App.module
export function MSALInstanceFactory(): IPublicClientApplication {
return new PublicClientApplication({
auth: {
clientId: 'xxxx',
authority: 'https://login.microsoftonline.com/common/',
redirectUri: '/',
postLogoutRedirectUri: '/#/login'
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE, // set to true for IE 11
},
system: {
loggerOptions: {
loggerCallback,
logLevel: LogLevel.Info,
piiLoggingEnabled: false
}
}
});
}
export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration {
const protectedResourceMap = new Map<string, Array<string>>();
protectedResourceMap.set('https://graph.microsoft.com/v1.0/me', ['user.read', 'mail.readWrite', 'email']);
// also tried these scopes ..
// protectedResourceMap.set('https://graph.microsoft.com/v1.0', ['user.read', 'mail.readWrite', 'email']);
// protectedResourceMap.set('https://graph.microsoft.com/v1.0/query', ['user.read', 'mail.readWrite', 'email']);
// protectedResourceMap.set('https://graph.microsoft.com/v1.0/search/query', ['user.read', 'mail.readWrite', 'email']);
return {
interactionType: InteractionType.Redirect,
protectedResourceMap
};
}
export function MSALGuardConfigFactory(): MsalGuardConfiguration {
return { interactionType: InteractionType.Redirect };
}
#NgModule({
imports: [
BrowserModule,
// etc..
],
declarations: [AppComponent],
providers: [
NgEventBus,
ChhServices,
SynclogService,
AppService,
AuthService,
GapiServices,
{
provide: ErrorHandler,
useClass: ErrorService,
},
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
},
{
provide: MSAL_INSTANCE,
useFactory: MSALInstanceFactory
},
{
provide: MSAL_GUARD_CONFIG,
useFactory: MSALGuardConfigFactory
},
{
provide: MSAL_INTERCEPTOR_CONFIG,
useFactory: MSALInterceptorConfigFactory
},
MsalService,
MsalGuard,
MsalBroadcastService
],
bootstrap: [AppComponent],
})
export class AppModule { }
Auth.service
signIn() {
console.log('AuthService::signIn');
this.msalService.loginPopup().subscribe((result) => {
this.accessToken = result['accessToken'];
console.log('authority', result, this.accessToken);
});
}
testGraphApi() {
// 200 OK
const apiGet = this.httpClient.get(`https://graph.microsoft.com/v1.0/me/messages/`).subscribe((data) => {
console.log('get', '/me/messages', data);
});
const categories: any[] = ['custom'];
const body = {
subject: '2320, with tags',
flag: { flagStatus: 'flagged' }, // notFlagged
categories,
body: {
contentType: 'html',
content: 'lalala'
},
inferenceClassification: 'other'
};
const id = 'AQMkADAwATM3ZmYAZS0zOTkANy02MTAwAC0wMAItMDAKAEYAAAM_TfJTK-tISYhjZdaCkkbgBwCPpkVcscQ9QJF-EDzB8h_oAAACAQwAAACPpkVcscQ9QJF-EDzB8h_oAAACHbIAAAA=';
// 200 OK
const apiPatch = this.httpClient.patch(`https://graph.microsoft.com/v1.0/me/messages/${id}`, body).subscribe((data) => {
console.log('patch', '/me/messages', data);
});
const bodySendMail = {
'message': {
'subject': 'Meet for lunch?',
'body': {
'contentType': 'Text',
'content': 'The new cafeteria is open.'
},
// etc..
}
}
const headers = new HttpHeaders({ 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.accessToken}` });
// 403 Forbidden
// "code": "ErrorAccessDenied",
// "message": "Access is denied. Check credentials and try again.",
const apiSendMail = this.httpClient.post(`https://graph.microsoft.com/v1.0/me/sendMail`, bodySendMail, { headers }).subscribe((data) => {
console.log('post', '/me/sendMail', data);
});
const bodySearch = {
'requests': [
{
'entityTypes': [
'message'
],
'query': {
'queryString': 'ref:6019d6bf1ce3425fb833559e'
},
'from': 0,
'size': 5
}
]
}
// 400 Bad Request
// "code": "AuthenticationError",
// "message": "Error authenticating with resource",
const apiSearch = this.httpClient.post(`https://graph.microsoft.com/v1.0/search/query`, bodySearch, { headers }).subscribe((data) => {
console.log('post', '/search/query', data);
});
}
// 403 Forbidden
// "code": "ErrorAccessDenied",
// "message": "Access is denied. Check credentials and try again."
Send mail API needs Mail.Send permission. When requesting /me endpoint which bases the current signed-in user, it should have the delegated permission.
So you need to add Mail.Send of delegated permission in the portal and add it in your code.
// 400 Bad Request
// "code": "AuthenticationError",
// "message": "Error authenticating with resource"
searchEntity: query API needs the Mail.ReadWrite delegated permission. This api only supports "work or school account". A work account typically uses an organization’s custom domain name or company name, such as "jon#contoso.com" or "xxx#yourTenantName.onmicrosoft.com".
You could test to request the api in Graph Explorer.
I am using graph API to add message rule which is forward a mail from user's inbox. Rule is getting added but mails are not forwarding to specified id.
Here are some details:
var data = {
"displayName": "From partner",
"sequence": 1,
"isEnabled": true,
"conditions": {
"isAutomaticForward": true
},
"actions": {
"forwardTo": [
{
"emailAddress": {
"name": "recipient name ",
"address": "email address"
}
}
],
"stopProcessingRules": true
}
}
axios.post("https://graph.microsoft.com/v1.0/users/{userId}/mailFolders/inbox/messageRules", data,
{
headers: {
"Authorization": "Bearer " + access_token
}
}
)
.then(response => {
console.log(response.data)
})
.catch(err => {
console.log(err.response)
})
Response is as expected but mails are not forwarding.
I tried the above payload, steps and it works for me!!
(1) Create a new rule using Graph API
POST https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messageRules
Content-type: application/json
{
"displayName": "From partner",
"sequence": 2,
"isAutomaticForward": true,
"actions": {
"forwardTo": [
{
"emailAddress": {
"name": "Alex Wilbur",
"address": "AlexW#contoso.onmicrosoft.com"
}
}
],
"stopProcessingRules": true
}
}
(2) Test whether the rule is working or not.
Result: It's working as expected
(3) Check that the above rule shows or not (as part of Outlook.office.com or Outlook UI's rule section)
Adding a snapshot that i captured from Outlook.office.com, mailbox settings!!
I have an inline bot similar to #pic and it works ok, when I type query word I can see pictures appear so I can choose one and send to a chat. The problem is when I do - 2 copies of same result are sent. This happens only on iOS client. On Android, PC and other platforms only one picture is being sent. I have checked all logs (the bot is done in Node.js) and for every request I have a single response. It seems to be a iOS client bug, although #pic bot works fine. Has someone encountered this bug or have an idea of what can cause it?
Example of answerInlineQuery response object
{
"inline_query_id": "817150058382989968",
"results": [
{
"type": "photo",
"id": "se090",
"photo_url": "http://www.shadowera.com/secardbot361/se090.jpg",
"thumb_url": "http://www.shadowera.com/secardbot361/se090.jpg",
"photo_width": 344,
"photo_height": 480,
"title": "Tracking Gear",
"description": "You can view the hands of opposing players.",
"caption": "king"
},
{...
UPDATE:
So I have created a simplest possible inline bot in node.js #iosinlinebot (you can try it) AND you have the same exact behaviour: only on iOS devices you will send 2 images to the chat once tapped on the result.
Here is the code:
exports.handler = function(event, context) {
console.log(event);
const https = require("https");
let answer = {
inline_query_id: event.inline_query.id,
results: [{
type: "photo",
id: "abcd",
photo_url: "https://lh3.googleusercontent.com/jVXglyWWL5J2y1vRN-7Jy3_ozvvZc4w5486IAkbAIrWcNN_vn7YuIvhc1JDtGq43BqGl=s180",
thumb_url: "https://lh3.googleusercontent.com/jVXglyWWL5J2y1vRN-7Jy3_ozvvZc4w5486IAkbAIrWcNN_vn7YuIvhc1JDtGq43BqGl=s180",
photo_width: 180,
photo_height: 180,
title: "title",
description: "description",
caption: "test"
}],
cache_time:1
};
let postBody = JSON.stringify(answer);
let options = {
hostname: "api.telegram.org",
port: 443,
path: "/bot" + process.env.TOKEN + "/answerInlineQuery",
method: "POST",
headers: {
'Content-Type': 'application/json',
'Content-Length': postBody.length
}
};
let postreq = https.request(options, (res) => {
res.setEncoding('utf8');
const body = [];
res.on('data', (chunk) => body.push(chunk));
res.on('end', () => {
let j = body.join('');
console.log(j);
//context.done(JSON.parse(j));
});
});
postreq.write(postBody);
postreq.end();
};
this is an event object (coming from telegram):
{
"update_id": 12345678,
"inline_query": {
"id": "123456789123456789",
"from": {
"id": 123456789,
"is_bot": false,
"first_name": "Firstname",
"username": "username",
"language_code": "it-IT"
},
"query": "test",
"offset": ""
}
}
UPDATE:
Thanks to Sedric Heidarizarei we were able to find the problem. It is a telegram iOS client bug. If InlineQueryResultPhoto object contains caption field, you user will post 2 images to the chat.
It is very important to close the Begin and the End of your regex with ^ and $.
For example a user with this regex /^[/]start/ can use start and start a and start b as Bot command And will allow Them to receive your photo, But with /^[/]start$/, The user must enter the exact /start Command.
1: Use This Module: node-telegram-bot-api
2: And Send Your Photo:
bot.onText(/^[/]start$/, (msg) => {
const opts = {
parse_mode: 'Markdown',
reply_markup: {
inline_keyboard: [[{
text: 'đź”™',
callback_data: 'back'
}]]
}
};
bot.sendPhoto(msg.chat.id, 'AgADBAADn64xBoABCx8L8trMV9eMqgDAAEC', opts); // Your Photo id
});
Notice:
Open an empty project and just use and check your InlineQueryResultPhoto.
update:
That is a Telegram bug for For temporary use, remove caption from your let answer ={}
So I have implemented the react native FBSDKGraphRequest and login button. Login is working correctly, but when i attempt a graph request of the user, instead of the complete object i expect the /me endpoint to return
{
"id": "162036280799349",
"birthday": "08/08/1980",
"email": "test_ppjeffg_eight\u0040tfbnw.net",
"first_name": "Test",
"gender": "male",
"last_name": "Eight",
"link": "https://www.facebook.com/app_scoped_user_id/162036280799349/",
"locale": "en_US",
"name": "Test Eight",
"timezone": -8,
"updated_time": "2015-07-28T18:22:16+0000",
"verified": false
}
I just get
Object {name: "Test Eight", id: "162036280799349"}
I very well may be doing the request incorrectly, though I've done everything according to documentation. Here is the relevant source code:
class LoadingOverlay extends BaseComponent{
constructor(props){
super(props);
this._bind(/*'_fetchFriendsRequestFunction'*/);
this.state = {isVisible: true,
token: null,
profileInfo: null}
}
_fetchGraphRequestFunction(){
console.log("start");
var fetchProfileRequest = new FBSDKGraphRequest((error, result) => {
if (error) {
alert('Error making request.');
} else {
// Data from request is in result
console.log(result);
}
}, '/me');
// Start the graph request.
fetchProfileRequest.start();
}
render(){
return(
<Overlay isVisible={this.state.isVisible}>
<BlurView style={styles.background} blurType="dark">
<FBSDKLoginButton
onLoginFinished={(error,result)=>{
if (error){
alert('Error Logging In.');
} else {
if (result.isCanceled){
alert('Login Cancelled.');
} else {
FBSDKAccessToken.getCurrentAccessToken((token)=>{
console.log(token.tokenString);
this._fetchGraphRequestFunction();
})
}
}
}}
onLogoutFinished={()=>console.log('Logged Out.')}
readPermissions={['public_profile', 'email', 'user_birthday', 'user_friends']}
publishPermissions={['publish_actions']}/>
</BlurView>
</Overlay>
);
}
}`
You can request additional parameters from Facebook by either appending them to the uri like this:
/me?fields=id,name,email
or by calling the addStringParameter function on the FBSDKGraphRequest object like this:
fetchProfileRequest.addStringParameter('picture,email,gender','fields');
However, what fields you get in return depends on the permission of your app and the settings of the user.
Also, note this little trick from the ios FB sdk documentation: https://developers.facebook.com/docs/reference/ios/current/class/FBSDKLoginButton/
Note, that if read permissions are specified, then publish permissions should not be specified.
So try sending an empty publishPermissions parameter and see if that fixed it.
More information here:
Publish or manage permissions are not permited to to be requested with read permissions FACEBOOK SDK
https://coderwall.com/p/gkeqcq/request-read-and-publish-permissions-simultaneously-using-ios-facebook-support
I'd love to see an example of the flow to make this work in react native if anyone has it.
Using loopback, I have created a connection to an existing API using the REST connector, which is working well. I would however like to pass through the oAuth token coming from the client.
I can get hold of the oAuth token by grabbing ctx.req.headers.authorization from the Model.beforeRemote method, but can't seem to figure out a way of passing it to the REST connector as a new header.
I've tried a couple of things:
Adding a hook using Model.observe (but this doesn't seem to fire with the REST connector).
Using a template with an authorization field - but have not been able to get this working correctly.
Any ideas appreciated.
With the connector below you should be able to pass the OAuth token into the function (as first parameter in the example). Does something like this not work for you?
{
connector: 'rest',
debug: false,
options: {
"headers": {
"accept": "application/json",
"content-type": "application/json",
"authorization": "{oauth}"
},
strictSSL: false,
},
operations: [
{
template: {
"method": "GET",
"url": "http://maps.googleapis.com/maps/api/geocode/{format=json}",
"query": {
"address": "{street},{city},{zipcode}",
"sensor": "{sensor=false}"
},
"options": {
"strictSSL": true,
"useQuerystring": true
},
"responsePath": "$.results[0].geometry.location"
},
functions: {
"geocode": ["oauth", "street", "city", "zipcode"]
}
}
]}
Wanted to answer this, and build on Bryan's comments. Firstly, in datasources.json, you'll want to setup the REST connector:
{
"name": "connect",
"connector": "rest",
"debug": "true",
"operations": [
{
"template": {
"method": "GET",
"url": "http://server/api",
"headers":{
"authorization": "Bearer {token}"
}
},
"functions": {
"get": ["token"]
}
}
]
}
As Bryan covered, it possible to put the auth header in each call, or at the root of the connector.
Secondly, and this is the bit I was stuck on, in order to pass the token to the API call from a model, it's required to generate a remote method that passes the token as a query parameter. This is what it looks like in this example:
module.exports = function (Model) {
Model.disableRemoteMethod('invoke', true);
Model.disableRemoteMethod('get', true);
Model.call = function (req, cb) {
var token = req.token;
Model.get(token, function (err, result) {
cb(null, result);
});
};
Model.remoteMethod(
'call',
{
http: {path: '/', verb: 'get'},
accepts: [
{arg: 'req', type: 'object', http: {source: 'req'}}
],
returns: {
root: true
}
}
);
};
Notice how the req argument is required in order to provide the request to the model. You also notice that I've disabled the original get and invoke methods (replacing it with a more REST-friendly resource).
Finally, you'll need to get the token into the request. For this, it's easy enough to use some middleware. Here's an example from server.js:
app.use('/api', function (req, res, next) {
oidc.authenticate(req, function (err, token) {
if (err) {
return res.send({status: 401, message: err});
}
req.token = token;
next();
});
});
In the above example, I'm using an internal OIDC provider to validate the token, but of course, you can use anything.