asked chatgpt to create a script to run through my gmail account and count the number of emails per sender and list the results in a spreadsheet - javascript

i'm not a programmer. i'm just trying to clean my gmail account and i figured i could put chatgpt to use. it came up with this code, but i'm getting an error when i run it in apps script: hoping someone can shed some light on what's wrong with it.
thanks
function countEmailsBySender() {
// Replace the following variables with your own values
var maxEmails = 4000; // the maximum number of emails to process
var spreadsheetId = 'YOUR_SPREADSHEET_ID'; // the ID of your Google Spreadsheet
var sheetName = 'Emails By Sender'; // the name of the sheet to create in your Google Spreadsheet
var clientId = 'YOUR_CLIENT_ID'; // your OAuth 2.0 client ID
var clientSecret = 'YOUR_CLIENT_SECRET'; // your OAuth 2.0 client secret
// Create a new sheet in the specified spreadsheet
var sheet = SpreadsheetApp.openById(spreadsheetId).insertSheet(sheetName);
// Get the authorization token
var service = getGmailService(clientId, clientSecret);
if (!service.hasAccess()) {
Logger.log(service.getAuthorizationUrl());
throw new Error('Authorization required');
}
// Get the list of messages
var query = 'is:inbox';
var threads = Gmail.Users.Threads.list('me', { q: query, maxResults: maxEmails }, { auth: service }).threads;
// Count the number of emails sent by each sender
var emailCount = {};
threads.forEach(function(thread) {
var messages = Gmail.Users.Messages.get('me', thread.messages[0].id, { auth: service }).payload.headers;
var sender = messages.find(header => header.name === 'From').value;
emailCount[sender] = (emailCount[sender] || 0) + 1;
});
// Sort the senders by the number of emails they sent
var senders = Object.keys(emailCount).sort(function(a, b) {
return emailCount[b] - emailCount[a];
});
// Output the senders and the number of emails they sent to the spreadsheet
sheet.appendRow(['Sender', 'Email Count']);
senders.forEach(function(sender) {
sheet.appendRow([sender, emailCount[sender]]);
});
Logger.log('Done.');
}
function getGmailService(clientId, clientSecret) {
return OAuth2.createService('gmail')
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setClientId(clientId)
.setClientSecret(clientSecret)
.setScope('https://www.googleapis.com/auth/gmail.readonly')
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getUserProperties())
.setCache(CacheService.getUserCache())
.setParam('access_type', 'offline')
.setParam('approval_prompt', 'force');
}
function authCallback(request) {
var service = getGmailService();
var authorized = service.handleCallback(request);
if (authorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Access denied.');
}
}
i get an error (ReferenceError: OAuth2 is not defined) on these lines :
return OAuth2.createService('gmail')
var service = getGmailService(clientId, clientSecret);
am i missing something?
thanks in advance
haven't tried anything else

Related

How to run certain scripts under the authority of a specific user?

