11cb0ef41Sopenharmony_ci'use strict'
21cb0ef41Sopenharmony_ci
31cb0ef41Sopenharmony_ciconst fetch = require('npm-registry-fetch')
41cb0ef41Sopenharmony_ciconst { HttpErrorBase } = require('npm-registry-fetch/lib/errors')
51cb0ef41Sopenharmony_ciconst EventEmitter = require('events')
61cb0ef41Sopenharmony_ciconst os = require('os')
71cb0ef41Sopenharmony_ciconst { URL } = require('url')
81cb0ef41Sopenharmony_ciconst log = require('proc-log')
91cb0ef41Sopenharmony_ci
101cb0ef41Sopenharmony_ci// try loginWeb, catch the "not supported" message and fall back to couch
111cb0ef41Sopenharmony_ciconst login = (opener, prompter, opts = {}) => {
121cb0ef41Sopenharmony_ci  const { creds } = opts
131cb0ef41Sopenharmony_ci  return loginWeb(opener, opts).catch(er => {
141cb0ef41Sopenharmony_ci    if (er instanceof WebLoginNotSupported) {
151cb0ef41Sopenharmony_ci      log.verbose('web login not supported, trying couch')
161cb0ef41Sopenharmony_ci      return prompter(creds)
171cb0ef41Sopenharmony_ci        .then(data => loginCouch(data.username, data.password, opts))
181cb0ef41Sopenharmony_ci    } else {
191cb0ef41Sopenharmony_ci      throw er
201cb0ef41Sopenharmony_ci    }
211cb0ef41Sopenharmony_ci  })
221cb0ef41Sopenharmony_ci}
231cb0ef41Sopenharmony_ci
241cb0ef41Sopenharmony_ciconst adduser = (opener, prompter, opts = {}) => {
251cb0ef41Sopenharmony_ci  const { creds } = opts
261cb0ef41Sopenharmony_ci  return adduserWeb(opener, opts).catch(er => {
271cb0ef41Sopenharmony_ci    if (er instanceof WebLoginNotSupported) {
281cb0ef41Sopenharmony_ci      log.verbose('web adduser not supported, trying couch')
291cb0ef41Sopenharmony_ci      return prompter(creds)
301cb0ef41Sopenharmony_ci        .then(data => adduserCouch(data.username, data.email, data.password, opts))
311cb0ef41Sopenharmony_ci    } else {
321cb0ef41Sopenharmony_ci      throw er
331cb0ef41Sopenharmony_ci    }
341cb0ef41Sopenharmony_ci  })
351cb0ef41Sopenharmony_ci}
361cb0ef41Sopenharmony_ci
371cb0ef41Sopenharmony_ciconst adduserWeb = (opener, opts = {}) => {
381cb0ef41Sopenharmony_ci  log.verbose('web adduser', 'before first POST')
391cb0ef41Sopenharmony_ci  return webAuth(opener, opts, { create: true })
401cb0ef41Sopenharmony_ci}
411cb0ef41Sopenharmony_ci
421cb0ef41Sopenharmony_ciconst loginWeb = (opener, opts = {}) => {
431cb0ef41Sopenharmony_ci  log.verbose('web login', 'before first POST')
441cb0ef41Sopenharmony_ci  return webAuth(opener, opts, {})
451cb0ef41Sopenharmony_ci}
461cb0ef41Sopenharmony_ci
471cb0ef41Sopenharmony_ciconst isValidUrl = u => {
481cb0ef41Sopenharmony_ci  try {
491cb0ef41Sopenharmony_ci    return /^https?:$/.test(new URL(u).protocol)
501cb0ef41Sopenharmony_ci  } catch (er) {
511cb0ef41Sopenharmony_ci    return false
521cb0ef41Sopenharmony_ci  }
531cb0ef41Sopenharmony_ci}
541cb0ef41Sopenharmony_ci
551cb0ef41Sopenharmony_ciconst webAuth = (opener, opts, body) => {
561cb0ef41Sopenharmony_ci  const { hostname } = opts
571cb0ef41Sopenharmony_ci  body.hostname = hostname || os.hostname()
581cb0ef41Sopenharmony_ci  const target = '/-/v1/login'
591cb0ef41Sopenharmony_ci  const doneEmitter = new EventEmitter()
601cb0ef41Sopenharmony_ci  return fetch(target, {
611cb0ef41Sopenharmony_ci    ...opts,
621cb0ef41Sopenharmony_ci    method: 'POST',
631cb0ef41Sopenharmony_ci    body,
641cb0ef41Sopenharmony_ci  }).then(res => {
651cb0ef41Sopenharmony_ci    return Promise.all([res, res.json()])
661cb0ef41Sopenharmony_ci  }).then(([res, content]) => {
671cb0ef41Sopenharmony_ci    const { doneUrl, loginUrl } = content
681cb0ef41Sopenharmony_ci    log.verbose('web auth', 'got response', content)
691cb0ef41Sopenharmony_ci    if (!isValidUrl(doneUrl) || !isValidUrl(loginUrl)) {
701cb0ef41Sopenharmony_ci      throw new WebLoginInvalidResponse('POST', res, content)
711cb0ef41Sopenharmony_ci    }
721cb0ef41Sopenharmony_ci    return content
731cb0ef41Sopenharmony_ci  }).then(({ doneUrl, loginUrl }) => {
741cb0ef41Sopenharmony_ci    log.verbose('web auth', 'opening url pair')
751cb0ef41Sopenharmony_ci
761cb0ef41Sopenharmony_ci    const openPromise = opener(loginUrl, doneEmitter)
771cb0ef41Sopenharmony_ci    const webAuthCheckPromise = webAuthCheckLogin(doneUrl, { ...opts, cache: false })
781cb0ef41Sopenharmony_ci      .then(authResult => {
791cb0ef41Sopenharmony_ci        log.verbose('web auth', 'done-check finished')
801cb0ef41Sopenharmony_ci
811cb0ef41Sopenharmony_ci        // cancel open prompt if it's present
821cb0ef41Sopenharmony_ci        doneEmitter.emit('abort')
831cb0ef41Sopenharmony_ci
841cb0ef41Sopenharmony_ci        return authResult
851cb0ef41Sopenharmony_ci      })
861cb0ef41Sopenharmony_ci
871cb0ef41Sopenharmony_ci    return Promise.all([openPromise, webAuthCheckPromise]).then(
881cb0ef41Sopenharmony_ci      // pick the auth result and pass it along
891cb0ef41Sopenharmony_ci      ([, authResult]) => authResult
901cb0ef41Sopenharmony_ci    )
911cb0ef41Sopenharmony_ci  }).catch(er => {
921cb0ef41Sopenharmony_ci    // cancel open prompt if it's present
931cb0ef41Sopenharmony_ci    doneEmitter.emit('abort')
941cb0ef41Sopenharmony_ci
951cb0ef41Sopenharmony_ci    if ((er.statusCode >= 400 && er.statusCode <= 499) || er.statusCode === 500) {
961cb0ef41Sopenharmony_ci      throw new WebLoginNotSupported('POST', {
971cb0ef41Sopenharmony_ci        status: er.statusCode,
981cb0ef41Sopenharmony_ci        headers: { raw: () => er.headers },
991cb0ef41Sopenharmony_ci      }, er.body)
1001cb0ef41Sopenharmony_ci    } else {
1011cb0ef41Sopenharmony_ci      throw er
1021cb0ef41Sopenharmony_ci    }
1031cb0ef41Sopenharmony_ci  })
1041cb0ef41Sopenharmony_ci}
1051cb0ef41Sopenharmony_ci
1061cb0ef41Sopenharmony_ciconst webAuthCheckLogin = (doneUrl, opts) => {
1071cb0ef41Sopenharmony_ci  return fetch(doneUrl, opts).then(res => {
1081cb0ef41Sopenharmony_ci    return Promise.all([res, res.json()])
1091cb0ef41Sopenharmony_ci  }).then(([res, content]) => {
1101cb0ef41Sopenharmony_ci    if (res.status === 200) {
1111cb0ef41Sopenharmony_ci      if (!content.token) {
1121cb0ef41Sopenharmony_ci        throw new WebLoginInvalidResponse('GET', res, content)
1131cb0ef41Sopenharmony_ci      } else {
1141cb0ef41Sopenharmony_ci        return content
1151cb0ef41Sopenharmony_ci      }
1161cb0ef41Sopenharmony_ci    } else if (res.status === 202) {
1171cb0ef41Sopenharmony_ci      const retry = +res.headers.get('retry-after') * 1000
1181cb0ef41Sopenharmony_ci      if (retry > 0) {
1191cb0ef41Sopenharmony_ci        return sleep(retry).then(() => webAuthCheckLogin(doneUrl, opts))
1201cb0ef41Sopenharmony_ci      } else {
1211cb0ef41Sopenharmony_ci        return webAuthCheckLogin(doneUrl, opts)
1221cb0ef41Sopenharmony_ci      }
1231cb0ef41Sopenharmony_ci    } else {
1241cb0ef41Sopenharmony_ci      throw new WebLoginInvalidResponse('GET', res, content)
1251cb0ef41Sopenharmony_ci    }
1261cb0ef41Sopenharmony_ci  })
1271cb0ef41Sopenharmony_ci}
1281cb0ef41Sopenharmony_ci
1291cb0ef41Sopenharmony_ciconst adduserCouch = (username, email, password, opts = {}) => {
1301cb0ef41Sopenharmony_ci  const body = {
1311cb0ef41Sopenharmony_ci    _id: 'org.couchdb.user:' + username,
1321cb0ef41Sopenharmony_ci    name: username,
1331cb0ef41Sopenharmony_ci    password: password,
1341cb0ef41Sopenharmony_ci    email: email,
1351cb0ef41Sopenharmony_ci    type: 'user',
1361cb0ef41Sopenharmony_ci    roles: [],
1371cb0ef41Sopenharmony_ci    date: new Date().toISOString(),
1381cb0ef41Sopenharmony_ci  }
1391cb0ef41Sopenharmony_ci  const logObj = {
1401cb0ef41Sopenharmony_ci    ...body,
1411cb0ef41Sopenharmony_ci    password: 'XXXXX',
1421cb0ef41Sopenharmony_ci  }
1431cb0ef41Sopenharmony_ci  log.verbose('adduser', 'before first PUT', logObj)
1441cb0ef41Sopenharmony_ci
1451cb0ef41Sopenharmony_ci  const target = '/-/user/org.couchdb.user:' + encodeURIComponent(username)
1461cb0ef41Sopenharmony_ci  return fetch.json(target, {
1471cb0ef41Sopenharmony_ci    ...opts,
1481cb0ef41Sopenharmony_ci    method: 'PUT',
1491cb0ef41Sopenharmony_ci    body,
1501cb0ef41Sopenharmony_ci  }).then(result => {
1511cb0ef41Sopenharmony_ci    result.username = username
1521cb0ef41Sopenharmony_ci    return result
1531cb0ef41Sopenharmony_ci  })
1541cb0ef41Sopenharmony_ci}
1551cb0ef41Sopenharmony_ci
1561cb0ef41Sopenharmony_ciconst loginCouch = (username, password, opts = {}) => {
1571cb0ef41Sopenharmony_ci  const body = {
1581cb0ef41Sopenharmony_ci    _id: 'org.couchdb.user:' + username,
1591cb0ef41Sopenharmony_ci    name: username,
1601cb0ef41Sopenharmony_ci    password: password,
1611cb0ef41Sopenharmony_ci    type: 'user',
1621cb0ef41Sopenharmony_ci    roles: [],
1631cb0ef41Sopenharmony_ci    date: new Date().toISOString(),
1641cb0ef41Sopenharmony_ci  }
1651cb0ef41Sopenharmony_ci  const logObj = {
1661cb0ef41Sopenharmony_ci    ...body,
1671cb0ef41Sopenharmony_ci    password: 'XXXXX',
1681cb0ef41Sopenharmony_ci  }
1691cb0ef41Sopenharmony_ci  log.verbose('login', 'before first PUT', logObj)
1701cb0ef41Sopenharmony_ci
1711cb0ef41Sopenharmony_ci  const target = '/-/user/org.couchdb.user:' + encodeURIComponent(username)
1721cb0ef41Sopenharmony_ci  return fetch.json(target, {
1731cb0ef41Sopenharmony_ci    ...opts,
1741cb0ef41Sopenharmony_ci    method: 'PUT',
1751cb0ef41Sopenharmony_ci    body,
1761cb0ef41Sopenharmony_ci  }).catch(err => {
1771cb0ef41Sopenharmony_ci    if (err.code === 'E400') {
1781cb0ef41Sopenharmony_ci      err.message = `There is no user with the username "${username}".`
1791cb0ef41Sopenharmony_ci      throw err
1801cb0ef41Sopenharmony_ci    }
1811cb0ef41Sopenharmony_ci    if (err.code !== 'E409') {
1821cb0ef41Sopenharmony_ci      throw err
1831cb0ef41Sopenharmony_ci    }
1841cb0ef41Sopenharmony_ci    return fetch.json(target, {
1851cb0ef41Sopenharmony_ci      ...opts,
1861cb0ef41Sopenharmony_ci      query: { write: true },
1871cb0ef41Sopenharmony_ci    }).then(result => {
1881cb0ef41Sopenharmony_ci      Object.keys(result).forEach(k => {
1891cb0ef41Sopenharmony_ci        if (!body[k] || k === 'roles') {
1901cb0ef41Sopenharmony_ci          body[k] = result[k]
1911cb0ef41Sopenharmony_ci        }
1921cb0ef41Sopenharmony_ci      })
1931cb0ef41Sopenharmony_ci      const { otp } = opts
1941cb0ef41Sopenharmony_ci      return fetch.json(`${target}/-rev/${body._rev}`, {
1951cb0ef41Sopenharmony_ci        ...opts,
1961cb0ef41Sopenharmony_ci        method: 'PUT',
1971cb0ef41Sopenharmony_ci        body,
1981cb0ef41Sopenharmony_ci        forceAuth: {
1991cb0ef41Sopenharmony_ci          username,
2001cb0ef41Sopenharmony_ci          password: Buffer.from(password, 'utf8').toString('base64'),
2011cb0ef41Sopenharmony_ci          otp,
2021cb0ef41Sopenharmony_ci        },
2031cb0ef41Sopenharmony_ci      })
2041cb0ef41Sopenharmony_ci    })
2051cb0ef41Sopenharmony_ci  }).then(result => {
2061cb0ef41Sopenharmony_ci    result.username = username
2071cb0ef41Sopenharmony_ci    return result
2081cb0ef41Sopenharmony_ci  })
2091cb0ef41Sopenharmony_ci}
2101cb0ef41Sopenharmony_ci
2111cb0ef41Sopenharmony_ciconst get = (opts = {}) => fetch.json('/-/npm/v1/user', opts)
2121cb0ef41Sopenharmony_ci
2131cb0ef41Sopenharmony_ciconst set = (profile, opts = {}) => {
2141cb0ef41Sopenharmony_ci  Object.keys(profile).forEach(key => {
2151cb0ef41Sopenharmony_ci    // profile keys can't be empty strings, but they CAN be null
2161cb0ef41Sopenharmony_ci    if (profile[key] === '') {
2171cb0ef41Sopenharmony_ci      profile[key] = null
2181cb0ef41Sopenharmony_ci    }
2191cb0ef41Sopenharmony_ci  })
2201cb0ef41Sopenharmony_ci  return fetch.json('/-/npm/v1/user', {
2211cb0ef41Sopenharmony_ci    ...opts,
2221cb0ef41Sopenharmony_ci    method: 'POST',
2231cb0ef41Sopenharmony_ci    body: profile,
2241cb0ef41Sopenharmony_ci  })
2251cb0ef41Sopenharmony_ci}
2261cb0ef41Sopenharmony_ci
2271cb0ef41Sopenharmony_ciconst listTokens = (opts = {}) => {
2281cb0ef41Sopenharmony_ci  const untilLastPage = (href, objects) => {
2291cb0ef41Sopenharmony_ci    return fetch.json(href, opts).then(result => {
2301cb0ef41Sopenharmony_ci      objects = objects ? objects.concat(result.objects) : result.objects
2311cb0ef41Sopenharmony_ci      if (result.urls.next) {
2321cb0ef41Sopenharmony_ci        return untilLastPage(result.urls.next, objects)
2331cb0ef41Sopenharmony_ci      } else {
2341cb0ef41Sopenharmony_ci        return objects
2351cb0ef41Sopenharmony_ci      }
2361cb0ef41Sopenharmony_ci    })
2371cb0ef41Sopenharmony_ci  }
2381cb0ef41Sopenharmony_ci  return untilLastPage('/-/npm/v1/tokens')
2391cb0ef41Sopenharmony_ci}
2401cb0ef41Sopenharmony_ci
2411cb0ef41Sopenharmony_ciconst removeToken = (tokenKey, opts = {}) => {
2421cb0ef41Sopenharmony_ci  const target = `/-/npm/v1/tokens/token/${tokenKey}`
2431cb0ef41Sopenharmony_ci  return fetch(target, {
2441cb0ef41Sopenharmony_ci    ...opts,
2451cb0ef41Sopenharmony_ci    method: 'DELETE',
2461cb0ef41Sopenharmony_ci    ignoreBody: true,
2471cb0ef41Sopenharmony_ci  }).then(() => null)
2481cb0ef41Sopenharmony_ci}
2491cb0ef41Sopenharmony_ci
2501cb0ef41Sopenharmony_ciconst createToken = (password, readonly, cidrs, opts = {}) => {
2511cb0ef41Sopenharmony_ci  return fetch.json('/-/npm/v1/tokens', {
2521cb0ef41Sopenharmony_ci    ...opts,
2531cb0ef41Sopenharmony_ci    method: 'POST',
2541cb0ef41Sopenharmony_ci    body: {
2551cb0ef41Sopenharmony_ci      password: password,
2561cb0ef41Sopenharmony_ci      readonly: readonly,
2571cb0ef41Sopenharmony_ci      cidr_whitelist: cidrs,
2581cb0ef41Sopenharmony_ci    },
2591cb0ef41Sopenharmony_ci  })
2601cb0ef41Sopenharmony_ci}
2611cb0ef41Sopenharmony_ci
2621cb0ef41Sopenharmony_ciclass WebLoginInvalidResponse extends HttpErrorBase {
2631cb0ef41Sopenharmony_ci  constructor (method, res, body) {
2641cb0ef41Sopenharmony_ci    super(method, res, body)
2651cb0ef41Sopenharmony_ci    this.message = 'Invalid response from web login endpoint'
2661cb0ef41Sopenharmony_ci    Error.captureStackTrace(this, WebLoginInvalidResponse)
2671cb0ef41Sopenharmony_ci  }
2681cb0ef41Sopenharmony_ci}
2691cb0ef41Sopenharmony_ci
2701cb0ef41Sopenharmony_ciclass WebLoginNotSupported extends HttpErrorBase {
2711cb0ef41Sopenharmony_ci  constructor (method, res, body) {
2721cb0ef41Sopenharmony_ci    super(method, res, body)
2731cb0ef41Sopenharmony_ci    this.message = 'Web login not supported'
2741cb0ef41Sopenharmony_ci    this.code = 'ENYI'
2751cb0ef41Sopenharmony_ci    Error.captureStackTrace(this, WebLoginNotSupported)
2761cb0ef41Sopenharmony_ci  }
2771cb0ef41Sopenharmony_ci}
2781cb0ef41Sopenharmony_ci
2791cb0ef41Sopenharmony_ciconst sleep = (ms) =>
2801cb0ef41Sopenharmony_ci  new Promise((resolve, reject) => setTimeout(resolve, ms))
2811cb0ef41Sopenharmony_ci
2821cb0ef41Sopenharmony_cimodule.exports = {
2831cb0ef41Sopenharmony_ci  adduserCouch,
2841cb0ef41Sopenharmony_ci  loginCouch,
2851cb0ef41Sopenharmony_ci  adduserWeb,
2861cb0ef41Sopenharmony_ci  loginWeb,
2871cb0ef41Sopenharmony_ci  login,
2881cb0ef41Sopenharmony_ci  adduser,
2891cb0ef41Sopenharmony_ci  get,
2901cb0ef41Sopenharmony_ci  set,
2911cb0ef41Sopenharmony_ci  listTokens,
2921cb0ef41Sopenharmony_ci  removeToken,
2931cb0ef41Sopenharmony_ci  createToken,
2941cb0ef41Sopenharmony_ci  webAuthCheckLogin,
2951cb0ef41Sopenharmony_ci}
296