update
This commit is contained in:
278
package/node_modules/npm-profile/lib/index.js
generated
vendored
Normal file
278
package/node_modules/npm-profile/lib/index.js
generated
vendored
Normal file
@@ -0,0 +1,278 @@
|
||||
const { URL } = require('node:url')
|
||||
const timers = require('node:timers/promises')
|
||||
const fetch = require('npm-registry-fetch')
|
||||
const { HttpErrorBase } = require('npm-registry-fetch/lib/errors')
|
||||
const { log } = require('proc-log')
|
||||
|
||||
// try loginWeb, catch the "not supported" message and fall back to couch
|
||||
const login = async (opener, prompter, opts = {}) => {
|
||||
try {
|
||||
return await loginWeb(opener, opts)
|
||||
} catch (er) {
|
||||
if (er instanceof WebLoginNotSupported) {
|
||||
log.verbose('web login', 'not supported, trying couch')
|
||||
const { username, password } = await prompter(opts.creds)
|
||||
return loginCouch(username, password, opts)
|
||||
}
|
||||
throw er
|
||||
}
|
||||
}
|
||||
|
||||
const adduser = async (opener, prompter, opts = {}) => {
|
||||
try {
|
||||
return await adduserWeb(opener, opts)
|
||||
} catch (er) {
|
||||
if (er instanceof WebLoginNotSupported) {
|
||||
log.verbose('web adduser', 'not supported, trying couch')
|
||||
const { username, email, password } = await prompter(opts.creds)
|
||||
return adduserCouch(username, email, password, opts)
|
||||
}
|
||||
throw er
|
||||
}
|
||||
}
|
||||
|
||||
const adduserWeb = (opener, opts = {}) => {
|
||||
log.verbose('web adduser', 'before first POST')
|
||||
return webAuth(opener, opts, { create: true })
|
||||
}
|
||||
|
||||
const loginWeb = (opener, opts = {}) => {
|
||||
log.verbose('web login', 'before first POST')
|
||||
return webAuth(opener, opts, {})
|
||||
}
|
||||
|
||||
const isValidUrl = u => {
|
||||
try {
|
||||
return /^https?:$/.test(new URL(u).protocol)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const webAuth = async (opener, opts, body) => {
|
||||
try {
|
||||
const res = await fetch('/-/v1/login', {
|
||||
...opts,
|
||||
method: 'POST',
|
||||
body,
|
||||
})
|
||||
|
||||
const content = await res.json()
|
||||
log.verbose('web auth', 'got response', content)
|
||||
|
||||
const { doneUrl, loginUrl } = content
|
||||
if (!isValidUrl(doneUrl) || !isValidUrl(loginUrl)) {
|
||||
throw new WebLoginInvalidResponse('POST', res, content)
|
||||
}
|
||||
|
||||
return await webAuthOpener(opener, loginUrl, doneUrl, opts)
|
||||
} catch (er) {
|
||||
if ((er.statusCode >= 400 && er.statusCode <= 499) || er.statusCode === 500) {
|
||||
throw new WebLoginNotSupported('POST', {
|
||||
status: er.statusCode,
|
||||
headers: er.headers,
|
||||
}, er.body)
|
||||
}
|
||||
throw er
|
||||
}
|
||||
}
|
||||
|
||||
const webAuthOpener = async (opener, loginUrl, doneUrl, opts) => {
|
||||
const abortController = new AbortController()
|
||||
const { signal } = abortController
|
||||
try {
|
||||
log.verbose('web auth', 'opening url pair')
|
||||
const [, authResult] = await Promise.all([
|
||||
opener(loginUrl, { signal }).catch((err) => {
|
||||
if (err.name === 'AbortError') {
|
||||
abortController.abort()
|
||||
return
|
||||
}
|
||||
throw err
|
||||
}),
|
||||
webAuthCheckLogin(doneUrl, { ...opts, cache: false }, { signal }).then((r) => {
|
||||
log.verbose('web auth', 'done-check finished')
|
||||
abortController.abort()
|
||||
return r
|
||||
}),
|
||||
])
|
||||
return authResult
|
||||
} catch (er) {
|
||||
abortController.abort()
|
||||
throw er
|
||||
}
|
||||
}
|
||||
|
||||
const webAuthCheckLogin = async (doneUrl, opts, { signal } = {}) => {
|
||||
signal?.throwIfAborted()
|
||||
|
||||
const res = await fetch(doneUrl, opts)
|
||||
const content = await res.json()
|
||||
|
||||
if (res.status === 200) {
|
||||
if (!content.token) {
|
||||
throw new WebLoginInvalidResponse('GET', res, content)
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
if (res.status === 202) {
|
||||
const retry = +res.headers.get('retry-after') * 1000
|
||||
if (retry > 0) {
|
||||
await timers.setTimeout(retry, null, { ref: false, signal })
|
||||
}
|
||||
return webAuthCheckLogin(doneUrl, opts, { signal })
|
||||
}
|
||||
|
||||
throw new WebLoginInvalidResponse('GET', res, content)
|
||||
}
|
||||
|
||||
const couchEndpoint = (username) => `/-/user/org.couchdb.user:${encodeURIComponent(username)}`
|
||||
|
||||
const putCouch = async (path, username, body, opts) => {
|
||||
const result = await fetch.json(`${couchEndpoint(username)}${path}`, {
|
||||
...opts,
|
||||
method: 'PUT',
|
||||
body,
|
||||
})
|
||||
result.username = username
|
||||
return result
|
||||
}
|
||||
|
||||
const adduserCouch = async (username, email, password, opts = {}) => {
|
||||
const body = {
|
||||
_id: `org.couchdb.user:${username}`,
|
||||
name: username,
|
||||
password: password,
|
||||
email: email,
|
||||
type: 'user',
|
||||
roles: [],
|
||||
date: new Date().toISOString(),
|
||||
}
|
||||
|
||||
log.verbose('adduser', 'before first PUT', {
|
||||
...body,
|
||||
password: 'XXXXX',
|
||||
})
|
||||
|
||||
return putCouch('', username, body, opts)
|
||||
}
|
||||
|
||||
const loginCouch = async (username, password, opts = {}) => {
|
||||
const body = {
|
||||
_id: `org.couchdb.user:${username}`,
|
||||
name: username,
|
||||
password: password,
|
||||
type: 'user',
|
||||
roles: [],
|
||||
date: new Date().toISOString(),
|
||||
}
|
||||
|
||||
log.verbose('login', 'before first PUT', {
|
||||
...body,
|
||||
password: 'XXXXX',
|
||||
})
|
||||
|
||||
try {
|
||||
return await putCouch('', username, body, opts)
|
||||
} catch (err) {
|
||||
if (err.code === 'E400') {
|
||||
err.message = `There is no user with the username "${username}".`
|
||||
throw err
|
||||
}
|
||||
|
||||
if (err.code !== 'E409') {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
const result = await fetch.json(couchEndpoint(username), {
|
||||
...opts,
|
||||
query: { write: true },
|
||||
})
|
||||
|
||||
for (const k of Object.keys(result)) {
|
||||
if (!body[k] || k === 'roles') {
|
||||
body[k] = result[k]
|
||||
}
|
||||
}
|
||||
|
||||
return putCouch(`/-rev/${body._rev}`, username, body, {
|
||||
...opts,
|
||||
forceAuth: {
|
||||
username,
|
||||
password: Buffer.from(password, 'utf8').toString('base64'),
|
||||
otp: opts.otp,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const get = (opts = {}) => fetch.json('/-/npm/v1/user', opts)
|
||||
|
||||
const set = (profile, opts = {}) => fetch.json('/-/npm/v1/user', {
|
||||
...opts,
|
||||
method: 'POST',
|
||||
// profile keys can't be empty strings, but they CAN be null
|
||||
body: Object.fromEntries(Object.entries(profile).map(([k, v]) => [k, v === '' ? null : v])),
|
||||
})
|
||||
|
||||
const paginate = async (href, opts, items = []) => {
|
||||
const result = await fetch.json(href, opts)
|
||||
items = items.concat(result.objects)
|
||||
if (result.urls.next) {
|
||||
return paginate(result.urls.next, opts, items)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
const listTokens = (opts = {}) => paginate('/-/npm/v1/tokens', opts)
|
||||
|
||||
const removeToken = async (tokenKey, opts = {}) => {
|
||||
await fetch(`/-/npm/v1/tokens/token/${tokenKey}`, {
|
||||
...opts,
|
||||
method: 'DELETE',
|
||||
ignoreBody: true,
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
const createToken = (password, readonly, cidrs, opts = {}) => fetch.json('/-/npm/v1/tokens', {
|
||||
...opts,
|
||||
method: 'POST',
|
||||
body: {
|
||||
password: password,
|
||||
readonly: readonly,
|
||||
cidr_whitelist: cidrs,
|
||||
},
|
||||
})
|
||||
|
||||
class WebLoginInvalidResponse extends HttpErrorBase {
|
||||
constructor (method, res, body) {
|
||||
super(method, res, body)
|
||||
this.message = 'Invalid response from web login endpoint'
|
||||
}
|
||||
}
|
||||
|
||||
class WebLoginNotSupported extends HttpErrorBase {
|
||||
constructor (method, res, body) {
|
||||
super(method, res, body)
|
||||
this.message = 'Web login not supported'
|
||||
this.code = 'ENYI'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
adduserCouch,
|
||||
loginCouch,
|
||||
adduserWeb,
|
||||
loginWeb,
|
||||
login,
|
||||
adduser,
|
||||
get,
|
||||
set,
|
||||
listTokens,
|
||||
removeToken,
|
||||
createToken,
|
||||
webAuthCheckLogin,
|
||||
webAuthOpener,
|
||||
}
|
Reference in New Issue
Block a user