Skip to content

Commit

Permalink
Fix oauth2client redirectUri usage (google#177)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Anthony Lai authored and grant committed May 21, 2018
1 parent 4f09743 commit dc72d5d
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 90 deletions.
36 changes: 1 addition & 35 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<string> = 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.
Expand Down
133 changes: 78 additions & 55 deletions src/auth.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<string> {
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<http.Server>((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<string>((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<string> {
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<string>((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();
});
});
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);
}
}

0 comments on commit dc72d5d

Please sign in to comment.