sso.js 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. 'use strict'
  2. const BB = require('bluebird')
  3. const figgyPudding = require('figgy-pudding')
  4. const log = require('npmlog')
  5. const npmConfig = require('../config/figgy-config.js')
  6. const npmFetch = require('npm-registry-fetch')
  7. const output = require('../utils/output.js')
  8. const openUrl = BB.promisify(require('../utils/open-url.js'))
  9. const otplease = require('../utils/otplease.js')
  10. const profile = require('libnpm/profile')
  11. const SsoOpts = figgyPudding({
  12. ssoType: 'sso-type',
  13. 'sso-type': {},
  14. ssoPollFrequency: 'sso-poll-frequency',
  15. 'sso-poll-frequency': {}
  16. })
  17. module.exports.login = function login (creds, registry, scope, cb) {
  18. const opts = SsoOpts(npmConfig()).concat({creds, registry, scope})
  19. const ssoType = opts.ssoType
  20. if (!ssoType) { return cb(new Error('Missing option: sso-type')) }
  21. // We're reusing the legacy login endpoint, so we need some dummy
  22. // stuff here to pass validation. They're never used.
  23. const auth = {
  24. username: 'npm_' + ssoType + '_auth_dummy_user',
  25. password: 'placeholder',
  26. email: 'support@npmjs.com',
  27. authType: ssoType
  28. }
  29. otplease(opts,
  30. opts => profile.loginCouch(auth.username, auth.password, opts)
  31. ).then(({token, sso}) => {
  32. if (!token) { throw new Error('no SSO token returned') }
  33. if (!sso) { throw new Error('no SSO URL returned by services') }
  34. return openUrl(sso, 'to complete your login please visit').then(() => {
  35. return pollForSession(registry, token, opts)
  36. }).then(username => {
  37. log.info('adduser', 'Authorized user %s', username)
  38. var scopeMessage = scope ? ' to scope ' + scope : ''
  39. output('Logged in as %s%s on %s.', username, scopeMessage, registry)
  40. return {token}
  41. })
  42. }).nodeify(cb)
  43. }
  44. function pollForSession (registry, token, opts) {
  45. log.info('adduser', 'Polling for validated SSO session')
  46. return npmFetch.json(
  47. '/-/whoami', opts.concat({registry, forceAuth: {token}})
  48. ).then(
  49. ({username}) => username,
  50. err => {
  51. if (err.code === 'E401') {
  52. return sleep(opts['sso-poll-frequency']).then(() => {
  53. return pollForSession(registry, token, opts)
  54. })
  55. } else {
  56. throw err
  57. }
  58. }
  59. )
  60. }
  61. function sleep (time) {
  62. return new BB((resolve) => {
  63. setTimeout(resolve, time)
  64. })
  65. }