Gmail Add-on PopToRoot not clearing history - gmail-addons

For a GMail add-on, I'm finding that "popToRoot()" is not working as expected. When this is called from within a universal action, the user is sent to the root, but there is a "back" arrow available for them to return to the previous card.
From the docs, that should not be happening. Any suggestions?
Sample code:
return CardService
.newActionResponseBuilder()
.setNavigation(
CardService
.newNavigation()
.popToRoot()
.updateCard(makeRootCard(messageId)))
.build();
where makeRootCard creates and returns a built card.

This problem occurred when trying to log out from a non-Google authentication. It turns out that the makeRootCard() method was returning an AuthorizationException as per the normal authorization flow. I had to make few workarounds to avoid that and give the updateCard method a raw card to work with.

Related

Cloud Schedule + Cloud Functions -> Gmail API watch() - WORKING NOW

This is my first post here. I am sorry if it's a repost, but I've been searching for more than one month for the answer to solve my problem in all websites and forums and until now... no answers!
My goal is to make a Gmail pub/sub watch() to make an action whenever I receive a new email.
To do so, according to the developer's website, I need to subscribe to Gmail watch() on a daily basis with the code:
request = {
'labelIds': ['INBOX'],
'topicName': 'projects/myproject/topics/mytopic'
}
gmail.users().watch(userId='me', body=request).execute()
Until now i have this a working scheduled task with a service account, with INVOKER Permissions. This part just works fine.
In my "initial autorization function" i have:
const {google} = require('googleapis');
// Retrieve OAuth2 config
const oauth2Client = new google.auth.OAuth2(
process.env.CLIENT_ID,
process.env.CLIENT_SECRET,
process.env.CALLBACK_URL
);
exports.oauth2init = (req, res) => {
// Define OAuth2 scopes
const scopes = [
'https://www.googleapis.com/auth/gmail.modify'
];
// Generate + redirect to OAuth2 consent form URL
const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
//prompt: 'none'// Required in order to receive a refresh token every time
});
return res.redirect(authUrl);
};
My issue now is that the access token is generated via (prompt) the first time and never updates to a new one ( the token expires after 1hour...) it means this code stops working after that period and a "manual" intervention is required. According with the documentation, i need to use "offline" method and on "prompt" i can omit (only requests permissions on the 1st time) or none (never asks), like is said here.
I managed how to make it work! tomorow i will continue with the process.
Should i post here my working code for reference?
Thanks!
I will rephrase the process you illustrated so that there is no ambiguity.
According the documentation you pushed:
You do not suscribed to watch(), you call watch()
watch() is an API call to the Gmail API that will enable automatic events publication on a pub/sub topic you define given conditions you specified. Who are you watching? On what events?
You suscribe to a Pub/Sub topic that is targeted by your previous watch() call
A process (e.g: Google cloud function) suscribes to the topic and will consume messages sent by the Gmail API
The call is to be renewed at least every seven days
Because Google needs to be sure you still need to monitor the targeted inbox, it needs a renewal from you. Another watch() call will act so.
Cloud scheduler will enable this periodic renewal
this service will trigger your renewal script you put in your question. To do so it needs to be authenticated to the platform that host the script. It is easier if your script is hosted in a google service (cloud function, cloud run,...) and the authent type depends on the target URL form. In all cases YOU DO NEED an authent token in your request header. The token is generated from a service account you created with the right permission to call your script (e.g: cloud run invoker). By default the scheduler has the right to generate a token from it
So far so good. Now comes the tricky part and you don't mention it in your question. How is authenticated your gmail api client? You cannot monitor someone inbox, unless this person gave you the permission to i.e you call the API with the right Oauth2 token. Indeed in the video you point they authenticat the user using this principe which is implemented in their code with Express-oauth2-handler.
So you will have a cloud function to init end user authent and watch to his/her inbox. The renewal should do so but problem is user will not be there for accepting the end user consent. Here comes the offline access but it is beyond the scope of your question. Finally a second functions will suscribe to the pubsub topic and consume the message as you need. See their implementation code which populate a spreadsheet.
The documentation you shared in the comments does not say that you can remove the token from the headers of the service account, also the gmail API documentation you also shared says that you only:
need to grant publish privileges to gmail-api-push#system.gserviceaccount.com. You can do this using the Cloud Pub/Sub Developer Console permissions interface following the resource-level access control instructions.
In order to achieve this basically what you will need is a setup of two cloud functions, the first scheduled function is responsible for setting up the watch(), and you can check this documentation for how to deploy a scheduled function, and the second function being triggered by the pubsub of gmail notifications, you can check this documentation for how to build an event triggered function. Both processes are similar.
NOTE: I have never user the Gmail API, so I am not sure if any extra steps are necessary but then again, the documentation implies that setting up the permissions of that service account is enough to make it work.
EDIT:
As per the information you have shared. The issue is likely that you are not properly setting the Service Account to authenticate with the Cloud Function. As per described in the documentation, you have to grant to the Service Account the role Cloud Functions Invoker in IAM.
Let me know if this fixed the issue.

