Why might Google Fusion Tables sqlGet queries stop working after Google+ sign-in through gapi.auth? - google-api

I'm writing a page that uses OAuth 2.0 via gapi.auth.authorize to authenticate a Google+ user and gapi.client.request to run Google Fusion Tables sqlGet queries. I find that my queries run fine before authentication, but then fail with a 403 "Insufficient Permission" error when run more than 30 seconds
after authentication.
The problem is demonstrated with this page:
https://googledrive.com/host/0B5Urq1jZb1MYSWloU3NTY2M4Qnc/test3b.htm
Please follow these steps:
Click "Query" to run a gapi.client.request Google Fusion Table SQL-get query returning a count of rows. This will run successfully until OAuth is used in steps 2 and 3.
Click "Start OAuth" to run an immediate:true authorization against Google+. If you are currently signed into Google+, your user name and ID will be displayed in the third button.
If your Google+ user name is not displayed in the third button, click the button ("Authorize") and sign into Google+.
Click the "Query" button again. The query will run without error when pressed within about 30 seconds of OAuth authorization. After that, the query fails with a 403 error. WHY?
Here is the source for the demo page:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<title>Test3b</title>
<style type="text/css">
</style>
<script src="scripts/jquery-1.10.2.min.js" type="text/javascript"></script>
<script type="text/javascript">
var g_domIsReady = false;
var g_gapiIsReady = false;
$(function () {
log("#$(function())");
g_domIsReady = true;
start();
});
function gapiIsReady() {
log("#gapiIsReady");
g_gapiIsReady = true;
start();
}
function start() {
// Make sure both the gapi.client and the DOM (per jquery) are ready.
if (!(g_gapiIsReady && g_domIsReady)) return;
// Define members.
log("#start - gapi and DOM are ready");
var m_apiKey = "AIzaSyAvb0NHQMwyPbMJRtz2zRL4wTiVjZDiois"; // Points to Google account (including Google Drive) at paloalto#geodesy.net.
var m_clientId = "868768273487-q295tdfr54uvo98v74891qakcr9ci0pf.apps.googleusercontent.com";
var m_scopes = "https://www.googleapis.com/auth/plus.me";
// Wire buttons.
var queryButton = document.getElementById('query-button');
queryButton.onclick = function () { runGetRequest(); return false; };
var startOAuthButton = document.getElementById('startOAuth-button');
startOAuthButton.onclick = function () { startOAuth(); return false; };
// Set-up the gapi.
gapi.client.setApiKey(m_apiKey);
//----------------------------------------------------------------------------
// gapi.client.request query functions.
//----------------------------------------------------------------------------
function runGetRequest() {
log("#runGetRequest");
var tableId = "1VZgvKyuh9uHXkQawpxg1MU8AlO8Mngl-sx7SP74"; // TR_TREE_E
var sql = "select count(GID) from " + tableId + " where GID > 50000";
var path = "/fusiontables/v1/query";
var restRequest = gapi.client.request({
path: path,
params: { 'sql': sql }
});
restRequest.execute(jsonCallback);
}
function jsonCallback(json) {
log("#jsonCallback");
var output = JSON.stringify(json);
log(output);
alert(output);
}
//----------------------------------------------------------------------------
// OAuth functions.
//----------------------------------------------------------------------------
function startOAuth() {
log("#startOAuth");
var authorizeButton = document.getElementById('authorize-button');
window.setTimeout(checkAuth, 1); // check auth in 1 ms
function checkAuth() {
log("#checkAuth");
gapi.auth.authorize({
client_id: m_clientId,
scope: m_scopes,
immediate: true
}, handleAuthResult);
}
function handleAuthResult(authResult) {
log("#handleAuthResult");
if (authResult && !authResult.error) {
log("#handleAuthResult - authResult=true");
log(authResult); // authResult is a token (with 3600 second expiration).
authorizeButton.disabled = true;
useAuthResults();
} else {
log("#handleAuthResult - authResult=false");
authorizeButton.disabled = false;
authorizeButton.onclick = handleAuthClick;
}
}
function handleAuthClick() {
log("#handleAuthClick");
gapi.auth.authorize({
client_id: m_clientId,
scope: m_scopes,
immediate: false
}, handleAuthResult);
return false;
}
function useAuthResults() {
log("#useAuthResults");
// Get the Google+ user's ID and name (member info).
gapi.client.load('plus', 'v1', function () {
log("#gapi.client.load callback");
var request = gapi.client.plus.people.get({ 'userId': 'me' });
request.execute(function (aInfo) {
log("#request.execute callback");
if (aInfo.code !== undefined) {
alert('Google+ API returned ' + aInfo.code + ': ' + aInfo.message);
} else {
// Here with successful sign-in. Display the user name.
log('Google+ user id, name: ' + aInfo.id + ', ' + aInfo.displayName);
authorizeButton.value = aInfo.displayName + " +" + aInfo.id;
}
});
});
}
}
}
function log(msg) {
if (console) console.log(msg);
}
</script>
<script src="https://apis.google.com/js/client.js?onload=gapiIsReady" type="text/javascript"></script>
</head>
<body>
<h1>Test3a</h1>
<p>This pages demonstrates a problem I am having using gapi.client.request with gapi.auth.</p>
<input type="button" id="query-button" value="Query"><br>
<input type="button" id="startOAuth-button" value="Start OAuth"><br>
<input type="button" id="authorize-button" value="Authorize"><br>
<p>Steps...</p>
<p>1. Click "Query" to run a gapi.client.request Google Fusion Table SQL-get query returning
a count of rows. This will run successfully until OAuth is used in steps 2 and 3.</p>
<p>2. Click "Start OAuth" to run an immediate:true authorization against Google+. If you
are currently signed into Google+, your user name will be displayed in the third button.</p>
<p>3. If your Google+ user name is not displayed in the third button, press it ("Authorize")
and sign into Google+.</p>
<p>4. Click the "Query" button again.
The query will run without error when pressed within about 30 seconds of OAuth authorization.
After that, the query fails with a 403 error. WHY?</p>
</body>
</html>
Please note that I intend to use the Google+ sign-in to track page usage specifics by user, not to enable the Fusion Tables queries.
I'm new to OAuth and gapi.client.request so this may be a simple misunderstanding on my part.
Thanks for any insights.

