npm-profile
Advanced tools
Comparing version 2.0.5 to 3.0.0
184
index.js
@@ -5,5 +5,10 @@ 'use strict' | ||
const url = require('url') | ||
const os = require('os') | ||
exports.adduserCouch = adduserCouch | ||
exports.loginCouch = loginCouch | ||
exports.adduserWeb = adduserWeb | ||
exports.loginWeb = loginWeb | ||
exports.login = login | ||
exports.adduser = adduser | ||
exports.login = login | ||
exports.get = get | ||
@@ -15,3 +20,105 @@ exports.set = set | ||
function adduser (username, email, password, conf) { | ||
// try loginWeb, catch the "not supported" message and fall back to couch | ||
function login (opener, prompter, conf) { | ||
validate('FFO', arguments) | ||
return loginWeb(opener, conf).catch(er => { | ||
if (er instanceof WebLoginNotSupported) { | ||
process.emit('log', 'verbose', 'web login not supported, trying couch') | ||
return prompter(conf.creds) | ||
.then(data => loginCouch(data.username, data.password, conf)) | ||
} else { | ||
throw er | ||
} | ||
}) | ||
} | ||
function adduser (opener, prompter, conf) { | ||
validate('FFO', arguments) | ||
return adduserWeb(opener, conf).catch(er => { | ||
if (er instanceof WebLoginNotSupported) { | ||
process.emit('log', 'verbose', 'web adduser not supported, trying couch') | ||
return prompter(conf.creds) | ||
.then(data => adduserCouch(data.username, data.email, data.password, conf)) | ||
} else { | ||
throw er | ||
} | ||
}) | ||
} | ||
function adduserWeb (opener, conf) { | ||
validate('FO', arguments) | ||
const body = { create: true } | ||
process.emit('log', 'verbose', 'web adduser', 'before first POST') | ||
return webAuth(opener, conf, body) | ||
} | ||
function loginWeb (opener, conf) { | ||
validate('FO', arguments) | ||
process.emit('log', 'verbose', 'web login', 'before first POST') | ||
return webAuth(opener, conf, {}) | ||
} | ||
function webAuth (opener, conf, body) { | ||
if (!conf.opts) conf.opts = {} | ||
const target = url.resolve(conf.registry, '-/v1/login') | ||
body.hostname = conf.hostname || os.hostname() | ||
return fetchJSON({ | ||
target: target, | ||
method: 'POST', | ||
body: body, | ||
opts: conf.opts, | ||
saveResponse: true | ||
}).then(result => { | ||
const res = result[0] | ||
const content = result[1] | ||
process.emit('log', 'verbose', 'web auth', 'got response', content) | ||
const doneUrl = content.doneUrl | ||
const loginUrl = content.loginUrl | ||
if (typeof doneUrl !== 'string' || | ||
typeof loginUrl !== 'string' || | ||
!doneUrl || !loginUrl) { | ||
throw new WebLoginInvalidResponse('POST', target, res, content) | ||
} | ||
process.emit('log', 'verbose', 'web auth', 'opening url pair') | ||
const doneConf = { | ||
target: doneUrl, | ||
method: 'GET', | ||
opts: conf.opts, | ||
saveResponse: true | ||
} | ||
return opener(loginUrl).then(() => fetchJSON(doneConf)).then(onDone) | ||
function onDone (result) { | ||
const res = result[0] | ||
const content = result[1] | ||
if (res.status === 200) { | ||
if (!content.token) { | ||
throw new WebLoginInvalidResponse('GET', doneUrl, res, content) | ||
} else { | ||
return content | ||
} | ||
} else if (res.status === 202) { | ||
const retry = +res.headers.get('retry-after') | ||
if (retry > 0) { | ||
return new Promise(resolve => setTimeout(resolve, 1000 * retry)) | ||
.then(() => fetchJSON(doneConf)).then(onDone) | ||
} else { | ||
return fetchJSON(doneConf).then(onDone) | ||
} | ||
} else { | ||
throw new WebLoginInvalidResponse('GET', doneUrl, res, content) | ||
} | ||
} | ||
}).catch(er => { | ||
if (er.statusCode >= 400 && er.statusCode <= 499) { | ||
throw new WebLoginNotSupported('POST', target, { | ||
status: er.statusCode, | ||
headers: { raw: () => er.headers } | ||
}, er.body) | ||
} else { | ||
throw er | ||
} | ||
}) | ||
} | ||
function adduserCouch (username, email, password, conf) { | ||
validate('SSSO', arguments) | ||
@@ -37,5 +144,9 @@ if (!conf.opts) conf.opts = {} | ||
return fetchJSON({target: target, method: 'PUT', body: userobj, opts: conf.opts}) | ||
.then(result => { | ||
result.username = username | ||
return result | ||
}) | ||
} | ||
function login (username, password, conf) { | ||
function loginCouch (username, password, conf) { | ||
validate('SSO', arguments) | ||
@@ -58,3 +169,6 @@ const userobj = { | ||
return fetchJSON(Object.assign({method: 'PUT', target: target, body: userobj}, conf)).catch(err => { | ||
if (err.code === 'E400') err.message = `There is no user with the username "${username}".` | ||
if (err.code === 'E400') { | ||
err.message = `There is no user with the username "${username}".` | ||
throw err | ||
} | ||
if (err.code !== 'E409') throw err | ||
@@ -80,2 +194,5 @@ return fetchJSON(Object.assign({method: 'GET', target: target + '?write=true'}, conf)).then(result => { | ||
}) | ||
}).then(result => { | ||
result.username = username | ||
return result | ||
}) | ||
@@ -153,28 +270,50 @@ } | ||
class General extends HttpErrorBase { | ||
class HttpErrorGeneral extends HttpErrorBase { | ||
constructor (method, target, res, body) { | ||
super(method, target, res, body) | ||
this.message = `Registry returned ${this.statusCode} for ${this.method} on ${this.href}` | ||
if (body && body.error) { | ||
this.message = `Registry returned ${this.statusCode} for ${this.method} on ${this.target}: ${body.error}` | ||
} else { | ||
this.message = `Registry returned ${this.statusCode} for ${this.method} on ${this.target}` | ||
} | ||
Error.captureStackTrace(this, HttpErrorGeneral) | ||
} | ||
} | ||
class AuthOTP extends HttpErrorBase { | ||
class WebLoginInvalidResponse extends HttpErrorBase { | ||
constructor (method, target, res, body) { | ||
super(method, target, res, body) | ||
this.message = 'Invalid response from web login endpoint' | ||
Error.captureStackTrace(this, WebLoginInvalidResponse) | ||
} | ||
} | ||
class WebLoginNotSupported extends HttpErrorBase { | ||
constructor (method, target, res, body) { | ||
super(method, target, res, body) | ||
this.message = 'Web login not supported' | ||
this.code = 'ENYI' | ||
Error.captureStackTrace(this, WebLoginNotSupported) | ||
} | ||
} | ||
class HttpErrorAuthOTP extends HttpErrorBase { | ||
constructor (method, target, res, body) { | ||
super(method, target, res, body) | ||
this.message = 'OTP required for authentication' | ||
this.code = 'EOTP' | ||
Error.captureStackTrace(this, AuthOTP) | ||
Error.captureStackTrace(this, HttpErrorAuthOTP) | ||
} | ||
} | ||
class AuthIPAddress extends HttpErrorBase { | ||
constructor (res, body) { | ||
class HttpErrorAuthIPAddress extends HttpErrorBase { | ||
constructor (method, target, res, body) { | ||
super(method, target, res, body) | ||
this.message = 'Login is not allowed from your IP address' | ||
this.code = 'EAUTHIP' | ||
Error.captureStackTrace(this, AuthIPAddress) | ||
Error.captureStackTrace(this, HttpErrorAuthIPAddress) | ||
} | ||
} | ||
class AuthUnknown extends HttpErrorBase { | ||
class HttpErrorAuthUnknown extends HttpErrorBase { | ||
constructor (method, target, res, body) { | ||
@@ -184,3 +323,3 @@ super(method, target, res, body) | ||
this.code = 'EAUTHIP' | ||
Error.captureStackTrace(this, AuthIPAddress) | ||
Error.captureStackTrace(this, HttpErrorAuthUnknown) | ||
} | ||
@@ -211,3 +350,3 @@ } | ||
} | ||
process.emit('log', 'http', 'request', '→',conf.method || 'GET', conf.target) | ||
process.emit('log', 'http', 'request', '→', conf.method || 'GET', conf.target) | ||
return fetch.defaults(conf.opts || {})(conf.target, fetchOpts).catch(err => { | ||
@@ -230,2 +369,3 @@ throw new FetchError(err, conf.method, conf.target) | ||
const content = result[1] | ||
const retVal = conf.saveResponse ? result : content | ||
process.emit('log', 'http', res.status, `← ${res.statusText} (${conf.target})`) | ||
@@ -235,16 +375,12 @@ if (res.status === 401 && res.headers.get('www-authenticate')) { | ||
if (auth.indexOf('ipaddress') !== -1) { | ||
throw new AuthIPAddress(conf.method, conf.target, res, content) | ||
throw new HttpErrorAuthIPAddress(conf.method, conf.target, res, content) | ||
} else if (auth.indexOf('otp') !== -1) { | ||
throw new AuthOTP(conf.method, conf.target, res, content) | ||
throw new HttpErrorAuthOTP(conf.method, conf.target, res, content) | ||
} else { | ||
throw new AuthUnknown(conf.method, conf.target, res, content) | ||
throw new HttpErrorAuthUnknown(conf.method, conf.target, res, content) | ||
} | ||
} else if (res.status < 200 || res.status >= 300) { | ||
if (typeof content === 'object' && content.error) { | ||
return content | ||
} else { | ||
throw new General(conf.method, conf.target, res, content) | ||
} | ||
throw new HttpErrorGeneral(conf.method, conf.target, res, content) | ||
} else { | ||
return content | ||
return retVal | ||
} | ||
@@ -270,2 +406,2 @@ }) | ||
} | ||
} | ||
} |
{ | ||
"name": "npm-profile", | ||
"version": "2.0.5", | ||
"version": "3.0.0", | ||
"description": "Library for updating an npmjs.com profile", | ||
@@ -22,4 +22,5 @@ "keywords": [], | ||
"files": [ | ||
"CHANGELOG.md", | ||
"index.js" | ||
] | ||
} |
156
README.md
@@ -19,4 +19,146 @@ # npm-profile | ||
### profile.adduser(username, email, password, config) → Promise | ||
### profile.adduser(opener, prompter, config) → Promise | ||
Tries to create a user new web based login, if that fails it falls back to | ||
using the legacy CouchDB APIs. | ||
* `opener` Function (url) → Promise, returns a promise that resolves after a browser has been opened for the user at `url`. | ||
* `prompter` Function (creds) → Promise, returns a promise that resolves to an object with `username`, `email` and `password` properties. | ||
* `config` Object | ||
* `creds` Object, passed through to prompter, common values are: | ||
* `username` String, default value for username | ||
* `email` String, default value for email | ||
* `registry` String (for reference, the npm registry is `https://registry.npmjs.org`) | ||
* `opts` Object, [make-fetch-happen options](https://www.npmjs.com/package/make-fetch-happen#extra-options) for setting | ||
things like cache, proxy, SSL CA and retry rules. | ||
#### **Promise Value** | ||
An object with the following properties: | ||
* `token` String, to be used to authenticate further API calls | ||
* `username` String, the username the user authenticated as | ||
#### **Promise Rejection** | ||
An error object indicating what went wrong. | ||
The `headers` property will contain the HTTP headers of the response. | ||
If the action was denied because it came from an IP address that this action | ||
on this account isn't allowed from then the `code` will be set to `EAUTHIP`. | ||
Otherwise the code will be `'E'` followed by the HTTP response code, for | ||
example a Forbidden response would be `E403`. | ||
### profile.login(opener, prompter, config) → Promise | ||
Tries to login using new web based login, if that fails it falls back to | ||
using the legacy CouchDB APIs. | ||
* `opener` Function (url) → Promise, returns a promise that resolves after a browser has been opened for the user at `url`. | ||
* `prompter` Function (creds) → Promise, returns a promise that resolves to an object with `username`, and `password` properties. | ||
* `config` Object | ||
* `creds` Object, passed through to prompter, common values are: | ||
* `name` String, default value for username | ||
* `registry` String (for reference, the npm registry is `https://registry.npmjs.org`) | ||
* `auth` Object, properties: `otp` | ||
the one-time password from a two-factor authentication device. | ||
* `opts` Object, [make-fetch-happen options](https://www.npmjs.com/package/make-fetch-happen#extra-options) for setting | ||
things like cache, proxy, SSL CA and retry rules. | ||
#### **Promise Value** | ||
An object with the following properties: | ||
* `token` String, to be used to authenticate further API calls | ||
* `username` String, the username the user authenticated as | ||
#### **Promise Rejection** | ||
An error object indicating what went wrong. | ||
The `headers` property will contain the HTTP headers of the response. | ||
If the action was denied because an OTP is required then `code` will be set | ||
to `EOTP`. This error code can only come from a legacy CouchDB login and so | ||
this should be retried with loginCouch. | ||
If the action was denied because it came from an IP address that this action | ||
on this account isn't allowed from then the `code` will be set to `EAUTHIP`. | ||
Otherwise the code will be `'E'` followed by the HTTP response code, for | ||
example a Forbidden response would be `E403`. | ||
### profile.adduserWeb(opener, config) → Promise | ||
Tries to create a user new web based login, if that fails it falls back to | ||
using the legacy CouchDB APIs. | ||
* `opener` Function (url) → Promise, returns a promise that resolves after a browser has been opened for the user at `url`. | ||
* `config` Object | ||
* `registry` String (for reference, the npm registry is `https://registry.npmjs.org`) | ||
* `opts` Object, [make-fetch-happen options](https://www.npmjs.com/package/make-fetch-happen#extra-options) for setting | ||
things like cache, proxy, SSL CA and retry rules. | ||
#### **Promise Value** | ||
An object with the following properties: | ||
* `token` String, to be used to authenticate further API calls | ||
* `username` String, the username the user authenticated as | ||
#### **Promise Rejection** | ||
An error object indicating what went wrong. | ||
The `headers` property will contain the HTTP headers of the response. | ||
If the registry does not support web-login then an error will be thrown with | ||
its `code` property set to `ENYI` . You should retry with `adduserCouch`. | ||
If you use `adduser` then this fallback will be done automatically. | ||
If the action was denied because it came from an IP address that this action | ||
on this account isn't allowed from then the `code` will be set to `EAUTHIP`. | ||
Otherwise the code will be `'E'` followed by the HTTP response code, for | ||
example a Forbidden response would be `E403`. | ||
### profile.loginWeb(opener, config) → Promise | ||
Tries to login using new web based login, if that fails it falls back to | ||
using the legacy CouchDB APIs. | ||
* `opener` Function (url) → Promise, returns a promise that resolves after a browser has been opened for the user at `url`. | ||
* `config` Object | ||
* `registry` String (for reference, the npm registry is `https://registry.npmjs.org`) | ||
* `opts` Object, [make-fetch-happen options](https://www.npmjs.com/package/make-fetch-happen#extra-options) for setting | ||
things like cache, proxy, SSL CA and retry rules. | ||
#### **Promise Value** | ||
An object with the following properties: | ||
* `token` String, to be used to authenticate further API calls | ||
* `username` String, the username the user authenticated as | ||
#### **Promise Rejection** | ||
An error object indicating what went wrong. | ||
The `headers` property will contain the HTTP headers of the response. | ||
If the registry does not support web-login then an error will be thrown with | ||
its `code` property set to `ENYI` . You should retry with `loginCouch`. | ||
If you use `login` then this fallback will be done automatically. | ||
If the action was denied because it came from an IP address that this action | ||
on this account isn't allowed from then the `code` will be set to `EAUTHIP`. | ||
Otherwise the code will be `'E'` followed by the HTTP response code, for | ||
example a Forbidden response would be `E403`. | ||
### profile.adduserCouch(username, email, password, config) → Promise | ||
```js | ||
@@ -45,4 +187,7 @@ profile.adduser(username, email, password, {registry}).then(result => { | ||
An object with a `token` property that can be passed into future authentication requests. | ||
An object with the following properties: | ||
* `token` String, to be used to authenticate further API calls | ||
* `username` String, the username the user authenticated as | ||
#### **Promise Rejection** | ||
@@ -63,3 +208,3 @@ | ||
### profile.login(username, password, config) → Promise | ||
### profile.loginCouch(username, password, config) → Promise | ||
@@ -94,4 +239,7 @@ ```js | ||
An object with a `token` property that can be passed into future authentication requests. | ||
An object with the following properties: | ||
* `token` String, to be used to authenticate further API calls | ||
* `username` String, the username the user authenticated as | ||
#### **Promise Rejection** | ||
@@ -98,0 +246,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
36144
5
367
577