Google Workspace Add-on for Calendar Conference Solution with OAuth2 integration fails on redirect with CORS - google-apps-script

I have built a google workspace addon for calendar conference solution using app script and oauth2 library.
My oauth client code is:
return (
OAuth2.createService("OAuthApp")
// Set the endpoint URLs.
.setAuthorizationBaseUrl(`${FRONTEND_DOMAIN}/auth/login`) // frontend api
.setTokenUrl(`${BACKEND_DOMAIN}/v1.0/oauth/exchange-code-for-token`) // backend api
// Set the client ID and secret.
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
// Set the name of the callback function that should be invoked to
// complete the OAuth flow.
.setCallbackFunction("authCallback")
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
.setCache(CacheService.getUserCache())
.setLock(LockService.getUserLock())
);
Where for setAuthorizationBaseUrl i have provided out frontend page to open login page for user to authenticate. When user authenticates successfully, backend redirects to the addon on https://script.google.com/macros/d/{SCRIPT_ID}/usercallback with auth code in params and status code 302.
However when i redirect the frontend to addon from backend i get this error in console:
Access to XMLHttpRequest at 'https://script.google.com/macros/d/{SCRIPT_ID}/usercallback?code=4611729c041e9d0f93506bcfa772707d' (redirected from 'https://{BACKEND_ENDPOINT}/oauth/verify-otp') from origin 'https://{FRONTEND_ENDPOINT}' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
My add on manifest is:
{
"oauthScopes": [
"https://www.googleapis.com/auth/calendar.addons.execute",
"https://www.googleapis.com/auth/calendar.events.readonly",
"https://www.googleapis.com/auth/calendar.addons.current.event.read",
"https://www.googleapis.com/auth/calendar.addons.current.event.write",
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/script.scriptapp"
],
"addOns": {
"common": {
"homepageTrigger": {
"enabled": false
},
"logoUrl": "https://lh3.googleusercontent.com/-WuPhRsFGKUc/XCng7m_FIxI/AAAAAAAAAGg/ASH4GCGDMs0d55OZQGCSIQHXjRAKnkeTQCLcBGAs/s400/jitsi-logo-96x96.png",
"name": "Conference App"
},
"calendar": {
"conferenceSolution": [
{
"id": 1,
"name": "Private Meeting",
"logoUrl": "https://lh3.googleusercontent.com/-WuPhRsFGKUc/XCng7m_FIxI/AAAAAAAAAGg/ASH4GCGDMs0d55OZQGCSIQHXjRAKnkeTQCLcBGAs/s400/jitsi-logo-96x96.png",
"onCreateFunction": "createPrivateMeeting"
}
],
"currentEventAccess": "READ_WRITE"
}
},
"timeZone": "America/New_York",
"runtimeVersion": "V8",
"dependencies": {
"enabledAdvancedServices": [
{
"userSymbol": "Calendar",
"serviceId": "calendar",
"version": "v3"
}
],
"libraries": [
{
"userSymbol": "OAuth2",
"version": "43",
"libraryId": "1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF"
}
]
},
"webapp": {
"access": "ANYONE",
"executeAs": "USER_ACCESSING"
},
"exceptionLogging": "STACKDRIVER"
}
From the network it seems the browser makes a preflight request when redirected from the backend to the google script and fails with cors error.
I tried to change the Content-Type in headers to text/plain;charset=utf-8 to avoid the preflight request but the cors issue still persists.
I am expecting the popup to get redirect to addon and exchange the auth code for auth token.

The mistake i was making was redirecting the popup to appscript from backend. Instead of returning the redirect uri with auth code concatenated in response from the backend to the popup/frontend and frontend redirects which fixes the cors mismatch error.

Related

a google verified app script, got 403 error: "The caller does not have permission"

I have a google verified, published app script deployed as a web app. It generates a form as a classwork in google classroom. When accessing the app from my own google classroom account, it runs ok. However, when accessing the app from other google classroom accounts, I got error:
"code": 403, "message": "The caller does not have permission", "status": "PERMISSION_DENIED".
Below is the appscript.json of the script:
{
"timeZone": "America/New_York",
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"dependencies": {
"enabledAdvancedServices": [
{
"userSymbol": "Classroom",
"version": "v1",
"serviceId": "classroom"
}
]
},
"webapp": {
"executeAs": "USER_ACCESSING",
"access": "ANYONE"
},
"executionApi": {
"access": "ANYONE"
},
"oauthScopes": [
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/forms",
"https://www.googleapis.com/auth/classroom.rosters.readonly"
]
}
The Oauth consent screen shows:
Verification Status
Verified
Your consent screen has been verified. If you make changes that
require verification later, you must resubmit your application for
review. Learn more
Publishing status
In production
User type
External
Do you have similar experience? What could be the problem, and how to get around this?
I found the problem: When switching from test mode to live mode, I missed removing the devMod setting.In addition, the scriptId must be changed to the deployed id (version controled) so that the script can be found.
It's very likely that the other "google classroom" users are getting the error because the aren't domain administrators.
From https://developers.google.com/classroom/guides/get-started
Who can use the API?
G Suite for Education domain administrators can use the API to programmatically provision courses on behalf of teachers, sync student information systems with Classroom, and get basic visibility into classes being taught in their domain.