I was able to allow other users to add a new SKU to a sheet without unprotecting it (Original post). Now I am trying to do the inverse, to allow users to delete an SKU without unprotecting the sheet.
I started with the following, which works as expected:
function deleteEachRow(){
const ss = SpreadsheetApp.getActive();
var SHEET = ss.getSheetByName("Ordering");
var RANGE = SHEET.getDataRange();
const ui = SpreadsheetApp.getUi();
const response = ui.prompt('WARNING: \r\n \r\n Ensure the following sheets DO NOT contain data before proceeding: \r\n \r\n Accessory INV \r\n Apparel INV \r\n Pending TOs \r\n \r\n Enter New SKU:', ui.ButtonSet.OK_CANCEL);
if (response.getSelectedButton() === ui.Button.OK) {
const text = response.getResponseText();
var rangeVals = RANGE.getValues();
//Reverse the 'for' loop.
for(var i = rangeVals.length-1; i >= 0; i--){
if(rangeVals[i][0] === text){
SHEET.deleteRow(i+1);
};
};
};
};
I tried to Frankenstein the above code into the answer I was provided. Now the script runs without error but fails to delete the entered SKU as expected. This is the script I am running:
function deleteEachRow1(){
const ss = SpreadsheetApp.getActive();
var SHEET = ss.getSheetByName("Ordering");
var RANGE = SHEET.getDataRange();
const ui = SpreadsheetApp.getUi();
const response = ui.prompt('WARNING: \r\n \r\n Ensure the following sheets DO NOT contain data before proceeding: \r\n \r\n Accessory INV \r\n Apparel INV \r\n Pending TOs \r\n \r\n Delete Which SKU?:', ui.ButtonSet.OK_CANCEL);
if (response.getSelectedButton() === ui.Button.OK) {
const text = response.getResponseText();
const webAppsUrl = "WEB APP URL"; // Pleas set your Web Apps URL.
const url = webAppsUrl + "?text=" + text;
const res = UrlFetchApp.fetch(url, {muteHttpExceptions: true});
// ui.alert(res.getContentText()); // You can see the response value using this line.
}
}
function doGet(e) {
const text = e.parameter.text;
const sheet = SpreadsheetApp.getActive().getSheetByName('Ordering');
var rangeVals = RANGE.getValues();
//Reverse the 'for' loop.
for(var i = rangeVals.length-1; i >= 0; i--){
if(rangeVals[i][0] === text){
SHEET.deleteRow(i+1);
};
};
myFunction();
return ContentService.createTextOutput(text);
}
// This script is from https://tanaikech.github.io/2017/07/31/converting-a1notation-to-gridrange-for-google-sheets-api/
function a1notation2gridrange1(a1notation) {
var data = a1notation.match(/(^.+)!(.+):(.+$)/);
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(data[1]);
var range = ss.getRange(data[2] + ":" + data[3]);
var gridRange = {
sheetId: ss.getSheetId(),
startRowIndex: range.getRow() - 1,
endRowIndex: range.getRow() - 1 + range.getNumRows(),
startColumnIndex: range.getColumn() - 1,
endColumnIndex: range.getColumn() - 1 + range.getNumColumns(),
};
if (!data[2].match(/[0-9]/)) delete gridRange.startRowIndex;
if (!data[3].match(/[0-9]/)) delete gridRange.endRowIndex;
return gridRange;
}
// Please run this function.
function myFunction() {
const email = "MY EMAIL"; // <--- Please set your email address.
// Please set your sheet names and unprotected ranges you want to use.
const obj = [
{ sheetName: "Ordering", unprotectedRanges: ["O5:P", "C2:E2"] },
{ sheetName: "Accessory INV", unprotectedRanges: ["E5:H"] },
{ sheetName: "Apparel INV", unprotectedRanges: ["E5:F"] },
{sheetName: "Pending TOs", unprotectedRanges: ["E6:H"] },
{sheetName: "INV REF", unprotectedRanges: ["C6:C"] },
];
// 1. Retrieve sheet IDs and protected range IDs.
const spreadsheetId = SpreadsheetApp.getActiveSpreadsheet().getId();
const sheets = Sheets.Spreadsheets.get(spreadsheetId, { ranges: obj.map(({ sheetName }) => sheetName), fields: "sheets(protectedRanges(protectedRangeId),properties(sheetId))" }).sheets;
const { protectedRangeIds, sheetIds } = sheets.reduce((o, { protectedRanges, properties: { sheetId } }) => {
if (protectedRanges && protectedRanges.length > 0) o.protectedRangeIds.push(protectedRanges.map(({ protectedRangeId }) => protectedRangeId));
o.sheetIds.push(sheetId);
return o;
}, { protectedRangeIds: [], sheetIds: [] });
// 2. Convert A1Notation to Gridrange.
const gridranges = obj.map(({ sheetName, unprotectedRanges }, i) => unprotectedRanges.map(f => a1notation2gridrange1(`${sheetName}!${f}`)));
// 3. Create request body.
const deleteProptectedRanges = protectedRangeIds.flatMap(e => e.map(id => ({ deleteProtectedRange: { protectedRangeId: id } })));
const protects = sheetIds.map((sheetId, i) => ({ addProtectedRange: { protectedRange: { editors: {users: [email]}, range: { sheetId }, unprotectedRanges: gridranges[i] } } }));
// 4. Request to Sheets API with the created request body.
Sheets.Spreadsheets.batchUpdate({ requests: [...deleteProptectedRanges, ...protects] }, spreadsheetId);
}
Probably the easiest way to do this would be to avoid using a button and using a checkbox with a installable edit trigger, which also has a great side effect of mobile support.
Proposed solution:
Using a checkbox
Hook it to a installable edit trigger, which runs as the user who installed the trigger. Therefore, if the owner installs the trigger, no matter who edits the sheet, the trigger runs as the owner, giving access to privileged resources including protected ranges.
The installable version runs with the authorization of the user who created the trigger, even if another user with edit access opens the spreadsheet.
Notes:
Advantage:
Code simplicity and maintainabilty. No need for webapp or any complicated setup.
Disadvantage: Security (with possible workaround)
If the code is bound to the sheet, editors of the sheet get direct access to the script of the sheet. So, any editor with malicious intentions would be able to modify the code. If the function with installable trigger has gmail permissions, any editor would be able to log all the emails of the owner. So,special attention needs to be paid to permissions requested. Note that, this is already the case with your web app setup. Any editor maybe able to modify doGet to access protected data. If the webapp is in a separate standalone script, this isn't a issue. You may also be able to fix this issue by setting the trigger at a predetermined version instead of Head version. See this answer for more information.

