Home > Back-end >  Google Web Apps - Get user email but run scripts as owner
Google Web Apps - Get user email but run scripts as owner

Time:11-11

I've recently gravitated to google web apps and I have a bit of a dilemma. I'm trying to build an app that is open to very specific users and the data they are viewing filters based on their access group.

In a google sheet I'm listing the user emails and their respective access groups. Column A - email, Column B - access group

The issue

When the user accesses the web app I'm using this to grab their email:

var email = Session.getActiveUser().getEmail();

And then I run this code to get their access group:

function validate(email){
  var sheet = SpreadsheetApp.openById(ID).getSheetByName(ssUserList);
  try{
    var group = getRowsData(sheet).find(e => e.userEmail === email).securityGroup;
    return group;
  } catch(e){
    return "Not Authorized";
  }
}

Because the user doesn't have access to my google sheet, they get an error when the function runs. And I can't deploy the web app to run as me because I need the user's email. I understand this very well.

What I've read:

Tons of other posts and articles about access tokens and credentials and urlFetchApps ... I don't understand any of it and to be honest I don't know which one makes more sense for my situation.

What I've tried:

I can't use the 1st usable option I've found which is to access web app 1 (which runs as user), then call web app 2 using the user email as a parameter because if they share that link from web app 2 then anyone could see the data and I'm working with really sensitive data.


I realize I could just put these parameters in a separate sheet and give them view only access and the scripts will run fine, but I'm extra and I want to do it right.

In reality I'm going to have a few other functions that will need to run as me. If you were in my shoes, where would you start? Or can someone explain it in layman's terms? Should I be looking into something like this? Any help is appreciated!

CodePudding user response:

Summary

One of the possibilities, as suggested here, is to create a separate web application to handle access to SpreadSheets.

The client (the main web app) would make a request through UrlFetchApp to the middleware (web app in charge of consulting the SpreadSheet), the middleware would make the needed queries and would return them to the client. Finally, depending on the response obtained, one content or another would be rendered.

Minimal Example

Configuring the Project

First, we create two GAS projects, one we call Main and the other Middleware. The main point is that the Middleware would run as USER_DEPLOYING and the client as USER_ACCESSING. This allows access to the sheet without requiring additional permissions.

The appscripts.json file would look like this on the client. :

  "oauthScopes": [
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/userinfo.email"
  ],  
  "webapp": {
    "executeAs": "USER_ACCESSING",
    "access": "ANYONE"
  }

And like this on the middleware:

  "oauthScopes": [
    "https://www.googleapis.com/auth/spreadsheets"
  ],
  "webapp": {
    "executeAs": "USER_DEPLOYING",
    "access": "ANYONE_ANONYMOUS"
  }

If you have any questions about editing or viewing appscript.json, check the Manifest and Scopes documentation.

Attention: "access": "ANYONE" and "access": "ANYONE_ANONYMOUS" are only being used for testing purposes. This is dangerous, and should be reviewed for the specific needs of your project.

Code Sample

As for the client, we only need to ask for the email of the user who is accessing through Session.getActiveUser().getEmail() and then send it to the middleware to obtain the response. Depending on the response obtained, we will render one content or another (I assume there are two roles present: USER and ADMIN)

Client
const doGet = () => {
  var data = {email: Session.getActiveUser().getEmail()}
  var options = {
    'method': 'POST',
    'contentType': 'application/json',
    'payload': JSON.stringify(data)
  }
  var fetch = UrlFetchApp.fetch(URL_MIDDLEWARE, options)

  var userAccess = JSON.parse(fetch).accessLevel
  return HtmlService.createHtmlOutput(
    userAccess === "ADMIN"
    ? `<h1>${data.email} - ADMIN USER</h1>`
    : userAccess === "USER" 
    ? `<h1>${data.email} - COMMON USER</h1>`
    : "<h1>Unauthorized</h1>" )
}

For the middleware we need to obtain that email and compare it with our sheet to check the access level of the user. Then we return the result.

Middleware
const doPost = (request) => {
  // destructuring the request
  const { parameter, postData: { contents, type } = {} } = request;
  const userEmail = JSON.parse(contents).email;
  let userAccess = SpreadsheetApp.openById(SPREADSHEET_ID).getRange('A1:B2').getValues()
  // This can be replaced by a filter function
  let userAccessLevel;
  for (let user of userAccess) { if (userEmail == user[0]) userAccessLevel = user[1] }
  return ContentService.createTextOutput(Utilities.jsonStringify({
    user: userEmail,
    accessLevel: userAccessLevel
  }))
};

Finally, you access the Main Web App to check that everything is working.

Remember that this is a test implementation, and should not be used in production. If you need more information on these topics, you can visit the following links:

  • Related