• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# npm-profile
2
3Provides functions for fetching and updating an npmjs.com profile.
4
5```js
6const profile = require('npm-profile')
7const result = await profile.get(registry, {token})
8//...
9```
10
11The API that this implements is documented here:
12
13* [authentication](https://github.com/npm/registry/blob/master/docs/user/authentication.md)
14* [profile editing](https://github.com/npm/registry/blob/master/docs/user/profile.md) (and two-factor authentication)
15
16## Table of Contents
17
18* [API](#api)
19  * Login and Account Creation
20    * [`adduser()`](#adduser)
21    * [`login()`](#login)
22    * [`adduserWeb()`](#adduser-web)
23    * [`loginWeb()`](#login-web)
24    * [`adduserCouch()`](#adduser-couch)
25    * [`loginCouch()`](#login-couch)
26  * Profile Data Management
27    * [`get()`](#get)
28    * [`set()`](#set)
29  * Token Management
30    * [`listTokens()`](#list-tokens)
31    * [`removeToken()`](#remove-token)
32    * [`createToken()`](#create-token)
33
34## API
35
36### <a name="adduser"></a> `> profile.adduser(opener, prompter, [opts]) → Promise`
37
38Tries to create a user new web based login, if that fails it falls back to
39using the legacy CouchDB APIs.
40
41* `opener` Function (url) → Promise, returns a promise that resolves after a browser has been opened for the user at `url`.
42* `prompter` Function (creds) → Promise, returns a promise that resolves to an object with `username`, `email` and `password` properties.
43* [`opts`](#opts) Object (optional) plus extra keys:
44  * `creds` Object, passed through to prompter, common values are:
45    * `username` String, default value for username
46    * `email` String, default value for email
47
48#### **Promise Value**
49
50An object with the following properties:
51
52* `token` String, to be used to authenticate further API calls
53* `username` String, the username the user authenticated as
54
55#### **Promise Rejection**
56
57An error object indicating what went wrong.
58
59The `headers` property will contain the HTTP headers of the response.
60
61If the action was denied because it came from an IP address that this action
62on this account isn't allowed from then the `code` will be set to `EAUTHIP`.
63
64Otherwise the code will be `'E'` followed by the HTTP response code, for
65example a Forbidden response would be `E403`.
66
67### <a name="login"></a> `> profile.login(opener, prompter, [opts]) → Promise`
68
69Tries to login using new web based login, if that fails it falls back to
70using the legacy CouchDB APIs.
71
72* `opener` Function (url) → Promise, returns a promise that resolves after a browser has been opened for the user at `url`.
73* `prompter` Function (creds) → Promise, returns a promise that resolves to an object with `username`, and `password` properties.
74* [`opts`](#opts) Object (optional) plus extra keys:
75  * `creds` Object, passed through to prompter, common values are:
76    * `name` String, default value for username
77
78#### **Promise Value**
79
80An object with the following properties:
81
82* `token` String, to be used to authenticate further API calls
83* `username` String, the username the user authenticated as
84
85#### **Promise Rejection**
86
87An error object indicating what went wrong.
88
89The `headers` property will contain the HTTP headers of the response.
90
91If the action was denied because an OTP is required then `code` will be set
92to `EOTP`.  This error code can only come from a legacy CouchDB login and so
93this should be retried with loginCouch.
94
95If the action was denied because it came from an IP address that this action
96on this account isn't allowed from then the `code` will be set to `EAUTHIP`.
97
98Otherwise the code will be `'E'` followed by the HTTP response code, for
99example a Forbidden response would be `E403`.
100
101### <a name="adduser-web"></a> `> profile.adduserWeb(opener, [opts]) → Promise`
102
103Tries to create a user new web based login, if that fails it falls back to
104using the legacy CouchDB APIs.
105
106* `opener` Function (url) → Promise, returns a promise that resolves after a browser has been opened for the user at `url`.
107* [`opts`](#opts) Object
108
109#### **Promise Value**
110
111An object with the following properties:
112
113* `token` String, to be used to authenticate further API calls
114* `username` String, the username the user authenticated as
115
116#### **Promise Rejection**
117
118An error object indicating what went wrong.
119
120The `headers` property will contain the HTTP headers of the response.
121
122If the registry does not support web-login then an error will be thrown with
123its `code` property set to `ENYI` . You should retry with `adduserCouch`.
124If you use `adduser` then this fallback will be done automatically.
125
126If the action was denied because it came from an IP address that this action
127on this account isn't allowed from then the `code` will be set to `EAUTHIP`.
128
129Otherwise the code will be `'E'` followed by the HTTP response code, for
130example a Forbidden response would be `E403`.
131
132### <a name="login-web"></a> `> profile.loginWeb(opener, [opts]) → Promise`
133
134Tries to login using new web based login, if that fails it falls back to
135using the legacy CouchDB APIs.
136
137* `opener` Function (url) → Promise, returns a promise that resolves after a browser has been opened for the user at `url`.
138* [`opts`](#opts) Object (optional)
139
140#### **Promise Value**
141
142An object with the following properties:
143
144* `token` String, to be used to authenticate further API calls
145* `username` String, the username the user authenticated as
146
147#### **Promise Rejection**
148
149An error object indicating what went wrong.
150
151The `headers` property will contain the HTTP headers of the response.
152
153If the registry does not support web-login then an error will be thrown with
154its `code` property set to `ENYI` . You should retry with `loginCouch`.
155If you use `login` then this fallback will be done automatically.
156
157If the action was denied because it came from an IP address that this action
158on this account isn't allowed from then the `code` will be set to `EAUTHIP`.
159
160Otherwise the code will be `'E'` followed by the HTTP response code, for
161example a Forbidden response would be `E403`.
162
163### <a name="adduser-couch"></a> `> profile.adduserCouch(username, email, password, [opts]) → Promise`
164
165```js
166const {token} = await profile.adduser(username, email, password, {registry})
167// `token` can be passed in through `opts` for authentication.
168```
169
170Creates a new user on the server along with a fresh bearer token for future
171authentication as this user.  This is what you see as an `authToken` in an
172`.npmrc`.
173
174If the user already exists then the npm registry will return an error, but
175this is registry specific and not guaranteed.
176
177* `username` String
178* `email` String
179* `password` String
180* [`opts`](#opts) Object (optional)
181
182#### **Promise Value**
183
184An object with the following properties:
185
186* `token` String, to be used to authenticate further API calls
187* `username` String, the username the user authenticated as
188
189#### **Promise Rejection**
190
191An error object indicating what went wrong.
192
193The `headers` property will contain the HTTP headers of the response.
194
195If the action was denied because an OTP is required then `code` will be set
196to `EOTP`.
197
198If the action was denied because it came from an IP address that this action
199on this account isn't allowed from then the `code` will be set to `EAUTHIP`.
200
201Otherwise the code will be `'E'` followed by the HTTP response code, for
202example a Forbidden response would be `E403`.
203
204### <a name="login-couch"></a> `> profile.loginCouch(username, password, [opts]) → Promise`
205
206```js
207let token
208try {
209  {token} = await profile.login(username, password, {registry})
210} catch (err) {
211  if (err.code === 'otp') {
212    const otp = await getOTPFromSomewhere()
213    {token} = await profile.login(username, password, {otp})
214  }
215}
216// `token` can now be passed in through `opts` for authentication.
217```
218
219Logs you into an existing user.  Does not create the user if they do not
220already exist.  Logging in means generating a new bearer token for use in
221future authentication. This is what you use as an `authToken` in an `.npmrc`.
222
223* `username` String
224* `email` String
225* `password` String
226* [`opts`](#opts) Object (optional)
227
228#### **Promise Value**
229
230An object with the following properties:
231
232* `token` String, to be used to authenticate further API calls
233* `username` String, the username the user authenticated as
234
235#### **Promise Rejection**
236
237An error object indicating what went wrong.
238
239If the object has a `code` property set to `EOTP` then that indicates that
240this account must use two-factor authentication to login.  Try again with a
241one-time password.
242
243If the object has a `code` property set to `EAUTHIP` then that indicates that
244this account is only allowed to login from certain networks and this ip is
245not on one of those networks.
246
247If the error was neither of these then the error object will have a
248`code` property set to the HTTP response code and a `headers` property with
249the HTTP headers in the response.
250
251### <a name="get"></a> `> profile.get([opts]) → Promise`
252
253```js
254const {name, email} = await profile.get({token})
255console.log(`${token} belongs to https://npm.im/~${name}, (mailto:${email})`)
256```
257
258Fetch profile information for the authenticated user.
259
260* [`opts`](#opts) Object
261
262#### **Promise Value**
263
264An object that looks like this:
265
266```js
267// "*" indicates a field that may not always appear
268{
269  tfa: null |
270       false |
271       {"mode": "auth-only", pending: Boolean} |
272       ["recovery", "codes"] |
273       "otpauth://...",
274  name: String,
275  email: String,
276  email_verified: Boolean,
277  created: Date,
278  updated: Date,
279  cidr_whitelist: null | ["192.168.1.1/32", ...],
280  fullname: String, // *
281  homepage: String, // *
282  freenode: String, // *
283  twitter: String,  // *
284  github: String    // *
285}
286```
287
288#### **Promise Rejection**
289
290An error object indicating what went wrong.
291
292The `headers` property will contain the HTTP headers of the response.
293
294If the action was denied because an OTP is required then `code` will be set
295to `EOTP`.
296
297If the action was denied because it came from an IP address that this action
298on this account isn't allowed from then the `code` will be set to `EAUTHIP`.
299
300Otherwise the code will be the HTTP response code.
301
302### <a name="set"></a> `> profile.set(profileData, [opts]) → Promise`
303
304```js
305await profile.set({github: 'great-github-account-name'}, {token})
306```
307
308Update profile information for the authenticated user.
309
310* `profileData` An object, like that returned from `profile.get`, but see
311  below for caveats relating to `password`, `tfa` and `cidr_whitelist`.
312* [`opts`](#opts) Object (optional)
313
314#### **SETTING `password`**
315
316This is used to change your password and is not visible (for obvious
317reasons) through the `get()` API.  The value should be an object with `old`
318and `new` properties, where the former has the user's current password and
319the latter has the desired new password. For example
320
321```js
322await profile.set({
323  password: {
324    old: 'abc123',
325    new: 'my new (more secure) password'
326  }
327}, {token})
328```
329
330#### **SETTING `cidr_whitelist`**
331
332The value for this is an Array.  Only valid CIDR ranges are allowed in it.
333Be very careful as it's possible to lock yourself out of your account with
334this.  This is not currently exposed in `npm` itself.
335
336```js
337await profile.set({
338  cidr_whitelist: [ '8.8.8.8/32' ]
339}, {token})
340// ↑ only one of google's dns servers can now access this account.
341```
342
343#### **SETTING `tfa`**
344
345Enabling two-factor authentication is a multi-step process.
346
3471. Call `profile.get` and check the status of `tfa`. If `pending` is true then
348   you'll need to disable it with `profile.set({tfa: {password, mode: 'disable'}, …)`.
3492. `profile.set({tfa: {password, mode}}, {registry, token})`
350   * Note that the user's `password` is required here in the `tfa` object,
351     regardless of how you're authenticating.
352   * `mode` is either `auth-only` which requires an `otp` when calling `login`
353     or `createToken`, or `mode` is `auth-and-writes` and an `otp` will be
354     required on login, publishing or when granting others access to your
355     modules.
356   * Be aware that this set call may require otp as part of the auth object.
357     If otp is needed it will be indicated through a rejection in the usual
358     way.
3593. If tfa was already enabled then you're just switch modes and a
360   successful response means that you're done.  If the tfa property is empty
361   and tfa _wasn't_ enabled then it means they were in a pending state.
3623. The response will have a `tfa` property set to an `otpauth` URL, as
363   [used by Google Authenticator](https://github.com/google/google-authenticator/wiki/Key-Uri-Format).
364   You will need to show this to the user for them to add to their
365   authenticator application.  This is typically done as a QRCODE, but you
366   can also show the value of the `secret` key in the `otpauth` query string
367   and they can type or copy paste that in.
3684. To complete setting up two factor auth you need to make a second call to
369   `profile.set` with `tfa` set to an array of TWO codes from the user's
370   authenticator, eg: `profile.set(tfa: [otp1, otp2]}, {registry, token})`
3715. On success you'll get a result object with a `tfa` property that has an
372   array of one-time-use recovery codes.  These are used to authenticate
373   later if the second factor is lost and generally should be printed and
374   put somewhere safe.
375
376Disabling two-factor authentication is more straightforward, set the `tfa`
377attribute to an object with a `password` property and a `mode` of `disable`.
378
379```js
380await profile.set({tfa: {password, mode: 'disable'}}, {token})
381```
382
383#### **Promise Value**
384
385An object reflecting the changes you made, see description for `profile.get`.
386
387#### **Promise Rejection**
388
389An error object indicating what went wrong.
390
391The `headers` property will contain the HTTP headers of the response.
392
393If the action was denied because an OTP is required then `code` will be set
394to `EOTP`.
395
396If the action was denied because it came from an IP address that this action
397on this account isn't allowed from then the `code` will be set to `EAUTHIP`.
398
399Otherwise the code will be the HTTP response code.
400
401### <a name="list-tokens"></a> `> profile.listTokens([opts]) → Promise`
402
403```js
404const tokens = await profile.listTokens({registry, token})
405console.log(`Number of tokens in your accounts: ${tokens.length}`)
406```
407
408Fetch a list of all of the authentication tokens the authenticated user has.
409
410* [`opts`](#opts) Object (optional)
411
412#### **Promise Value**
413
414An array of token objects. Each token object has the following properties:
415
416* key — A sha512 that can be used to remove this token.
417* token — The first six characters of the token UUID.  This should be used
418  by the user to identify which token this is.
419* created — The date and time the token was created
420* readonly — If true, this token can only be used to download private modules. Critically, it CAN NOT be used to publish.
421* cidr_whitelist — An array of CIDR ranges that this token is allowed to be used from.
422
423#### **Promise Rejection**
424
425An error object indicating what went wrong.
426
427The `headers` property will contain the HTTP headers of the response.
428
429If the action was denied because an OTP is required then `code` will be set
430to `EOTP`.
431
432If the action was denied because it came from an IP address that this action
433on this account isn't allowed from then the `code` will be set to `EAUTHIP`.
434
435Otherwise the code will be the HTTP response code.
436
437### <a name="remove-token"><a> `> profile.removeToken(token|key, opts) → Promise`
438
439```js
440await profile.removeToken(key, {token})
441// token is gone!
442```
443
444Remove a specific authentication token.
445
446* `token|key` String, either a complete authentication token or the key returned by `profile.listTokens`.
447* [`opts`](#opts) Object (optional)
448
449#### **Promise Value**
450
451No value.
452
453#### **Promise Rejection**
454
455An error object indicating what went wrong.
456
457The `headers` property will contain the HTTP headers of the response.
458
459If the action was denied because an OTP is required then `code` will be set
460to `EOTP`.
461
462If the action was denied because it came from an IP address that this action
463on this account isn't allowed from then the `code` will be set to `EAUTHIP`.
464
465Otherwise the code will be the HTTP response code.
466
467### <a name="create-token"></a> `> profile.createToken(password, readonly, cidr_whitelist, [opts]) → Promise`
468
469```js
470const newToken = await profile.createToken(
471  password, readonly, cidr_whitelist, {token, otp}
472)
473// do something with the newToken
474```
475
476Create a new authentication token, possibly with restrictions.
477
478* `password` String
479* `readonly` Boolean
480* `cidr_whitelist` Array
481* [`opts`](#opts) Object Optional
482
483#### **Promise Value**
484
485The promise will resolve with an object very much like the one's returned by
486`profile.listTokens`.  The only difference is that `token` is not truncated.
487
488```js
489{
490  token: String,
491  key: String,    // sha512 hash of the token UUID
492  cidr_whitelist: [String],
493  created: Date,
494  readonly: Boolean
495}
496```
497
498#### **Promise Rejection**
499
500An error object indicating what went wrong.
501
502The `headers` property will contain the HTTP headers of the response.
503
504If the action was denied because an OTP is required then `code` will be set
505to `EOTP`.
506
507If the action was denied because it came from an IP address that this action
508on this account isn't allowed from then the `code` will be set to `EAUTHIP`.
509
510Otherwise the code will be the HTTP response code.
511
512### <a name="opts"></a> options objects
513
514The various API functions accept an optional `opts` object as a final
515argument. This opts object can either be a regular Object, or a
516[`figgy-pudding`](https://npm.im/figgy-pudding) options object instance.
517
518Unless otherwise noted, the options accepted are the same as the
519[`npm-registry-fetch`
520options](https://www.npmjs.com/package/npm-registry-fetch#fetch-opts).
521
522Of particular note are `opts.registry`, and the auth-related options:
523
524* `opts.token` - used for Bearer auth
525* `opts.username` and `opts.password` - used for Basic auth
526* `opts.otp` - the 2fa OTP token
527
528## <a name="logging"></a> Logging
529
530This modules logs by emitting `log` events on the global `process` object.
531These events look like this:
532
533```js
534process.emit('log', 'loglevel', 'feature', 'message part 1', 'part 2', 'part 3', 'etc')
535```
536
537`loglevel` can be one of: `error`, `warn`, `notice`, `http`, `timing`, `info`, `verbose`, and `silly`.
538
539`feature` is any brief string that describes the component doing the logging.
540
541The remaining arguments are evaluated like `console.log` and joined together with spaces.
542
543A real world example of this is:
544
545```js
546  process.emit('log', 'http', 'request', '→', conf.method || 'GET', conf.target)
547```
548
549To handle the log events, you would do something like this:
550
551```js
552const log = require('npmlog')
553process.on('log', function (level) {
554  return log[level].apply(log, [].slice.call(arguments, 1))
555})
556```
557