diff --git a/package.json b/package.json index a7da69e6..8a2bf703 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "lint": "tslint --project tslint.json && echo 'No lint errors. All good!'", "test": "nyc --cache false mocha --timeout 100000 -- tests/*.js", "coverage": "nyc --cache false report --reporter=text-lcov | coveralls", - "prettier": "./node_modules/prettier/bin-prettier.js --parser typescript --single-quote --bracket-spacing --print-width 110 --trailing-comma all src/**/*.ts --write" + "prettier": "./node_modules/.bin/prettier --parser typescript --single-quote --bracket-spacing --print-width 110 --trailing-comma all src/**/*.ts --write" }, "bin": { "clasp": "./src/index.js" diff --git a/src/auth.ts b/src/auth.ts index aa127f6b..086e9743 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -14,33 +14,6 @@ import { ClaspCredentials, ERROR, LOG, URL, checkIfOnline, getOAuthSettings, log import open = require('opn'); import readline = require('readline'); -// Auth is complicated. Consider yourself warned. -// tslint:disable:max-line-length -// GLOBAL: clasp login will store this (~/.clasprc.json): -// { -// "access_token": "XXX", -// "refresh_token": "1/k4rt_hgxbeGdaRag2TSVgnXgUrWcXwerPpvlzGG1peHVfzI58EZH0P25c7ykiRYd", -// "scope": "https://www.googleapis.com/auth/script.projects https://www.googleapis.com/auth/script ...", -// "token_type": "Bearer", -// "expiry_date": 1539130731398 -// } -// LOCAL: clasp login will store this (./.clasprc.json): -// { -// "token": { -// "access_token": "XXX", -// "refresh_token": "1/k4rw_hgxbeGdaRag2TSVgnXgUrWcXwerPpvlzGG1peHVfzI58EZH0P25c7ykiRYd", -// "scope": "https://www.googleapis.com/auth/script.projects https://www.googleapis.com/auth/script ...", -// "token_type": "Bearer", -// "expiry_date": 1539130731398 -// }, -// // Settings -// "oauth2ClientSettings": { -// "clientId": "807925367021-infvb16rd7lasqi22q2npeahkeodfrq5.apps.googleusercontent.com", -// "clientSecret": "9dbdeOCRHUyriewCoDrLHtPg", -// "redirectUri": "http://localhost" -// }, -// "isLocalCreds": true -// } // API settings // @see https://developers.google.com/oauthplayground/ const REDIRECT_URI_OOB = 'urn:ietf:wg:oauth:2.0:oob'; @@ -106,21 +79,22 @@ export async function authorize(options: { } // Set scopes - let scope = (options.creds) ? - // Set scopes to custom scopes - options.scopes : [ - // Default to clasp scopes - 'https://www.googleapis.com/auth/script.deployments', // Apps Script deployments - 'https://www.googleapis.com/auth/script.projects', // Apps Script management - 'https://www.googleapis.com/auth/script.webapp.deploy', // Apps Script Web Apps - 'https://www.googleapis.com/auth/drive.metadata.readonly', // Drive metadata - 'https://www.googleapis.com/auth/drive.file', // Create Drive files - 'https://www.googleapis.com/auth/service.management', // Cloud Project Service Management API - 'https://www.googleapis.com/auth/logging.read', // StackDriver logs + let scope = options.creds + ? // Set scopes to custom scopes + options.scopes + : [ + // Default to clasp scopes + 'https://www.googleapis.com/auth/script.deployments', // Apps Script deployments + 'https://www.googleapis.com/auth/script.projects', // Apps Script management + 'https://www.googleapis.com/auth/script.webapp.deploy', // Apps Script Web Apps + 'https://www.googleapis.com/auth/drive.metadata.readonly', // Drive metadata + 'https://www.googleapis.com/auth/drive.file', // Create Drive files + 'https://www.googleapis.com/auth/service.management', // Cloud Project Service Management API + 'https://www.googleapis.com/auth/logging.read', // StackDriver logs - // Extra scope since service.management doesn't work alone - 'https://www.googleapis.com/auth/cloud-platform', - ]; + // Extra scope since service.management doesn't work alone + 'https://www.googleapis.com/auth/cloud-platform', + ]; if (options.creds && scope.length === 0) { scope = [ // Default to clasp scopes @@ -186,10 +160,27 @@ export async function authorize(options: { * Loads the Apps Script API credentials for the CLI. * Required before every API call. */ -export async function loadAPICredentials(local = false): Promise { - // Gets the OAuth settings. May be local or global. - const rc: ClaspToken = await getOAuthSettings(local); +export async function loadAPICredentials(): Promise { + // Gets the OAuth settings. + // loads both + const rcLocalPromise: Promise = getOAuthSettings(true); + const rcGlobalPromise: Promise = getOAuthSettings(false); + + // wait the promises + const [rcLocal, rcGlobal] = await Promise.all([rcLocalPromise, rcGlobalPromise]); + + const rc: ClaspToken | null = rcGlobal + ? // override the global with local settings + Object.assign(rcGlobal, rcLocal) + : rcLocal; + + if (!rc) { + logError(null, ERROR.NO_CREDENTIALS); + throw new Error('Never reaches here.'); + } + await setOauthClientCredentials(rc); + return rc; } @@ -202,7 +193,8 @@ export async function loadAPICredentials(local = false): Promise { */ async function authorizeWithLocalhost( oAuth2ClientOptions: OAuth2ClientOptions, - oAuth2ClientAuthUrlOpts: GenerateAuthUrlOpts): Promise { + oAuth2ClientAuthUrlOpts: GenerateAuthUrlOpts, +): Promise { // Wait until the server is listening, otherwise we don't have // the server port needed to set up the Oauth2Client. const server = await new Promise((resolve, _) => { @@ -241,7 +233,8 @@ async function authorizeWithLocalhost( */ async function authorizeWithoutLocalhost( oAuth2ClientOptions: OAuth2ClientOptions, - oAuth2ClientAuthUrlOpts: GenerateAuthUrlOpts): Promise { + oAuth2ClientAuthUrlOpts: GenerateAuthUrlOpts, +): Promise { const client = new OAuth2Client({ ...oAuth2ClientOptions, redirectUri: REDIRECT_URI_OOB, @@ -286,21 +279,22 @@ async function setOauthClientCredentials(rc: ClaspToken) { // Set credentials and refresh them. try { await checkIfOnline(); - if (rc.isLocalCreds) { - localOAuth2Client = new OAuth2Client({ - clientId: rc.oauth2ClientSettings.clientId, - clientSecret: rc.oauth2ClientSettings.clientSecret, - redirectUri: rc.oauth2ClientSettings.redirectUri, - }); - localOAuth2Client.setCredentials(rc.token); - await refreshCredentials(localOAuth2Client); - } - // Always use the global credentials too for non-run functions. - globalOAuth2Client.setCredentials(rc.token); - await refreshCredentials(globalOAuth2Client); + + const oAuth2Client = rc.isLocalCreds + ? new OAuth2Client({ + clientId: rc.oauth2ClientSettings.clientId, + clientSecret: rc.oauth2ClientSettings.clientSecret, + redirectUri: rc.oauth2ClientSettings.redirectUri, + }) + : globalOAuth2Client; + + oAuth2Client.setCredentials(rc.token); + await refreshCredentials(oAuth2Client); // Save the credentials. - await (rc.isLocalCreds ? DOTFILE.RC_LOCAL() : DOTFILE.RC).write(rc); + const dotFileRc = rc.isLocalCreds ? DOTFILE.RC_LOCAL() : DOTFILE.RC; + + await dotFileRc.write(rc); } catch (err) { logError(null, ERROR.ACCESS_TOKEN + err); } diff --git a/src/commands.ts b/src/commands.ts index ecf001e7..4fd699b9 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -617,8 +617,7 @@ export const run = async (functionName: string, cmd: { nondev: boolean; params: */ async function runFunction(functionName: string, params: any[]) { try { - // Load local credentials. - await loadAPICredentials(true); + await loadAPICredentials(); const localScript = await getLocalScript(); spinner.setSpinnerTitle(`Running function: ${functionName}`).start(); const res = await localScript.scripts.run({ diff --git a/src/utils.ts b/src/utils.ts index 8a0229a3..72585114 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -46,15 +46,13 @@ export const hasOauthClientSettings = (local = false): boolean => * Gets the OAuth client settings from rc file. * @param {boolean} local If true, gets the local OAuth settings. Global otherwise. * Should be used instead of `DOTFILE.RC?().read()` - * @returns {Promise} A promise to get the rc file as object. + * @returns {Promise} A promise to get the rc file as object. */ -export function getOAuthSettings(local: boolean): Promise { - const RC = (local) ? DOTFILE.RC_LOCAL() : DOTFILE.RC; +export function getOAuthSettings(local: boolean): Promise { + const RC = local ? DOTFILE.RC_LOCAL() : DOTFILE.RC; return RC.read() .then((rc: ClaspToken) => rc) - .catch((err: any) => { - logError(err, ERROR.NO_CREDENTIALS(local)); - }); + .catch((_: any) => null); } // Helpers to get Apps Script project URLs @@ -96,8 +94,7 @@ Forgot ${PROJECT_NAME} commands? Get help:\n ${PROJECT_NAME} --help`, LOGS_UNAVAILABLE: 'StackDriver logs are getting ready, try again soon.', NO_API: (enable: boolean, api: string) => `API ${api} doesn\'t exist. Try \'clasp apis ${enable ? 'enable' : 'disable'} sheets\'.`, - NO_CREDENTIALS: (local:boolean) => `Could not read API credentials. ` + - `Are you logged in ${local ? 'locall' : 'globall'}y?`, + NO_CREDENTIALS: 'Could not read API credentials. Are you logged in?', NO_FUNCTION_NAME: 'N/A', NO_GCLOUD_PROJECT: `No projectId found in your ${DOT.PROJECT.PATH} file.`, NO_LOCAL_CREDENTIALS: `Requires local crendetials:\n\n ${PROJECT_NAME} login --creds `,