I don't have all the answers for you, but here I think are some that may help:
Before you have the user sign-in with G+, the gapi.client.request object is adding a "key=yourAPIKey" parameter to each request.
After you have the user sign-in with G+, the gapi.client.request object is adding a "key=yourAPIKey" parameter to each request and is sending an "Authorization: Bearer ya.xxxxxx" header with each request, representing an access token for the user that is logged in.
I think the reason you're seeing a 403 is because the access token is being sent to the server, but the token does not include a scope authorizating access to FusionTables data. When no access token is sent - this validation is not performed.
If you actually wanted to access data that the user owned, then you need to have the user consent to give your application access to their data by including an appropriate scope in your gapi.auth.authorize call (e.g. "https://www.googleapis.com/auth/fusiontables").
However since I don't think you are trying to access data on behalf of a specific user, what I think you really want to do is prevent the "Authorization" header being sent at all during your call to Fusion Table API.
I can't see an easy way to prevent the gapi.client.request library from sending that header when the user is logged in, so an alternate solution might be to instead create an HTTP object not using the gapi.client.request library (e.g. use XMLHttpRequest directly) - and manually include the "key=yourAPIKey" in each request.
(What I can't explain is why you're seeing 30 seconds of differing behavior...)

Related

Window object is undefined after deploy to netlify

I want to build an email verification. After the user registers, the user gets an email and clicks on it for verification purposes. The email-link invokes a netlify lambda function (api end point). Inside the link is a jwt token, which I decode on the backend. I used
window.location.href
for it and sliced the part I needed and decoded it. On localhost, it works fine, however, if I deploy it to netlify, I get an
window is undefined
error. I read that you have to check for
typeof window !== 'undefined'
However, if I add that to my lambda function I don't get any console.log statements.
exports.handler = async (event, context, callback) => {
if (typeof window !== 'undefined') {
let url = window.location.href
let index = url.indexOf("=");
let token = url.slice(index+1)
console.log(token, 'token here')
const decoded = jwt.verify(token, process.env.SECRET);
console.log('confirm registration route triggered',decoded)
if (decoded) {
const { email } = decoded;
console.log(decoded, 'decoded here')
User.findOneAndUpdate({email: email}, {verified: true },(...e)=>{
console.log(e)
});
} else {
console.log('could not update user')
//redirect user to page with message about email confirmation link expiration
//and proposal to register again
}
console.log('confirm registration got invoked')
}
return {
statusCode: 400,
body: "Oops"
}
};
I read that the function first runs on the server when deployed and afterwards on the client. Seems like it does not run on my client, as I invoke the api-endpoint directly? I'm quite a beginner when it comes to API-Endpoints, thanks for reading!
In case you have the same issue when deploying to netlify, you have to run
event.queryStringParameters
which gives you access to the query parts of your url.

