Skip to content
This repository has been archived by the owner on Aug 11, 2022. It is now read-only.

Commit

Permalink
config: only send token to registry hosts
Browse files Browse the repository at this point in the history
Fixes: #8380
Credit: @othiym23
Reviewed-By: @zkat
  • Loading branch information
othiym23 committed Mar 17, 2016
1 parent df2cc02 commit fea8cc9
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 27 deletions.
8 changes: 5 additions & 3 deletions doc/cli/npm-adduser.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ registries. Can be used with `--registry` and / or `--scope`, e.g.
npm adduser --registry=http://private-registry.example.com --always-auth

This will ensure that all requests to that registry (including for tarballs)
include an authorization header. See `always-auth` in `npm-config(7)` for more
details on always-auth. Registry-specific configuration of `always-auth` takes
precedence over any global configuration.
include an authorization header. This setting may be necessary for use with
private registries where metadata and package tarballs are stored on hosts with
different hostnames. See `always-auth` in `npm-config(7)` for more details on
always-auth. Registry-specific configuration of `always-auth` takes precedence
over any global configuration.

## SEE ALSO

Expand Down
2 changes: 1 addition & 1 deletion lib/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ function add (args, where, cb) {
break
case "remote":
// get auth, if possible
mapToRegistry(spec, npm.config, function (err, uri, auth) {
mapToRegistry(p.raw, npm.config, function (err, uri, auth) {
if (err) return cb(err)

addRemoteTarball(p.spec, {name : p.name}, null, auth, cb)
Expand Down
19 changes: 10 additions & 9 deletions lib/config/get-credentials-by-uri.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,16 @@ function getCredentialsByURI (uri) {
alwaysAuth : undefined
}

if (this.get(nerfed + ":_authToken")) {
c.token = this.get(nerfed + ":_authToken")
// used to override scope matching for tokens as well as legacy auth
if (this.get(nerfed + ':always-auth') !== undefined) {
var val = this.get(nerfed + ':always-auth')
c.alwaysAuth = val === 'false' ? false : !!val
} else if (this.get('always-auth') !== undefined) {
c.alwaysAuth = this.get('always-auth')
}

if (this.get(nerfed + ':_authToken')) {
c.token = this.get(nerfed + ':_authToken')
// the bearer token is enough, don't confuse things
return c
}
Expand Down Expand Up @@ -58,13 +66,6 @@ function getCredentialsByURI (uri) {
c.email = this.get("email")
}

if (this.get(nerfed + ":always-auth") !== undefined) {
var val = this.get(nerfed + ":always-auth")
c.alwaysAuth = val === "false" ? false : !!val
} else if (this.get("always-auth") !== undefined) {
c.alwaysAuth = this.get("always-auth")
}

if (c.username && c.password) {
c.auth = new Buffer(c.username + ":" + c.password).toString("base64")
}
Expand Down
52 changes: 48 additions & 4 deletions lib/utils/map-to-registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,53 @@ function mapToRegistry(name, config, cb) {
var auth = config.getCredentialsByURI(registry)

// normalize registry URL so resolution doesn't drop a piece of registry URL
var normalized = registry.slice(-1) !== "/" ? registry+"/" : registry
var uri = url.resolve(normalized, name)
log.silly("mapToRegistry", "uri", uri)
var normalized = registry.slice(-1) !== '/' ? registry + '/' : registry
var uri
log.silly('mapToRegistry', 'data', data)
if (data.type === 'remote') {
uri = data.spec
} else {
uri = url.resolve(normalized, name)
}

log.silly('mapToRegistry', 'uri', uri)

cb(null, uri, scopeAuth(uri, registry, auth), normalized)
}

function scopeAuth (uri, registry, auth) {
var cleaned = {
scope: auth.scope,
email: auth.email,
alwaysAuth: auth.alwaysAuth,
token: undefined,
username: undefined,
password: undefined,
auth: undefined
}

var requestHost
var registryHost

if (auth.token || auth.auth || (auth.username && auth.password)) {
requestHost = url.parse(uri).hostname
registryHost = url.parse(registry).hostname

if (requestHost === registryHost) {
cleaned.token = auth.token
cleaned.auth = auth.auth
cleaned.username = auth.username
cleaned.password = auth.password
} else if (auth.alwaysAuth) {
log.verbose('scopeAuth', 'alwaysAuth set for', registry)
cleaned.token = auth.token
cleaned.auth = auth.auth
cleaned.username = auth.username
cleaned.password = auth.password
} else {
log.silly('scopeAuth', uri, "doesn't share host with registry", registry)
}
}

cb(null, uri, auth, normalized)
return cleaned
}
119 changes: 119 additions & 0 deletions test/tap/bearer-token-check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
var resolve = require('path').resolve
var writeFileSync = require('graceful-fs').writeFileSync

var mkdirp = require('mkdirp')
var mr = require('npm-registry-mock')
var osenv = require('osenv')
var rimraf = require('rimraf')
var test = require('tap').test

var common = require('../common-tap.js')
var toNerfDart = require('../../lib/config/nerf-dart.js')

var pkg = resolve(__dirname, 'install-bearer-check')
var outfile = resolve(pkg, '_npmrc')
var modules = resolve(pkg, 'node_modules')
var tarballPath = '/scoped-underscore/-/scoped-underscore-1.3.1.tgz'
// needs to be a different hostname to verify tokens (not) being sent correctly
var tarballURL = 'http://lvh.me:' + common.port + tarballPath
var tarball = resolve(__dirname, '../fixtures/scoped-underscore-1.3.1.tgz')

var server

var EXEC_OPTS = { cwd: pkg }

function mocks (server) {
var auth = 'Bearer 0xabad1dea'
server.get(tarballPath, { authorization: auth }).reply(403, {
error: 'token leakage',
reason: 'This token should not be sent.'
})
server.get(tarballPath).replyWithFile(200, tarball)
}

test('setup', function (t) {
mr({ port: common.port, plugin: mocks }, function (er, s) {
server = s
t.ok(s, 'set up mock registry')
setup()
t.end()
})
})

test('authed npm install with tarball not on registry', function (t) {
common.npm(
[
'install',
'--loglevel', 'silent',
'--json',
'--fetch-retries', 0,
'--userconfig', outfile
],
EXEC_OPTS,
function (err, code, stdout, stderr) {
t.ifError(err, 'test runner executed without error')
t.equal(code, 0, 'npm install exited OK')
t.notOk(stderr, 'no output on stderr')
try {
var results = JSON.parse(stdout)
} catch (ex) {
console.error('#', ex)
t.ifError(ex, 'stdout was valid JSON')
}

if (results) {
var installedversion = {
'name': '@scoped/underscore',
'version': '1.3.1',
'from': 'http://lvh.me:1337/scoped-underscore/-/scoped-underscore-1.3.1.tgz',
'dependencies': {}
}
t.isDeeply(results[0], installedversion, '@scoped/underscore installed')
}

t.end()
}
)
})

test('cleanup', function (t) {
server.close()
cleanup()
t.end()
})

var contents = '@scoped:registry=' + common.registry + '\n' +
toNerfDart(common.registry) + ':_authToken=0xabad1dea\n'

var json = {
name: 'test-package-install',
version: '1.0.0'
}

var shrinkwrap = {
name: 'test-package-install',
version: '1.0.0',
dependencies: {
'@scoped/underscore': {
resolved: tarballURL,
from: '>=1.3.1 <2',
version: '1.3.1'
}
}
}

function setup () {
cleanup()
mkdirp.sync(modules)
writeFileSync(resolve(pkg, 'package.json'), JSON.stringify(json, null, 2) + '\n')
writeFileSync(outfile, contents)
writeFileSync(
resolve(pkg, 'npm-shrinkwrap.json'),
JSON.stringify(shrinkwrap, null, 2) + '\n'
)
}

function cleanup () {
process.chdir(osenv.tmpdir())
rimraf.sync(pkg)
}
14 changes: 7 additions & 7 deletions test/tap/config-credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ test("set with token", function (t) {
}, "needs only token")

var expected = {
scope : "//registry.lvh.me:8661/",
token : "simple-token",
username : undefined,
password : undefined,
email : undefined,
auth : undefined,
alwaysAuth : undefined
scope: '//registry.lvh.me:8661/',
token: 'simple-token',
username: undefined,
password: undefined,
email: undefined,
auth: undefined,
alwaysAuth: false
}

t.same(conf.getCredentialsByURI(URI), expected, "got bearer token and scope")
Expand Down
81 changes: 78 additions & 3 deletions test/tap/map-to-registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ test('mapRegistryToURI', function (t) {
password: undefined,
email: undefined,
auth: undefined,
alwaysAuth: undefined
alwaysAuth: false
})
t.equal(registry, 'http://reg.npm/design/-/rewrite/')
})
Expand All @@ -66,7 +66,7 @@ test('mapRegistryToURI', function (t) {
password: undefined,
email: undefined,
auth: undefined,
alwaysAuth: undefined
alwaysAuth: false
})
t.equal(registry, 'http://reg.npm/-/rewrite/')
})
Expand All @@ -84,8 +84,83 @@ test('mapRegistryToURI', function (t) {
password: undefined,
email: undefined,
auth: undefined,
alwaysAuth: undefined
alwaysAuth: false
})
t.equal(registry, 'http://reg.npm/design/-/rewrite/relative/')
})
})

