How do I update my google sheet in v4? - google-sheets

I am new in google sheets v4 and I just want to know how can I update my google sheet in v4. I am using Nodejs and following is the code sample link which I am using Method: spreadsheets.values.update

You can use the sample script of the link. In you case, combining Quickstart and the sample script may be useful for you. The sample script is as follows.
In this sample script, the text of sample text is imported to the cell a1 of Sheet1.
Sample script :
var fs = require('fs');
var readline = require('readline');
var google = require('googleapis');
var googleAuth = require('google-auth-library');
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/sheets.googleapis.com-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/spreadsheets', 'https://www.googleapis.com/auth/drive'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'sheets.googleapis.com-nodejs-quickstart.json';
// Load client secrets from a local file.
fs.readFile('client_secret.json', function processClientSecrets(err, content) {
if (err) {
console.log('Error loading client secret file: ' + err);
return;
}
// Authorize a client with the loaded credentials, then call the
// Google Sheets API.
authorize(JSON.parse(content), valuesUpdate);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
*
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
var clientSecret = credentials.installed.client_secret;
var clientId = credentials.installed.client_id;
var redirectUrl = credentials.installed.redirect_uris[0];
var auth = new googleAuth();
var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function(err, token) {
if (err) {
getNewToken(oauth2Client, callback);
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client);
}
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
*
* #param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback to call with the authorized
* client.
*/
function getNewToken(oauth2Client, callback) {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
console.log('Authorize this app by visiting this url: ', authUrl);
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter the code from that page here: ', function(code) {
rl.close();
oauth2Client.getToken(code, function(err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err);
return;
}
oauth2Client.credentials = token;
storeToken(token);
callback(oauth2Client);
});
});
}
/**
* Store token to disk be used in later program executions.
*
* #param {Object} token The token to store to disk.
*/
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code != 'EEXIST') {
throw err;
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to ' + TOKEN_PATH);
}
function valuesUpdate(auth) {
var sheets = google.sheets('v4');
var request = {
// The ID of the spreadsheet to update.
spreadsheetId: 'my-spreadsheet-id', // TODO: Update placeholder value.
// The A1 notation of the values to update.
range: 'Sheet1!a1:a1', // TODO: Update placeholder value.
// How the input data should be interpreted.
valueInputOption: 'RAW', // TODO: Update placeholder value.
resource: {'values': [['sample text']]},
auth: auth,
};
sheets.spreadsheets.values.update(request, function(err, response) {
if (err) {
console.error(err);
return;
}
// TODO: Change code below to process the `response` object:
console.log(JSON.stringify(response, null, 2));
});
}
IMPORTANT :
Please modify my-spreadsheet-id to yours in above script.
This sample script supposes that the script of Quickstart works fine.
After run the script of Quickstart, please remove sheets.googleapis.com-nodejs-quickstart.json once, before run the above sample script. After remove the file, please run the above script. By this, the refresh token with the new scopes is retrieved and it is used for updating values.
If you want to use this script, please use googleapis v24 or less. Because the latest version doesn't work. Because the following error occurs, even if valueInputOption is set.
Error: 'valueInputOption' is required but not specified
I believe that this error will be modified in the near future.
If I misunderstand your question, I'm sorry.

Related

How to Refresh Token Using Google OAuth2 (Javascript/REST)

I'm trying to get a new token from Google OAuth2 but I keep getting this error:
Here is my code (I'm using Expo to build React Native apps):
const uri = 'https://oauth2.googleapis.com/token'
const headerr = {
'Content-Type': 'Content-Type: application/x-www-form-urlencoded'
}
const bodyy = {
"client_id": '******************',
"refresh_token": `${refreshToken}`,
"grant_type":"refresh_token",
"access_type":"offline"
}
const fitnesss = await fetch(uri, {
method: "POST",
headers: headerr,
body: JSON.stringify(bodyy)
});
fitnesss.json().then(res => {
console.log(res)
})
Does anyone know how to solve this?
unsupported grant type means that the grant type refresh token is not supported with the language you are using.
The reason for this is that JavaScript is client sided which would mean that you would need to have the refresh token in the code. Anyone who viewed the source in the browser would be able to see and use your refresh token.
To use refresh tokens use a server sided language. For example Node.js
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Drive API.
authorize(JSON.parse(content), listFiles);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getAccessToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
function getAccessToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}

