diff --git a/github.js b/github.js index b2cb5ca..c169733 100644 --- a/github.js +++ b/github.js @@ -31,10 +31,11 @@ catch(e) {} var execGit = require('./exec-git'); function createRemoteStrings(auth, hostname) { - var authString = auth ? (encodeURIComponent(auth.username) + ':' + encodeURIComponent(auth.password) + '@') : ''; + var authString = auth.username ? (encodeURIComponent(auth.username) + ':' + encodeURIComponent(auth.password) + '@') : ''; hostname = hostname || 'github.com'; this.remoteString = 'https://' + authString + hostname + '/'; + this.authSuffix = auth.token ? '?access_token=' + auth.token : ''; if (hostname == 'github.com') this.apiRemoteString = 'https://' + authString + 'api.github.com/'; @@ -44,10 +45,6 @@ function createRemoteStrings(auth, hostname) { this.apiRemoteString = 'https://' + authString + hostname + '/api/v3/'; } -// avoid storing passwords as plain text in config -function encodeCredentials(auth) { - return new Buffer(auth.username + ':' + auth.password).toString('base64'); -} function decodeCredentials(str) { var auth = new Buffer(str, 'base64').toString('utf8').split(':'); @@ -80,6 +77,10 @@ function readNetrc(hostname) { } } +function isGithubToken(token) { + return token.match(/[0-9a-f]{40}/); +} + var GithubLocation = function(options, ui) { // ensure git is installed @@ -97,19 +98,15 @@ var GithubLocation = function(options, ui) { this.versionString = options.versionString + '.1'; // Give the environment precedence over options object - if(process.env.JSPM_GITHUB_AUTH_TOKEN) { - options.auth = process.env.JSPM_GITHUB_AUTH_TOKEN; - } else if (options.username && !options.auth) { - options.auth = encodeCredentials(options); - // NB deprecate old auth eventually - // delete options.username; - // delete options.password; - } + var auth = process.env.JSPM_GITHUB_AUTH_TOKEN || options.auth; - if (typeof options.auth == 'string') { - this.auth = decodeCredentials(options.auth); - } - else { + if (auth) { + if (isGithubToken(auth)) { + this.auth = { token: auth }; + } else { + this.auth = decodeCredentials(auth); + } + } else { this.auth = readNetrc(options.hostname); } @@ -147,7 +144,7 @@ var GithubLocation = function(options, ui) { this.remote = options.remote; - createRemoteStrings.call(this, this.auth, options.hostname); + createRemoteStrings.call(this, this.auth || {}, options.hostname); }; function clearDir(dir) { @@ -198,20 +195,14 @@ function configureCredentials(config, ui) { return Promise.resolve() .then(function() { - ui.log('info', 'If using two-factor authentication or to avoid using your password you can generate an access token at %https://' + (config.hostname || 'github.com') + '/settings/tokens%. Ensure it has `public_repo` scope access.'); - return ui.input('Enter your GitHub username'); - }) - .then(function(username) { - auth.username = username; - if (auth.username) - return ui.input('Enter your GitHub password or access token', null, true); + ui.log('info', 'You can generate an access token at %https://' + (config.hostname || 'github.com') + '/settings/tokens%.'); + return ui.input('Enter your GitHub access token'); }) - .then(function(password) { - auth.password = password; - if (!auth.username) - return false; - - return ui.confirm('Would you like to test these credentials?', true); + .then(function(token) { + auth.token = token; + if (auth.token) { + return ui.confirm('Would you like to test these credentials?', true); + } }) .then(function(test) { if (!test) @@ -223,7 +214,7 @@ function configureCredentials(config, ui) { createRemoteStrings.call(remotes, auth, config.hostname); return asp(request)({ - uri: remotes.apiRemoteString + 'user', + uri: remotes.apiRemoteString + 'user' + remotes.authSuffix, headers: { 'User-Agent': 'jspm', 'Accept': 'application/vnd.github.v3+json' @@ -234,7 +225,7 @@ function configureCredentials(config, ui) { }) .then(function(res) { if (res.statusCode == 401) { - ui.log('warn', 'Provided GitHub credentials are not authorized, try re-entering your password or access token.'); + ui.log('warn', 'Provided GitHub credentials are not authorized, try re-entering your access token.'); } else if (res.statusCode != 200) { ui.log('warn', 'Invalid response code, %' + res.statusCode + '%'); @@ -253,10 +244,10 @@ function configureCredentials(config, ui) { .then(function(redo) { if (redo) return configureCredentials(config, ui); - return encodeCredentials(auth); + return auth.token; }); - else if (auth.username) - return encodeCredentials(auth); + else if (auth.token) + return auth.token; else return null; }); @@ -302,6 +293,7 @@ GithubLocation.prototype = { locate: function(repo) { var self = this; var remoteString = this.remoteString; + var authSuffix = this.authSuffix; if (repo.split('/').length !== 2) throw "GitHub packages must be of the form `owner/repo`."; @@ -309,7 +301,7 @@ GithubLocation.prototype = { // request the repo to check that it isn't a redirect return new Promise(function(resolve, reject) { request(extend({ - uri: remoteString + repo, + uri: remoteString + repo + authSuffix, headers: { 'User-Agent': 'jspm' }, @@ -352,8 +344,7 @@ GithubLocation.prototype = { execGit('ls-remote ' + remoteString.replace(/(['"()])/g, '\\\$1') + repo + '.git refs/tags/* refs/heads/*', execOpt, function(err, stdout, stderr) { if (err) { if (err.toString().indexOf('not found') == -1) { - // dont show plain text passwords in error - var error = new Error(stderr.toString().replace(remoteString, '')); + var error = new Error(stderr); error.hideStack = true; error.retriable = true; reject(error); @@ -410,7 +401,7 @@ GithubLocation.prototype = { var ui = this.ui; return asp(request)({ - uri: this.apiRemoteString + 'repos/' + repo + '/contents/package.json', + uri: this.apiRemoteString + 'repos/' + repo + '/contents/package.json' + this.authSuffix, headers: { 'User-Agent': 'jspm', 'Accept': 'application/vnd.github.v3.raw' @@ -546,13 +537,14 @@ GithubLocation.prototype = { var execOpt = this.execOpt; var max_repo_size = this.max_repo_size; var remoteString = this.remoteString; + var authSuffix = this.authSuffix; var self = this; // Download from the git archive return new Promise(function(resolve, reject) { request({ - uri: remoteString + repo + '/archive/' + version + '.tar.gz', + uri: remoteString + repo + '/archive/' + version + '.tar.gz' + authSuffix, headers: { 'accept': 'application/octet-stream' }, strictSSL: self.defaultRequestOptions.strictSSL }) diff --git a/test.js b/test.js index 6ed821a..1dc8c8c 100644 --- a/test.js +++ b/test.js @@ -4,8 +4,8 @@ github = new github({ baseDir: '.', log: true, tmpDir: '.', - username: '', - password: '' + auth: '', + token: '' }); github.lookup('angular/bower-angular')