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

Add support for requiring 2fa on a per-package basis #175

Merged
merged 1 commit into from
Jul 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lib/access.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ subcommands.public = function (uri, params, cb) {
subcommands.restricted = function (uri, params, cb) {
return setAccess.call(this, 'restricted', uri, params, cb)
}
subcommands['2fa-required'] = function (uri, params, cb) {
return setRequires2fa.call(this, true, uri, params, cb)
}
subcommands['2fa-not-required'] = function (uri, params, cb) {
return setRequires2fa.call(this, false, uri, params, cb)
}

function setAccess (access, uri, params, cb) {
return this.request(apiUri(uri, 'package', params.package, 'access'), {
Expand All @@ -25,6 +31,14 @@ function setAccess (access, uri, params, cb) {
}, cb)
}

function setRequires2fa (requires2fa, uri, params, cb) {
return this.request(apiUri(uri, 'package', params.package, 'access'), {
method: 'POST',
auth: params.auth,
body: JSON.stringify({ publish_requires_tfa: requires2fa })
}, cb)
}

subcommands.grant = function (uri, params, cb) {
var reqUri = apiUri(uri, 'team', params.scope, params.team, 'package')
return this.request(reqUri, {
Expand Down
35 changes: 24 additions & 11 deletions lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,13 +289,26 @@ function requestDone (method, where, cb) {
}

if (!parsed.error) {
er = makeError(
'Registry returned ' + response.statusCode +
' for ' + method +
' on ' + where,
name,
response.statusCode
)
if (response.statusCode === 401 && response.headers['www-authenticate']) {
const auth = response.headers['www-authenticate'].split(/,\s*/).map(s => s.toLowerCase())
if (auth.indexOf('ipaddress') !== -1) {
er = makeError('Login is not allowed from your IP address', name, response.statusCode, 'EAUTHIP')
} else if (auth.indexOf('otp') !== -1) {
er = makeError('OTP required for this operation', name, response.statusCode, 'EOTP')
} else {
er = makeError('Unable to authenticate, need: ' + response.headers['www-authenticate'], name, response.statusCode, 'EAUTHUNKNOWN')
}
} else {
const msg = parsed.message ? ': ' + parsed.message : ''
er = makeError(
'Registry returned ' + response.statusCode +
' for ' + method +
' on ' + where +
msg,
name,
response.statusCode
)
}
} else if (name && parsed.error === 'not_found') {
er = makeError('404 Not Found: ' + name, name, response.statusCode)
} else if (name && parsed.error === 'User not found') {
Expand All @@ -312,12 +325,12 @@ function requestDone (method, where, cb) {
}.bind(this)
}

function makeError (message, name, code) {
function makeError (message, name, statusCode, code) {
var er = new Error(message)
if (name) er.pkgid = name
if (code) {
er.statusCode = code
er.code = 'E' + code
if (statusCode) {
er.statusCode = statusCode
er.code = code || 'E' + statusCode
}
return er
}
36 changes: 35 additions & 1 deletion test/access.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var UNSCOPED = {
}

var commands = [
'public', 'restricted', 'grant', 'revoke', 'ls-packages', 'ls-collaborators'
'public', 'restricted', 'grant', 'revoke', 'ls-packages', 'ls-collaborators', '2fa-required', '2fa-not-required'
]

test('access public', function (t) {
Expand All @@ -44,6 +44,40 @@ test('access public', function (t) {
})
})

test('access 2fa-required', function (t) {
server.expect('POST', '/-/package/%40foo%2Fbar/access', function (req, res) {
t.equal(req.method, 'POST', 'requested with POST')
onJsonReq(req, function (json) {
t.deepEqual(json, { publish_requires_tfa: true }, 'request payload ok')
res.statusCode = 200
res.json('ok')
})
})
var params = Object.create(PARAMS)
params.package = '@foo/bar'
client.access('2fa-required', URI, params, function (error, data) {
t.ifError(error, 'no errors')
t.end()
})
})

test('access 2fa-not-required', function (t) {
server.expect('POST', '/-/package/%40foo%2Fbar/access', function (req, res) {
t.equal(req.method, 'POST', 'requested with POST')
onJsonReq(req, function (json) {
t.deepEqual(json, { publish_requires_tfa: false }, 'request payload ok')
res.statusCode = 200
res.json('ok')
})
})
var params = Object.create(PARAMS)
params.package = '@foo/bar'
client.access('2fa-not-required', URI, params, function (error, data) {
t.ifError(error, 'no errors')
t.end()
})
})

test('access restricted', function (t) {
server.expect('POST', '/-/package/%40foo%2Fbar/access', function (req, res) {
t.equal(req.method, 'POST')
Expand Down