Use MSAL in Microsoft Teams not work JS

I work on an MS Teams app with a connection to MS Graph.
I'm trying to get an access token for MS Graph in MS Teams. To get a token I'm using MSAL js.
If I run the App with gulp serve I receive a valid token and I have access to the MS Graph endpoints. But if I build the app and install it in MS Teams the function userAgentApplication.acquireTokenSilent(config) is never executed. I tested it with a console.log before and after the call. There is no error thrown.
Do you have any idea why the above snippet is not executed in MS Teams (app and webapp)?
NEW:
On Home:
export function login() {
const url = window.location.origin + '/login.html';
microsoftTeams.authentication.authenticate({
url: url,
width: 600,
height: 535,
successCallback: function(result: string) {
console.log('Login succeeded: ' + result);
let data = localStorage.getItem(result) || '';
localStorage.removeItem(result);
let tokenResult = JSON.parse(data);
storeToken(tokenResult.accessToken)
},
failureCallback: function(reason) {
console.log('Login failed: ' + reason);
}
});
}
On Login
microsoftTeams.initialize();
// Get the tab context, and use the information to navigate to Azure AD login page
microsoftTeams.getContext(function (context) {
// Generate random state string and store it, so we can verify it in the callback
let state = _guid();
localStorage.setItem("simple.state", state);
localStorage.removeItem("simple.error");
// See https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols-implicit
// for documentation on these query parameters
let queryParams = {
client_id: "XXX",
response_type: "id_token token",
response_mode: "fragment",
scope: "https://graph.microsoft.com/User.Read openid",
redirect_uri: window.location.origin + "/login-end.html",
nonce: _guid(),
state: state,
login_hint: context.loginHint,
};
// Go to the AzureAD authorization endpoint (tenant-specific endpoint, not "common")
// For guest users, we want an access token for the tenant we are currently in, not the home tenant of the guest.
let authorizeEndpoint = `https://login.microsoftonline.com/${context.tid}/oauth2/v2.0/authorize?` + toQueryString(queryParams);
window.location.assign(authorizeEndpoint);
});
// Build query string from map of query parameter
function toQueryString(queryParams) {
let encodedQueryParams = [];
for (let key in queryParams) {
encodedQueryParams.push(key + "=" + encodeURIComponent(queryParams[key]));
}
return encodedQueryParams.join("&");
}
// Converts decimal to hex equivalent
// (From ADAL.js: https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/dev/lib/adal.js)
function _decimalToHex(number) {
var hex = number.toString(16);
while (hex.length < 2) {
hex = '0' + hex;
}
return hex;
}
// Generates RFC4122 version 4 guid (128 bits)
// (From ADAL.js: https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/dev/lib/adal.js)
function _guid() {...}
on login end
microsoftTeams.initialize();
localStorage.removeItem("simple.error");
let hashParams = getHashParameters();
if (hashParams["error"]) {
// Authentication/authorization failed
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure(hashParams["error"]);
} else if (hashParams["access_token"]) {
// Get the stored state parameter and compare with incoming state
let expectedState = localStorage.getItem("simple.state");
if (expectedState !== hashParams["state"]) {
// State does not match, report error
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure("StateDoesNotMatch");
} else {
// Success -- return token information to the parent page.
// Use localStorage to avoid passing the token via notifySuccess; instead we send the item key.
let key = "simple.result";
localStorage.setItem(key, JSON.stringify({
idToken: hashParams["id_token"],
accessToken: hashParams["access_token"],
tokenType: hashParams["token_type"],
expiresIn: hashParams["expires_in"]
}));
microsoftTeams.authentication.notifySuccess(key);
}
} else {
// Unexpected condition: hash does not contain error or access_token parameter
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure("UnexpectedFailure");
}
// Parse hash parameters into key-value pairs
function getHashParameters() {
let hashParams = {};
location.hash.substr(1).split("&").forEach(function (item) {
let s = item.split("="),
k = s[0],
v = s[1] && decodeURIComponent(s[1]);
hashParams[k] = v;
});
return hashParams;
}
Even though my answer is quite late, but I faced the same problem recently.
The solution is farely simple: MSALs silent login does not work in MSTeams (yet) as MSTeams relies on an IFrame Approach that is not supported by MSAL.
You can read all about it in this Github Issue
Fortunately, they are about to release a fix for this in Version msal 1.2.0 and there is already an npm-installable beta which should make this work:
npm install msal#1.2.0-beta.2
Update: I tried this myself - and the beta does not work as well. This was confirmed by Microsoft in my own Github Issue.
So I guess at the time being, you simply can't use MSAL for MS Teams.