Google Sheets Scripts - run scripts as administrator / owner

I have Google Sheet, name TEST https://docs.google.com/spreadsheets/d/1HsRwknyZBmZZ9nibDfNpOwqkVsFGThDyrTwspV-5_4U/edit?usp=sharing
Sheet: Arkusz 1
Column A: all people can edit
Column B: only owner can edit
Library (for everyone): https://script.google.com/macros/s/AKfycbzpnEMhIG-0dMp54q3W4UxoT71-lSdfF7Qxf7rq_j6gJMNIxuCS/exec
A user cannot add a row because it is blocked by column B, which belongs only to the admin.
How can I create macro, which allow user to add new rows?
I have three scripts:
function insertRow() {
var ss = SpreadsheetApp.getActive()
var sheetName = ss.getActiveSheet().getName()
var row = ss.getActiveRange().getRow()
var numRows = Browser.inputBox('Insert Rows', 'Enter the number of rows to insert', Browser.Buttons.OK);
Logger.log(numRows)
var url ="https://script.google.com/macros/s/AKfycbzpnEMhIG-0dMp54q3W4UxoT71-lSdfF7Qxf7rq_j6gJMNIxuCS/exec"
var queryString = "?sheetName="+sheetName+"&rowNo="+row+"&noOfRows="+numRows
url = url + queryString
Logger.log(url)
var request = UrlFetchApp.fetch(url)
if (request != 'Success')
Browser.msgBox(request)
}
Second:
function doGet(e) {
var param = e.queryString
var parameters = param.split("&")
// This just checks only 3 parameters are present else gives a invalid link
if (param != null && parameters.length == 3){
param = e.parameter
var name = param.sheetName
var row = Number(param.rowNo)
var numOfRows = Number(param.noOfRows)
} else{
return ContentService.createTextOutput("Invalid query")
}
try{
var ss = SpreadsheetApp.openById("https://docs.google.com/spreadsheets/d/1HsRwknyZBmZZ9nibDfNpOwqkVsFGThDyrTwspV-5_4U")
var sheet = ss.getSheetByName(name)
sheet.insertRowsAfter(row, numOfRows);
var source_range = sheet.getRange(row,1,1,sheet.getLastColumn());
var target_range = sheet.getRange(row+1,1,numOfRows);
source_range.copyTo(target_range);
}
catch (err){
return ContentService.createTextOutput("error: "+err)
}
return ContentService.createTextOutput("Success")
}
And after clicked function insertRow and filled number of rows I have doPost(e) information.
Could you help me?
On the solution you provided below, I see that the issue is in mainScript
function mainScript(e) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
// assign the sheet to a variable and use it below instead of spreadsheet
var sheet = spreadsheet.getSheetByName('ZNC')
sheet.getRange('A2').activate()
sheet.insertRowsBefore(sheet.getActiveRange().getRow(), 1);
}
Hmm, I created solution, but I think there's a bug somewhere, because it doesn't add the line, even though everything is correct and the script is published as public.
function ZNCWiersz() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('ZNC'), true);
const activeSheet = SpreadsheetApp.getActiveSheet().getSheetName();
const url = ScriptApp.getService().getUrl();
UrlFetchApp.fetch(`${url}?sheetName=${activeSheet}`, {
headers: { authorization: "Bearer " + ScriptApp.getOAuthToken() },
});
// DriveApp.getFiles() // This is used for automatically detecting the scope of "https://www.googleapis.com/auth/drive.readonly". This scope is used for the access token.
}
// When runScript() is run, this function is run.
const doGet = (e) => ContentService.createTextOutput(mainScript(e));
// This script is run by Web Apps.
function mainScript(e) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
spreadsheet.getSheetByName('ZNC')
spreadsheet.getRange('A2').activate()
spreadsheet.insertRowsBefore(spreadsheet.getActiveRange().getRow(), 1);
}