Google Apps Script V8 runtime issue with throwException()

I am trying to migrate my current GSuite Add-on script project to V8 engine as per Google's recommendation. I got everything working except that whereever I use:
CardService
.newAuthorizationException()
.setCustomUiCallback('create3PAuthorizationUi')
.throwException();
Add-on is stopping with a runtime error.
Any ideas?
Problem
You are missing the required setAuthorizationUrl method in the chain. I am not sure why switching back to Rhino runtime works for you, probably the error detection is improved.
Also, since we do not have the code for the logout function that causes the error, you might have another migration issue unrelated to authorization.
Solution
Your snippet should look something like this:
const authUrl = "https://example.com";
CardService
.newAuthorizationException()
.setAuthorizationUrl(authUrl)
.setCustomUiCallback('create3PAuthorizationUi')
.throwException();
Issue Tracker note
The issue I created on the issue tracker is now closed as intended behavior. Turns out the URI provided to the method (see authUrl) must be a valid URI. Technically, it accepts invalid and unreachable URIs, the only requirement is that the argument must be a string.
Because during testing I relied on the ScriptApp.getService().getUrl() method call chain on a new project not deployed as a Web App, therefore the authUrl held null.

In Google Apps Script, avoid second 'Review Permissions' prompt, possibly by using approval_prompt

