관리-도구
편집 파일: index.js
'use strict' const fetch = require('make-fetch-happen').defaults({retry: false}) const validate = require('aproba') const url = require('url') exports.adduser = adduser exports.login = login exports.get = get exports.set = set exports.listTokens = listTokens exports.removeToken = removeToken exports.createToken = createToken function adduser (username, email, password, conf) { validate('SSSO', arguments) if (!conf.opts) conf.opts = {} const userobj = { _id: 'org.couchdb.user:' + username, name: username, password: password, email: email, type: 'user', roles: [], date: new Date().toISOString() } const logObj = {} Object.keys(userobj).forEach(k => { logObj[k] = k === 'password' ? 'XXXXX' : userobj[k] }) process.emit('log', 'verbose', 'adduser', 'before first PUT', logObj) const target = url.resolve(conf.registry, '-/user/org.couchdb.user:' + encodeURIComponent(username)) return fetchJSON({target: target, method: 'PUT', body: userobj, opts: conf.opts}) } function login (username, password, conf) { validate('SSO', arguments) const userobj = { _id: 'org.couchdb.user:' + username, name: username, password: password, type: 'user', roles: [], date: new Date().toISOString() } const logObj = {} Object.keys(userobj).forEach(k => { logObj[k] = k === 'password' ? 'XXXXX' : userobj[k] }) process.emit('log', 'verbose', 'login', 'before first PUT', logObj) const target = url.resolve(conf.registry, '-/user/org.couchdb.user:' + encodeURIComponent(username)) return fetchJSON(Object.assign({method: 'PUT', target: target, body: userobj}, conf)).catch(err => { if (err.code === 'E400') err.message = `There is no user with the username "${username}".` if (err.code !== 'E409') throw err return fetchJSON(Object.assign({method: 'GET', target: target + '?write=true'}, conf)).then(result => { Object.keys(result).forEach(function (k) { if (!userobj[k] || k === 'roles') { userobj[k] = result[k] } }) const req = { method: 'PUT', target: target + '/-rev/' + userobj._rev, body: userobj, auth: { basic: { username: username, password: password } } } return fetchJSON(Object.assign({}, conf, req)) }) }) } function get (conf) { validate('O', arguments) const target = url.resolve(conf.registry, '-/npm/v1/user') return fetchJSON(Object.assign({target: target}, conf)) } function set (profile, conf) { validate('OO', arguments) const target = url.resolve(conf.registry, '-/npm/v1/user') Object.keys(profile).forEach(key => { // profile keys can't be empty strings, but they CAN be null if (profile[key] === '') profile[key] = null }) return fetchJSON(Object.assign({target: target, method: 'POST', body: profile}, conf)) } function listTokens (conf) { validate('O', arguments) return untilLastPage(`-/npm/v1/tokens`) function untilLastPage (href, objects) { return fetchJSON(Object.assign({target: url.resolve(conf.registry, href)}, conf)).then(result => { objects = objects ? objects.concat(result.objects) : result.objects if (result.urls.next) { return untilLastPage(result.urls.next, objects) } else { return objects } }) } } function removeToken (tokenKey, conf) { validate('SO', arguments) const target = url.resolve(conf.registry, `-/npm/v1/tokens/token/${tokenKey}`) return fetchJSON(Object.assign({target: target, method: 'DELETE'}, conf)) } function createToken (password, readonly, cidrs, conf) { validate('SBAO', arguments) const target = url.resolve(conf.registry, '-/npm/v1/tokens') const props = { password: password, readonly: readonly, cidr_whitelist: cidrs } return fetchJSON(Object.assign({target: target, method: 'POST', body: props}, conf)) } function FetchError (err, method, target) { err.method = method err.href = target return err } class HttpErrorBase extends Error { constructor (method, target, res, body) { super() this.headers = res.headers.raw() this.statusCode = res.status this.code = 'E' + res.status this.method = method this.target = target this.body = body this.pkgid = packageName(target) } } class General extends HttpErrorBase { constructor (method, target, res, body) { super(method, target, res, body) this.message = `Registry returned ${this.statusCode} for ${this.method} on ${this.href}` } } class AuthOTP extends HttpErrorBase { constructor (method, target, res, body) { super(method, target, res, body) this.message = 'OTP required for authentication' this.code = 'EOTP' Error.captureStackTrace(this, AuthOTP) } } class AuthIPAddress extends HttpErrorBase { constructor (res, body) { super(method, target, res, body) this.message = 'Login is not allowed from your IP address' this.code = 'EAUTHIP' Error.captureStackTrace(this, AuthIPAddress) } } class AuthUnknown extends HttpErrorBase { constructor (method, target, res, body) { super(method, target, res, body) this.message = 'Unable to authenticate, need: ' + res.headers.get('www-authenticate') this.code = 'EAUTHIP' Error.captureStackTrace(this, AuthIPAddress) } } function authHeaders (auth) { const headers = {} if (!auth) return headers if (auth.otp) headers['npm-otp'] = auth.otp if (auth.token) { headers['Authorization'] = 'Bearer ' + auth.token } else if (auth.basic) { const basic = auth.basic.username + ':' + auth.basic.password headers['Authorization'] = 'Basic ' + Buffer.from(basic).toString('base64') } return headers } function fetchJSON (conf) { const fetchOpts = { method: conf.method, headers: Object.assign({}, conf.headers || (conf.auth && authHeaders(conf.auth)) || {}) } if (conf.body != null) { fetchOpts.headers['Content-Type'] = 'application/json' fetchOpts.body = JSON.stringify(conf.body) } process.emit('log', 'http', 'request', '→',conf.method || 'GET', conf.target) return fetch.defaults(conf.opts || {})(conf.target, fetchOpts).catch(err => { throw new FetchError(err, conf.method, conf.target) }).then(res => { if (res.headers.get('content-type') === 'application/json') { return res.json().then(content => [res, content]) } else { return res.buffer().then(content => { try { return [res, JSON.parse(content)] } catch (_) { return [res, content] } }) } }).then(result => { const res = result[0] const content = result[1] process.emit('log', 'http', res.status, `← ${res.statusText} (${conf.target})`) if (res.status === 401 && res.headers.get('www-authenticate')) { const auth = res.headers.get('www-authenticate').split(/,\s*/).map(s => s.toLowerCase()) if (auth.indexOf('ipaddress') !== -1) { throw new AuthIPAddress(conf.method, conf.target, res, content) } else if (auth.indexOf('otp') !== -1) { throw new AuthOTP(conf.method, conf.target, res, content) } else { throw new AuthUnknown(conf.method, conf.target, res, content) } } else if (res.status < 200 || res.status >= 300) { if (typeof content === 'object' && content.error) { return content } else { throw new General(conf.method, conf.target, res, content) } } else { return content } }) } function packageName (href) { try { let basePath = url.parse(href).pathname.substr(1) if (!basePath.match(/^-/)) { basePath = basePath.split('/') var index = basePath.indexOf('_rewrite') if (index === -1) { index = basePath.length - 1 } else { index++ } return decodeURIComponent(basePath[index]) } } catch (_) { // this is ok } }