how to getRange of more than one cell in same row in a script

Im trying to make a script that sends email alert when specific fields reaches certain values. It works fine for one field, but how Do I do it so it sends alert when any of the fields in specific row range reaches that value.
Im using this code:
function CheckSales() {
// Tikrinam laukelio value
var monthSalesRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("reportai").getRange("A15");
var monthSales = monthSalesRange.getValue();
// tikrinam value
if (monthSales < 200){
// pasiimam email adresa
var emailRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1").getRange("B2");
var emailAddress = emailRange.getValue();
// Siuncima email.
var message = 'Mazas likutis: ' + monthSales; // Second column
var subject = 'Mazas Likutis';
MailApp.sendEmail(emailAddress, subject, message);
}
}
I tried using .getRange("A15:E15") but still it send alert only when A15 reaches value less than 200, its not reacting to B15, C15 ect.
You need to iterate the values in A15:E15 with a loop. Try this:
function checkSales() {
const monthSalesRange = SpreadsheetApp.getActive().getRange('reportai!A15:E15');
const emailAddress = SpreadsheetApp.getActive().getRange('Sheet1!B2').getValue();
const subject = 'Mazas Likutis';
let message = 'Mazas likutis: ';
const alerts = [];
monthSalesRange.getValues().flat()
.forEach(sales => {
if (Number(sales) && sales < 200) {
alerts.push(sales);
}
});
if (alerts.length) {
MailApp.sendEmail(emailAddress, subject, message + alerts.join(', '));
}
}
Some of the best resources for learning Google Apps Script include the Beginner's Guide, the New Apps Script Editor guide, the Fundamentals of Apps Script with Google Sheets codelab, the Extending Google Sheets page, javascript.info, Mozilla Developer Network and Apps Script at Stack Overflow.

How to get active user email with installabled onEdit trigger? [duplicate]

