Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
112 changes: 53 additions & 59 deletions src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<ClaspToken> {
// Gets the OAuth settings. May be local or global.
const rc: ClaspToken = await getOAuthSettings(local);
export async function loadAPICredentials(): Promise<ClaspToken> {
// Gets the OAuth settings.
// loads both
const rcLocalPromise: Promise<ClaspToken | null> = getOAuthSettings(true);
const rcGlobalPromise: Promise<ClaspToken | null> = 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;
}

Expand All @@ -202,7 +193,8 @@ export async function loadAPICredentials(local = false): Promise<ClaspToken> {
*/
async function authorizeWithLocalhost(
oAuth2ClientOptions: OAuth2ClientOptions,
oAuth2ClientAuthUrlOpts: GenerateAuthUrlOpts): Promise<Credentials> {
oAuth2ClientAuthUrlOpts: GenerateAuthUrlOpts,
): Promise<Credentials> {
// 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<http.Server>((resolve, _) => {
Expand Down Expand Up @@ -241,7 +233,8 @@ async function authorizeWithLocalhost(
*/
async function authorizeWithoutLocalhost(
oAuth2ClientOptions: OAuth2ClientOptions,
oAuth2ClientAuthUrlOpts: GenerateAuthUrlOpts): Promise<Credentials> {
oAuth2ClientAuthUrlOpts: GenerateAuthUrlOpts,
): Promise<Credentials> {
const client = new OAuth2Client({
...oAuth2ClientOptions,
redirectUri: REDIRECT_URI_OOB,
Expand Down Expand Up @@ -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);
}
Expand Down
3 changes: 1 addition & 2 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
13 changes: 5 additions & 8 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ClaspToken>} A promise to get the rc file as object.
* @returns {Promise<ClaspToken | null>} A promise to get the rc file as object.
*/
export function getOAuthSettings(local: boolean): Promise<ClaspToken> {
const RC = (local) ? DOTFILE.RC_LOCAL() : DOTFILE.RC;
export function getOAuthSettings(local: boolean): Promise<ClaspToken | null> {
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
Expand Down Expand Up @@ -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 <file.json>`,
Expand Down