endoplasmic / google-assistant

A node.js implementation of the Google Assistant SDK
MIT License
283 stars 75 forks source link

Handling of refresh token? #75

Closed pauleeeeee closed 4 years ago

pauleeeeee commented 4 years ago

Does this library automatically handle refreshing the user's access token if the token is expired? It does not seem that way. More context:

Environment: Google Cloud Function Status: Works until access token expires. Then, invocations of the cloud function fail due to invalid credentials. Other information:

endoplasmic commented 4 years ago

The library does handle the auto refreshing of tokens.

What happens when you try to start a new session when the token expires? I've never seen this happen (and I've had an instance of this running on a server for months).

Can you paste the error the next time you get it? I'm going to start a session and let it sit there and see what happens when I try again in an hour or so.

pauleeeeee commented 4 years ago

Thanks for your response. Here is the error: Conversation Error: { Error: 16 UNAUTHENTICATED: Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project. at Object.exports.createStatusError (/srv/node_modules/grpc/src/common.js:91:15) at ClientDuplexStream._emitStatusIfDone (/srv/node_modules/grpc/src/client.js:233:26) at ClientDuplexStream._receiveStatus (/srv/node_modules/grpc/src/client.js:211:8) at Object.onReceiveStatus (/srv/node_modules/grpc/src/client_interceptors.js:1311:15) at InterceptingListener._callNext (/srv/node_modules/grpc/src/client_interceptors.js:568:42) at InterceptingListener.onReceiveStatus (/srv/node_modules/grpc/src/client_interceptors.js:618:8) at /srv/node_modules/grpc/src/client_interceptors.js:1127:18 code: 16, metadata: Metadata { _internal_repr: { 'www-authenticate': [Array] }, flags: 0 }, details: 'Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.' }

I think it has to do with the fact that I am not passing an oAuth2Client object... although I wish too but can't because of typings file doesn't specify it...?

endoplasmic commented 4 years ago

no no, oAuth2Client is optional (you can see in the example files), it's just another way to use the regular Google OAuth (say from a web sign-in) without having to ask the user to auth again.

Mind pasting some of your code? I didn't create the typings file (was a PR), but I'm happy to look at what you've got to see if I can find anything out of wack.

pauleeeeee commented 4 years ago

Thanks for your response again Here is my code:

import * as functions from 'firebase-functions';
import { google } from 'googleapis';
const OAuth2 = google.auth.OAuth2; 
import {resolve} from 'path';

// const googleAssistantCredentials = require('../src/credentials.json');
// const googleAssistantTokens = require('../src/googleAssistantTokens.json');

const authConfig = {
    keyFilePath: resolve(__dirname,'../src/googleAssistantCredentials.json'),
    savedTokensPath: resolve(__dirname,'../src/googleAssistantTokens.json')
}

import GoogleAssistant = require("google-assistant");

export const googleAssistant = functions.https.onRequest((request, response)=>{

    if (request.body.command){

        var command = request.body.command;
        const conversation = {
            isNew:true,
            textQuery:command 
        }

        const startConversation = (conversation:any) => {
            conversation
                .on('response', (text:any) => {
                    console.log('Assistant Text Response:', text);
                    response.status(200).send(text);
                })
                .on('error', (error:any) => {
                    console.log('Conversation Error:', error);
                    response.status(200).send('failed');
                  });
        }

        const assistant: GoogleAssistant = new GoogleAssistant(authConfig);

        assistant.on('ready', () => {
            // start a conversation!
            assistant.start(conversation);
          })
          .on('started', startConversation)
          .on('error', (error:any) => {
            console.log('Assistant Error:', error);
            response.status(200).send('failed');
          });

    } else {
        console.log('error no command received')
        response.status(200).send('failed');
    }
});
pauleeeeee commented 4 years ago

Maybe because I am declaring the auth config outside the scope of the cloud function?

endoplasmic commented 4 years ago

Your code pasted a bit wacky, but it looks like you're going new GoogleAssistant each time there is a new request. You only need to init it once and then your startConversation call should happen on the request.

pauleeeeee commented 4 years ago

Yeah sorry about the formatting - I tried to fix it but it just wouldn't take. Thanks for the idea. I will move the init to the global scope outside of the cloud function, re-up my credentials, and try again.

endoplasmic commented 4 years ago

so for formatting, wrap it in tripple tick ` (without the spaces)

endoplasmic commented 4 years ago

Just wanted to check in to see how you made out. Moving the new GoogleAssistant out of the request fix things up?

pauleeeeee commented 4 years ago

Hi endoplasmic,

So I moved the instantiation out of the function scope, but that means that the function could not receive the "ready" event (which means the callback to start did not fire). So I instead called the assistant.start() function which worked just fine. However, after one hour, I still got the unauthenticated error. I think it has to do with the way that your Auth module refreshes the token. I am using Firebase and I am just not sure that the tokens are refreshed and saved to the global memory of my Firebase instance. So, I used the oAuth2Client library to update my tokens myself on each function invocation (if necessary). Then I modified the index.d.ts file in your library to allow me to pass the oAuth2Client object. This worked for me. I would suggest configuring the library to allow the user to pass the oAuth2Client object. I know that you have done this in JS but, as I mentioned, you cannot do so in a strict TypeScript environment.

Hope that gives you some ideas... and thank you for this library and all of your help!

endoplasmic commented 4 years ago

Glad you got it sorted. Would you mind doing a PR on that index file? That way others in your shoes can benefit!

pauleeeeee commented 4 years ago

It would be my very first pull request ever... So... Just be warned LOL