This question already has an answer here:
onEdit trigger doesn't catch current user
(1 answer)
Closed 3 months ago.
I have a Google spreadsheet with some data. I wrote script to track changes of some specific columns.
function onOpen() {
var ss = SpreadsheetApp.getActive();
var menuItems = [
{name: 'Turn on', functionName: 'createSpreadsheetEditTrigger'}
];
ss.addMenu('Tracker', menuItems);
}
function changeTrack(){
const ss = SpreadsheetApp.getActiveSpreadsheet();
const ui = SpreadsheetApp.getUi();
var ws = ss.getActiveSheet();
const headerRow = 4;
const editBodyCols = [2, 3, 4, 5];
const fResultCol = 6;
var range = ws.getActiveRange();
var row = range.getRow();
var col = range.getColumn();
let target1 = ws.getRange(row, fResultCol);
let target2 = ws.getRange(row, fResultCol + 1)
let activeUser = getCurrentUserEmail();
if(row > headerRow && editBodyCols.some(x => x === col) === true){
if(target1.getValue() !== ""){
target2.setValue(result(ss, ws, row, activeUser)[1]);
} else {
target1.setValue(result(ss, ws, row, activeUser)[0])
target2.setValue(result(ss, ws, row, activeUser)[1])
}
}
}
function createSpreadsheetEditTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('changeTrack')
.forSpreadsheet(ss).onEdit()
.create();
}
function date(){
return Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy-MM-dd HH:mm:ss");
}
function result(ss, ws, row, activeUser) {
const ssName = ss.getName();
let data = `Создал ${activeUser} ${date()}`;
let exp = `Файл ${ssName}, Лист ${ws.getName()}, изменил ${activeUser}, строка № ${row}, ${date()}`;
let adds = [];
adds.push([data],[exp]);
return adds;
}
function getCurrentUserEmail()
{
var email=Session.getActiveUser().getEmail();
return email;
}
My problem is to get active user's email. This script can get it but not all the time. Seems like random success. It means sometimes I can get expected value, sometimes not. I don't understand what is it depends from.
Where I'm wrong and how to fix it?
From the documentation on Session.getActiveUser():
Gets information about the current user. If security policies do not allow access to the user's identity, User.getEmail() returns a blank string. The circumstances in which the email address is available vary: for example, the user's email address is not available in any context that allows a script to run without that user's authorization, like a simple onOpen(e) or onEdit(e) trigger, a custom function in Google Sheets, or a web app deployed to "execute as me" (that is, authorized by the developer instead of the user).
So this seems pretty expected and there is no hard workaround you can make to retrieve the users mail. You should maybe just ask for it and see if they be willingly want to give it to you.
Although if you are the developer or the users are inside your organization this restrictions may be ignored:
However, these restrictions generally do not apply if the developer runs the script themselves or belongs to the same G Suite domain as the user.
Based on the comment by b-frid.
The flow:
create a custom menu and tell each user to run the function twice. First time for authorization, and the second time to actually run the code.
this will install the same trigger with the user's privileges and let the onEdit trigger get the email of an active user (author of the trigger)
video-instruction:
https://www.loom.com/share/30b11e4d012447f7a1efdd9a7eac4fca
gist:
https://gist.github.com/Max-Makhrov/e3e89e7fe0c6e86b68a4be1262e53629
Code:
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('😎📬 run me 2x times')
.addItem('please let script to see your email', 'install')
.addToUi();
}
// function test() {
// var e = {
// range: SpreadsheetApp.getActive().getSheetByName('test_onEdit').getRange('B2')
// }
// edit_(e)
// }
function edit_(e) {
var targetcol = 1;
if (e.range.getSheet().getName() === 'Sheet1') {
var user = Session.getActiveUser().getEmail();
if (user !== '') {
var col = e.range.getColumn();
var rows = e.range.getHeight();
var ratgetrange = e.range.offset(
0,
targetcol - col,
rows,
1);
ratgetrange.setValue(user);
}
}
}
function install() {
setOnEditTrigger_(
SpreadsheetApp.getActive().getId(),
'edit_');
}
/**
* create onEdit trigger
*
* #param {string} spreadsheetId
* #param {string} functionName
*
*/
function setOnEditTrigger_(spreadsheetId, functionName) {
console.log('OnEdit trigger ' + functionName +
' for new file' +
spreadsheetId);
var trigger;
if (existsOnEditTrigger_(functionName, spreadsheetId)) {
console.log('stopped execution. Trigger exists.');
return;
}
trigger = ScriptApp
.newTrigger(functionName)
.forSpreadsheet(spreadsheetId)
.onEdit()
.create();
console.log('Created new trigger!')
return trigger.getUniqueId();
}
/**
* check if onEdit trigger exists
*
* #param {string} spreadsheetId
* #param {string} functionName
*
*/
function existsOnEditTrigger_(functionName, spreadsheetId) {
var triggers = ScriptApp.getProjectTriggers();
var trigger = {};
for (var i = 0; i < triggers.length; i++) {
trigger = triggers[i];
if (
trigger.getHandlerFunction() === functionName &&
trigger.getTriggerSourceId() === spreadsheetId &&
trigger.getEventType().toString() === 'ON_EDIT') return true;
}
return false;
}
Original comment:
The key is separate triggers for each user. Then I simply added code
to execute the update only if the Session.getActiveUser().getEmail()
call does not come back blank. Of course, because each's user's
trigger will run, the function will execute x times where x = the
number of users (i.e. triggers), but with the check for a blank return
value the logic only runs once (and so no overwrites). A bit clumsy
indeed, and perhaps not practical if you have more than a handful of
users, but workable in my case.

