From dc72d5dd4f167da2a463d266cb5cad00f785f8c5 Mon Sep 17 00:00:00 2001 From: Anthony Lai Date: Mon, 21 May 2018 09:22:11 -0700 Subject: [PATCH] Fix oauth2client redirectUri usage (#177) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes the oauth2client usage warnings ( ``` src/auth.ts(63,22): error TS2341: Property 'redirectUri' is private and only accessible within class 'OAuth2Client'. src/auth.ts(80,18): error TS2341: Property 'redirectUri' is private and only accessible within class 'OAuth2Client'. ``` ) by refactoring authorize functions and moves authorize into the auth module. I've tested this change with the flows of clasp login / clasp login --no-localhost / clasp login --ownkey With this, we'll be down to 0 build warnings! 🎉 - [x] `npm run test` succeeds. - [x] `npm run lint` succeeds. - [x] Appropriate changes to README are included in PR. --- index.ts | 36 +------------- src/auth.ts | 133 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 79 insertions(+), 90 deletions(-) diff --git a/index.ts b/index.ts index 7d97e012..95af81b6 100755 --- a/index.ts +++ b/index.ts @@ -39,8 +39,7 @@ import { DOT, PROJECT_NAME, PROJECT_MANIFEST_BASENAME, ClaspSettings, ProjectSettings, DOTFILE, spinner, logError, ERROR, getScriptURL, getProjectSettings, getFileType, getAPIFileType, checkIfOnline, saveProjectId, manifestExists } from './src/utils.js'; -import { oauth2Client, getAPICredentials, - authorizeWithLocalhost, authorizeWithoutLocalhost } from './src/auth.js'; +import { oauth2Client, getAPICredentials, authorize } from './src/auth'; import { LOG } from './src/commands.js'; // An Apps Script API File interface AppsScriptFile { @@ -63,39 +62,6 @@ const script = google.script({ auth: oauth2Client, }); -/** - * 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. - */ -function authorize(useLocalhost: boolean, writeToOwnKey: boolean) { - // const codes = oauth2Client.generateCodeVerifier(); - // See https://developers.google.com/identity/protocols/OAuth2InstalledApp#step1-code-verifier - const options = { - access_type: 'offline', - scope: [ - 'https://www.googleapis.com/auth/script.deployments', - 'https://www.googleapis.com/auth/script.projects', - 'https://www.googleapis.com/auth/drive.metadata.readonly', - 'https://www.googleapis.com/auth/script.webapp.deploy', - ], - // code_challenge_method: 'S256', - // code_challenge: codes.codeChallenge, - }; - const authCode: Promise = useLocalhost ? - authorizeWithLocalhost(options) : - authorizeWithoutLocalhost(options); - authCode.then((code: string) => { - return new Promise((res: Function, rej: Function) => { - oauth2Client.getToken(code).then((token) => res(token.tokens)); - }); - }).then((token: object) => { - writeToOwnKey ? DOTFILE.RC_LOCAL.write(token) : DOTFILE.RC.write(token); - }) - .then(() => console.log(LOG.AUTH_SUCCESSFUL)) - .catch((err: string) => console.error(ERROR.ACCESS_TOKEN + err)); -} - /** * Recursively finds all files that are part of the current project, and those that are ignored * by .claspignore and calls the passed callback function with the file lists. diff --git a/src/auth.ts b/src/auth.ts index ca2a337a..95e58361 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,19 +1,29 @@ import { OAuth2Client } from 'google-auth-library'; -import { ClaspSettings, DOTFILE } from './utils.js'; +import { ClaspSettings, DOTFILE, ERROR } from './utils'; import * as http from 'http'; import * as url from 'url'; -const open = require('open'); -const readline = require('readline'); -import { LOG } from './commands.js'; +import open = require('open'); +import readline = require('readline'); +import { LOG } from './commands'; // API settings // @see https://developers.google.com/oauthplayground/ -export const REDIRECT_URI_OOB = 'urn:ietf:wg:oauth:2.0:oob'; -export const oauth2Client = new OAuth2Client({ +const REDIRECT_URI_OOB = 'urn:ietf:wg:oauth:2.0:oob'; +const oauth2ClientAuthUrlOpts = { + access_type: 'offline', + scope: [ + 'https://www.googleapis.com/auth/script.deployments', + 'https://www.googleapis.com/auth/script.projects', + 'https://www.googleapis.com/auth/drive.metadata.readonly', + 'https://www.googleapis.com/auth/script.webapp.deploy', + ], +}; +const oauth2ClientSettings = { clientId: '1072944905499-vm2v2i5dvn0a0d2o4ca36i1vge8cvbn0.apps.googleusercontent.com', clientSecret: 'v6V3fKV_zWU7iw1DrpO1rknX', redirectUri: 'http://localhost', -}); +}; +export const oauth2Client = new OAuth2Client(oauth2ClientSettings); /** * Loads the Apps Script API credentials for the CLI. @@ -38,61 +48,74 @@ export function getAPICredentials(cb: (rc: ClaspSettings | void) => void) { } /** - * Requests authorization to manage Apps Scrpit projects. Spins up + * Requests authorization to manage Apps Script projects. Spins up * a temporary HTTP server to handle the auth redirect. - * - * @param {Object} opts OAuth2 options TODO formalize options - * @return {Promise} Promise resolving with the authorization code */ -export function authorizeWithLocalhost(opts: any): Promise { - return new Promise((res: Function, rej: Function) => { - const server = http.createServer((req: http.ServerRequest, resp: http.ServerResponse) => { - const urlParts = url.parse(req.url || '', true); - if (urlParts.query.code) { - res(urlParts.query.code); - } else { - rej(urlParts.query.error); - } - resp.end(LOG.AUTH_PAGE_SUCCESSFUL); - setTimeout(() => { // TODO Remove hack to shutdown server. - process.exit(); - }, 1000); - }); - - server.listen(0, () => { - oauth2Client.redirectUri = `http://localhost:${server.address().port}`; - const authUrl = oauth2Client.generateAuthUrl(opts); - console.log(LOG.AUTHORIZE(authUrl)); - open(authUrl); - }); +async function authorizeWithLocalhost() { + // 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, _) => { + const s = http.createServer(); + s.listen(0, () => resolve(s)); + }); + const client = new OAuth2Client({ + ...oauth2ClientSettings, + redirectUri: `http://localhost:${server.address().port}`}); + const authCode = await new Promise((res, rej) => { + server.on('request', (req: http.ServerRequest, resp: http.ServerResponse) => { + const urlParts = url.parse(req.url || '', true); + if (urlParts.query.code) { + res(urlParts.query.code as string); + } else { + rej(urlParts.query.error); + } + resp.end(LOG.AUTH_PAGE_SUCCESSFUL); }); - } + const authUrl = client.generateAuthUrl(oauth2ClientAuthUrlOpts); + console.log(LOG.AUTHORIZE(authUrl)); + open(authUrl); + }); + server.close(); + return (await client.getToken(authCode)).tokens; +} /** * Requests authorization to manage Apps Script projects. Requires the * user to manually copy/paste the authorization code. No HTTP server is * used. - * - * @param {Object} opts OAuth2 options - * @return {Promise} Promise resolving with the authorization code */ -export function authorizeWithoutLocalhost(opts: any): Promise { - oauth2Client.redirectUri = REDIRECT_URI_OOB; - const authUrl = oauth2Client.generateAuthUrl(opts); - console.log(LOG.AUTHORIZE(authUrl)); - - return new Promise((res, rej) => { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - rl.question(LOG.AUTH_CODE, (code: string) => { - if (code && code.length) { - res(code); - } else { - rej("No authorization code entered."); - } - rl.close(); - }); +async function authorizeWithoutLocalhost() { + const client = new OAuth2Client({...oauth2ClientSettings, redirectUri: REDIRECT_URI_OOB}); + const authUrl = client.generateAuthUrl(oauth2ClientAuthUrlOpts); + console.log(LOG.AUTHORIZE(authUrl)); + const authCode = await new Promise((res, rej) => { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, }); - } \ No newline at end of file + rl.question(LOG.AUTH_CODE, (code: string) => { + if (code && code.length) { + res(code); + } else { + rej("No authorization code entered."); + } + rl.close(); + }); + }); + return (await client.getToken(authCode)).tokens; +} + +/** + * 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. + */ +export async function authorize(useLocalhost: boolean, writeToOwnKey: boolean) { + try { + const token = await (useLocalhost ? authorizeWithLocalhost() : authorizeWithoutLocalhost()); + await (writeToOwnKey ? DOTFILE.RC_LOCAL.write(token) : DOTFILE.RC.write(token)); + console.log(LOG.AUTH_SUCCESSFUL); + } catch(err) { + console.error(ERROR.ACCESS_TOKEN + err); + } +}