From b11da48636e748ec1eca79ac0660a7ccd34a2341 Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Tue, 29 Dec 2020 13:45:55 -0800 Subject: [PATCH] version 3.1.15 - oauth support --- .gitignore | 3 +- README.md | 20 +++------ docs/README.md | 19 ++++----- docs/classes/google-spreadsheet.md | 26 +++++++++--- docs/getting-started/authentication.md | 58 +++++++++++++++++++------- docs/getting-started/limitations.md | 21 ---------- docs/index.html | 11 +---- lib/GoogleSpreadsheet.js | 4 +- package.json | 4 +- 9 files changed, 83 insertions(+), 83 deletions(-) delete mode 100644 docs/getting-started/limitations.md diff --git a/.gitignore b/.gitignore index 5c89a66..ee29e8f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules/ ignore/ .env .DS_Store -examples/ \ No newline at end of file +examples/ +TODO \ No newline at end of file diff --git a/README.md b/README.md index a2f2fcf..dbbc47f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Known Vulnerabilities](https://snyk.io/test/github/theoephraim/node-google-spreadsheet/badge.svg?targetFile=package.json)](https://snyk.io/test/github/theoephraim/node-google-spreadsheet?targetFile=package.json) [![NPM](https://img.shields.io/npm/dw/google-spreadsheet)](https://www.npmtrends.com/google-spreadsheet) -- multiple auth options - API key, service account, oauth +- multiple auth options - service account (w/ optional impersonation), OAuth 2.0, API key (read-only) - cell-based API - read, write, bulk-updates, formatting - row-based API - read, update, delete (based on the old v3 row-based calls) - managing worksheets - add, remove, resize, change title, formatting @@ -16,7 +16,7 @@ Full docs available at [https://theoephraim.github.io/node-google-spreadsheet](h > **🚨 Google Deprecation Warning - affects older version (v2) of this module 🚨** > -> Google is [phasing out their old v3 api](https://cloud.google.com/blog/products/g-suite/migrate-your-apps-use-latest-sheets-api), which the older version of this module used to use. Originally they were going to shut it down on March 3rd 2020, but have pushed that date back to January 2021. +> Google is [phasing out their old v3 api](https://cloud.google.com/blog/products/g-suite/migrate-your-apps-use-latest-sheets-api), which the older version of this module used. Originally they were going to shut it down on March 3rd 2020, but have pushed that date back to June 2021. **Regardless, please upgrade to the latest version of this module (v3) which uses the newer sheets v4 API** @@ -28,7 +28,7 @@ Full docs available at [https://theoephraim.github.io/node-google-spreadsheet](h ## Examples _the following examples are meant to give you an idea of just some of the things you can do_ -!> NOTE - To keep the examples more concise, I'm calling await [at the top level](https://v8.dev/features/top-level-await) which is not allowed by default in most versions of node. If you need to call await in a script at the root level, you must instead wrap it in an async function like so: +> **IMPORTANT NOTE** - To keep the examples concise, I'm calling await [at the top level](https://v8.dev/features/top-level-await) which is not allowed by default in most versions of node. If you need to call await in a script at the root level, you must instead wrap it in an async function like so: ```javascript (async function() { @@ -41,22 +41,14 @@ _the following examples are meant to give you an idea of just some of the things ```javascript const { GoogleSpreadsheet } = require('google-spreadsheet'); -// spreadsheet key is the long id in the sheets URL +// Initialize the sheet - doc ID is the long id in the sheets URL const doc = new GoogleSpreadsheet(''); -// use service account creds +// Initialize Auth - see more available options at https://theoephraim.github.io/node-google-spreadsheet/#/getting-started/authentication await doc.useServiceAccountAuth({ client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL, private_key: process.env.GOOGLE_PRIVATE_KEY, }); -// OR load directly from json file if not in secure environment -await doc.useServiceAccountAuth(require('./creds-from-google.json')); -// OR use service account to impersonate a user (see https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority) -await doc.useServiceAccountAuth(require('./creds-from-google.json'), 'some-user@my-domain.com'); -// OR use a pre-configured Google OAuth2Client (check the Authentication docs for more info) -doc.useOAuth2Client(oAuth2Client); -// OR use API key -- only for read-only access to public sheets -doc.useApiKey('YOUR-API-KEY'); await doc.loadInfo(); // loads document properties and worksheets console.log(doc.title); @@ -152,7 +144,7 @@ None yet - get in touch! Contributions are welcome, but please follow the existing conventions, use the linter, add relevant tests, add relevant documentation. -These docs are generated using [docsify](https://docsify.js.org). To preview and run locally so you can make edits, run `npm run docs:preview` and head to http://localhost:3000 +The docs site is generated using [docsify](https://docsify.js.org). To preview and run locally so you can make edits, run `npm run docs:preview` and head to http://localhost:3000 The content lives in markdown files in the docs folder. ## License diff --git a/docs/README.md b/docs/README.md index b5d11e9..e1c2e7b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,6 @@ +_Welcome to the docs site for_ # google-spreadsheet > The most popular [Google Sheets API](https://developers.google.com/sheets/api/reference/rest) wrapper for javascript @@ -9,7 +10,7 @@ [![Known Vulnerabilities](https://snyk.io/test/github/theoephraim/node-google-spreadsheet/badge.svg?targetFile=package.json)](https://snyk.io/test/github/theoephraim/node-google-spreadsheet?targetFile=package.json) [![NPM](https://img.shields.io/npm/dw/google-spreadsheet)](https://www.npmtrends.com/google-spreadsheet) -- multiple auth options - API key, service account, oauth +- multiple auth options - service account (w/ optional impersonation), OAuth 2.0, API key (read-only) - cell-based API - read, write, bulk-updates, formatting - row-based API - read, update, delete (based on the old v3 row-based calls) - managing worksheets - add, remove, resize, change title, formatting @@ -19,7 +20,7 @@ Full docs available at [https://theoephraim.github.io/node-google-spreadsheet](h > **🚨 Google Deprecation Warning - affects older version (v2) of this module 🚨** > -> Google is [phasing out their old v3 api](https://cloud.google.com/blog/products/g-suite/migrate-your-apps-use-latest-sheets-api), which the older version of this module used to use. Originally they were going to shut it down on March 3rd 2020, but have pushed that date back to January 2021. +> Google is [phasing out their old v3 api](https://cloud.google.com/blog/products/g-suite/migrate-your-apps-use-latest-sheets-api), which the older version of this module used. Originally they were going to shut it down on March 3rd 2020, but have pushed that date back to June 2021. **Regardless, please upgrade to the latest version of this module (v3) which uses the newer sheets v4 API** @@ -31,7 +32,7 @@ Full docs available at [https://theoephraim.github.io/node-google-spreadsheet](h ## Examples _the following examples are meant to give you an idea of just some of the things you can do_ -!> NOTE - To keep the examples more concise, I'm calling await [at the top level](https://v8.dev/features/top-level-await) which is not allowed by default in most versions of node. If you need to call await in a script at the root level, you must instead wrap it in an async function like so: +> **IMPORTANT NOTE** - To keep the examples concise, I'm calling await [at the top level](https://v8.dev/features/top-level-await) which is not allowed by default in most versions of node. If you need to call await in a script at the root level, you must instead wrap it in an async function like so: ```javascript (async function() { @@ -44,20 +45,14 @@ _the following examples are meant to give you an idea of just some of the things ```javascript const { GoogleSpreadsheet } = require('google-spreadsheet'); -// spreadsheet key is the long id in the sheets URL +// Initialize the sheet - doc ID is the long id in the sheets URL const doc = new GoogleSpreadsheet(''); -// use service account creds +// Initialize Auth - see more available options at https://theoephraim.github.io/node-google-spreadsheet/#/getting-started/authentication await doc.useServiceAccountAuth({ client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL, private_key: process.env.GOOGLE_PRIVATE_KEY, }); -// OR load directly from json file if not in secure environment -await doc.useServiceAccountAuth(require('./creds-from-google.json')); -// OR use service account to impersonate a user (see https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority) -await doc.useServiceAccountAuth(require('./creds-from-google.json'), 'some-user@my-domain.com'); -// OR use API key -- only for read-only access to public sheets -doc.useApiKey('YOUR-API-KEY'); await doc.loadInfo(); // loads document properties and worksheets console.log(doc.title); @@ -153,7 +148,7 @@ None yet - get in touch! Contributions are welcome, but please follow the existing conventions, use the linter, add relevant tests, add relevant documentation. -These docs are generated using [docsify](https://docsify.js.org). To preview and run locally so you can make edits, run `npm run docs:preview` and head to http://localhost:3000 +The docs site is generated using [docsify](https://docsify.js.org). To preview and run locally so you can make edits, run `npm run docs:preview` and head to http://localhost:3000 The content lives in markdown files in the docs folder. ## License diff --git a/docs/classes/google-spreadsheet.md b/docs/classes/google-spreadsheet.md index 1c0d738..5fbd8b9 100644 --- a/docs/classes/google-spreadsheet.md +++ b/docs/classes/google-spreadsheet.md @@ -91,8 +91,8 @@ Param|Type|Required|Description - ✨ **Side effects** - all requests will now authenticate using these credentials -> See [Getting Started > Authentication](getting-started/authentication) for more details - +> See [Getting Started > Authentication > Service Account](getting-started/authentication#service-account) for more details +http://localhost:3000/#/getting-started/authentication?id=service-account #### `useApiKey(key)` :id=fn-useApiKey > Set API-key to use for auth - only allows read-only access to public docs @@ -103,10 +103,23 @@ Param|Type|Required|Description - ✨ **Side effects** - all requests will now authenticate using this api key only -> See [Getting Started > Authentication](getting-started/authentication) for more details +> See [Getting Started > Authentication > API Key](getting-started/authentication#api-key) for more details + + +#### `useOAuth2Client(oAuth2Client)` :id=fn-useOAuth2Client +> Use [Google's OAuth2Client](https://github.com/googleapis/google-auth-library-nodejs#oauth2) to authenticate on behalf of a user + +Param|Type|Required|Description +---|---|---|--- +`oAuth2Client`|OAuth2Client|✅|Configured OAuth2Client + +- ✨ **Side effects** - requests will use oauth access token to authenticate requests. New access token will be generated if token is expired. + +> See [Getting Started > Authentication > OAuth 2.0](getting-started/authentication#oauth) for more details + #### `useRawAccessToken(token)` :id=fn-useRawAccessToken -> Set token to use for auth - managed elsewhere +> Set raw token to use for auth - managed elsewhere Param|Type|Required|Description ---|---|---|--- @@ -114,7 +127,10 @@ Param|Type|Required|Description - ✨ **Side effects** - all requests will now authenticate using this api key only -!> This assumes you are creating and managing/refreshing the token yourself. Deeper oauth support coming soon... +!> This assumes you are creating and managing/refreshing the token yourself + + + ### Basic info diff --git a/docs/getting-started/authentication.md b/docs/getting-started/authentication.md index 1f06eb1..2da0f2d 100644 --- a/docs/getting-started/authentication.md +++ b/docs/getting-started/authentication.md @@ -7,7 +7,7 @@ You have several options for how you want to connect, but most projects should u - [Service Account](#service-account) - connects as a specific "bot" user generated by google for your application - [API-key](#api-key) - only identifies your application, provides read-only access -- [OAuth](#oauth) - connect on behalf of a specific user +- [OAuth](#oauth) - connect on behalf of a specific user using OAuth **👉 BUT FIRST -- Set up your google project & enable the sheets API 👈** 1. Go to the [Google Developers Console](https://console.developers.google.com/) @@ -25,7 +25,7 @@ This is a 2-legged oauth method and designed to be "an account that belongs to y Use this for an app that needs to access a set of documents that you have full access to, or can at least be shared with your service account. ([read more](https://developers.google.com/identity/protocols/OAuth2ServiceAccount)) -You may also grant your service account ["domain-wide delegation"](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority) which enables it to impersonate any user within your org. This can be helpful if you need to connect on behalf of users only within your organization. +You may also grant your service account ["domain-wide delegation"](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority) which enables it to impersonate any user within your org. This can be helpful if you need to connect on behalf of users _only within your organization_. __Setup Instructions__ @@ -80,7 +80,7 @@ Google requires this so they can at least meter your usage of their API. __Setup Instructions__ 1. Follow steps above to set up project and enable sheets API 2. Create an API key for your project - - In the sidebar on the left, select **Credentials** + - Navigate to the [credentials section of the google developer console](https://console.cloud.google.com/apis/credentials) - Click blue "+ CREATE CREDENITALS" and select "API key" option - Copy the API key 3. OPTIONAL - click "Restrict key" on popup to set up restrictions @@ -98,26 +98,52 @@ doc.useApiKey(process.env.GOOGLE_API_KEY); ## 👨‍💻 OAuth 2.0 :id=oauth **connect on behalf of a user with an Oauth token** -Use [Google's OAuth2Client](https://github.com/googleapis/google-auth-library-nodejs#oauth2) to authenticate. +Use [Google's OAuth2Client](https://github.com/googleapis/google-auth-library-nodejs#oauth2) to authenticate. + +Handling Oauth and how it works is out of scope of this project - but the info you need can be found [here](https://developers.google.com/identity/protocols/oauth2). + +Nevertheless, here is a rough outline of what to do with a few tips: +1. Follow steps above to set up project and enable sheets API +2. Create OAuth 2.0 credentials for your project (**these are not the same as the service account credentials described above**) + - Navigate to the [credentials section of the google developer console](https://console.cloud.google.com/apis/credentials) + - Click blue "+ CREATE CREDENITALS" and select "Oauth Client ID" option + - Select your application type and set up authorized domains / callback URIs + - Record your client ID and secret + - You will need to go through an Oauth Consent screen verification process to use these credentials for a production app with many users +3. For each user you want to connect on behalf of, you must get them to authorize your app which involves asking their permissions by redirecting them to a google-hosted URL + - generate the oauth consent page url and redirect the user to it + - there are many tools, [google provided](https://github.com/googleapis/google-api-nodejs-client#oauth2-client) and [more](https://www.npmjs.com/package/simple-oauth2) [generic](https://www.npmjs.com/package/hellojs) or you can even generate the URL yourself + - make sure you use the credentials generated above + - make sure you include the [appropriate scopes](https://developers.google.com/identity/protocols/oauth2/scopes#sheets) for your application + - the callback URL (if successful) will include a short lived authorization code + - you can then exchange this code for the user's oauth tokens which include: + - an access token (that expires) which can be used to make API requests on behalf of the user, limited to the scopes requested and approved + - a refresh token (that does not expire) which can be used to generate new access tokens + - save these tokens somewhere secure like a database (ideally you should encrypt these before saving!) +4. Initialize an OAuth2Client with your apps oauth credentials and the user's tokens, and pass the client to your GoogleSpreadsheet object + ```javascript -// clientId and clientSecret are required to refresh the access token automatically -const = oAuth2Client = new OAuth2Client({ +const { OAuth2Client } = require('google-auth-library'); + +// Initialize the OAuth2Client with your app's oauth credentials +const oauthClient = new OAuth2Client({ clientId: process.env.GOOGLE_OAUTH_CLIENT_ID, clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET -}) +}); -// Pre-configure the client with credentials you have stored in e.g. your databse -// At a minimum, provide only the refresh_token. The client will use it to retrieve -// a fresh access_token, with a fresh expiration date. -oAuth2Client.credentials.access_token = accessToken; -oAuth2Client.credentials.refresh_token = refreshToken; -oAuth2Client.credentials.expiry_date = expiryDate; // Unix epoch milliseconds +// Pre-configure the client with credentials you have stored in e.g. your database +// NOTE - refresh_token is required, whilt the access token and expiryDate are optional +// (the refresh token is used to generate a missing/expired access token) +const { accessToken, refreshToken, expiryDate } = await fetchUserGoogleCredsFromDatabase(); +oauthClient.credentials.access_token = accessToken; +oauthClient.credentials.refresh_token = refreshToken; +oauthClient.credentials.expiry_date = expiryDate; // Unix epoch milliseconds // Listen in whenever a new access token is obtained. You might want to store them in your database. // Mind that the refresh_token never changes (unless it's revoked, in which case your end-user will -// need to go through the full authentication flow again), so storing the new access_token is optional. -oAuth2Client.on('tokens', credentials => { +// need to go through the full authentication flow again), so storing the new access_token is optional. +oauthClient.on('tokens', credentials => { console.log(credentials.access_token); console.log(credentials.scope); console.log(credentials.expiry_date); @@ -125,5 +151,5 @@ oAuth2Client.on('tokens', credentials => { }) const doc = new GoogleSpreadsheet(''); -doc.useOAuth2Client(oauth2Client); +doc.useOAuth2Client(oauthClient); ``` diff --git a/docs/getting-started/limitations.md b/docs/getting-started/limitations.md deleted file mode 100644 index d39c04f..0000000 --- a/docs/getting-started/limitations.md +++ /dev/null @@ -1,21 +0,0 @@ - -## Google's API Limitations - -Google's API is somewhat limiting. Calls are made to two differently designed APIs, one made to deal with cells, and one to deal with rows. These APIs will let you manage the data in your sheets, but you cannot make any modifications to the formatting of the cells. - -### Row-Based API Limitations - -The row-based API assumes that the "header row" (first row) of your sheet is set. They have limitations on the column names they will accept - all lowercase with no symbols or spaces. If the values in your sheet do not follow their rules, their API will adapt the key it actually returns to you. I recommend just following their rules to avoid confusion. - -You _can_ set a formula value into a cell using the row-based API, but when reading rows, you cannot access the formula, or even be aware that there is one in the cell. Any cells with formulas will return the calculated value of the formula. If you try to update a row, the cell with a formula will be overwritten to its calculated value. - -**IMPORTANT** The row-based API also assumes there are no empty rows in your sheet. If any row is completely empty, you will not be able to access any rows after the empty row using the row-based API. - - -### Limitations - -Google's sheets v3 API supported "row-based feeds" natively and so supported the concept of rows a bit better. - -- If you want to pull rows sorted by a column, you must actually update the sorting in the document -- You must ensure there are no empty rows - an empty row is treated as the end of the data - diff --git a/docs/index.html b/docs/index.html index 5cfac8b..508b463 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,21 +6,12 @@ - +