Skip to content

Commit

Permalink
Add headless oauth support (#28)
Browse files Browse the repository at this point in the history
* Fix license ID

* Skip fetch on create if manifest already exists

* Add headless auth support

* Really resolve merge conflicts...

* Minor style cleanup
  • Loading branch information
sqrrrl authored and grant committed Jan 27, 2018
1 parent bbf93d7 commit 41794a1
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 37 deletions.
138 changes: 101 additions & 37 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const recursive = require('recursive-readdir');
const Spinner = require('cli-spinner').Spinner;
const splitLines = require('split-lines');
const url = require('url');
const readline = require('readline');
require('http-shutdown').extend();

// Debug
const DEBUG = false;
Expand Down Expand Up @@ -102,12 +104,11 @@ const DOTFILE = {

// API settings
// @see https://developers.google.com/oauthplayground/
const REDIRECT_PORT = 2020;
const REDIRECT_URI_OOB = 'urn:ietf:wg:oauth:2.0:oob';
const oauth2Client = new OAuth2(
'1072944905499-vm2v2i5dvn0a0d2o4ca36i1vge8cvbn0.apps.googleusercontent.com', // CLIENT_ID
'v6V3fKV_zWU7iw1DrpO1rknX', // CLIENT_SECRET
'http://localhost:' + REDIRECT_PORT
// 'urn:ietf:wg:oauth:2.0:oob' // REDIRECT_URI (@see OAuth2InstalledApp)
'1072944905499-vm2v2i5dvn0a0d2o4ca36i1vge8cvbn0.apps.googleusercontent.com', // CLIENT_ID
'v6V3fKV_zWU7iw1DrpO1rknX', // CLIENT_SECRET
'http://localhost'
);
const script = google.script({
version: 'v1',
Expand Down Expand Up @@ -249,6 +250,96 @@ function getAPICredentials(cb) {
});
}

/**
* Requests authorization to manage Apps Scrpit 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) {
let opts = {
access_type: 'offline',
scope: [
'https://www.googleapis.com/auth/script.deployments',
'https://www.googleapis.com/auth/script.projects',
],
}
let authCode = useLocalhost ?
authorizeWithLocalhost(opts) :
authorizeWithoutLocalhost(opts);

authCode.then(code => {
return new Promise((res, rej) => {
oauth2Client.getToken(code, (err, token) => {
if (err) return rej(err);
return res(token);
})
})
})
.then(token => DOTFILE.RC.write(token))
.then(() => console.log(LOG.AUTH_SUCCESSFUL))
.catch(err => console.error(ERROR.ACCESS_TOKEN + err));
}

/**
* Requests authorization to manage Apps Scrpit projects. Spins up
* a temporary HTTP server to handle the auth redirect.
*
* @param {Object} opts OAuth2 options
* @return {Promise} Promise resolving with the authorization code
*/
function authorizeWithLocalhost(opts) {
return new Promise((res, rej) => {
let server = http.createServer(function(req, resp) {
let urlParts = url.parse(req.url, true);
let code = urlParts.query.code;
if (urlParts.query.code) {
res(urlParts.query.code);
} else {
rej(urlParts.query.error);
}
resp.end(LOG.AUTH_PAGE_SUCCESSFUL);
server.shutdown();
}).withShutdown();

server.listen(0, () => {
oauth2Client._redirectUri = 'http://localhost:' + server.address().port;
let authUrl = oauth2Client.generateAuthUrl(opts);
console.log(LOG.AUTHORIZE(authUrl));
open(authUrl);
});
});
}

/**
* Requests authorization to manage Apps Scrpit 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
*/

function authorizeWithoutLocalhost(opts) {
oauth2Client._redirectUri = REDIRECT_URI_OOB;
let authUrl = oauth2Client.generateAuthUrl(opts);
console.log(LOG.AUTHORIZE(authUrl));

return new Promise((res, rej) => {
let rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question(LOG.AUTH_CODE, (code) => {
if (code && code.length) {
res(code);
} else {
rej("No authorization code entered.");
}
rl.close();
});
});
}

/**
* Gets the local file type from the API FileType.
* @param {string} type The file type returned by Apps Script
Expand Down Expand Up @@ -306,41 +397,14 @@ program
program
.command('login')
.description('Log in to script.google.com')
.action(() => {
.option('--no-localhost', 'Do not run a local server, manually enter code instead')
.action((cmd) => {
// Try to read the RC file.
DOTFILE.RC.read().then((rc) => {
console.warn(ERROR.LOGGED_IN);
}).catch((err) => {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: [
'https://www.googleapis.com/auth/script.deployments',
'https://www.googleapis.com/auth/script.projects',
],
});
console.log(LOG.AUTHORIZE(authUrl));
open(authUrl);

// Create a local HTTP server that reads the OAuth token
var app = connect();
app.use(function (req, res) {
var url_parts = url.parse(req.url, true);
var code = url_parts.query.code;
if (url_parts.query.code) {
oauth2Client.getToken(code, (err, token) => {
if (err) return console.error(ERROR.ACCESS_TOKEN + err);
DOTFILE.RC.write(token).then(() => {
// Kill the CLI after DOTFILE write
console.log(LOG.AUTH_SUCCESSFUL);
process.exit(0);
});
});
}
res.end(LOG.AUTH_PAGE_SUCCESSFUL);
});
http.createServer(app)
.listen(REDIRECT_PORT);
});
authorize(cmd.localhost);
})
});

/**
Expand Down Expand Up @@ -378,7 +442,7 @@ program
if (error) {
logError(error, ERROR.CREATE);
} else {
var scriptId = res.scriptId;
let scriptId = res.scriptId;
console.log(LOG.CREATE_PROJECT_FINISH(scriptId));
saveProjectId(scriptId)
if (!manifestExists()) {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"find-parent-dir": "^0.3.0",
"fs": "^0.0.1-security",
"googleapis": "^24.0.0",
"http-shutdown": "^1.2.0",
"mkdirp": "^0.5.1",
"open": "^0.0.5",
"path": "^0.12.7",
Expand Down

0 comments on commit 41794a1

Please sign in to comment.