test('mapToRegistry token scoping', function (t) {
npm.config.set('scope', '')
npm.config.set('registry', 'https://reg.npm/')
npm.config.set('//reg.npm/:_authToken', 'r-token')

t.test('pass token to registry host', function (t) {
mapRegistry(
'https://reg.npm/packages/e/easy-1.0.0.tgz',
npm.config,
function (er, uri, auth, registry) {
t.ifError(er, 'mapRegistryToURI worked')
t.equal(uri, 'https://reg.npm/packages/e/easy-1.0.0.tgz')
t.deepEqual(auth, {
scope: '//reg.npm/',
token: 'r-token',
username: undefined,
password: undefined,
email: undefined,
auth: undefined,
alwaysAuth: false
})
t.equal(registry, 'https://reg.npm/')
}
)
t.end()
})

t.test("don't pass token to non-registry host", function (t) {
mapRegistry(
'https://butts.lol/packages/e/easy-1.0.0.tgz',
npm.config,
function (er, uri, auth, registry) {
t.ifError(er, 'mapRegistryToURI worked')
t.equal(uri, 'https://butts.lol/packages/e/easy-1.0.0.tgz')
t.deepEqual(auth, {
scope: '//reg.npm/',
token: undefined,
username: undefined,
password: undefined,
email: undefined,
auth: undefined,
alwaysAuth: false
})
t.equal(registry, 'https://reg.npm/')
}
)
t.end()
})

t.test('pass token to non-registry host with always-auth', function (t) {
npm.config.set('always-auth', true)
mapRegistry(
'https://butts.lol/packages/e/easy-1.0.0.tgz',
npm.config,
function (er, uri, auth, registry) {
t.ifError(er, 'mapRegistryToURI worked')
t.equal(uri, 'https://butts.lol/packages/e/easy-1.0.0.tgz')
t.deepEqual(auth, {
scope: '//reg.npm/',
token: 'r-token',
username: undefined,
password: undefined,
email: undefined,
auth: undefined,
alwaysAuth: true
})
t.equal(registry, 'https://reg.npm/')
}
)
t.end()
})

t.end()
})

0 comments on commit fea8cc9

Please sign in to comment.