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