How to take long access (offline) to Google API?

The task I need to solve is to get "gapi.auth2" access for Google Sheets and Google Drive through JS in WebBrowser and than to use this permissions granted from user directly and byitself for a long time (untill user gets back this permissions).
I need to have possibility to change (add some info) to Google Sheet of the user. User had to configure what info will be added. That's why I need to save and use access info (access & refresh tokens) offline.
Code I tried:
<div>
<p>Google Sheets API Quickstart</p>
<!--Add buttons to initiate auth sequence and sign out-->
<button id="authorize_button" style="display: none;">Authorize</button>
<button id="signout_button" style="display: none;">Sign Out</button>
<pre id="content" style="white-space: pre-wrap;"></pre>
<script type="text/javascript">
// Client ID and API key from the Developer Console
var CLIENT_ID = 'MyID.apps.googleusercontent.com';
var API_KEY = 'MyKey';
// Array of API discovery doc URLs for APIs used by the quickstart
var DISCOVERY_DOCS = ["https://sheets.googleapis.com/$discovery/rest?version=v4"];
// Authorization scopes required by the API; multiple scopes can be
// included, separated by spaces.
var SCOPES = "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive";
var authorizeButton = document.getElementById('authorize_button');
var signoutButton = document.getElementById('signout_button');
/**
* On load, called to load the auth2 library and API client library.
*/
function handleClientLoad() {
gapi.load('client:auth2', initClient);
}
/**
* Initializes the API client library and sets up sign-in state
* listeners.
*/
function initClient() {
gapi.client.init({
apiKey: API_KEY,
clientId: CLIENT_ID,
discoveryDocs: DISCOVERY_DOCS,
scope: SCOPES
}).then(function () {
// Listen for sign-in state changes.
gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus);
// Handle the initial sign-in state.
updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
authorizeButton.onclick = handleAuthClick;
signoutButton.onclick = handleSignoutClick;
}, function(error) {
appendPre(JSON.stringify(error, null, 2));
});
}
/**
* Called when the signed in status changes, to update the UI
* appropriately. After a sign-in, the API is called.
*/
function updateSigninStatus(isSignedIn) {
if (isSignedIn) {
authorizeButton.style.display = 'none';
signoutButton.style.display = 'block';
listMajors();
} else {
authorizeButton.style.display = 'block';
signoutButton.style.display = 'none';
}
}
/**
* Sign in the user upon button click.
*/
function handleAuthClick(event) {
let instance = gapi.auth2.getAuthInstance();
console.log(instance);
let promise = instance.signIn();
console.log(gapi.auth2.getAuthInstance());
console.log(promise);
console.log(gapi.client);
console.log(gapi.client.getToken());
console.log(gapi.auth2);
console.log(gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse());
console.log(gapi.auth2.getAuthInstance().currentUser.get());
console.log(gapi.auth2.getAuthInstance().currentUser);
console.log(instance.currentUser);
console.log(instance.currentUser.ie);
console.log(instance.currentUser.ie.uc);
console.log(instance.currentUser.ie.uc.access_token);
console.log(auth);
let auth = instance.currentUser.ie.uc.access_token;
let a = document.getElementById('my');
a.href += auth;
let xhr = new XMLHttpRequest();
xhr.setRequestHeader('Authorization', 'Bearer ' + "ya29.a0Adw1xeXfLePcaSdeluMmvtDrnrgbDibLToC22Vw5yOfIhOpQ1TqxFlROBozsBPbXL1GRYHumMltcLuJWNygtZ1m8IRpfx9n7I-oLQi4BpgaU98nx7InjquPqJ8Yc2aGvN9ac6HI9rqlDPJFTbhxKP37SzG_31x_u6vs");
xhr.
xhr.send();
}
/**
* Sign out the user upon button click.
*/
function handleSignoutClick(event) {
gapi.auth2.getAuthInstance().signOut();
}
/**
* Append a pre element to the body containing the given message
* as its text node. Used to display the results of the API call.
*
* param {string} message Text to be placed in pre element.
*/
function appendPre(message) {
var pre = document.getElementById('content');
var textContent = document.createTextNode(message + '\n');
pre.appendChild(textContent);
}
/**
* Print the names and majors of students in a sample spreadsheet:
* https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
*/
function listMajors() {
gapi.client.sheets.spreadsheets.values.get({
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
range: 'Class Data!A2:E',
}).then(function(response) {
var range = response.result;
if (range.values.length > 0) {
appendPre('Name, Major:');
for (i = 0; i < range.values.length; i++) {
var row = range.values[i];
// Print columns A and E, which correspond to indices 0 and 4.
appendPre(row[0] + ', ' + row[4]);
}
} else {
appendPre('No data found.');
}
}, function(response) {
appendPre('Error: ' + response.result.error.message);
});
}
</script>
</div>
<script async defer src="https://apis.google.com/js/api.js"
onload="this.onload=function(){};handleClientLoad()"
onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
Also I tried to type this into browser: "https://accounts.google.com/o/oauth2/auth?access_type=offline&prompt=consent&redirect_uri=https://localhost:44325/&client_id=MyID.apps.googleusercontent.com&scope=https://www.googleapis.com/auth/spreadsheets+https://www.googleapis.com/auth/drive&response_type=code&state=/profile"
redirect_uri is wrong, and adding it in console do not help.
When I try to use Quichstart .NET App it works fine, but I get only my tokens, not my clients.
How to get offline access to my clients Google Data using JS? Where can I get refresh token if my client gives me access through JS in WebBrowser?
The correct link was - https://accounts.google.com/o/oauth2/auth?access_type=offline&prompt=consent&redirect_uri=https://localhost:44325&client_id=MyID.apps.googleusercontent.com&scope=https://www.googleapis.com/auth/spreadsheets+https://www.googleapis.com/auth/drive&response_type=code&state=/profile
"redirect_uri=https://localhost:44325" not "redirect_uri=https://localhost:44325/"
Then when I was redirected to "https://localhost:44325/" the link was next "https://localhost:44325/?state=/profile&code=4/xwFM---SOME SYMBOLS---&scope=https://www.googleapis.com/auth/drive%20https://www.googleapis.com/auth/spreadsheets"
Where "4/xwFM---SOME SYMBOLS---" is my refresh token as I gues. I'll check it tomorrow.
UPD. This code I use then in POST request to "https://accounts.google.com/o/oauth2/token" with the following fields set:
grant_type=authorization_code
code='the code from the previous step'
client_id='the client ID token created in the APIs Console'
client_secret='the client secret corresponding to the client ID'
redirect_uri='the URI registered with the client ID'
Details: https://developers.google.com/android-publisher/authorization