How to Update Device Configuration using Google Cloud functions and MQTT bridge

I am using the Google Cloud IoT with Pub/Sub.
I have a device reading sensor data and sending it to a topic in Pub/Sub.
I have a topic cloud function that is triggered by this message and I would like to have the device configuration updated, however I am unable to do so due to the following permission error.
index.js :
/**
* Triggered from a message on a Cloud Pub/Sub topic.
*
* #param {!Object} event The Cloud Functions event.
* #param {!Function} The callback function.
*/
var google = require('googleapis');
//var tt = google.urlshortener('v1');
//console.log(Object.getOwnPropertyNames(google.getAPIs()));
var cloudiot = google.cloudiot('v1');
function handleDeviceGet(authClient, name, device_id, err, data) {
if (err) {
console.log('Error with get device:', device_id);
console.log(err);
return;
}
console.log('Got device:', device_id);
console.log(data);
console.log(data.config);
var data2 = JSON.parse(
Buffer.from(data.config.binaryData, 'base64').toString());
console.log(data2);
data2.on = !data2.on;
console.log(data2);
var request2 = {
name: name,
resource: {
'versionToUpdate' : data.config.version,
'binaryData' : Buffer(JSON.stringify(data2)).toString('base64')
},
auth: authClient
};
console.log('request2' + request2);
var devices = cloudiot.projects.locations.registries.devices;
devices.modifyCloudToDeviceConfig(request2, (err, data) => {
if (err) {
console.log('Error patching device:', device_id);
console.log(err);
} else {
console.log('Patched device:', device_id);
console.log(data);
}
});
}
const handleAuth = (device_id) => {
console.log(device_id);
return (err, authClient) => {
const project_id = 'animated-bonsai-195009';
const cloud_region = 'us-central1';
const registry_id = 'reg1';
const name = `projects / ${project_id} /locations / ${cloud_region} /` +
`registries / ${registry_id} /devices / ${device_id}`;
if (err) {
console.log(err);
}
if (authClient.createScopedRequired &&
authClient.createScopedRequired()) {
authClient = authClient.createScoped(
['https://www.googleapis.com/auth/cloud-platforme']);
}
var request = {
name: name,
auth: authClient
};
// Get device version
var devices = cloudiot.projects.locations.registries.devices;
devices.get(request, (err, data) =>
handleDeviceGet(authClient, name, device_id, err, data));
}
};
exports.subscribe = (event, callback) => {
// The Cloud Pub/Sub Message object.
const pubsubMessage = event.data;
// We're just going to log the message to prove that
// it worked.
var obj = JSON.parse(Buffer.from(pubsubMessage.data, 'base64').toString());
console.log(Buffer.from(pubsubMessage.data, 'base64').toString());
console.log(event);
console.log(Object.getOwnPropertyNames(event));
console.log(callback);
let message = {
"watter": 1
};
message = new Buffer(JSON.stringify(message));
const req = {
name: event.data.deviceId,
resource: message
};
console.log(obj.deviceId);
google.auth.getApplicationDefault(handleAuth(obj['deviceId']));
// Don't forget to call the callback.
callback();
};
package.json :
{
"name": "sample-pubsub",
"version": "0.0.1",
"dependencies": {
"googleapis": "25.0.0"
}
}
Error:
A few options:
Check that you have enabled API access for the Google Cloud IoT Core API for the project used when creating the Google Cloud Function.
Check that you have enabled billing for your project
If you are deploying your Google Cloud Functions with gcloud beta functions deploy ... from the folder with your .js and package.json files, you may want to set the environment variables (GCLOUD_PROJECT and GOOGLE_APPLICATION_CREDENTIALS) or use gcloud auth application-default login before deploying in case you have multiple Google Cloud projects and need to enable the API on the configured one.
Update This community tutorial shows you how to do this - note that there have been some updates to Google Cloud Functions that require you to use a newer version of the Node JS client library as is done in the NodeJS sample and as corrected in this PR, note the version of the client library in package.json.

