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