From 59d08a741485a54945cb8edd0744f89a90f380ae Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Mon, 14 Feb 2022 22:21:58 -0600 Subject: [PATCH 1/4] update googleActions and related files to use service-level auth where viable --- server/app.js | 2 +- server/googleActions.js | 69 ++++++++++++++++++----------------------- server/homebrew.api.js | 6 +--- 3 files changed, 32 insertions(+), 45 deletions(-) diff --git a/server/app.js b/server/app.js index 3bffb4ff9..b7bca2b52 100644 --- a/server/app.js +++ b/server/app.js @@ -25,7 +25,7 @@ const getBrewFromId = asyncHandler(async (id, accessType)=>{ if(id.length > 12) { const googleId = id.slice(0, -12); id = id.slice(-12); - brew = await GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, id, accessType); + brew = await GoogleActions.readFileMetadata(googleId, id, accessType); } else { brew = await HomebrewModel.get(accessType == 'edit' ? { editId: id } : { shareId: id }); brew = brew.toObject(); // Convert MongoDB object to standard Javascript Object diff --git a/server/googleActions.js b/server/googleActions.js index 9d17abdce..29152198b 100644 --- a/server/googleActions.js +++ b/server/googleActions.js @@ -5,7 +5,22 @@ const { nanoid } = require('nanoid'); const token = require('./token.js'); const config = require('./config.js'); -//let oAuth2Client; +const keys = typeof(config.get('service_account')) == 'string' ? + JSON.parse(config.get('service_account')) : + config.get('service_account'); +let serviceAuth; +try { + serviceAuth = google.auth.fromJSON(keys); + serviceAuth.scopes = [ + 'https://www.googleapis.com/auth/drive', + 'https://www.googleapis.com/auth/drive.appdata', + 'https://www.googleapis.com/auth/drive.file', + 'https://www.googleapis.com/auth/drive.metadata' + ]; +} catch (err) { + console.warn(err); +} +google.options({ auth: serviceAuth || config.get('google_api_key') }); const GoogleActions = { @@ -43,7 +58,7 @@ const GoogleActions = { }, getGoogleFolder : async (auth)=>{ - const drive = google.drive({ version: 'v3', auth: auth }); + const drive = google.drive({ version: 'v3', auth }); fileMetadata = { 'name' : 'Homebrewery', @@ -81,13 +96,11 @@ const GoogleActions = { listGoogleBrews : async (req, res)=>{ - oAuth2Client = GoogleActions.authCheck(req.account, res); + const oAuth2Client = GoogleActions.authCheck(req.account, res); //TODO: Change to service account to allow non-owners to view published files. // Requires a driveId parameter in the drive.files.list command - // const keys = JSON.parse(config.get('service_account')); - // const auth = google.auth.fromJSON(keys); - // auth.scopes = ['https://www.googleapis.com/auth/drive']; + // Then remove the `auth` parameter from the drive object initialization const drive = google.drive({ version: 'v3', auth: oAuth2Client }); @@ -129,8 +142,8 @@ const GoogleActions = { return brews; }, - existsGoogleBrew : async (auth, id)=>{ - const drive = google.drive({ version: 'v3', auth: auth }); + existsGoogleBrew : async (id)=>{ + const drive = google.drive({ version: 'v3' }); const result = await drive.files.get({ fileId: id }) .catch((err)=>{ @@ -144,10 +157,10 @@ const GoogleActions = { return false; }, - updateGoogleBrew : async (auth, brew)=>{ - const drive = google.drive({ version: 'v3', auth: auth }); + updateGoogleBrew : async (brew)=>{ + const drive = google.drive({ version: 'v3' }); - if(await GoogleActions.existsGoogleBrew(auth, brew.googleId) == true) { + if(await GoogleActions.existsGoogleBrew(brew.googleId) == true) { await drive.files.update({ fileId : brew.googleId, resource : { @@ -180,7 +193,7 @@ const GoogleActions = { }, newGoogleBrew : async (auth, brew)=>{ - const drive = google.drive({ version: 'v3', auth: auth }); + const drive = google.drive({ version: 'v3', auth }); const media = { mimeType : 'text/plain', @@ -248,9 +261,8 @@ const GoogleActions = { return newHomebrew; }, - readFileMetadata : async (auth, id, accessId, accessType)=>{ - - const drive = google.drive({ version: 'v3', auth: auth }); + readFileMetadata : async (id, accessId, accessType)=>{ + const drive = google.drive({ version: 'v3' }); const obj = await drive.files.get({ fileId : id, @@ -269,16 +281,7 @@ const GoogleActions = { throw ('Share ID does not match'); } - //Access file using service account. Using API key only causes "automated query" lockouts after a while. - - const keys = typeof(config.get('service_account')) == 'string' ? - JSON.parse(config.get('service_account')) : - config.get('service_account'); - - const serviceAuth = google.auth.fromJSON(keys); - serviceAuth.scopes = ['https://www.googleapis.com/auth/drive']; - - const serviceDrive = google.drive({ version: 'v3', auth: serviceAuth }); + const serviceDrive = google.drive({ version: 'v3' }); const file = await serviceDrive.files.get({ fileId : id, @@ -320,8 +323,7 @@ const GoogleActions = { }, deleteGoogleBrew : async (req, res, id)=>{ - - oAuth2Client = GoogleActions.authCheck(req.account, res); + const oAuth2Client = GoogleActions.authCheck(req.account, res); const drive = google.drive({ version: 'v3', auth: oAuth2Client }); const googleId = id.slice(0, -12); @@ -354,16 +356,7 @@ const GoogleActions = { }, increaseView : async (id, accessId, accessType, brew)=>{ - //service account because this is modifying another user's file properties - //so we need extended scope - const keys = typeof(config.get('service_account')) == 'string' ? - JSON.parse(config.get('service_account')) : - config.get('service_account'); - - const auth = google.auth.fromJSON(keys); - auth.scopes = ['https://www.googleapis.com/auth/drive']; - - const drive = google.drive({ version: 'v3', auth: auth }); + const drive = google.drive({ version: 'v3' }); await drive.files.update({ fileId : brew.googleId, @@ -380,8 +373,6 @@ const GoogleActions = { console.error(err); //return res.status(500).send('Error while saving'); }); - - return; } }; diff --git a/server/homebrew.api.js b/server/homebrew.api.js index e0da74dca..745a02ec0 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -167,15 +167,11 @@ const newGoogleBrew = async (req, res, next)=>{ }; const updateGoogleBrew = async (req, res, next)=>{ - let oAuth2Client; - - try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); } - const brew = excludePropsFromUpdate(req.body); brew.text = mergeBrewText(brew); try { - const updatedBrew = await GoogleActions.updateGoogleBrew(oAuth2Client, brew); + const updatedBrew = await GoogleActions.updateGoogleBrew(brew); return res.status(200).send(updatedBrew); } catch (err) { return res.status(err.response?.status || 500).send(err); From 927345b131786e917106ce5c4295c000668ac9e5 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Wed, 16 Feb 2022 16:53:34 -0600 Subject: [PATCH 2/4] update app, googleActions, and homebrew api based on PR feedback --- server/app.js | 13 +++-- server/googleActions.js | 102 ++++++++++++++-------------------------- server/homebrew.api.js | 6 ++- 3 files changed, 48 insertions(+), 73 deletions(-) diff --git a/server/app.js b/server/app.js index b7bca2b52..637dfbf64 100644 --- a/server/app.js +++ b/server/app.js @@ -200,13 +200,16 @@ app.get('/user/:username', async (req, res, next)=>{ }); if(ownAccount && req?.account?.googleId){ - const googleBrews = await GoogleActions.listGoogleBrews(req, res) - .catch((err)=>{ - console.error(err); - }); + const auth = await GoogleActions.authCheck(req.account, res); + let googleBrews = await GoogleActions.listGoogleBrews(auth) + .catch((err)=>{ + console.error(err); + }); - if(googleBrews) + if(googleBrews) { + googleBrews = googleBrews.map((brew)=>({ ...brew, authors: [req.account.username] })); brews = _.concat(brews, googleBrews); + } } req.brews = _.map(brews, (brew)=>{ diff --git a/server/googleActions.js b/server/googleActions.js index 29152198b..5a7bfca34 100644 --- a/server/googleActions.js +++ b/server/googleActions.js @@ -12,10 +12,7 @@ let serviceAuth; try { serviceAuth = google.auth.fromJSON(keys); serviceAuth.scopes = [ - 'https://www.googleapis.com/auth/drive', - 'https://www.googleapis.com/auth/drive.appdata', - 'https://www.googleapis.com/auth/drive.file', - 'https://www.googleapis.com/auth/drive.metadata' + 'https://www.googleapis.com/auth/drive' ]; } catch (err) { console.warn(err); @@ -94,15 +91,8 @@ const GoogleActions = { return folderId; }, - listGoogleBrews : async (req, res)=>{ - - const oAuth2Client = GoogleActions.authCheck(req.account, res); - - //TODO: Change to service account to allow non-owners to view published files. - // Requires a driveId parameter in the drive.files.list command - // Then remove the `auth` parameter from the drive object initialization - - const drive = google.drive({ version: 'v3', auth: oAuth2Client }); + listGoogleBrews : async (auth)=>{ + const drive = google.drive({ version: 'v3', auth }); const obj = await drive.files.list({ pageSize : 1000, @@ -110,18 +100,18 @@ const GoogleActions = { q : 'mimeType != \'application/vnd.google-apps.folder\' and trashed = false' }) .catch((err)=>{ - console.log(`Error Listing Google Brews`); + console.log(`Error Listing Google Brews`); console.error(err); throw (err); //TODO: Should break out here, but continues on for some reason. - }); + }); if(!obj.data.files.length) { - console.log('No files found.'); - } + console.log('No files found.'); + } const brews = obj.data.files.map((file)=>{ - return { + return { text : '', shareId : file.properties.shareId, editId : file.properties.editId, @@ -135,59 +125,41 @@ const GoogleActions = { views : parseInt(file.properties.views), tags : '', published : file.properties.published ? file.properties.published == 'true' : false, - authors : [req.account.username], //TODO: properly save and load authors to google drive systems : [] }; }); return brews; }, - existsGoogleBrew : async (id)=>{ - const drive = google.drive({ version: 'v3' }); - - const result = await drive.files.get({ fileId: id }) - .catch((err)=>{ - console.log('error checking file exists...'); - console.error(err); - return false; - }); - - if(result){return true;} - - return false; - }, - updateGoogleBrew : async (brew)=>{ const drive = google.drive({ version: 'v3' }); - if(await GoogleActions.existsGoogleBrew(brew.googleId) == true) { - await drive.files.update({ - fileId : brew.googleId, - resource : { - name : `${brew.title}.txt`, - description : `${brew.description}`, - properties : { - title : brew.title, - published : brew.published, - version : brew.version, - renderer : brew.renderer, - tags : brew.tags, - pageCount : brew.pageCount, - systems : brew.systems.join() - } - }, - media : { - mimeType : 'text/plain', - body : brew.text + await drive.files.update({ + fileId : brew.googleId, + resource : { + name : `${brew.title}.txt`, + description : `${brew.description}`, + properties : { + title : brew.title, + published : brew.published, + version : brew.version, + renderer : brew.renderer, + tags : brew.tags, + pageCount : brew.pageCount, + systems : brew.systems.join() } - }) - .catch((err)=>{ - console.log('Error saving to google'); - console.error(err); - throw (err); - //return res.status(500).send('Error while saving'); - }); - } + }, + media : { + mimeType : 'text/plain', + body : brew.text + } + }) + .catch((err)=>{ + console.log('Error saving to google'); + console.error(err); + throw (err); + //return res.status(500).send('Error while saving'); + }); return (brew); }, @@ -322,9 +294,8 @@ const GoogleActions = { } }, - deleteGoogleBrew : async (req, res, id)=>{ - const oAuth2Client = GoogleActions.authCheck(req.account, res); - const drive = google.drive({ version: 'v3', auth: oAuth2Client }); + deleteGoogleBrew : async (auth, id)=>{ + const drive = google.drive({ version: 'v3', auth }); const googleId = id.slice(0, -12); const accessId = id.slice(-12); @@ -336,7 +307,6 @@ const GoogleActions = { .catch((err)=>{ console.log('Error loading from Google'); console.error(err); - return; }); if(obj && obj.data.properties.editId != accessId) { @@ -351,8 +321,6 @@ const GoogleActions = { console.log('Can\'t delete Google file'); console.error(err); }); - - return res.status(200).send(); }, increaseView : async (id, accessId, accessType, brew)=>{ diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 745a02ec0..747c2360c 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -185,6 +185,10 @@ router.put('/api/update/:id', updateBrew); router.put('/api/updateGoogle/:id', updateGoogleBrew); router.delete('/api/:id', deleteBrew); router.get('/api/remove/:id', deleteBrew); -router.get('/api/removeGoogle/:id', (req, res)=>{GoogleActions.deleteGoogleBrew(req, res, req.params.id);}); +router.get('/api/removeGoogle/:id', async (req, res)=>{ + const auth = await GoogleActions.authCheck(req.account, res); + await GoogleActions.deleteGoogleBrew(auth, req.params.id); + return res.status(200).send(); +}); module.exports = router; From 7cc7bd47865e7c94e6618371c97ab63e82001f4c Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Wed, 16 Feb 2022 21:51:14 -0600 Subject: [PATCH 3/4] update readFileMetadata to be getGoogleBrew --- server/app.js | 2 +- server/googleActions.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/app.js b/server/app.js index 637dfbf64..de51c8882 100644 --- a/server/app.js +++ b/server/app.js @@ -25,7 +25,7 @@ const getBrewFromId = asyncHandler(async (id, accessType)=>{ if(id.length > 12) { const googleId = id.slice(0, -12); id = id.slice(-12); - brew = await GoogleActions.readFileMetadata(googleId, id, accessType); + brew = await GoogleActions.getGoogleBrew(googleId, id, accessType); } else { brew = await HomebrewModel.get(accessType == 'edit' ? { editId: id } : { shareId: id }); brew = brew.toObject(); // Convert MongoDB object to standard Javascript Object diff --git a/server/googleActions.js b/server/googleActions.js index 5a7bfca34..f713d54b6 100644 --- a/server/googleActions.js +++ b/server/googleActions.js @@ -233,7 +233,7 @@ const GoogleActions = { return newHomebrew; }, - readFileMetadata : async (id, accessId, accessType)=>{ + getGoogleBrew : async (id, accessId, accessType)=>{ const drive = google.drive({ version: 'v3' }); const obj = await drive.files.get({ From 8ea2780a4421182af2cf2007658781c35c4aa214 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Wed, 16 Feb 2022 22:03:01 -0600 Subject: [PATCH 4/4] add log line to explain what to do when the service account cannot be found --- server/googleActions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/googleActions.js b/server/googleActions.js index f713d54b6..6184b2aa6 100644 --- a/server/googleActions.js +++ b/server/googleActions.js @@ -16,6 +16,7 @@ try { ]; } catch (err) { console.warn(err); + console.log('Please make sure that a Google Service Account is set up properly in your config files.'); } google.options({ auth: serviceAuth || config.get('google_api_key') });