Why is OAuth2 with Gmail Nodejs Nodemailer producing "Username and Password not accepted" error

OAuth2 is producing "Username and Password not accepted" error when try to send email with Gmail+ Nodejs+Nodemailer
Code - Nodejs - Nodemailer and xoauth2
var nodemailer = require("nodemailer");
var generator = require('xoauth2').createXOAuth2Generator({
user: "", // Your gmail address.
clientId: "",
clientSecret: "",
refreshToken: "",
});
// listen for token updates
// you probably want to store these to a db
generator.on('token', function(token){
console.log('New token for %s: %s', token.user, token.accessToken);
});
// login
var smtpTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
xoauth2: generator
}
});
var mailOptions = {
to: "",
subject: 'Hello ', // Subject line
text: 'Hello world ', // plaintext body
html: '<b>Hello world </b>' // html body
};
smtpTransport.sendMail(mailOptions, function(error, info) {
if (error) {
console.log(error);
} else {
console.log('Message sent: ' + info.response);
}
smtpTransport.close();
});
issues:
I used Google OAuth2 playground to create the tokens, https://developers.google.com/oauthplayground/
It looks to grab a valid accessToken ok, using the refreshToken, (i.e. it prints the new access token on the screen.) No errors until it tries to send the email.
I added the optional accessToken: but got the same error. ( "Username and Password not accepted")
I am not 100% sure about the "username", the docs say it needs a "user" email address - I guess the email of the account that created to token, but is not 100% clear. I have tried several things and none worked.
I have searched the options on the gmail accounts, did not find anything that looks wrong.
Also, when I did this with Java, it needed the google userID rather than the email address, not sure why this is using the email address and the Java is using the UserId.
nodemailer fails with a "compose" scope
The problem was the "scope"
it fails with:
https://www.googleapis.com/auth/gmail.compose
but works ok if I use
https://mail.google.com/
Simply just do the following:
1- Get credentials.json file from here https://developers.google.com/gmail/api/quickstart/nodejs press enable the Gmail API and then choose Desktop app
2- Save this file somewhere along with your credentials file
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://mail.google.com'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if(err){
return console.log('Error loading client secret file:', err);
}
// Authorize the client with credentials, then call the Gmail API.
authorize(JSON.parse(content), getAuth);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if(err){
return getNewToken(oAuth2Client, callback);
}
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
function getAuth(auth){
}
3 - Run this file by typing in your terminal: node THIS_FILE.js
4- You'll have token.json file
5- take user information from credentials.json and token.json and fill them in the following function
const nodemailer = require('nodemailer');
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;
const email = 'gmail email'
const clientId = ''
const clientSecret = ''
const refresh = ''
const oauth2Client = new OAuth2(
clientId,
clientSecret,
);
oauth2Client.setCredentials({
refresh_token: refresh
});
const newAccessToken = oauth2Client.getAccessToken()
let transporter = nodemailer.createTransport(
{
service: 'Gmail',
auth: {
type: 'OAuth2',
user: email,
clientId: clientId,
clientSecret: clientSecret,
refreshToken: refresh,
accessToken: newAccessToken
}
},
{
// default message fields
// sender info
from: 'Firstname Lastname <your gmail email>'
}
);
const mailOptions = {
from: email,
to: "",
subject: "Node.js Email with Secure OAuth",
generateTextFromHTML: true,
html: "<b>test</b>"
};
transporter.sendMail(mailOptions, (error, response) => {
error ? console.log(error) : console.log(response);
transporter.close();
});
If your problem is the scopes, here is some help to fix
Tried to add this as an edit to the top answer but it was rejected, don't really know why this is off topic?
See the note here: https://nodemailer.com/smtp/oauth2/#troubleshooting
How to modify the scopes
The scopes are baked into the authorization step when you get your first refresh_token. If you are generating your refresh token via code (for example using the Node.js sample) then the revised scope needs to be set when you request your authUrl.
For the Node.js sample you need to modify SCOPES:
// If modifying these scopes, delete token.json.
-const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
+const SCOPES = ['https://mail.google.com'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
And then the call to oAuth2Client.generateAuthUrl will produce a url that will request authorization from the user to accept full access.
from the Node.js sample:
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});

