Node.js GoogleActions帐户尚未链接错误

Node.js GoogleActions帐户尚未链接错误,node.js,google-oauth,dialogflow-es,actions-on-google,Node.js,Google Oauth,Dialogflow Es,Actions On Google,我正在尝试在我的nodejs谷歌助手应用程序上实现oauth2身份验证,该应用程序是使用(DialogFlow或API.ai和谷歌操作)开发的 所以我遵循了这个。但我总是得到“看起来你的测试oauth帐户还没有链接。”错误。当我试图打开“调试”选项卡上显示的url时,它显示500个断开的url错误 对话框流满填充 index.js 'use strict'; const functions = require('firebase-functions'); // Cloud Functions

我正在尝试在我的nodejs谷歌助手应用程序上实现oauth2身份验证,该应用程序是使用(DialogFlow或API.ai和谷歌操作)开发的

所以我遵循了这个。但我总是得到“看起来你的测试oauth帐户还没有链接。”错误。当我试图打开“调试”选项卡上显示的url时,它显示500个断开的url错误

对话框流满填充

index.js

'use strict';

const functions = require('firebase-functions'); // Cloud Functions for Firebase library
const DialogflowApp = require('actions-on-google').DialogflowApp; // Google Assistant helper library

const googleAssistantRequest = 'google'; // Constant to identify Google Assistant requests

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
  console.log('Request headers: ' + JSON.stringify(request.headers));
  console.log('Request body: ' + JSON.stringify(request.body));

  // An action is a string used to identify what needs to be done in fulfillment
  let action = request.body.result.action; // https://dialogflow.com/docs/actions-and-parameters

  // Parameters are any entites that Dialogflow has extracted from the request.
  const parameters = request.body.result.parameters; // https://dialogflow.com/docs/actions-and-parameters

  // Contexts are objects used to track and store conversation state
  const inputContexts = request.body.result.contexts; // https://dialogflow.com/docs/contexts

  // Get the request source (Google Assistant, Slack, API, etc) and initialize DialogflowApp
  const requestSource = (request.body.originalRequest) ? request.body.originalRequest.source : undefined;
  const app = new DialogflowApp({request: request, response: response});

  // Create handlers for Dialogflow actions as well as a 'default' handler
  const actionHandlers = {
    // The default welcome intent has been matched, welcome the user (https://dialogflow.com/docs/events#default_welcome_intent)
    'input.welcome': () => {
      // Use the Actions on Google lib to respond to Google requests; for other requests use JSON
      //+app.getUser().authToken
      if (requestSource === googleAssistantRequest) {
        sendGoogleResponse('Hello, Welcome to my Dialogflow agent!'); // Send simple response to user
      } else {
        sendResponse('Hello, Welcome to my Dialogflow agent!'); // Send simple response to user
      }
    },
    // The default fallback intent has been matched, try to recover (https://dialogflow.com/docs/intents#fallback_intents)
    'input.unknown': () => {
      // Use the Actions on Google lib to respond to Google requests; for other requests use JSON
      if (requestSource === googleAssistantRequest) {
        sendGoogleResponse('I\'m having trouble, can you try that again?'); // Send simple response to user
      } else {
        sendResponse('I\'m having trouble, can you try that again?'); // Send simple response to user
      }
    },
    // Default handler for unknown or undefined actions
    'default': () => {
      // Use the Actions on Google lib to respond to Google requests; for other requests use JSON
      if (requestSource === googleAssistantRequest) {
        let responseToUser = {
          //googleRichResponse: googleRichResponse, // Optional, uncomment to enable
          //googleOutputContexts: ['weather', 2, { ['city']: 'rome' }], // Optional, uncomment to enable
          speech: 'This message is from Dialogflow\'s Cloud Functions for Firebase editor!', // spoken response
          displayText: 'This is from Dialogflow\'s Cloud Functions for Firebase editor! :-)' // displayed response
        };
        sendGoogleResponse(responseToUser);
      } else {
        let responseToUser = {
          //richResponses: richResponses, // Optional, uncomment to enable
          //outputContexts: [{'name': 'weather', 'lifespan': 2, 'parameters': {'city': 'Rome'}}], // Optional, uncomment to enable
          speech: 'This message is from Dialogflow\'s Cloud Functions for Firebase editor!', // spoken response
          displayText: 'This is from Dialogflow\'s Cloud Functions for Firebase editor! :-)' // displayed response
        };
        sendResponse(responseToUser);
      }
    }
  };

  // If undefined or unknown action use the default handler
  if (!actionHandlers[action]) {
    action = 'default';
  }

  // Run the proper handler function to handle the request from Dialogflow
  actionHandlers[action]();

  // Function to send correctly formatted Google Assistant responses to Dialogflow which are then sent to the user
  function sendGoogleResponse (responseToUser) {
    if (typeof responseToUser === 'string') {
      app.ask(responseToUser); // Google Assistant response
    } else {
      // If speech or displayText is defined use it to respond
      let googleResponse = app.buildRichResponse().addSimpleResponse({
        speech: responseToUser.speech || responseToUser.displayText,
        displayText: responseToUser.displayText || responseToUser.speech
      });

      // Optional: Overwrite previous response with rich response
      if (responseToUser.googleRichResponse) {
        googleResponse = responseToUser.googleRichResponse;
      }

      // Optional: add contexts (https://dialogflow.com/docs/contexts)
      if (responseToUser.googleOutputContexts) {
        app.setContext(...responseToUser.googleOutputContexts);
      }

      app.ask(googleResponse); // Send response to Dialogflow and Google Assistant
    }
  }

  // Function to send correctly formatted responses to Dialogflow which are then sent to the user
  function sendResponse (responseToUser) {
    // if the response is a string send it as a response to the user
    if (typeof responseToUser === 'string') {
      let responseJson = {};
      responseJson.speech = responseToUser; // spoken response
      responseJson.displayText = responseToUser; // displayed response
      response.json(responseJson); // Send response to Dialogflow
    } else {
      // If the response to the user includes rich responses or contexts send them to Dialogflow
      let responseJson = {};

      // If speech or displayText is defined, use it to respond (if one isn't defined use the other's value)
      responseJson.speech = responseToUser.speech || responseToUser.displayText;
      responseJson.displayText = responseToUser.displayText || responseToUser.speech;

      // Optional: add rich messages for integrations (https://dialogflow.com/docs/rich-messages)
      responseJson.data = responseToUser.richResponses;

      // Optional: add contexts (https://dialogflow.com/docs/contexts)
      responseJson.contextOut = responseToUser.outputContexts;

      response.json(responseJson); // Send response to Dialogflow
    }
  }
});