I have searched StackOverflow, etc. for a solution to this problem, and several answers 'point me in a direction' (mentioning approval_prompt = auto not approval_prompt = force), but none are applicable (as far as I can tell) to my situation.
I have a Web Application hosted at www.mjpanel.com that expects to use a Google Apps Script that I 'own', but the Javascript at www.mjpanel.com calls the Google Apps Script (deployed as a Web App with doGet()) as a Web Service. It expects the web service call to return various JSON objects.
If the user has not yet authorized my application, the call to the Google Apps Script Web App / Web Service will not return a JSON object, causing www.mjpanel.com Javascript code to fail saying "Invalid Request" (because it isn't a JSON object as my code expects).
To prevent this from happening, www.mjpanel.com uses gapi.auth2.init to get the permissions/scopes it needs. I'm developing everything now, so if/whenever the Google Apps Script evolve to use something (like sending GMail emails as the user) that is new, I have been figuring out the scope to request, adding it to the list of scopes in the gapi.auth2.init call, and everything is fine. The next time a user uses the app., they get initially prompted for the newly added scope, then everything proceeds fine.
However, now sometimes one of my test users has a Web Service call fail because Google Apps Script is returning another request for permissions for a 'new permission' of 'Have Offline Access'.
There's nothing about my script that would warrant the user needing to grant this permission.
When I research, a lot of stuff (mostly about requesting OAuth2 stuff in a 'structure' different than the way my app. is set up) says it has to do with submitting a 'approval_prompt=force' in my request URL.
However, the way I have my app set up, all the URLs I would use (aside from my 'custom stuff' in the query string) are dictated by Google Apps Script. And I can't find any place where any URL I use has an approval_prompt in it.
I can't figure out where I would need to configure that approval_prompt to be auto (as is recommended in the 'successful answers' I find).
Of if the idea of approval_prompt is 'on the wrong track', any information in general to help me solve this problem would be greatly appreciated.
Thanks in advance for any help you can give me.
Unfortunately the Google Apps Script native OAuth flow includes approval_prompt=force. This causes the following conditions:
If cookie exists in browser and has permission for this application the consent screen will not be displayed.
If cookie exists in browser but does not have permission for this application: consent screen will be displayed
If cookie does not exist in browser and the application has permissions: application will request 'Have offline access'.
The easiest way around this is to manage your own OAuth flow and use the Execution API. The following link will take you to the javascript quick start.
https://developers.google.com/apps-script/guides/rest/quickstart/target-script

Google Drive OAuth 2 flow giving invalid_scope error

My Google Drive app requests the following scopes when exchanging a code for an access token:
https://www.googleapis.com/auth/drive.file
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile
https://www.googleapis.com/auth/drive.install
In particular, this is the query string of the URL that is eventually being requested from Google during the exchange:
code=XXXXXXXXXX&grant_type=authorization_code&redirect_uri=XXXXXXXXXXX& scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.file+ https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email +https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile +https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.install &client_id=XXXXXX.apps.googleusercontent.com&client_secret=XXXXXX
The response is a 400 error, with the error message "invalid_scope". What am I doing wrong?
[Edit] Additional information:
The error only happens when the user clicks through from Google Drive to create a new document. If I initiate the authentication/authorization flow from my own app, the list of scopes is accepted just fine. If the user clicks through the actual Drive app to create a new document, I get invalid_scopes.
The invalid scope is drive.install. If I remove that from the list of requested scopes when the user shows up to create a new document, things start working again. Does that make any sense at all? If the user has the Drive app installed already via us requesting that scope, why would requesting that same scope when the user shows up from the Drive app cause a problem of any kind?
I experienced a similar problem. The solution was to pass an array of scopes to the google client:
google_client.authorization.scope=[
'https://www.googleapis.com/auth/calendar.readonly',
'https://www.googleapis.com/auth/drive.appdata']
rather than a concatenated string of scopes
google_client.authorization.scope="https://www.googleapis.com/auth/calendar.readonly%2Bhttps://www.googleapis.com/auth/drive.appdata"
The GET request in the Rails log looked identical, but the result was very different!
There is a chance that You might be using tab instead of space between two auth like
https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/drive.appdata
putted on new line to show tabs
always use one white space between these two links to authorization.
this had happened with me.
The new google api (at the moment of this answer is posted) requires scope attribute to be one string and scopes separated with white space. So like this
var SCOPES = "https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/gmail.send";
gapi.auth2.init({
client_id:CLIENT_ID,
scope: SCOPES
}).then (...)
You could try not escaping the + symbols. That worked for us.

How does one one avoid 404's on changes.list thumbnails when signed in with multiple accounts?

I am getting 404's for image thumbnails when I'm signed on multiple accounts and making API calls for changes.list.
The full steps to reproduce the issue are described as below:
Be signed in with two accounts, one a personal gmail account, and one a Google apps account, say User_gmail and User_gapps respectively
Make API calls to get URLs with to get a list of changes in drive.
Load up the thumbnails for images.
Expected behaviour:
Thumbnail images work.
Actual behaviour:
When I am signed in with User_gmail and User_gapps and I pick
User_gmail during the authentication flow, I can see thumbails for
all modified documents.
When I am signed in only with User_gapps or User_gmail, I can see
thumbnails correctly.
However, when signed in as both users, and I pick User_gapps as the user during the authentication flow, I see broken thumbnails.
Update: the easiest way to reproduce this is sign into multiple accounts, one personal and one apps account, and use this api. If you authorize the API with your work account, and execute a request, and click on the "thumbnailLink" URLs, you can see 404s.
https://developers.google.com/apis-explorer/#p/drive/v2/drive.changes.list?includeDeleted=false
There are two ways to see a Google Document's thumbnail. After getting the URL of the thumbnail through the Google Drive API this URL can be viewed using either 'Cookie' authorization or OAuth 2.0 Authorization.
You seem to be using the 'Cookie' authorization (i.e. the user is currently signed in the browser). What happens is that the browser will use one of the two accounts by default and unfortunately it might not use the correct account.
In that specific scenario you can append &authuser=0 or &authuser=1 to the URL of the thumbnail this will force using an account or the other, one of the two values will work.
The issue is that you don't know which value will work (1 or 0) so you would have to test the two URLs programmatically using Javascript to see which one works or fails.
The other way to load a thumbnail is to use OAuth 2 auth. This is more robust. You can do that easily by appending &access_token=<Your_OAuth_2_access_token>. Make sure you use the same OAuth access token (formerly known as Bearer token) that you used in your API requests. This way you are sure that the image will load. this will even work if the user has signed-out of his Google Account. You will need access to the actual file's data. So this won't work if you have authorized the drive.readonly.metadata scope for instance. YOu need to authorize one of the following scopes:
https://www.googleapis.com/auth/drive.file
https://www.googleapis.com/auth/drive
https://www.googleapis.com/auth/drive.readonly
Just beware: access tokens are only valid for 1h.