google drive API verified domain not working with changes watch

According to https://developers.google.com/drive/api/v3/reference/changes/watch,
you need an https address to make it works if you need to watch over a change of resources.
I've configured the domain successfully by going to Google Cloud Console > Domain Verification > Add Domain.
Then I use Postman to make a call to test it:
https://www.googleapis.com/drive/v3/changes/watch?pageToken=nextPageToken
{
// "kind": "api#channel",
"id": "1234231",
"expiration": 1656402233000,
"type": "webhook",
"payload": true,
"address": "https://mrnoc.blogspot.com",
"params": {
"pageToken": "nextPageToken"
}
}
However, it failed and generated this error message:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "push.webhookUrlUnauthorized",
"message": "Unauthorized WebHook callback channel: https://mrnoc.blogspot.com"
}
],
"code": 401,
"message": "Unauthorized WebHook callback channel: https://mrnoc.blogspot.com"
}
}
I have no idea how it doesn't work, I've tried to look around but it a dead end as it seems like I've configured everything properly.
Please help if you know what might stop it from working.
Thank you

401 when using client_credentials auth flow

Everything works great with Postman and authorization_code grant. But I am trying to connect to FHIR with client_credentials flow, meaning no UI.
I am calling the url https://login.microsoftonline.com/xxxxxxxx-c9a9-4be5-a9f7-xxxxxxxxxxxx/oauth2/v2.0/token with the parameters:
grant_type: client_credentials
client_id: [my fhir application ID]
scope: https://[myCompany].azurehealthcareapis.com/.default
client_secret: [mySecret]
With that, I get back a token
{
"token_type": "Bearer",
"expires_in": 3599,
"ext_expires_in": 3599,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiJodHRwczovL2JvbmZoaXIuYXp1cmVoZWFsdGhjYXJlYXBpcy5jb20iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8wZjIzNjI5Yy1jOWE5LTRiZTUtYTlmNy1iZGI1ODU1M2Q3YjUvIiwiaWF0IjoxNjIyMjMwNjExLCJuYmYiOjE2MjIyMzA2MTEsImV4cCI6MTYyMjIzNDUxMSwiYWlvIjoiRTJaZ1lERGJ3WFZqendaRGt4M25ZcmhXcnNoMEJBQT0iLCJhcHBpZCI6ImY3YTA0ZWZjLTE1YjItNDVlMi05NzU5LWI0ZGQ0ZTdjN2Q5ZiIsImFwcGlkYWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzBmMjM2MjljLWM5YTktNGJlNS1hOWY3LWJkYjU4NTUzZDdiNS8iLCJvaWQiOiJlYTFiMmI4MC1kYTdiLTQ2YmEtYjgyOS01YzdlNTllZmVmYzciLCJyaCI6IjAuQVhjQW5HSWpENm5KNVV1cDk3MjFoVlBYdGZ4T29QZXlGZUpGbDFtMDNVNThmWjkzQUFBLiIsInN1YiI6ImVhMWIyYjgwLWRhN2ItNDZiYS1iODI5LTVjN2U1OWVmZWZjNyIsInRpZCI6IjBmMjM2MjljLWM5YTktNGJlNS1hOWY3LWJkYjU4NTUzZDdiNSIsInV0aSI6InYwSnhfOEM0c1VtQ1ZGQUZoY3AtQWciLCJ2ZXIiOiIxLjAifQ.QMHS5OoWYflq30owYolvwzDkRJm4sG29G11Z_Qct_pPuj_ULm6hR4vC_jydqsq7eDFGxA1wb_Y8hJXVKTHBu1ij9_SKSlKhNZ6KmkqrvOhTaADFGw36albKNgII_xzA-gmeAOKQuKX9Q9wZmPfJETx5NJuJnG1qAnexvhQkhMv8AgiznnU9VbaIoAAvObHx9E5Pb5nesSmOhVwMxZRjBrTHqz9ryFUDYq3Pciuz6HvVF7ro9IijUg9d8r2da8HuXGXvZiJXkfiEW6OuR1RLv9QDol6WjAOKTB12q07iFFgDL0UTinWLY--3dn0raVyd7ZtT_yzLNRZ9iqX_XXXXX"
}
Now when I call the url https://[myCompany].azurehealthcareapis.com/Patient I get 401 response.
{
"resourceType": "OperationOutcome",
"id": "114e91311cbd11458e3d3284db6c9826",
"issue": [
{
"severity": "error",
"code": "login",
"diagnostics": "Authentication failed."
}
]
}
This is what I have for Api Permissions
On the Fhir service, select Access control (IAM) from the left menu (if you are using Azure RBAC).
From there, click on Role Assignments
Then search for your your App Registration
This allows your app to have permissions to the Fhir service outside of that of the logged in users.