// Construct rich response for Google Assistant
const app = new DialogflowApp();
const googleRichResponse = app.buildRichResponse()
  .addSimpleResponse('This is the first simple response for Google Assistant')
  .addSuggestions(
    ['Suggestion Chip', 'Another Suggestion Chip'])
    // Create a basic card and add it to the rich response
  .addBasicCard(app.buildBasicCard(`This is a basic card.  Text in a
 basic card can include "quotes" and most other unicode characters
 including emoji The answer you referenced had an update posted on October 25th indicating they had taken action to prevent you from entering in a google.com endpoint as your auth provider for Account Linking. It seems possible that they may have taken other actions to prevent using Google's auth servers in this way.

If you're using your own auth server, the error 500 would indicate an error on your oauth server, and you should check your oauth server for errors.

Update to answer some of your other questions.

But don't know how to implement an oauth endpoint

Google provides guidance (but not code) on what you need to do for a minimal OAuth service, either using the Implicit Flow or the Authorization Code Flow, and how to test it.

whether it should be a separate cloud function or it has to be included within the existing one

It should be separate - it is even arguable that it must be separate. In both the Implicit Flow and the Authorization Code Flow, you need to provide a URL endpoint where users will be redirected to log into your service. For the Authorization Code Flow, you'll also need an additional webhook that the Assistant will use to exchange tokens.

The function behind these needs to be very very different than what you're doing for the Dialogflow webhook. While someone could probably make a single function that handles all of the different tasks - there is no need to. You'll be providing the OAuth URLs separately.

However, your Dialogflow webhook does have some relationship with your OAuth server. In particular, the tokens that the OAuth server hands to the Assistant will be handed back to the Dialogflow webhook, so Dialogflow needs some way to get the user's information based on that token. There are many ways to do this, but to list just a few:

  • The token could be a JWT and contain the user information as claims in the body. The Dialogflow webhook should use the public key to verify the token is valid and needs to know the format of the claims.

  • The OAuth server and the Dialogflow webhook could use a shared account database, and the OAuth server store the token as a key to the user account and delete expired keys. The Dialogflow webhook could then use the token it gets as a key to look up the user.

  • The OAuth server might have a(nother) webhook where Dialogflow could request user information, passing the key as an Authorization header and getting a reply. (This is what Google does, for example.)

The exact solutions depends on your needs and what resources you have available to you.

And also I am so confused with how oauth authorization code flow will actually work.. Let's assume we are on the Assistant app, once the user say "talk to foo app", does it automatically opens a web browser for oauth code exchange process?

Broadly speaking - yes. The details vary (and can change), but don't get too fixated on the details.

If you're using the Assistant on a speaker, you'll be prompted to open the Home app which should be showing a card saying what Action wants permission. Clicking on the card will open a browser or webview to the Actions website to begin the flow.

If you're using the Assistant on a mobile device, it prompts you directly and then opens a browser or webview to the Actions website to begin the flow.

The auth flow basically involves:

  • Having the user authenticate themselves, if necessary.
  • Having the user authorize the Assistant to access your resources on the user's behalf.
  • It then redirects to Google's servers with a one-time code.
  • Google's servers then take the code... and close the window. That's the extent of what the user's see.
Behind the scenes, Google takes this code and, since you're using the Authorization Code Flow, exchanges it for an auth token and a refresh token at the token exchange URL.

Then, whenever the user uses your Action, it will send an auth token along with the rest of the request to your server.

Plz suggest the necessary package for OAuth2 configuration

That I can't do. For starters - it completely depends on your other resources and requirements. (And this is why StackOverflow doesn't like people asking for suggestions like this.)

There are packages out there (you can search for them) that let you setup an OAuth2 server. I'm sure someone out there provides OAuth-as-a-service, although I don't know any offhand. Finally, as noted above, you can write a minimal OAuth2 server using the guidance from Google.

Trying to create a proxy for Google's OAuth is... probably possible... not as easy as it first seems... likely not as secure as anyone would be happy with... and possibly (but not necessarily, IANAL) a violation of Google's Terms of Service.

can't we store the user's email address by this approach?

Well, you can store whatever you want in the user's account. But this is the user's account for your Action.

You can, for example, access Google APIs on behalf of your user to get their email address or whatever else they have authorized you to do with Google. The user account that you have will likely store the OAuth tokens that you use to access Google's server. But you should logically think of that as separate from the code that the Assistant uses to access your server.

My implementation of a minimal oauth2 server(works for the implicit flow but doesn't store the user session).

taken from https://developers.google.com/identity/protocols/OAuth2UserAgent.

function oauth2SignIn() {
        // Google's OAuth 2.0 endpoint for requesting an access token
        var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';

        // Create element to open OAuth 2.0 endpoint in new window.
        var form = document.createElement('form');
        form.setAttribute('method', 'GET'); // Send as a GET request.
        form.setAttribute('action', oauth2Endpoint);

        //Get the state and redirect_uri parameters from the request
        var searchParams = new URLSearchParams(window.location.search);
        var state = searchParams.get("state");
        var redirect_uri = searchParams.get("redirect_uri");
        //var client_id = searchParams.get("client_id");

        // Parameters to pass to OAuth 2.0 endpoint.
        var params = {
          'client_id': YOUR_CLIENT_ID,
          'redirect_uri': redirect_uri,
          'scope': 'email',
          'state': state,
          'response_type': 'token',
          'include_granted_scopes': 'true'
        };

        // Add form parameters as hidden input values.
        for (var p in params) {
          var input = document.createElement('input');
          input.setAttribute('type', 'hidden');
          input.setAttribute('name', p);
          input.setAttribute('value', params[p]);
          form.appendChild(input);
        }

        // Add form to page and submit it to open the OAuth 2.0 endpoint.
        document.body.appendChild(form);
        form.submit();
      }
“严格使用”;
const functions=require('firebase-functions');//Firebase库的云函数
const DialogflowApp=require('actions-on-google')。DialogflowApp;//谷歌助手库
const googleAssistantRequest='google';//用于标识Google助手请求的常量
exports.dialogflowFirebaseFulfillment=functions.https.onRequest((请求,响应)=>{
log('Request headers:'+JSON.stringify(Request.headers));
log('Request body:'+JSON.stringify(Request.body));
//action是一个字符串,用于标识在实现过程中需要执行的操作
let action=request.body.result.action;//https://dialogflow.com/docs/actions-and-parameters
//参数是Dialogflow从请求中提取的任何实体。
常量参数=request.body.result.parameters;//https://dialogflow.com/docs/actions-and-parameters
//上下文是用于跟踪和存储会话状态的对象
const inputContexts=request.body.result.contexts;//https://dialogflow.com/docs/contexts
//获取请求源(GoogleAssistant、Slack、API等)并初始化DialogflowApp
const requestSource=(request.body.originalRequest)?request.body.originalRequest.source:未定义;
const-app=new-DialogflowApp({request:request,response:response});
//创建Dialogflow操作的处理程序以及“默认”处理程序
const actionHandlers={
//默认欢迎意图已匹配,欢迎用户(https://dialogflow.com/docs/events#default_welcome_intent)
'输入。欢迎':()=>{
//使用googlelib上的操作响应Google请求;对于其他请求,使用JSON
//+app.getUser().authToken
if(requestSource==googleAssistantRequest){
sendGoogleResponse('您好,欢迎使用我的Dialogflow代理!');//向用户发送简单响应
}否则{
sendResponse('您好,欢迎使用我的Dialogflow代理!');//向用户发送简单响应
}
},
//默认回退意图已匹配,请尝试恢复(https://dialogflow.com/docs/intents#fallback_intents)
'输入.未知':()=>{
//使用googlelib上的操作响应Google请求;对于其他请求,使用JSON
if(requestSource==googleAssistantRequest){
sendGoogleResponse('我遇到问题,你能再试一次吗?');//向用户发送简单响应
}否则{
sendResponse('我遇到问题,您能再试一次吗?');//向用户发送简单响应
}
},
//未知或未定义操作的默认处理程序
'默认值':()=>{
//使用googlelib上的操作响应Google请求;对于其他请求,使用JSON
if(requestSource==googleAssistantRequest){
让responseToUser={
//googleRichResponse:googleRichResponse,//可选,取消注释以启用
//GoogleOutputContext:['weather',2,{['city']:'rome'}],//可选,取消注释以启用
speech:'此消息来自Dialogflow的Firebase editor云函数!',//语音响应
displayText:'这是来自Dialogflow的Firebase编辑器云函数!:-)'//显示的响应
};
sendGoogleResponse(responseToUser);
}否则{
让responseToUser={
//richResponses:richResponses,//可选,取消注释以启用
//OutputContext:[{'name':'weather','lifespan':2,'parameters':{'city':'Rome'}}],//可选,取消注释以启用
speech:'此消息来自Dialogflow的Firebase editor云函数!',//语音响应
displayText:'这是来自Dialogflow的Firebase编辑器云函数!:-)'//显示的响应
};
sendResponse(responseToUser);
}
}
};
//如果未定义或未知操作,请使用默认处理程序
如果(!actionHandlers[action]){
动作='默认';
}
//运行适当的处理程序函数来处理来自Dialogflow的请求
动作处理程序[动作]();
//函数将格式正确的Google Assistant响应发送到Dialogflow,然后发送给用户
函数sendGoogleResponse(responseToUser){
if(响应类型用户==='string'){
app.ask(responseToUser);//谷歌助手响应
}否则{
//如果定义了speech或displayText,则使用它进行响应
让googleResponse=app.buildRichResponse().addSimpleResponse({
语音:responseToUser.speech | | responseToUser.displayText,
displayText:responseToUser.displayText | | responseToUser.speech
});
//可选:使用丰富响应覆盖以前的响应
if(responseToUser.googleRichResponse){
googleResponse=responseToUser.googleRichResponse;
}
//可选:添加上下文(https://dialogflow.com/docs/contexts)
if(responseToUser.googleOutputContext){
app.setContext(…responseToUser.GoogleOutputContext);
}
app.ask(googleResponse);//将响应发送给Dialogflow和Google助手
}
}
//函数将格式正确的响应发送到Dialogflow,然后发送给用户
函数sendResponse(responseToUser){
//如果响应是字符串,则将其作为响应发送给用户
if(响应类型用户==='string'){
让responseJson={};
responseJson.speech=responseToUser;//语音响应
responseJson.displayText=responseToUser;//显示的响应
json(responseJson);//发送响应
`accessToken = req.get("originalRequest").get("data").get("user").get("accessToken")
r = requests.get(link)
print("Email Id= " + r.json()["email"])
print("Name= " + r.json()["name"])`