How can I login to Meteor with native device Facebook?

Suppose I logged into my device's Facebook authentication, like system Facebook on iOS. I obtain an access token.
How can I use the access token to login to Meteor's Facebook Oauth provider?
To login with Facebook using an access token obtained by another means, like iOS Facebook SDK, define a method on the server that calls the appropriate Accounts method:
$FB = function () {
if (Meteor.isClient) {
throw new Meteor.Error(500, "Cannot run on client.");
}
var args = Array.prototype.slice.call(arguments);
if (args.length === 0) {
return;
}
var path = args[0];
var i = 1;
// Concatenate strings together in args
while (_.isString(args[i])) {
path = path + "/" + args[i];
i++;
}
if (_.isUndefined(path)) {
throw new Meteor.Error(500, 'No Facebook API path provided.');
}
var FB = Meteor.npmRequire('fb');
var fbResponse = Meteor.sync(function (done) {
FB.napi.apply(FB, [path].concat(args.splice(i)).concat([done]));
});
if (fbResponse.error !== null) {
console.error(fbResponse.error.stack);
throw new Meteor.Error(500, "Facebook API error.", {error: fbResponse.error, request: args});
}
return fbResponse.result;
};
Meteor.methods({
/**
* Login to Meteor with a Facebook access token
* #param accessToken Your Facebook access token
* #returns {*}
*/
facebookLoginWithAccessToken: function (accessToken) {
check(accessToken, String);
var serviceData = {
accessToken: accessToken
};
// Confirm that your accessToken is you
try {
var tokenInfo = $FB('debug_token', {
input_token: accessToken,
access_token: Meteor.settings.facebook.appId + '|' + Meteor.settings.facebook.secret
});
} catch (e) {
throw new Meteor.Error(500, 'Facebook login failed. An API error occurred.');
}
if (!tokenInfo.data.is_valid) {
throw new Meteor.Error(503, 'This access token is not valid.');
}
if (tokenInfo.data.app_id !== Meteor.settings.facebook.appId) {
throw new Meteor.Error(503, 'This token is not for this app.');
}
// Force the user id to be the access token's user id
serviceData.id = tokenInfo.data.user_id;
// Returns a token you can use to login
var loginResult = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, {});
// Login the user
this.setUserId(loginResult.userId);
// Return the token and the user id
return loginResult;
}
}
This code depends on the meteorhacks:npm package. You should call meteor add meteorhacks:npm and have a package.json file with the Facebook node API: { "fb": "0.7.0" }.
If you use demeteorizer to deploy your app, you will have to edit the output package.json and set the scrumptious dependency from "0.0.1" to "0.0.0".
On the client, call the method with the appropriate parameters, and you're logged in!
In Meteor 0.8+, the result of Accounts.updateOrCreateUserFromExternalService has changed to an object containing {userId: ...} and furthermore, no longer has the stamped token.
You can get the accessToken in the Meteor.user() data at Meteor.user().services.facebook.accessToken (be aware this can only be accessed on the server side as the services field is not exposed to the client.
So when a user logs in with facebook on your meteor site these fields would be populated with the user's facebook data. If you check your meteor user's database with mongo or some other gui tool you could see all the fields which you have access to.
Building on DrPangloss' most excellent answer above, combining it with this awesome post: http://meteorhacks.com/extending-meteor-accounts.html
You'll run into some issues using ObjectiveDDP in trying to get the client persist the login. Include the header:
#import "MeteorClient+Private.h"
And manually set the required internals. Soon I'll make a meteorite package and an extension to MyMeteor (https://github.com/premosystems/MyMeteor) but for now it's manual.
loginRequest: {"accessToken":"XXXXXb3Qh6sBADEKeEkzWL2ItDon4bMl5B8WLHZCb3qfL11NR4HKo4TXZAgfXcySav5Y8mavDqZAhZCZCnDDzVbdNmaBAlVZAGENayvuyStkTYHQ554fLadKNz32Dym4wbILisPNLZBjDyZAlfSSgksZCsQFxGPlovaiOjrAFXwBYGFFZAMypT9D4qcZC6kdGH2Xb9V1yHm4h6ugXXXXXX","fbData":{"link":"https://www.facebook.com/app_scoped_user_id/10152179306019999/","id":"10152179306019999","first_name":"users' first name","name":"user's Full Name","gender":"male","last_name":"user's last name","email":"users#email.com","locale":"en_US","timezone":-5,"updated_time":"2014-01-11T23:41:29+0000","verified":true}}
Meteor.startup(
function(){
Accounts.registerLoginHandler(function(loginRequest) {
//there are multiple login handlers in meteor.
//a login request go through all these handlers to find it's login hander
//so in our login handler, we only consider login requests which has admin field
console.log('loginRequest: ' + JSON.stringify(loginRequest));
if(loginRequest.fbData == undefined) {
return undefined;
}
//our authentication logic :)
if(loginRequest.accessToken == undefined) {
return null;
} else {
// TODO: Verfiy that the token from facebook is valid...
// https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.0#checktoken
// graph.facebook.com/debug_token? input_token={token-to-inspect}&access_token={app-token-or-admin-token}
}
//we create a user if not exists, and get the userId
var email = loginRequest.fbData.email || "-" + id + "#facebook.com";
var serviceData = {
id: loginRequest.fbData.id,
accessToken: loginRequest.accessToken,
email: email
};
var options = {
profile: {
name: loginRequest.fbData.name
}
};
var user = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, options);
console.log('Logged in from facebook: ' + user.userId);
//send loggedin user's user id
return {
userId: user.userId
}
});
}
);
This answer could be improved further as we can now directly debug the token from a REST http request using futures. Credit still goes to #DoctorPangloss for the principal steps necessary.
//Roughly like this - I removed it from a try/catch
var future = new Future();
var serviceData = {
accessToken: accessToken,
email: email
};
var input = Meteor.settings.private.facebook.id + '|' + Meteor.settings.private.facebook.secret
var url = "https://graph.facebook.com/debug_token?input_token=" + accessToken + "&access_token=" + input
HTTP.call( 'GET', url, function( error, response ) {
if (error) {
future.throw(new Meteor.Error(503, 'A error validating your login has occured.'));
}
var info = response.data.data
if (!info.is_valid) {
future.throw(new Meteor.Error(503, 'This access token is not valid.'));
}
if (info.app_id !== Meteor.settings.private.facebook.id) {
future.throw(new Meteor.Error(503, 'This token is not for this app.'));
}
// Force the user id to be the access token's user id
serviceData.id = info.user_id;
// Returns a token you can use to login
var user = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, {});
if(!user.userId){
future.throw(new Meteor.Error(500, "Failed to create user"));
}
//Add email & user details if necessary
Meteor.users.update(user.userId, { $set : { fname : fname, lname : lname }})
Accounts.addEmail(user.userId, email)
//Generate your own access token!
var token = Accounts._generateStampedLoginToken()
Accounts._insertLoginToken(user.userId, token);
// Return the token and the user id
future.return({
'x-user-id' : user.userId,
'x-auth-token' : token.token
})
});
return future.wait();
Use this instead of the JS lib suggested by #DoctorPangloss. Follow the same principles he suggested but this avoids the need to integrate an additional library

Resources