From 0ce9d5dfef1198a16290db96f6987778df4b86b5 Mon Sep 17 00:00:00 2001 From: Campion Fellin Date: Sun, 24 Jun 2018 22:41:07 -0700 Subject: [PATCH] Change login flow (experimental) (#223) This is the flow (more or less): `clasp login`: logs you in using the default `clasp` credentials. Saves `.clasprc.json` to your `~` directory. `clasp login --creds`: logs you in using the file `credentials.json` in the directory that you are running the command in. Saves `.clasprc.json` to your current directory. `clasp login --creds other_creds.json`: logs you in using the credentials in `other_creds.json`. Saves `.clasprc.json` to your current directory. Your credentials file should look like this: ``` {"installed":{"client_id":"239267426989-275aglft6htcfsdj7t5d3csdlogchamh.apps.googleusercontent.com","project_id":"project-id-xxxxxxxxx","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"xxxxxxxx","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}} ``` Signed-off-by: campionfellin Related to #204 - [x] `npm run test` succeeds. - [x] `npm run lint` succeeds. - [ ] Appropriate changes to README are included in PR. --- index.ts | 6 ++++-- src/auth.ts | 34 ++++++++++++++++++++++++++++------ src/utils.ts | 4 ++++ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/index.ts b/index.ts index 45cd8ed3..e17ad26c 100755 --- a/index.ts +++ b/index.ts @@ -60,14 +60,16 @@ commander * Logs the user in. Saves the client credentials to an rc file. * @name login * @param {string?} [--no-localhost] Do not run a local server, manually enter code instead. - * @param {string?} [--ownkey] Save .clasprc.json file to current working directory. + * @param {string?} [--creds] Relative path to credentials (from GCP). + * @example login (uses default clasp credentials) + * @example login --creds credentials.json (uses your credentials file). * @see test */ commander .command('login') .description('Log in to script.google.com') .option('--no-localhost', 'Do not run a local server, manually enter code instead') - .option('--ownkey', 'Save .clasprc.json file to current working directory') + .option('--creds ', 'Relative path to credentials (from GCP).') .action(login); /** diff --git a/src/auth.ts b/src/auth.ts index 60e707ed..6a1c932b 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -12,6 +12,7 @@ import { Script } from 'googleapis/build/src/apis/script/v1'; import { ClaspSettings, DOTFILE, ERROR, LOG, checkIfOnline, logError } from './utils'; import open = require('open'); import readline = require('readline'); +import * as fs from 'fs'; // API settings // @see https://developers.google.com/oauthplayground/ @@ -32,7 +33,7 @@ const oauth2ClientSettings = { clientSecret: 'v6V3fKV_zWU7iw1DrpO1rknX', redirectUri: 'http://localhost', }; -export const oauth2Client = new OAuth2Client(oauth2ClientSettings); +const oauth2Client = new OAuth2Client(oauth2ClientSettings); // Google API clients export const script = google.script({ @@ -52,11 +53,30 @@ export const drive = google.drive({ * Requests authorization to manage Apps Script projects. * @param {boolean} useLocalhost True if a local HTTP server should be run * to handle the auth response. False if manual entry used. + * @param {string} creds location of credentials file. */ -async function authorize(useLocalhost: boolean, writeToOwnKey: boolean) { +async function authorize(useLocalhost: boolean, creds: string) { + let ownCreds = false; + try { + const credentials = JSON.parse(fs.readFileSync(creds, 'utf8')); + if (credentials && credentials.installed && credentials.installed.client_id + && credentials.installed.client_secret) { + oauth2ClientSettings.clientId = credentials.installed.client_id; + oauth2ClientSettings.clientSecret = credentials.installed.client_secret; + ownCreds = true; + console.log(LOG.CREDENTIALS_FOUND); + } else { + logError(null, ERROR.BAD_CREDENTIALS_FILE); + } + } catch(err) { + if (err.code === 'ENOENT') { + logError(null, ERROR.CREDENTIALS_DNE); + } + console.log(LOG.DEFAULT_CREDENTIALS); + } try { const token = await (useLocalhost ? authorizeWithLocalhost() : authorizeWithoutLocalhost()); - await (writeToOwnKey ? DOTFILE.RC_LOCAL.write(token) : DOTFILE.RC.write(token)); + await (ownCreds ? DOTFILE.RC_LOCAL.write(token) : DOTFILE.RC.write(token)); console.log(LOG.AUTH_SUCCESSFUL); process.exit(0); // gracefully exit after successful login } catch(err) { @@ -141,9 +161,11 @@ async function authorizeWithoutLocalhost() { /** * Logs the user in. Saves the client credentials to an rc file. - * @param options the localhost and ownkey options from commander + * @param {object} options the localhost and creds options from commander. + * @param {boolean} options.localhost authorize without http server. + * @param {string} options.creds location of credentials file. */ -export function login(options: { localhost: boolean, ownkey: boolean}) { +export function login(options: { localhost: boolean, creds: string}) { DOTFILE.RC.read().then((rc: ClaspSettings) => { console.warn(ERROR.LOGGED_IN); }).catch(async (err: string) => { @@ -151,7 +173,7 @@ export function login(options: { localhost: boolean, ownkey: boolean}) { console.warn(ERROR.LOGGED_IN); }).catch(async (err: string) => { await checkIfOnline(); - authorize(options.localhost, options.ownkey); + authorize(options.localhost, options.creds); }); }); } \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 27a6c091..c80d2a4a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -81,10 +81,12 @@ export const DOTFILE = { // Error messages (some errors take required params) export const ERROR = { ACCESS_TOKEN: `Error retrieving access token: `, + BAD_CREDENTIALS_FILE: 'Incorrect credentials file format.', COMMAND_DNE: (command: string) => `🤔 Unknown command "${command}"\n Forgot ${PROJECT_NAME} commands? Get help:\n ${PROJECT_NAME} --help`, CONFLICTING_FILE_EXTENSION: (name: string) => `File names: ${name}.js/${name}.gs conflict. Only keep one.`, CREATE: 'Error creating script.', + CREDENTIALS_DNE: 'Credentials file not found.', DEPLOYMENT_COUNT: `Unable to deploy; Scripts may only have up to 20 versioned deployments at a time.`, FOLDER_EXISTS: `Project file (${DOT.PROJECT.PATH}) already exists.`, FS_DIR_WRITE: 'Could not create directory.', @@ -120,6 +122,8 @@ export const LOG = { CLONING: 'Cloning files...', CREATE_PROJECT_FINISH: (scriptId: string) => `Created new script: ${getScriptURL(scriptId)}`, CREATE_PROJECT_START: (title: string) => `Creating new script: ${title}...`, + CREDENTIALS_FOUND: 'Credentials found, using those to login...', + DEFAULT_CREDENTIALS: 'No credentials given, continuing with default...', DEPLOYMENT_CREATE: 'Creating deployment...', DEPLOYMENT_DNE: 'No deployed versions of script.', DEPLOYMENT_LIST: (scriptId: string) => `Listing deployments...`,