Linking Google account with existing account created using email in Parse.com

I have implemented google login in parse. Here is my code:
var querystring = require('querystring');
var _ = require('underscore');
var Buffer = require('buffer').Buffer;
var googleValidateEndpoint = 'https://www.googleapis.com/oauth2/v1/userinfo';
var TokenStorage = Parse.Object.extend("TokenStorage");
var restrictedAcl = new Parse.ACL();
restrictedAcl.setPublicReadAccess(false);
restrictedAcl.setPublicWriteAccess(false);
Parse.Cloud.define('accessGoogleUser', function(req, res) {
var data = req.params;
var token = data.code;
/**
* Validate that code and state have been passed in as query parameters.
* Render an error page if this is invalid.
*/
if (!(data && data.code)) {
res.error('Invalid auth response received.');
return;
}
Parse.Cloud.useMasterKey();
Parse.Promise.as().then(function() {
// Validate & Exchange the code parameter for an access token from Google
return getGoogleAccessToken(data.code);
}).then(function(httpResponse) {
var userData = httpResponse.data;
if (userData && userData.id) {
return upsertGoogleUser(token, userData, data.email);
} else {
return Parse.Promise.error("Unable to parse Google data");
}
}).then(function(user) {
/**
* Send back the session token in the response to be used with 'become/becomeInBackground' functions
*/
res.success(user.getSessionToken());
}, function(error) {
/**
* If the error is an object error (e.g. from a Parse function) convert it
* to a string for display to the user.
*/
if (error && error.code && error.error) {
error = error.code + ' ' + error.error;
}
res.error(JSON.stringify(error));
});
});
var getGoogleAccessToken = function(code) {
var body = querystring.stringify({
access_token: code
});
return Parse.Cloud.httpRequest({
url: googleValidateEndpoint + '?access_token=' + code
});
}
var upsertGoogleUser = function(accessToken, googleData, emailId) {
var query = new Parse.Query(TokenStorage);
query.equalTo('accountId', googleData.id);
//query.ascending('createdAt');
// Check if this googleId has previously logged in, using the master key
return query.first({ useMasterKey: true }).then(function(tokenData) {
// If not, create a new user.
if (!tokenData) {
return newGoogleUser(accessToken, googleData, emailId);
}
// If found, fetch the user.
var user = tokenData.get('user');
return user.fetch({ useMasterKey: true }).then(function(user) {
// Update the access_token if it is different.
if (accessToken !== tokenData.get('accessToken')) {
tokenData.set('accessToken', accessToken);
}
/**
* This save will not use an API request if the token was not changed.
* e.g. when a new user is created and upsert is called again.
*/
return tokenData.save(null, { useMasterKey: true });
}).then(function(obj) {
// Reset password
password = new Buffer(24);
_.times(24, function(i) {
password.set(i, _.random(0, 255));
});
password = password.toString('base64')
user.setPassword(password);
return user.save();
}).then(function(user) {
// ReLogin
// This line is what I am talking about
return Parse.User.logIn(user.get('username'), password);
}).then(function(obj) {
// Return the user object.
return Parse.Promise.as(obj);
});
});
}
var newGoogleUser = function(accessToken, googleData, email) {
var user = new Parse.User();
// Generate a random username and password.
var username = new Buffer(24);
var password = new Buffer(24);
_.times(24, function(i) {
username.set(i, _.random(0, 255));
password.set(i, _.random(0, 255));
});
var name = googleData.name;
// name = name.split(" ");
// var fullname = name;
// if(name.length > 1)
// var lastName = name[name.length-1];
user.set("username", username.toString('base64'));
user.set("password", password.toString('base64'));
user.set("email", email);
user.set("fullName", name);
// user.set("last_name", lastName);
user.set("accountType", 'google');
// Sign up the new User
return user.signUp().then(function(user) {
// create a new TokenStorage object to store the user+Google association.
var ts = new TokenStorage();
ts.set('user', user);
ts.set('accountId', googleData.id);
ts.set('accessToken', accessToken);
ts.setACL(restrictedAcl);
// Use the master key because TokenStorage objects should be protected.
return ts.save(null, { useMasterKey: true });
}).then(function(tokenStorage) {
return upsertGoogleUser(accessToken, googleData);
});
}
It works perfectly fine. Now the problem I am facing is that I want to link google account with an existing parse account created using email or username & password. The problem in doing so is that to login/signup using google I have to reset the password of the user to login so as to get the session token. See this line in the code -> [This line is what I am talking about]. So if I do so an existing user who had earlier used username/email & password to login won't be able to login again using email since I have reset his/her password. I have seen this and all the other links related to this but none of which solves this problem.
Can somebody here guide me in the right direction?
Log added as response to one of the comments:
{"accountType":"google","createdAt":"2016-01-07T17:30:57.429Z","email":"skdkaney#gmail.com","fullName":"ashdakhs basdkbney","updatedAt":"2016-01-07T17:30:57.429Z","username":"owt3h0ZZEZQ1K7if55W2oo3TBLfeWM6m","objectId":"lSlsdsZ9"}
Added upsert function as per comment request:
var upsertGoogleUser = function(accessToken, googleData, emailId) {
var query = new Parse.Query(TokenStorage);
query.equalTo('accountId', googleData.id);
//query.ascending('createdAt');
// Check if this googleId has previously logged in, using the master key
return query.first({ useMasterKey: true }).then(function(tokenData) {
// If not, create a new user.
if (!tokenData) {
return newGoogleUser(accessToken, googleData, emailId);
}
// If found, fetch the user.
var userw = tokenData.get('user');
var users_id = userw.id;
var query2 = new Parse.Query(Parse.User);
query2.equalTo('objectId',users_id);
// The new query added
return query2.first({ useMasterKey: true }).then(function(user) {
// Update the access_token if it is different.
// if (accessToken !== tokenData.get('accessToken')) {
// tokenData.set('accessToken', accessToken);
// }
console.log(user);
console.log("******");
/**
* This save will not use an API request if the token was not changed.
* e.g. when a new user is created and upsert is called again.
*/
// return tokenData.save(null, { useMasterKey: true });
}).then(function(obj) {
console.log(obj);
// console.log(user);
var result = user ;
// Return the user object.
return Parse.Promise.as(result); // this is the user object acquired above
});
After a discussion with OP, there are possible solutions to this matter but each of them have pros and cons.
Disabling Revocable Session
Since the introduction of Revocable Session, getSessionToken will always return undefined even with master key. To turn it off, go to App Settings >> Users >> Turn off Require revocable sessions.
Then, in upsertGoogleUser method, you just need to return the user object from tokenData.get('user'). It is enough to call user.getSessionToken() in your main cloud function. The final method should look like:
var upsertGoogleUser = function(accessToken, googleData, emailId) {
Parse.Cloud.useMasterKey();
var query = new Parse.Query(TokenStorage);
query.equalTo('accountId', googleData.id);
//query.ascending('createdAt');
// Check if this googleId has previously logged in, using the master key
return query.first().then(function(tokenData) {
// If not, create a new user.
if (!tokenData) {
return newGoogleUser(accessToken, googleData, emailId);
}
// If found, fetch the user.
var userw = tokenData.get('user');
var users_id = userw.id;
var query2 = new Parse.Query(Parse.User);
query2.equalTo('objectId',users_id);
return query2.first().then(function(user) {
console.log(user);
console.log(user.getSessionToken());
console.log("******");
return Parse.Promise.as(user);
});
});
};
User Password Input
In order not to change user's password, we can ask user to input his password once we successfully authenticated Google data. We then use the input password to log user in. This is not a good UX, since the purpose of Google Login is to increase usability by letting users not entering password.
Query on Parse.Session
This is a possible solution if you want to use "Revocable Session" feature. In the code above, instead of querying on Parse.User, we can look for any revocable session in Parse.Session class. Then we can call getSessionToken on returned object. This is not optimal solution in cases that we need to know which devices the user is logged in.
Reference:
Parse's Enhanced Session: http://blog.parse.com/announcements/announcing-new-enhanced-sessions/

Categories