Google old api of profile information is not working, What is the new API URL?

Google old profile API not working now.
I used this API link before,
(http://picasaweb.google.com/data/entry/api/user/sakiremail#gmail.com?alt=json)
but it's not working now. Can't get profile information. What is the new API URL?
here I have created a demo app to work with. Before test please create an auth client id. Also, add an authorized redirect URI to http://localhost if you are checking on local.
<html lang="en">
<head>
<meta name="google-signin-scope" content="profile email">
</head>
<body>
Login
<script>
/*
* Create form to request access token from Google's OAuth 2.0 server.
*/
function oauthSignIn() {
// Google's OAuth 2.0 endpoint for requesting an access token
var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
// Create <form> element to submit parameters to OAuth 2.0 endpoint.
var form = document.createElement('form');
form.setAttribute('method', 'GET'); // Send as a GET request.
form.setAttribute('action', oauth2Endpoint);
// Parameters to pass to OAuth 2.0 endpoint.
var params = {'client_id': 'YOUR_APP_CLIENT_ID',
'redirect_uri': 'AUTHENTICATED_REDIRECT_URI',
'response_type': 'token',
'scope': 'profile',
'include_granted_scopes': 'true',
'state': 'pass-through value'};
// 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();
}
var hash = window.location.hash.substr(1);
var hashresult = hash.split('&').reduce(function (result, item) {
var parts = item.split('=');
result[parts[0]] = parts[1];
return result;
}, {});
if(hashresult.access_token){
console.log(hashresult.access_token);
fetch('https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token='+hashresult.access_token)
.then(function(response) {
return response.json();
})
.then(function(userdata) {
console.log(userdata);
});
}
</script>
</body>
</html>

YouTube Data API: add a subscription

I'm using YouTube's V3 Data API to add a subscription to a channel. This occurs on a Wordpress installation.
I added Google APIs (for oauth) on Wordpress theme functions:
wp_enqueue_script( 'googleapi', 'https://apis.google.com/js/client.js?onload=googleApiClientReady', array(), '1.0.0', true );
I added in the same way the oauth javascript file, which is the first one here: https://developers.google.com/youtube/v3/code_samples/javascript.
Following this guide(https://developers.google.com/youtube/v3/docs/subscriptions/insert (Apps Script)), I extended the OAuth js with the addSubscription method.
Google Client API seems to be loaded and working as it calls correctly googleApiClientReady on the oauth javascript.
So, this is how the subscription is being inserted:
OAUTH JAVASCRIPT
... ... ...
// After the API loads
function handleAPILoaded() {
addSubscription();
}
function addSubscription() {
// Replace this channel ID with the channel ID you want to subscribe to
var channelId = 'this is filled with the channel ID';
var resource = {
snippet: {
resourceId: {
kind: 'youtube#channel',
channelId: channelId
}
}
};
try {
var response = YouTube.Subscriptions.insert(resource, 'snippet');
jQuery('#success').show();
} catch (e) {
if(e.message.match('subscriptionDuplicate')) {
jQuery('#success').show();
} else {
jQuery('#fail').show();
alert("Please send us a mail () with the following: ERROR: " + e.message);
}
}
So, the first error comes with
YouTube.Subscriptions.insert(resource, 'snippet')
It says YouTube is not defined. I replaced it with:
gapi.client.youtube.subscriptions.insert(resource, 'snippet');
And that error went away. When checking response, as the subscription isn't completed, this is what I get
{"wc":1,"hg":{"Ph":null,"hg":{"path":"/youtube/v3/subscriptions","method":"POST","params":{},"headers":{},"body":"snippet","root":"https://www.googleapis.com"},"wc":"auto"}}
So, I would like to know what's happening on that POST request and what's the solution to this.
I can post the full OAuth file, but it's just as in the example, plus that addSubscription method at the end.
Okay, I got it working, the problem was on the POST request. Here is the full method working:
// Subscribes the authorized user to the channel specified
function addSubscription(channelSub) {
var resource = {
part: 'id,snippet',
snippet: {
resourceId: {
kind: 'youtube#channel',
channelId: channelSub
}
}
};
var request = gapi.client.youtube.subscriptions.insert(resource);
request.execute(function (response) {
var result = response.result;
if (result) {
// alert("Subscription completed");
}
} else {
// alert("Subscripion failed");
// ...
}
});
}
Also make sure to load Google Apps API (in fact without it the authorize/login button won't work) and jQuery.
Any chance you can post everything that made this work...all the JS entire auth.js save for your private keys, im working on this exact problem.

Facebook Open Graph Submit Action

Hello Facebook Masters,
I am using Facebook Open Graph application for my magento site.
For this we need to create one application in facebook and we need to
add buttons like, want, wish etc. Thats all fine I have created the
buttons as well. But we have to approve that button from facebook. we
need to post the product url for making approval submition for the
want button
i am not able to post the product url. Please help me..
Here is my code used for posting the url :
http://ogp.me/ns#
zoomin-magento:http://ogp.me/ns/apps/zoomin-magento#"> OG
Tutorial App
function postCook() {
FB.api(
'/me/zoomin-magento:want',
'post',
{ product: 'http://camera.zoomin.com/camera-phones-1/sony-ericsson-xperia-ray-st18i.html'
},
function(response) {
if (!response || response.error) {
alert('Error occured');
} else {
alert('Cook was successful! Action ID: ' + response.id);
}
}); }
window.fbAsyncInit = function() {
FB.init({
appId : '368510486563356', // App ID
status : true, // check login status
cookie : true, // enable cookies to allow the server to access the session
xfbml : true // parse XFBML
});
};
// Load the SDK Asynchronously
(function(d){
var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
d.getElementsByTagName('head')[0].appendChild(js);
}(document)); </script>
Stuffed Cookies
You have not explained why you cannot share the URL. Nevertheless, for circumstances where the approval team cannot see the app, you can make a video of the app in actions, demonstrating the action being taken, and submit that.
Also check the doc # https://developers.facebook.com/docs/opengraph/opengraph-approval/

Resources