authorizationUrl on sidebar of gmail addons using appscript

I am using Google cloud datastore which is exposed by jersey rest, i have made authentication using oauth2 to access rest APIS.
Now I want to call restful API from my gmail addons to populate some lovs on gmail sidebar.
I have tried oauth2 authentication provided on https://github.com/googlesamples/apps-script-oauth2.
How do i show authorizationUrl on sidebar of gmail using gmail Addons?
CODEBASE
appscript.json
{
"timeZone": "America/Los_Angeles",
"dependencies": {
"enabledAdvancedServices": [{
"userSymbol": "Gmail",
"serviceId": "gmail",
"version": "v1"
}],
"libraries": [{
"userSymbol": "OAuth2",
"libraryId": "1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF",
"version": "24",
"developmentMode": true
}]
},
"oauthScopes": ["https://www.googleapis.com/auth/gmail.addons.execute", "https://www.googleapis.com/auth/gmail.addons.current.message.metadata", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/gmail.readonly", "https://www.googleapis.com/auth/script.external_request", "https://www.googleapis.com/auth/script.storage"],
"gmail": {
"name": "Gmail Add-on Quickstart",
"logoUrl": "https://www.gstatic.com/images/icons/material/system/2x/bookmark_black_24dp.png",
"contextualTriggers": [{
"unconditional": {
},
"onTriggerFunction": "buildAddOn"
}],
"primaryColor": "#4285F4",
"secondaryColor": "#4285F4",
"openLinkUrlPrefixes": ["https://mail.google.com/"],
"version": "TRUSTED_TESTER_V2"
}
}
Code.gs
function buildAddOn(e) {
var driveService = createOauthService();
Logger.log('Access'+driveService.hasAccess()); // GETTING FALSE HERE
Logger.log('Auth URL'+driveService.getAuthorizationUrl()); // GETTING PROPER AUTH URL.
Logger.log('Token'+driveService.getAccessToken()); // NEED TOKEN TO BE USED ON REST CALL
}
function createOauthService() {
return OAuth2.createService('drive')
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setClientId('MY CLOUD CONSOLE CLIENT ID')
.setClientSecret('MY SECREAT')
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getUserProperties())
.setScope('https://www.googleapis.com/auth/drive')
.setParam('login_hint', Session.getActiveUser().getEmail())
.setParam('access_type', 'offline');
.setParam('approval_prompt', 'force');
}
Is this correct way or am i missing something ?
Please suggest.
One documented way to do this is to return a card from your buildAddOn() function which contains a button configured to launch the authorization sequence.
function buildAuthorizeCard() {
var authorizationUrl = getOAuthService().getAuthorizationUrl();
var card = CardService.newCardBuilder();
var section = CardService.newCardSection();
section.addWidget(CardService.newTextButton()
.setText("Authorize MyService")
.setAuthorizationAction(
CardService.newAuthorizationAction()
.setAuthorizationUrl(authorizationUrl)
));
card.addSection(section);
return card.build();
}
You can give that a try.

Cannot load material design css from CDN using Firebase Hosting

I am getting the following error:
XMLHttpRequest cannot load
https://code.getmdl.io/1.3.0/material.indigo-pink.min.css. No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'https://nhalistonfirebase.firebaseapp.com' is
therefore not allowed access.
Here's my firebase.json file:
{
"hosting": {
"public": "public",
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
],
// Add the "headers" section within "hosting".
"headers": [
{
"source": "**/*.#(eot|otf|ttf|ttc|woff|font.css)",
"headers": [
{
"key": "Access-Control-Allow-Origin",
"value": "*"
}
]
},
{
"source": "**/*.#(jpg|jpeg|gif|png)",
"headers": [
{
"key": "Cache-Control",
"value": "max-age=7200"
}
]
}
]
}
}
How should this be corrected?
code.getmdl.io doesn’t send the Access-Control-Allow-Origin response header that’s necessary to make browsers allow your frontend JavaScript code to access the response.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS has more details.
The firebase.json file shown in the question is for the https://nhalistonfirebase.firebaseapp.com site I guess? If so, it doesn’t matter what CORS config you do there—what instead matters is what CORS config is set on the site your code is sending the request to.
And the code.getmdl.io site apparently has no CORS config to allow cross-origin requests.
But you can get around this by using a CORS proxy. You can instead use this request URL:
https://cors-anywhere.herokuapp.com/https://code.getmdl.io/1.3.0/material.indigo-pink.min.css
That sends the request through https://cors-anywhere.herokuapp.com, which forwards the request to https://code.getmdl.io/1.3.0/material.indigo-pink.min.css and then receives the response. The https://cors-anywhere.herokuapp.com backend adds the Access-Control-Allow-Origin header to the response and passes that back to your requesting frontend code.
The browser will then allow your frontend code to access the response, because that response with the Access-Control-Allow-Origin response header is what the browser sees.
You can also easily set up your own CORS proxy using https://github.com/Rob--W/cors-anywhere/