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