Add auth and blacklist APIs
This commit is contained in:
parent
cc70428986
commit
0e1f26849b
.gitignore
client
package.jsonpublic.keyscripts
server
AuthAPI.jsBlacklistAPI.jsPackageBlacklist.jsonStatsAPI.js
yarn.lock__tests__
actions
addToBlacklist.jscreateAuth.jsremoveFromBlacklist.jsshowAuth.jsshowBlacklist.jsshowPublicKey.jsshowStats.js
createServer.jscreateServer.test.jscreateStatsServer.jsmiddleware
utils/__tests__
|
@ -18,3 +18,6 @@ yarn-error.log*
|
||||||
|
|
||||||
# redis
|
# redis
|
||||||
dump.rdb
|
dump.rdb
|
||||||
|
|
||||||
|
# keys
|
||||||
|
private.key
|
||||||
|
|
|
@ -33,7 +33,7 @@ unpkg is not affiliated with or supported by npm, Inc. in any way. Please do not
|
||||||
|
|
||||||
### Abuse
|
### Abuse
|
||||||
|
|
||||||
unpkg maintains [a blacklist](https://github.com/unpkg/unpkg.com/blob/master/server/PackageBlacklist.json) of packages that are known to be malicious. If you find such a package on npm, please take a moment to submit a PR that adds it to the list!
|
unpkg maintains of packages that are known to be malicious. If you find such a package on npm, please let us know!
|
||||||
|
|
||||||
### Feedback
|
### Feedback
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import App from './App'
|
|
||||||
|
|
||||||
it('renders without crashing', () => {
|
|
||||||
const div = document.createElement('div')
|
|
||||||
ReactDOM.render(<App />, div)
|
|
||||||
})
|
|
|
@ -47,7 +47,7 @@ class Layout extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.adjustUnderline()
|
this.adjustUnderline()
|
||||||
|
|
||||||
fetch('/_stats/last-month')
|
fetch('/_stats?period=last-month')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(stats => this.setState({ stats }))
|
.then(stats => this.setState({ stats }))
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"algoliasearch": "^3.24.3",
|
"algoliasearch": "^3.24.3",
|
||||||
"babel-plugin-unpkg-rewrite": "^3.1.0",
|
"babel-plugin-unpkg-rewrite": "^3.1.0",
|
||||||
|
"body-parser": "^1.18.2",
|
||||||
"cors": "^2.8.1",
|
"cors": "^2.8.1",
|
||||||
"countries-list": "^1.3.2",
|
"countries-list": "^1.3.2",
|
||||||
"csso": "^3.1.1",
|
"csso": "^3.1.1",
|
||||||
|
@ -20,10 +21,12 @@
|
||||||
"gunzip-maybe": "^1.4.0",
|
"gunzip-maybe": "^1.4.0",
|
||||||
"invariant": "^2.2.2",
|
"invariant": "^2.2.2",
|
||||||
"isomorphic-fetch": "^2.2.1",
|
"isomorphic-fetch": "^2.2.1",
|
||||||
|
"jsonwebtoken": "^8.1.0",
|
||||||
"mime": "^1.4.0",
|
"mime": "^1.4.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"morgan": "^1.8.1",
|
"morgan": "^1.8.1",
|
||||||
"ndjson": "^1.5.0",
|
"ndjson": "^1.5.0",
|
||||||
|
"node-forge": "^0.7.1",
|
||||||
"os-tmpdir": "^1.0.2",
|
"os-tmpdir": "^1.0.2",
|
||||||
"pretty-bytes": "^3",
|
"pretty-bytes": "^3",
|
||||||
"prop-types": "^15.5.8",
|
"prop-types": "^15.5.8",
|
||||||
|
@ -91,7 +94,8 @@
|
||||||
"<rootDir>/config/polyfills.js"
|
"<rootDir>/config/polyfills.js"
|
||||||
],
|
],
|
||||||
"testPathIgnorePatterns": [
|
"testPathIgnorePatterns": [
|
||||||
"<rootDir>[/\\\\](build|docs|node_modules|scripts)[/\\\\]"
|
"<rootDir>[/\\\\](build|docs|node_modules|scripts)[/\\\\]",
|
||||||
|
"__tests__/utils"
|
||||||
],
|
],
|
||||||
"testEnvironment": "node",
|
"testEnvironment": "node",
|
||||||
"testURL": "http://localhost",
|
"testURL": "http://localhost",
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtWG6vJVKV8+hGDXtYS3i
|
||||||
|
JN8DO4xsKAM7n72IMH3489J1UUwdFdP3CKAAQzl8kcet/9q5CrLeUnW5oQNezQiC
|
||||||
|
FcSgF/KhJBITMWe5IIVWZOsFMvvNR+vISSL6We842gEAZWJbo2HZdFTdZjfino/4
|
||||||
|
CL3Sr0Ue9PFVHcVkT9V7uS7f/7VbwKFbxdpesYeq8odNFPQy6rhmSBT9v0mGK36K
|
||||||
|
f7kPuVqV7xlZ8nfiHdP+TAP2I4Iv2Ok7kMMy2qPjwizCShPcLIHzmyVdRuoUvxTf
|
||||||
|
cvC/cI3NUC7Qconn9tEtyvFzegdhS0tQD+Mq9eWAEZYp0rV/TkkaAYkIOkVQoiwQ
|
||||||
|
9QIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
|
@ -0,0 +1,120 @@
|
||||||
|
const BlacklistAPI = require('../server/BlacklistAPI')
|
||||||
|
|
||||||
|
const blacklist = [
|
||||||
|
'goodjsproject',
|
||||||
|
'thisoneisevil',
|
||||||
|
'03087dd164d4722425d74e095ff30bc2',
|
||||||
|
'sf1b195d16f3f3c695888e7cde1b20978f',
|
||||||
|
'sfd6e5f9f15adcc48d2fcb380e5aab44e5',
|
||||||
|
'sfd1f03b91ff97ff303bb69254ec3a4fd3',
|
||||||
|
'sf12fefe1d7b5bbe4d5661ff1bf6ea47bb',
|
||||||
|
'sfabae91ef175b31df2d7e77ed948206f7',
|
||||||
|
'sf149006f0b0c1c7e50e81181d9f5eba2d',
|
||||||
|
'sfe6bd27516125ae460d5c2e63feb70c97',
|
||||||
|
'sf09f01e9b87c212046c002a26f5117e87',
|
||||||
|
'sf77de34c6c6f180be3a03226cee219442',
|
||||||
|
'sf00e7081dec64c8557a40a79749e79d6c',
|
||||||
|
'sfb2c291b5ce9ee8cbc0ed4f9e7ab7c3d1',
|
||||||
|
'sf25d1f870f3355f4b02c34e65e451a8ef',
|
||||||
|
'sf18d48571145efc20316195ae19cf7aeb',
|
||||||
|
'sf694f2c2280ca2943d482059797ea1c97',
|
||||||
|
'sf2f8a5346ecda7e03f803b398dd40b869',
|
||||||
|
'sfaeebc97309de527e56215588f9c23dd3',
|
||||||
|
'sf2976f560d7ec7f8b63d7adca6728aed2',
|
||||||
|
'sf67de3bc862ca765e8cb9a72cf3453230',
|
||||||
|
'sfe906b050b2f380096de1c090dafdbb29',
|
||||||
|
'sfc98c62f745bac6d5f04c6e97e8294cec',
|
||||||
|
'sf979c8da13b915e5eaa5d84373a7c4a9b',
|
||||||
|
'sf6039ffb1c35d521773e4dcba7abf446c',
|
||||||
|
'sfb2adb7312558f6dde15d99619ee7da27',
|
||||||
|
'sfcba23f36564e58894e7aeebda67d3682',
|
||||||
|
'sfe0786b97c862c7485d9cec1b912bc634',
|
||||||
|
'sf024cf5fc99edfb929828b09084a9b6af',
|
||||||
|
'sf7d6d35c4dc2d6be739993cf054b00d35',
|
||||||
|
'sf78d0d2fbe897c94458a4822d624c969a',
|
||||||
|
'sf2b1bbe2c61f6f90cda322a61338003af',
|
||||||
|
'sff6a953f5f9960c2a1c907e186778d42f',
|
||||||
|
'sf748f0c277591c02438e9c325a8cb4ff5',
|
||||||
|
'sf606d995c90fd7c9fe17529a4697e4eb6',
|
||||||
|
'sf695459fe0eb159619268fdf542d3bb25',
|
||||||
|
'sf262b2b03ae6881a72365e05451b303ee',
|
||||||
|
'sf8a8a260ab891ae90eae8155548683053',
|
||||||
|
'sf9a307b3da7bfdcbb043c43857310d35b',
|
||||||
|
'sf3e5834662f7c780cbb60ee5740622d12',
|
||||||
|
'sff3a09cde265bde00a96ec01536a97029',
|
||||||
|
'sf364acb7b43655b3496aa65c7d6bb561a',
|
||||||
|
'sf89b5b735e7ba3bdfa52e7ae65026f9aa',
|
||||||
|
'sfca1c0beaf0309c9c322adddaa1cbd40b',
|
||||||
|
'sfef72db8254fe8714860d52da25b25fcc',
|
||||||
|
'sff3114fad67cd8b012706478c4b9fed39',
|
||||||
|
'sf1f60afa19f0a841aed6481bfcd91631c',
|
||||||
|
'sf785d637bd20673dbf2213f77e3df1cba',
|
||||||
|
'sf6142d76572f10a23113135dbee19dcf3',
|
||||||
|
'sf37544977bf1874afa4d4e9e282f2bf4a',
|
||||||
|
'sf0a49054aeba63d7f829eb3d02f0ad942',
|
||||||
|
'sfcce0358fa72b83d85569c22e715a920e',
|
||||||
|
'sf13890e52703bd39f71fb3815e555f0b7',
|
||||||
|
'sfe00cc3e9fec6974a4c1131bdb0ce5ee6',
|
||||||
|
'sf1343e7a08fa0ff25b6d215a3532bde13',
|
||||||
|
'sff4f6781701d8edd0e7909201c356d7c9',
|
||||||
|
'sf3d6dca96e72a14ecaed6ea97549fc088',
|
||||||
|
'sfaec6e7e100bfadbc4cf244adf277da15',
|
||||||
|
'sfa1b8f9052a194f3e9791769ce4cb352f',
|
||||||
|
'sf8cfdc43795d38e3d6ba6a57aae334c29',
|
||||||
|
'sf4606344a64d98d96120d5532b57b2a8b',
|
||||||
|
'sf7bdb20e4d622f6569f3e8503138c859d',
|
||||||
|
'sf0f4536523bf44a482a6bf466707c135e',
|
||||||
|
'sfc4a5204967899f098e8c6486cc60af7d',
|
||||||
|
'sf646dffa91307c680266d8e855b36f0be',
|
||||||
|
'sfa68a6cbd527e17f7e55b574b6a5a53ae',
|
||||||
|
'sff677d3897a4a7090aecfd5e13a6fc90b',
|
||||||
|
'sfbd0ffe85dc79f626ccb492a78aeda94f',
|
||||||
|
'sf37fe85de86dd3973db690594ae8b7bfc',
|
||||||
|
'sf54e90e7565b48823679af97d05189af4',
|
||||||
|
'sfb4ecfd026f1962076f3004cadc11931a',
|
||||||
|
'sfdb26212339dc18d5dd794fa800d4e5a6',
|
||||||
|
'sf4fdb84f60bad69846dd9fc9e2328a4f9',
|
||||||
|
'sfcfc008ec1b2a6daf70571b7480ba6aa3',
|
||||||
|
'sf1566a39461daff958cf2e4291ef13381',
|
||||||
|
'sf8a2b7d68f1c7c7f34381dc1a198465b4',
|
||||||
|
'sf3931b37c61d5b34186ca58f889d48047',
|
||||||
|
'sf109ad06b86e6be8f0c3e94d5e4893f47',
|
||||||
|
'sffefe6195a8b014a1cc7d9cf2449d1b50',
|
||||||
|
'sf85bacaf85076693e911b948b2c02535a',
|
||||||
|
'sf340a5f85afd785510da83f9cabf15726',
|
||||||
|
'sffee5fae47344c13e9d7c6db0bb403b76',
|
||||||
|
'sf4ec8bfe49e5a941b82bd07927f198b5d',
|
||||||
|
'sf2b10045997d4f1f120a5393be267cd52',
|
||||||
|
'sf14d2825be098ee2f80ead23cb181b8e4',
|
||||||
|
'sfc1c052ab23baf866cc73b3c585c65503',
|
||||||
|
'sfed9d0c920ecc6694c82ae859c1699758',
|
||||||
|
'sf15c3851aa68992e9b80ec11211e401bc',
|
||||||
|
'sf9c16721aff8f5ebb4fe7731a409eb622',
|
||||||
|
'sfc65f86a6d8598a4171dec7f4c99fc856',
|
||||||
|
'sf9b9ab3f53b6a705d772ca41a233be838',
|
||||||
|
'sf0d200aa244146e0054f93c7f98c134c8',
|
||||||
|
'sf3d9b13c2b94dea2a11a697d11f3312a8',
|
||||||
|
'sfdf5195f21fffc06298b7c0b4f6bcb9ba',
|
||||||
|
'sff25c5beafcd6f66bc2cc21e84f8aec85',
|
||||||
|
'copyfish-npm-2-8-5',
|
||||||
|
'54e90e7565b48823679af97d05189af4',
|
||||||
|
'15c3851aa68992e9b80ec11211e401bc',
|
||||||
|
'4fdb84f60bad69846dd9fc9e2328a4f9',
|
||||||
|
'14d2825be098ee2f80ead23cb181b8e4',
|
||||||
|
'024cf5fc99edfb929828b09084a9b6af',
|
||||||
|
'8a2b7d68f1c7c7f34381dc1a198465b4',
|
||||||
|
'7bdb20e4d622f6569f3e8503138c859d',
|
||||||
|
'694f2c2280ca2943d482059797ea1c97',
|
||||||
|
'b2c291b5ce9ee8cbc0ed4f9e7ab7c3d1',
|
||||||
|
'9b9ab3f53b6a705d772ca41a233be838',
|
||||||
|
'df5195f21fffc06298b7c0b4f6bcb9ba',
|
||||||
|
'fefe6195a8b014a1cc7d9cf2449d1b50',
|
||||||
|
'fee5fae47344c13e9d7c6db0bb403b76',
|
||||||
|
'2976f560d7ec7f8b63d7adca6728aed2',
|
||||||
|
'd6e5f9f15adcc48d2fcb380e5aab44e5',
|
||||||
|
'c98c62f745bac6d5f04c6e97e8294cec',
|
||||||
|
'abae91ef175b31df2d7e77ed948206f7',
|
||||||
|
'09f01e9b87c212046c002a26f5117e87'
|
||||||
|
]
|
||||||
|
|
||||||
|
blacklist.forEach(BlacklistAPI.addPackage)
|
|
@ -0,0 +1,19 @@
|
||||||
|
const AuthAPI = require('../server/AuthAPI')
|
||||||
|
|
||||||
|
const scopes = {}
|
||||||
|
|
||||||
|
AuthAPI.createToken(scopes).then(
|
||||||
|
token => {
|
||||||
|
// Verify it, just to be sure.
|
||||||
|
AuthAPI.verifyToken(token).then(payload => {
|
||||||
|
console.log(token, '\n')
|
||||||
|
console.log(JSON.stringify(payload, null, 2), '\n')
|
||||||
|
console.log(AuthAPI.getPublicKey())
|
||||||
|
process.exit()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error(error)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
)
|
|
@ -0,0 +1,118 @@
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const crypto = require('crypto')
|
||||||
|
const jwt = require('jsonwebtoken')
|
||||||
|
const invariant = require('invariant')
|
||||||
|
const forge = require('node-forge')
|
||||||
|
const db = require('./RedisClient')
|
||||||
|
|
||||||
|
let keys
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
keys = {
|
||||||
|
public: fs.readFileSync(path.resolve(__dirname, '../public.key'), 'utf8'),
|
||||||
|
private: process.env.PRIVATE_KEY
|
||||||
|
}
|
||||||
|
|
||||||
|
invariant(keys.private, 'Missing $PRIVATE_KEY environment variable')
|
||||||
|
} else {
|
||||||
|
// Generate a random keypair for dev/testing.
|
||||||
|
// See https://gist.github.com/sebadoom/2b70969e70db5da9a203bebd9cff099f
|
||||||
|
const keypair = forge.rsa.generateKeyPair({ bits: 2048 })
|
||||||
|
keys = {
|
||||||
|
public: forge.pki.publicKeyToPem(keypair.publicKey, 72),
|
||||||
|
private: forge.pki.privateKeyToPem(keypair.privateKey, 72)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentSeconds() {
|
||||||
|
return Math.floor(Date.now() / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTokenId() {
|
||||||
|
return crypto.randomBytes(16).toString('hex')
|
||||||
|
}
|
||||||
|
|
||||||
|
function createToken(scopes = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const payload = {
|
||||||
|
jti: createTokenId(),
|
||||||
|
iss: 'https://unpkg.com',
|
||||||
|
iat: getCurrentSeconds(),
|
||||||
|
scopes
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt.sign(payload, keys.private, { algorithm: 'RS256' }, (error, token) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
resolve(token)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const RevokedTokensSet = 'revoked-tokens'
|
||||||
|
|
||||||
|
function verifyToken(token) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const options = { algorithms: ['RS256'] }
|
||||||
|
|
||||||
|
jwt.verify(token, keys.public, options, (error, payload) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
if (payload.jti) {
|
||||||
|
db.sismember(RevokedTokensSet, payload.jti, (error, value) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
resolve(value === 0 ? payload : null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
resolve(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function revokeToken(token) {
|
||||||
|
return verifyToken(token).then(payload => {
|
||||||
|
if (payload) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.sadd(RevokedTokensSet, payload.jti, error => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAllRevokedTokens() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.del(RevokedTokensSet, error => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPublicKey() {
|
||||||
|
return keys.public
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createToken,
|
||||||
|
verifyToken,
|
||||||
|
revokeToken,
|
||||||
|
removeAllRevokedTokens,
|
||||||
|
getPublicKey
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
const db = require('./RedisClient')
|
||||||
|
|
||||||
|
const BlacklistSet = 'blacklisted-packages'
|
||||||
|
|
||||||
|
function addPackage(packageName) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.sadd(BlacklistSet, packageName, (error, value) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
resolve(value === 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePackage(packageName) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.srem(BlacklistSet, packageName, (error, value) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
resolve(value === 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAllPackages() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.del(BlacklistSet, error => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPackages() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.smembers(BlacklistSet, (error, value) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
resolve(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function containsPackage(packageName) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.sismember(BlacklistSet, packageName, (error, value) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
resolve(value === 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
addPackage,
|
||||||
|
removePackage,
|
||||||
|
removeAllPackages,
|
||||||
|
getPackages,
|
||||||
|
containsPackage
|
||||||
|
}
|
|
@ -1,118 +0,0 @@
|
||||||
{
|
|
||||||
"blacklist": [
|
|
||||||
"goodjsproject",
|
|
||||||
"thisoneisevil",
|
|
||||||
"03087dd164d4722425d74e095ff30bc2",
|
|
||||||
"sf1b195d16f3f3c695888e7cde1b20978f",
|
|
||||||
"sfd6e5f9f15adcc48d2fcb380e5aab44e5",
|
|
||||||
"sfd1f03b91ff97ff303bb69254ec3a4fd3",
|
|
||||||
"sf12fefe1d7b5bbe4d5661ff1bf6ea47bb",
|
|
||||||
"sfabae91ef175b31df2d7e77ed948206f7",
|
|
||||||
"sf149006f0b0c1c7e50e81181d9f5eba2d",
|
|
||||||
"sfe6bd27516125ae460d5c2e63feb70c97",
|
|
||||||
"sf09f01e9b87c212046c002a26f5117e87",
|
|
||||||
"sf77de34c6c6f180be3a03226cee219442",
|
|
||||||
"sf00e7081dec64c8557a40a79749e79d6c",
|
|
||||||
"sfb2c291b5ce9ee8cbc0ed4f9e7ab7c3d1",
|
|
||||||
"sf25d1f870f3355f4b02c34e65e451a8ef",
|
|
||||||
"sf18d48571145efc20316195ae19cf7aeb",
|
|
||||||
"sf694f2c2280ca2943d482059797ea1c97",
|
|
||||||
"sf2f8a5346ecda7e03f803b398dd40b869",
|
|
||||||
"sfaeebc97309de527e56215588f9c23dd3",
|
|
||||||
"sf2976f560d7ec7f8b63d7adca6728aed2",
|
|
||||||
"sf67de3bc862ca765e8cb9a72cf3453230",
|
|
||||||
"sfe906b050b2f380096de1c090dafdbb29",
|
|
||||||
"sfc98c62f745bac6d5f04c6e97e8294cec",
|
|
||||||
"sf979c8da13b915e5eaa5d84373a7c4a9b",
|
|
||||||
"sf6039ffb1c35d521773e4dcba7abf446c",
|
|
||||||
"sfb2adb7312558f6dde15d99619ee7da27",
|
|
||||||
"sfcba23f36564e58894e7aeebda67d3682",
|
|
||||||
"sfe0786b97c862c7485d9cec1b912bc634",
|
|
||||||
"sf024cf5fc99edfb929828b09084a9b6af",
|
|
||||||
"sf7d6d35c4dc2d6be739993cf054b00d35",
|
|
||||||
"sf78d0d2fbe897c94458a4822d624c969a",
|
|
||||||
"sf2b1bbe2c61f6f90cda322a61338003af",
|
|
||||||
"sff6a953f5f9960c2a1c907e186778d42f",
|
|
||||||
"sf748f0c277591c02438e9c325a8cb4ff5",
|
|
||||||
"sf606d995c90fd7c9fe17529a4697e4eb6",
|
|
||||||
"sf695459fe0eb159619268fdf542d3bb25",
|
|
||||||
"sf262b2b03ae6881a72365e05451b303ee",
|
|
||||||
"sf8a8a260ab891ae90eae8155548683053",
|
|
||||||
"sf9a307b3da7bfdcbb043c43857310d35b",
|
|
||||||
"sf3e5834662f7c780cbb60ee5740622d12",
|
|
||||||
"sff3a09cde265bde00a96ec01536a97029",
|
|
||||||
"sf364acb7b43655b3496aa65c7d6bb561a",
|
|
||||||
"sf89b5b735e7ba3bdfa52e7ae65026f9aa",
|
|
||||||
"sfca1c0beaf0309c9c322adddaa1cbd40b",
|
|
||||||
"sfef72db8254fe8714860d52da25b25fcc",
|
|
||||||
"sff3114fad67cd8b012706478c4b9fed39",
|
|
||||||
"sf1f60afa19f0a841aed6481bfcd91631c",
|
|
||||||
"sf785d637bd20673dbf2213f77e3df1cba",
|
|
||||||
"sf6142d76572f10a23113135dbee19dcf3",
|
|
||||||
"sf37544977bf1874afa4d4e9e282f2bf4a",
|
|
||||||
"sf0a49054aeba63d7f829eb3d02f0ad942",
|
|
||||||
"sfcce0358fa72b83d85569c22e715a920e",
|
|
||||||
"sf13890e52703bd39f71fb3815e555f0b7",
|
|
||||||
"sfe00cc3e9fec6974a4c1131bdb0ce5ee6",
|
|
||||||
"sf1343e7a08fa0ff25b6d215a3532bde13",
|
|
||||||
"sff4f6781701d8edd0e7909201c356d7c9",
|
|
||||||
"sf3d6dca96e72a14ecaed6ea97549fc088",
|
|
||||||
"sfaec6e7e100bfadbc4cf244adf277da15",
|
|
||||||
"sfa1b8f9052a194f3e9791769ce4cb352f",
|
|
||||||
"sf8cfdc43795d38e3d6ba6a57aae334c29",
|
|
||||||
"sf4606344a64d98d96120d5532b57b2a8b",
|
|
||||||
"sf7bdb20e4d622f6569f3e8503138c859d",
|
|
||||||
"sf0f4536523bf44a482a6bf466707c135e",
|
|
||||||
"sfc4a5204967899f098e8c6486cc60af7d",
|
|
||||||
"sf646dffa91307c680266d8e855b36f0be",
|
|
||||||
"sfa68a6cbd527e17f7e55b574b6a5a53ae",
|
|
||||||
"sff677d3897a4a7090aecfd5e13a6fc90b",
|
|
||||||
"sfbd0ffe85dc79f626ccb492a78aeda94f",
|
|
||||||
"sf37fe85de86dd3973db690594ae8b7bfc",
|
|
||||||
"sf54e90e7565b48823679af97d05189af4",
|
|
||||||
"sfb4ecfd026f1962076f3004cadc11931a",
|
|
||||||
"sfdb26212339dc18d5dd794fa800d4e5a6",
|
|
||||||
"sf4fdb84f60bad69846dd9fc9e2328a4f9",
|
|
||||||
"sfcfc008ec1b2a6daf70571b7480ba6aa3",
|
|
||||||
"sf1566a39461daff958cf2e4291ef13381",
|
|
||||||
"sf8a2b7d68f1c7c7f34381dc1a198465b4",
|
|
||||||
"sf3931b37c61d5b34186ca58f889d48047",
|
|
||||||
"sf109ad06b86e6be8f0c3e94d5e4893f47",
|
|
||||||
"sffefe6195a8b014a1cc7d9cf2449d1b50",
|
|
||||||
"sf85bacaf85076693e911b948b2c02535a",
|
|
||||||
"sf340a5f85afd785510da83f9cabf15726",
|
|
||||||
"sffee5fae47344c13e9d7c6db0bb403b76",
|
|
||||||
"sf4ec8bfe49e5a941b82bd07927f198b5d",
|
|
||||||
"sf2b10045997d4f1f120a5393be267cd52",
|
|
||||||
"sf14d2825be098ee2f80ead23cb181b8e4",
|
|
||||||
"sfc1c052ab23baf866cc73b3c585c65503",
|
|
||||||
"sfed9d0c920ecc6694c82ae859c1699758",
|
|
||||||
"sf15c3851aa68992e9b80ec11211e401bc",
|
|
||||||
"sf9c16721aff8f5ebb4fe7731a409eb622",
|
|
||||||
"sfc65f86a6d8598a4171dec7f4c99fc856",
|
|
||||||
"sf9b9ab3f53b6a705d772ca41a233be838",
|
|
||||||
"sf0d200aa244146e0054f93c7f98c134c8",
|
|
||||||
"sf3d9b13c2b94dea2a11a697d11f3312a8",
|
|
||||||
"sfdf5195f21fffc06298b7c0b4f6bcb9ba",
|
|
||||||
"sff25c5beafcd6f66bc2cc21e84f8aec85",
|
|
||||||
"copyfish-npm-2-8-5",
|
|
||||||
"54e90e7565b48823679af97d05189af4",
|
|
||||||
"15c3851aa68992e9b80ec11211e401bc",
|
|
||||||
"4fdb84f60bad69846dd9fc9e2328a4f9",
|
|
||||||
"14d2825be098ee2f80ead23cb181b8e4",
|
|
||||||
"024cf5fc99edfb929828b09084a9b6af",
|
|
||||||
"8a2b7d68f1c7c7f34381dc1a198465b4",
|
|
||||||
"7bdb20e4d622f6569f3e8503138c859d",
|
|
||||||
"694f2c2280ca2943d482059797ea1c97",
|
|
||||||
"b2c291b5ce9ee8cbc0ed4f9e7ab7c3d1",
|
|
||||||
"9b9ab3f53b6a705d772ca41a233be838",
|
|
||||||
"df5195f21fffc06298b7c0b4f6bcb9ba",
|
|
||||||
"fefe6195a8b014a1cc7d9cf2449d1b50",
|
|
||||||
"fee5fae47344c13e9d7c6db0bb403b76",
|
|
||||||
"2976f560d7ec7f8b63d7adca6728aed2",
|
|
||||||
"d6e5f9f15adcc48d2fcb380e5aab44e5",
|
|
||||||
"c98c62f745bac6d5f04c6e97e8294cec",
|
|
||||||
"abae91ef175b31df2d7e77ed948206f7",
|
|
||||||
"09f01e9b87c212046c002a26f5117e87"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,14 +1,17 @@
|
||||||
const cf = require('./CloudflareAPI')
|
|
||||||
const db = require('./RedisClient')
|
const db = require('./RedisClient')
|
||||||
|
const CloudflareAPI = require('./CloudflareAPI')
|
||||||
const PackageBlacklist = require('./PackageBlacklist').blacklist
|
const BlacklistAPI = require('./BlacklistAPI')
|
||||||
|
|
||||||
function prunePackages(packagesMap) {
|
function prunePackages(packagesMap) {
|
||||||
PackageBlacklist.forEach(packageName => {
|
return Promise.all(
|
||||||
delete packagesMap[packageName]
|
Object.keys(packagesMap).map(packageName =>
|
||||||
})
|
BlacklistAPI.isBlacklisted(packageName).then(blacklisted => {
|
||||||
|
if (blacklisted) {
|
||||||
return packagesMap
|
delete packagesMap[packageName]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).then(() => packagesMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDayKey(date) {
|
function createDayKey(date) {
|
||||||
|
@ -87,25 +90,25 @@ function sumMaps(maps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function addDailyMetrics(result) {
|
function addDailyMetrics(result) {
|
||||||
return Promise.all(result.timeseries.map(addDailyMetricsToTimeseries)).then(
|
return Promise.all(
|
||||||
() => {
|
result.timeseries.map(addDailyMetricsToTimeseries)
|
||||||
result.totals.requests.package = sumMaps(
|
).then(() => {
|
||||||
result.timeseries.map(timeseries => {
|
result.totals.requests.package = sumMaps(
|
||||||
return timeseries.requests.package
|
result.timeseries.map(timeseries => {
|
||||||
})
|
return timeseries.requests.package
|
||||||
)
|
})
|
||||||
|
)
|
||||||
|
|
||||||
result.totals.bandwidth.package = sumMaps(
|
result.totals.bandwidth.package = sumMaps(
|
||||||
result.timeseries.map(timeseries => timeseries.bandwidth.package)
|
result.timeseries.map(timeseries => timeseries.bandwidth.package)
|
||||||
)
|
)
|
||||||
|
|
||||||
result.totals.requests.protocol = sumMaps(
|
result.totals.requests.protocol = sumMaps(
|
||||||
result.timeseries.map(timeseries => timeseries.requests.protocol)
|
result.timeseries.map(timeseries => timeseries.requests.protocol)
|
||||||
)
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractPublicInfo(data) {
|
function extractPublicInfo(data) {
|
||||||
|
@ -140,8 +143,12 @@ function extractPublicInfo(data) {
|
||||||
const DomainNames = ['unpkg.com', 'npmcdn.com']
|
const DomainNames = ['unpkg.com', 'npmcdn.com']
|
||||||
|
|
||||||
function fetchStats(since, until) {
|
function fetchStats(since, until) {
|
||||||
return cf.getZones(DomainNames).then(zones => {
|
return CloudflareAPI.getZones(DomainNames).then(zones => {
|
||||||
return cf.getZoneAnalyticsDashboard(zones, since, until).then(dashboard => {
|
return CloudflareAPI.getZoneAnalyticsDashboard(
|
||||||
|
zones,
|
||||||
|
since,
|
||||||
|
until
|
||||||
|
).then(dashboard => {
|
||||||
return {
|
return {
|
||||||
timeseries: dashboard.timeseries.map(extractPublicInfo),
|
timeseries: dashboard.timeseries.map(extractPublicInfo),
|
||||||
totals: extractPublicInfo(dashboard.totals)
|
totals: extractPublicInfo(dashboard.totals)
|
||||||
|
@ -154,14 +161,9 @@ const oneMinute = 1000 * 60
|
||||||
const oneHour = oneMinute * 60
|
const oneHour = oneMinute * 60
|
||||||
const oneDay = oneHour * 24
|
const oneDay = oneHour * 24
|
||||||
|
|
||||||
function getStats(since, until, callback) {
|
function getStats(since, until) {
|
||||||
let promise = fetchStats(since, until)
|
const promise = fetchStats(since, until)
|
||||||
|
return until - since > oneDay ? promise.then(addDailyMetrics) : promise
|
||||||
if (until - since > oneDay) promise = promise.then(addDailyMetrics)
|
|
||||||
|
|
||||||
promise.then(value => {
|
|
||||||
callback(null, value)
|
|
||||||
}, callback)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
const AuthAPI = require('../AuthAPI')
|
||||||
|
|
||||||
|
describe('Auth API', () => {
|
||||||
|
beforeEach(done => {
|
||||||
|
AuthAPI.removeAllRevokedTokens().then(() => done(), done)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates tokens with the right scopes', done => {
|
||||||
|
const scopes = {
|
||||||
|
blacklist: {
|
||||||
|
add: true,
|
||||||
|
remove: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthAPI.createToken(scopes).then(token => {
|
||||||
|
AuthAPI.verifyToken(token).then(payload => {
|
||||||
|
expect(payload.jti).toEqual(expect.any(String))
|
||||||
|
expect(payload.iss).toEqual(expect.any(String))
|
||||||
|
expect(payload.iat).toEqual(expect.any(Number))
|
||||||
|
expect(payload.scopes).toMatchObject(scopes)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('refuses to verify revoked tokens', done => {
|
||||||
|
const scopes = {}
|
||||||
|
|
||||||
|
AuthAPI.createToken(scopes).then(token => {
|
||||||
|
AuthAPI.revokeToken(token).then(() => {
|
||||||
|
AuthAPI.verifyToken(token).then(payload => {
|
||||||
|
expect(payload).toBe(null)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,24 @@
|
||||||
|
const BlacklistAPI = require('../BlacklistAPI')
|
||||||
|
|
||||||
|
describe('Blacklist API', () => {
|
||||||
|
beforeEach(done => {
|
||||||
|
BlacklistAPI.removeAllPackages().then(() => done(), done)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds and removes packages to/from the blacklist', done => {
|
||||||
|
const packageName = 'bad-package'
|
||||||
|
|
||||||
|
BlacklistAPI.addPackage(packageName).then(() => {
|
||||||
|
BlacklistAPI.getPackages().then(packageNames => {
|
||||||
|
expect(packageNames).toEqual([packageName])
|
||||||
|
|
||||||
|
BlacklistAPI.removePackage(packageName).then(() => {
|
||||||
|
BlacklistAPI.getPackages().then(packageNames => {
|
||||||
|
expect(packageNames).toEqual([])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,201 @@
|
||||||
|
const request = require('supertest')
|
||||||
|
const createServer = require('../createServer')
|
||||||
|
const withBlacklist = require('./utils/withBlacklist')
|
||||||
|
const withRevokedToken = require('./utils/withRevokedToken')
|
||||||
|
const withToken = require('./utils/withToken')
|
||||||
|
|
||||||
|
describe('The server', () => {
|
||||||
|
let server
|
||||||
|
beforeEach(() => {
|
||||||
|
server = createServer()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects invalid package names', done => {
|
||||||
|
request(server)
|
||||||
|
.get('/_invalid/index.js')
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('redirects invalid query params', done => {
|
||||||
|
request(server)
|
||||||
|
.get('/react?main=index&invalid')
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.statusCode).toBe(302)
|
||||||
|
expect(res.headers.location).toBe('/react?main=index')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('redirects /_meta to ?meta', done => {
|
||||||
|
request(server)
|
||||||
|
.get('/_meta/react?main=index')
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.statusCode).toBe(302)
|
||||||
|
expect(res.headers.location).toBe('/react?main=index&meta')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not serve blacklisted packages', done => {
|
||||||
|
withBlacklist(['bad-package'], () => {
|
||||||
|
request(server)
|
||||||
|
.get('/bad-package/index.js')
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('POST /_auth', () => {
|
||||||
|
it('creates a new auth token', done => {
|
||||||
|
request(server)
|
||||||
|
.post('/_auth')
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.body).toHaveProperty('token')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GET /_auth', () => {
|
||||||
|
describe('with no auth', () => {
|
||||||
|
it('echoes back null', done => {
|
||||||
|
request(server)
|
||||||
|
.get('/_auth')
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.body).toHaveProperty('auth')
|
||||||
|
expect(res.body.auth).toBe(null)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with a revoked auth token', () => {
|
||||||
|
it('echoes back null', done => {
|
||||||
|
withRevokedToken({ some: { scope: true } }, token => {
|
||||||
|
request(server)
|
||||||
|
.get('/_auth?token=' + token)
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.body).toHaveProperty('auth')
|
||||||
|
expect(res.body.auth).toBe(null)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with a valid auth token', () => {
|
||||||
|
it('echoes back the auth payload', done => {
|
||||||
|
withToken({ some: { scope: true } }, token => {
|
||||||
|
request(server)
|
||||||
|
.get('/_auth?token=' + token)
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.body).toHaveProperty('auth')
|
||||||
|
expect(typeof res.body.auth).toBe('object')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GET /_publicKey', () => {
|
||||||
|
it('echoes the public key', done => {
|
||||||
|
request(server)
|
||||||
|
.get('/_publicKey')
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.text).toMatch(/PUBLIC KEY/)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('POST /_blacklist', () => {
|
||||||
|
describe('with no auth', () => {
|
||||||
|
it('is forbidden', done => {
|
||||||
|
request(server)
|
||||||
|
.post('/_blacklist')
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with the "blacklist.add" scope', () => {
|
||||||
|
it('can add to the blacklist', done => {
|
||||||
|
withToken({ blacklist: { add: true } }, token => {
|
||||||
|
request(server)
|
||||||
|
.post('/_blacklist')
|
||||||
|
.send({ token, packageName: 'bad-package' })
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
expect(res.headers['content-location']).toEqual(
|
||||||
|
'/_blacklist/bad-package'
|
||||||
|
)
|
||||||
|
expect(res.body.ok).toBe(true)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GET /_blacklist', () => {
|
||||||
|
describe('with no auth', () => {
|
||||||
|
it('is forbidden', done => {
|
||||||
|
request(server)
|
||||||
|
.get('/_blacklist')
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with the "blacklist.read" scope', () => {
|
||||||
|
it('can read the blacklist', done => {
|
||||||
|
withToken({ blacklist: { read: true } }, token => {
|
||||||
|
request(server)
|
||||||
|
.get('/_blacklist?token=' + token)
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('DELETE /_blacklist/:packageName', () => {
|
||||||
|
describe('with no auth', () => {
|
||||||
|
it('is forbidden', done => {
|
||||||
|
request(server)
|
||||||
|
.delete('/_blacklist/bad-package')
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with the "blacklist.remove" scope', () => {
|
||||||
|
it('can remove a package from the blacklist', done => {
|
||||||
|
withToken({ blacklist: { remove: true } }, token => {
|
||||||
|
request(server)
|
||||||
|
.delete('/_blacklist/bad-package')
|
||||||
|
.send({ token })
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
expect(res.body.ok).toBe(true)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,7 @@
|
||||||
|
const BlacklistAPI = require('../../BlacklistAPI')
|
||||||
|
|
||||||
|
function withBlacklist(blacklist, callback) {
|
||||||
|
return Promise.all(blacklist.map(BlacklistAPI.addPackage)).then(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = withBlacklist
|
|
@ -0,0 +1,12 @@
|
||||||
|
const withToken = require('./withToken')
|
||||||
|
const AuthAPI = require('../../AuthAPI')
|
||||||
|
|
||||||
|
function withRevokedToken(scopes, callback) {
|
||||||
|
withToken(scopes, token => {
|
||||||
|
AuthAPI.revokeToken(token).then(() => {
|
||||||
|
callback(token)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = withRevokedToken
|
|
@ -0,0 +1,7 @@
|
||||||
|
const AuthAPI = require('../../AuthAPI')
|
||||||
|
|
||||||
|
function withToken(scopes, callback) {
|
||||||
|
AuthAPI.createToken(scopes).then(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = withToken
|
|
@ -0,0 +1,48 @@
|
||||||
|
const validateNpmPackageName = require('validate-npm-package-name')
|
||||||
|
const BlacklistAPI = require('../BlacklistAPI')
|
||||||
|
|
||||||
|
function addToBlacklist(req, res) {
|
||||||
|
const packageName = req.body.packageName
|
||||||
|
|
||||||
|
if (!packageName) {
|
||||||
|
return res
|
||||||
|
.status(403)
|
||||||
|
.send({ error: 'Missing "packageName" body parameter' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const nameErrors = validateNpmPackageName(packageName).errors
|
||||||
|
|
||||||
|
// Disallow invalid package names.
|
||||||
|
if (nameErrors) {
|
||||||
|
const reason = nameErrors.join(', ')
|
||||||
|
return res.status(403).send({
|
||||||
|
error: `Invalid package name "${packageName}" (${reason})`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
BlacklistAPI.addPackage(packageName).then(
|
||||||
|
added => {
|
||||||
|
if (added) {
|
||||||
|
const userId = req.user.jti
|
||||||
|
console.log(
|
||||||
|
`Package "${packageName}" was added to the blacklist by ${userId}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.set({ 'Content-Location': `/_blacklist/${packageName}` }).send({
|
||||||
|
ok: true,
|
||||||
|
message: `Package "${packageName}" was ${added
|
||||||
|
? 'added to'
|
||||||
|
: 'already in'} the blacklist`
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error(error)
|
||||||
|
res.status(500).send({
|
||||||
|
error: `Unable to add "${packageName}" to the blacklist`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = addToBlacklist
|
|
@ -0,0 +1,24 @@
|
||||||
|
const AuthAPI = require('../AuthAPI')
|
||||||
|
|
||||||
|
const DefaultScopes = {
|
||||||
|
blacklist: {
|
||||||
|
read: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAuth(req, res) {
|
||||||
|
AuthAPI.createToken(DefaultScopes).then(
|
||||||
|
token => {
|
||||||
|
res.send({ token })
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
res.status(500).send({
|
||||||
|
error: 'Unable to generate auth token'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = createAuth
|
|
@ -0,0 +1,42 @@
|
||||||
|
const validateNpmPackageName = require('validate-npm-package-name')
|
||||||
|
const BlacklistAPI = require('../BlacklistAPI')
|
||||||
|
|
||||||
|
function removeFromBlacklist(req, res) {
|
||||||
|
const packageName = req.params.packageName
|
||||||
|
|
||||||
|
const nameErrors = validateNpmPackageName(packageName).errors
|
||||||
|
|
||||||
|
// Disallow invalid package names.
|
||||||
|
if (nameErrors) {
|
||||||
|
const reason = nameErrors.join(', ')
|
||||||
|
return res.status(403).send({
|
||||||
|
error: `Invalid package name "${packageName}" (${reason})`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
BlacklistAPI.removePackage(packageName).then(
|
||||||
|
removed => {
|
||||||
|
if (removed) {
|
||||||
|
const userId = req.user.jti
|
||||||
|
console.log(
|
||||||
|
`Package "${packageName}" was removed from the blacklist by ${userId}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
ok: true,
|
||||||
|
message: `Package "${packageName}" was ${removed
|
||||||
|
? 'removed from'
|
||||||
|
: 'not in'} the blacklist`
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error(error)
|
||||||
|
res.status(500).send({
|
||||||
|
error: `Unable to remove "${packageName}" from the blacklist`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = removeFromBlacklist
|
|
@ -0,0 +1,5 @@
|
||||||
|
function showAuth(req, res) {
|
||||||
|
res.send({ auth: req.user })
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = showAuth
|
|
@ -0,0 +1,17 @@
|
||||||
|
const BlacklistAPI = require('../BlacklistAPI')
|
||||||
|
|
||||||
|
function showBlacklist(req, res) {
|
||||||
|
BlacklistAPI.getPackages().then(
|
||||||
|
blacklist => {
|
||||||
|
res.send({ blacklist })
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error(error)
|
||||||
|
res.status(500).send({
|
||||||
|
error: 'Unable to fetch blacklist'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = showBlacklist
|
|
@ -0,0 +1,7 @@
|
||||||
|
const AuthAPI = require('../AuthAPI')
|
||||||
|
|
||||||
|
function showPublicKey(req, res) {
|
||||||
|
res.type('text').send(AuthAPI.getPublicKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = showPublicKey
|
|
@ -0,0 +1,62 @@
|
||||||
|
const subDays = require('date-fns/sub_days')
|
||||||
|
const startOfDay = require('date-fns/start_of_day')
|
||||||
|
const startOfSecond = require('date-fns/start_of_second')
|
||||||
|
const StatsAPI = require('../StatsAPI')
|
||||||
|
|
||||||
|
function showStats(req, res) {
|
||||||
|
let since, until
|
||||||
|
switch (req.query.period) {
|
||||||
|
case 'last-day':
|
||||||
|
until = startOfDay(new Date())
|
||||||
|
since = subDays(until, 1)
|
||||||
|
break
|
||||||
|
case 'last-week':
|
||||||
|
until = startOfDay(new Date())
|
||||||
|
since = subDays(until, 7)
|
||||||
|
break
|
||||||
|
case 'last-month':
|
||||||
|
until = startOfDay(new Date())
|
||||||
|
since = subDays(until, 30)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
until = req.query.until
|
||||||
|
? new Date(req.query.until)
|
||||||
|
: startOfSecond(new Date())
|
||||||
|
since = new Date(req.query.since)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(since.getTime())) {
|
||||||
|
return res.status(403).send({ error: '?since is not a valid date' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(until.getTime())) {
|
||||||
|
return res.status(403).send({ error: '?until is not a valid date' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (until <= since) {
|
||||||
|
return res
|
||||||
|
.status(403)
|
||||||
|
.send({ error: '?until date must come after ?since date' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (until >= new Date()) {
|
||||||
|
return res.status(403).send({ error: '?until must be a date in the past' })
|
||||||
|
}
|
||||||
|
|
||||||
|
StatsAPI.getStats(since, until).then(
|
||||||
|
stats => {
|
||||||
|
res
|
||||||
|
.set({
|
||||||
|
'Cache-Control': 'public, max-age=60',
|
||||||
|
'Cache-Tag': 'stats'
|
||||||
|
})
|
||||||
|
.send(stats)
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error(error)
|
||||||
|
res.status(500).send({ error: 'Unable to fetch stats' })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = showStats
|
|
@ -1,23 +1,21 @@
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
|
const bodyParser = require('body-parser')
|
||||||
const cors = require('cors')
|
const cors = require('cors')
|
||||||
const morgan = require('morgan')
|
const morgan = require('morgan')
|
||||||
|
|
||||||
const checkBlacklist = require('./middleware/checkBlacklist')
|
const checkBlacklist = require('./middleware/checkBlacklist')
|
||||||
const packageURL = require('./middleware/packageURL')
|
|
||||||
const fetchFile = require('./middleware/fetchFile')
|
const fetchFile = require('./middleware/fetchFile')
|
||||||
|
const parseURL = require('./middleware/parseURL')
|
||||||
|
const requireAuth = require('./middleware/requireAuth')
|
||||||
const serveFile = require('./middleware/serveFile')
|
const serveFile = require('./middleware/serveFile')
|
||||||
|
const userToken = require('./middleware/userToken')
|
||||||
|
|
||||||
morgan.token('fwd', function(req) {
|
morgan.token('fwd', function(req) {
|
||||||
return req.get('x-forwarded-for').replace(/\s/g, '')
|
return req.get('x-forwarded-for').replace(/\s/g, '')
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of packages we refuse to serve.
|
|
||||||
*/
|
|
||||||
const PackageBlacklist = require('./PackageBlacklist').blacklist
|
|
||||||
|
|
||||||
function errorHandler(err, req, res, next) {
|
function errorHandler(err, req, res, next) {
|
||||||
console.error(err.stack)
|
console.error(err.stack)
|
||||||
|
|
||||||
|
@ -47,7 +45,6 @@ function createServer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(errorHandler)
|
app.use(errorHandler)
|
||||||
app.use(cors())
|
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
express.static('build', {
|
express.static('build', {
|
||||||
|
@ -55,19 +52,36 @@ function createServer() {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
app.use(cors())
|
||||||
|
app.use(bodyParser.json())
|
||||||
|
app.use(userToken)
|
||||||
|
|
||||||
const createStatsServer = require('./createStatsServer')
|
app.get('/_publicKey', require('./actions/showPublicKey'))
|
||||||
app.use('/_stats', createStatsServer())
|
|
||||||
|
app.post('/_auth', require('./actions/createAuth'))
|
||||||
|
app.get('/_auth', require('./actions/showAuth'))
|
||||||
|
|
||||||
|
app.post(
|
||||||
|
'/_blacklist',
|
||||||
|
requireAuth('blacklist.add'),
|
||||||
|
require('./actions/addToBlacklist')
|
||||||
|
)
|
||||||
|
app.get(
|
||||||
|
'/_blacklist',
|
||||||
|
requireAuth('blacklist.read'),
|
||||||
|
require('./actions/showBlacklist')
|
||||||
|
)
|
||||||
|
app.delete(
|
||||||
|
'/_blacklist/:packageName',
|
||||||
|
requireAuth('blacklist.remove'),
|
||||||
|
require('./actions/removeFromBlacklist')
|
||||||
|
)
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
|
app.get('/_stats', require('./actions/showStats'))
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(
|
app.use('/', parseURL, checkBlacklist, fetchFile, serveFile)
|
||||||
'/',
|
|
||||||
packageURL,
|
|
||||||
checkBlacklist(PackageBlacklist),
|
|
||||||
fetchFile,
|
|
||||||
serveFile
|
|
||||||
)
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
const request = require('supertest')
|
|
||||||
const createServer = require('./createServer')
|
|
||||||
|
|
||||||
describe('The server app', function() {
|
|
||||||
let app
|
|
||||||
beforeEach(() => {
|
|
||||||
app = createServer()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('rejects invalid package names', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get('/_invalid/index.js')
|
|
||||||
.then(res => {
|
|
||||||
expect(res.statusCode).toBe(403)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('redirects invalid query params', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get('/react?main=index&invalid')
|
|
||||||
.then(res => {
|
|
||||||
expect(res.statusCode).toBe(302)
|
|
||||||
expect(res.headers.location).toBe('/react?main=index')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('redirects /_meta to ?meta', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get('/_meta/react?main=index')
|
|
||||||
.then(res => {
|
|
||||||
expect(res.statusCode).toBe(302)
|
|
||||||
expect(res.headers.location).toBe('/react?main=index&meta')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,87 +0,0 @@
|
||||||
const express = require('express')
|
|
||||||
const subDays = require('date-fns/sub_days')
|
|
||||||
const startOfDay = require('date-fns/start_of_day')
|
|
||||||
const startOfSecond = require('date-fns/start_of_second')
|
|
||||||
const StatsAPI = require('./StatsAPI')
|
|
||||||
|
|
||||||
function serveArbitraryStats(req, res) {
|
|
||||||
const now = startOfSecond(new Date())
|
|
||||||
const since = req.query.since ? new Date(req.query.since) : subDays(now, 30)
|
|
||||||
const until = req.query.until ? new Date(req.query.until) : now
|
|
||||||
|
|
||||||
if (isNaN(since.getTime())) {
|
|
||||||
return res.status(403).send({ error: '?since is not a valid date' })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNaN(until.getTime())) {
|
|
||||||
return res.status(403).send({ error: '?until is not a valid date' })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (until <= since) {
|
|
||||||
return res
|
|
||||||
.status(403)
|
|
||||||
.send({ error: '?until date must come after ?since date' })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (until > now) {
|
|
||||||
return res.status(403).send({ error: '?until must be a date in the past' })
|
|
||||||
}
|
|
||||||
|
|
||||||
StatsAPI.getStats(since, until, (error, stats) => {
|
|
||||||
if (error) {
|
|
||||||
console.error(error)
|
|
||||||
res.status(500).send({ error: 'Unable to fetch stats' })
|
|
||||||
} else {
|
|
||||||
res
|
|
||||||
.set({
|
|
||||||
'Cache-Control': 'public, max-age=60',
|
|
||||||
'Cache-Tag': 'stats'
|
|
||||||
})
|
|
||||||
.send(stats)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function servePastDaysStats(days, req, res) {
|
|
||||||
const until = startOfDay(new Date())
|
|
||||||
const since = subDays(until, days)
|
|
||||||
|
|
||||||
StatsAPI.getStats(since, until, (error, stats) => {
|
|
||||||
if (error) {
|
|
||||||
console.error(error)
|
|
||||||
res.status(500).send({ error: 'Unable to fetch stats' })
|
|
||||||
} else {
|
|
||||||
res
|
|
||||||
.set({
|
|
||||||
'Cache-Control': 'public, max-age=60',
|
|
||||||
'Cache-Tag': 'stats'
|
|
||||||
})
|
|
||||||
.send(stats)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function serveLastMonthStats(req, res) {
|
|
||||||
servePastDaysStats(30, req, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
function serveLastWeekStats(req, res) {
|
|
||||||
servePastDaysStats(7, req, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
function serveLastDayStats(req, res) {
|
|
||||||
servePastDaysStats(1, req, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
function createStatsServer() {
|
|
||||||
const app = express.Router()
|
|
||||||
|
|
||||||
app.get('/', serveArbitraryStats)
|
|
||||||
app.get('/last-month', serveLastMonthStats)
|
|
||||||
app.get('/last-week', serveLastWeekStats)
|
|
||||||
app.get('/last-day', serveLastDayStats)
|
|
||||||
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = createStatsServer
|
|
|
@ -1,15 +1,26 @@
|
||||||
function checkBlacklist(blacklist) {
|
const BlacklistAPI = require('../BlacklistAPI')
|
||||||
return function(req, res, next) {
|
|
||||||
// Do not allow packages that have been blacklisted.
|
function checkBlacklist(req, res, next) {
|
||||||
if (blacklist.includes(req.packageName)) {
|
BlacklistAPI.containsPackage(req.packageName).then(
|
||||||
res
|
blacklisted => {
|
||||||
.status(403)
|
// Disallow packages that have been blacklisted.
|
||||||
.type('text')
|
if (blacklisted) {
|
||||||
.send(`Package "${req.packageName}" is blacklisted`)
|
res
|
||||||
} else {
|
.status(403)
|
||||||
next()
|
.type('text')
|
||||||
|
.send(`Package "${req.packageName}" is blacklisted`)
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
res.status(500).send({
|
||||||
|
error: 'Unable to fetch the blacklist'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = checkBlacklist
|
module.exports = checkBlacklist
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const validateNPMPackageName = require('validate-npm-package-name')
|
const validateNpmPackageName = require('validate-npm-package-name')
|
||||||
const parsePackageURL = require('../utils/parsePackageURL')
|
const parsePackageURL = require('../utils/parsePackageURL')
|
||||||
const createSearch = require('./utils/createSearch')
|
const createSearch = require('./utils/createSearch')
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ function sanitizeQuery(query) {
|
||||||
/**
|
/**
|
||||||
* Parse and validate the URL.
|
* Parse and validate the URL.
|
||||||
*/
|
*/
|
||||||
function packageURL(req, res, next) {
|
function parseURL(req, res, next) {
|
||||||
// Redirect /_meta/path to /path?meta.
|
// Redirect /_meta/path to /path?meta.
|
||||||
if (req.path.match(/^\/_meta\//)) {
|
if (req.path.match(/^\/_meta\//)) {
|
||||||
req.query.meta = ''
|
req.query.meta = ''
|
||||||
|
@ -46,28 +46,30 @@ function packageURL(req, res, next) {
|
||||||
// Redirect requests with unknown query params to their equivalents
|
// Redirect requests with unknown query params to their equivalents
|
||||||
// with only known params so they can be served from the cache. This
|
// with only known params so they can be served from the cache. This
|
||||||
// prevents people using random query params designed to bust the cache.
|
// prevents people using random query params designed to bust the cache.
|
||||||
if (!queryIsKnown(req.query))
|
if (!queryIsKnown(req.query)) {
|
||||||
return res.redirect(302, req.path + createSearch(sanitizeQuery(req.query)))
|
return res.redirect(302, req.path + createSearch(sanitizeQuery(req.query)))
|
||||||
|
}
|
||||||
|
|
||||||
const url = parsePackageURL(req.url)
|
const url = parsePackageURL(req.url)
|
||||||
|
|
||||||
// Do not allow invalid URLs.
|
// Disallow invalid URLs.
|
||||||
if (url == null)
|
if (url == null) {
|
||||||
return res
|
return res
|
||||||
.status(403)
|
.status(403)
|
||||||
.type('text')
|
.type('text')
|
||||||
.send(`Invalid URL: ${req.url}`)
|
.send(`Invalid URL: ${req.url}`)
|
||||||
|
}
|
||||||
|
|
||||||
const nameErrors = validateNPMPackageName(url.packageName).errors
|
const nameErrors = validateNpmPackageName(url.packageName).errors
|
||||||
|
|
||||||
// Do not allow invalid package names.
|
// Disallow invalid package names.
|
||||||
if (nameErrors)
|
if (nameErrors) {
|
||||||
|
const reason = nameErrors.join(', ')
|
||||||
return res
|
return res
|
||||||
.status(403)
|
.status(403)
|
||||||
.type('text')
|
.type('text')
|
||||||
.send(
|
.send(`Invalid package name "${url.packageName}" (${reason})`)
|
||||||
`Invalid package name: ${url.packageName} (${nameErrors.join(', ')})`
|
}
|
||||||
)
|
|
||||||
|
|
||||||
req.packageName = url.packageName
|
req.packageName = url.packageName
|
||||||
req.packageVersion = url.packageVersion
|
req.packageVersion = url.packageVersion
|
||||||
|
@ -80,4 +82,4 @@ function packageURL(req, res, next) {
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = packageURL
|
module.exports = parseURL
|
|
@ -0,0 +1,40 @@
|
||||||
|
/**
|
||||||
|
* Adds the given scope to the array in req.auth if the user has sufficient
|
||||||
|
* permissions. Otherwise rejects the request.
|
||||||
|
*/
|
||||||
|
function requireAuth(scope) {
|
||||||
|
let checkScopes
|
||||||
|
if (scope.includes('.')) {
|
||||||
|
const parts = scope.split('.')
|
||||||
|
checkScopes = scopes =>
|
||||||
|
parts.reduce((memo, part) => memo && memo[part], scopes) != null
|
||||||
|
} else {
|
||||||
|
checkScopes = scopes => scopes[scope] != null
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(req, res, next) {
|
||||||
|
if (req.auth && req.auth.includes(scope)) {
|
||||||
|
return next() // Already auth'd
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = req.user
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(403).send({ error: 'Missing auth token' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user.scopes || !checkScopes(user.scopes)) {
|
||||||
|
return res.status(403).send({ error: 'Insufficient scopes' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.auth) {
|
||||||
|
req.auth.push(scope)
|
||||||
|
} else {
|
||||||
|
req.auth = [scope]
|
||||||
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = requireAuth
|
|
@ -0,0 +1,41 @@
|
||||||
|
const AuthAPI = require('../AuthAPI')
|
||||||
|
|
||||||
|
const ReadMethods = { GET: true, HEAD: true }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets req.user from the payload in the auth token in the request.
|
||||||
|
*/
|
||||||
|
function userToken(req, res, next) {
|
||||||
|
if (req.user) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = (ReadMethods[req.method] ? req.query : req.body).token
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
req.user = null
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthAPI.verifyToken(token).then(
|
||||||
|
payload => {
|
||||||
|
req.user = payload
|
||||||
|
next()
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
if (error.name === 'JsonWebTokenError') {
|
||||||
|
res.status(403).send({
|
||||||
|
error: `Bad auth token: ${error.message}`
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
res.status(500).send({
|
||||||
|
error: 'Unable to verify auth'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = userToken
|
|
@ -1,4 +1,4 @@
|
||||||
const getFileContentType = require('./getFileContentType')
|
const getFileContentType = require('../getFileContentType')
|
||||||
|
|
||||||
it('gets a content type of text/plain for LICENSE|README|CHANGES|AUTHORS|Makefile', () => {
|
it('gets a content type of text/plain for LICENSE|README|CHANGES|AUTHORS|Makefile', () => {
|
||||||
expect(getFileContentType('AUTHORS')).toBe('text/plain')
|
expect(getFileContentType('AUTHORS')).toBe('text/plain')
|
|
@ -1,10 +1,10 @@
|
||||||
const parsePackageURL = require('./parsePackageURL')
|
const parsePackageURL = require('../parsePackageURL')
|
||||||
|
|
||||||
describe('parsePackageURL', () => {
|
describe('parsePackageURL', () => {
|
||||||
it('parses plain packages', () => {
|
it('parses plain packages', () => {
|
||||||
expect(parsePackageURL('/history@1.0.0/umd/history.min.js')).toEqual({
|
expect(parsePackageURL('/history@1.0.0/umd/history.min.js')).toEqual({
|
||||||
pathname: '/history@1.0.0/umd/history.min.js',
|
pathname: '/history@1.0.0/umd/history.min.js',
|
||||||
search: null,
|
search: '',
|
||||||
query: {},
|
query: {},
|
||||||
packageName: 'history',
|
packageName: 'history',
|
||||||
packageVersion: '1.0.0',
|
packageVersion: '1.0.0',
|
||||||
|
@ -15,7 +15,7 @@ describe('parsePackageURL', () => {
|
||||||
it('parses plain packages with a hyphen in the name', () => {
|
it('parses plain packages with a hyphen in the name', () => {
|
||||||
expect(parsePackageURL('/query-string@5.0.0/index.js')).toEqual({
|
expect(parsePackageURL('/query-string@5.0.0/index.js')).toEqual({
|
||||||
pathname: '/query-string@5.0.0/index.js',
|
pathname: '/query-string@5.0.0/index.js',
|
||||||
search: null,
|
search: '',
|
||||||
query: {},
|
query: {},
|
||||||
packageName: 'query-string',
|
packageName: 'query-string',
|
||||||
packageVersion: '5.0.0',
|
packageVersion: '5.0.0',
|
||||||
|
@ -26,7 +26,7 @@ describe('parsePackageURL', () => {
|
||||||
it('parses plain packages with no version specified', () => {
|
it('parses plain packages with no version specified', () => {
|
||||||
expect(parsePackageURL('/query-string/index.js')).toEqual({
|
expect(parsePackageURL('/query-string/index.js')).toEqual({
|
||||||
pathname: '/query-string/index.js',
|
pathname: '/query-string/index.js',
|
||||||
search: null,
|
search: '',
|
||||||
query: {},
|
query: {},
|
||||||
packageName: 'query-string',
|
packageName: 'query-string',
|
||||||
packageVersion: 'latest',
|
packageVersion: 'latest',
|
||||||
|
@ -37,7 +37,7 @@ describe('parsePackageURL', () => {
|
||||||
it('parses plain packages with version spec', () => {
|
it('parses plain packages with version spec', () => {
|
||||||
expect(parsePackageURL('/query-string@>=4.0.0/index.js')).toEqual({
|
expect(parsePackageURL('/query-string@>=4.0.0/index.js')).toEqual({
|
||||||
pathname: '/query-string@>=4.0.0/index.js',
|
pathname: '/query-string@>=4.0.0/index.js',
|
||||||
search: null,
|
search: '',
|
||||||
query: {},
|
query: {},
|
||||||
packageName: 'query-string',
|
packageName: 'query-string',
|
||||||
packageVersion: '>=4.0.0',
|
packageVersion: '>=4.0.0',
|
||||||
|
@ -48,7 +48,7 @@ describe('parsePackageURL', () => {
|
||||||
it('parses scoped packages', () => {
|
it('parses scoped packages', () => {
|
||||||
expect(parsePackageURL('/@angular/router@4.3.3/src/index.d.ts')).toEqual({
|
expect(parsePackageURL('/@angular/router@4.3.3/src/index.d.ts')).toEqual({
|
||||||
pathname: '/@angular/router@4.3.3/src/index.d.ts',
|
pathname: '/@angular/router@4.3.3/src/index.d.ts',
|
||||||
search: null,
|
search: '',
|
||||||
query: {},
|
query: {},
|
||||||
packageName: '@angular/router',
|
packageName: '@angular/router',
|
||||||
packageVersion: '4.3.3',
|
packageVersion: '4.3.3',
|
||||||
|
@ -59,7 +59,7 @@ describe('parsePackageURL', () => {
|
||||||
it('parses package names with a period in them', () => {
|
it('parses package names with a period in them', () => {
|
||||||
expect(parsePackageURL('/index.js')).toEqual({
|
expect(parsePackageURL('/index.js')).toEqual({
|
||||||
pathname: '/index.js',
|
pathname: '/index.js',
|
||||||
search: null,
|
search: '',
|
||||||
query: {},
|
query: {},
|
||||||
packageName: 'index.js',
|
packageName: 'index.js',
|
||||||
packageVersion: 'latest',
|
packageVersion: 'latest',
|
135
yarn.lock
135
yarn.lock
|
@ -919,6 +919,10 @@ base64-js@^1.0.2:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"
|
||||||
|
|
||||||
|
base64url@2.0.0, base64url@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb"
|
||||||
|
|
||||||
basic-auth@~1.1.0:
|
basic-auth@~1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.1.0.tgz#45221ee429f7ee1e5035be3f51533f1cdfd29884"
|
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.1.0.tgz#45221ee429f7ee1e5035be3f51533f1cdfd29884"
|
||||||
|
@ -957,6 +961,21 @@ bluebird@^3.4.6:
|
||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
|
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
|
||||||
|
|
||||||
|
body-parser@^1.18.2:
|
||||||
|
version "1.18.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454"
|
||||||
|
dependencies:
|
||||||
|
bytes "3.0.0"
|
||||||
|
content-type "~1.0.4"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.1"
|
||||||
|
http-errors "~1.6.2"
|
||||||
|
iconv-lite "0.4.19"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
qs "6.5.1"
|
||||||
|
raw-body "2.3.2"
|
||||||
|
type-is "~1.6.15"
|
||||||
|
|
||||||
boolbase@~1.0.0:
|
boolbase@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
||||||
|
@ -1013,6 +1032,10 @@ bser@1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
node-int64 "^0.4.0"
|
node-int64 "^0.4.0"
|
||||||
|
|
||||||
|
buffer-equal-constant-time@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||||
|
|
||||||
buffer@^4.9.0:
|
buffer@^4.9.0:
|
||||||
version "4.9.1"
|
version "4.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
|
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
|
||||||
|
@ -1037,6 +1060,10 @@ bytes@2.5.0:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.5.0.tgz#4c9423ea2d252c270c41b2bdefeff9bb6b62c06a"
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.5.0.tgz#4c9423ea2d252c270c41b2bdefeff9bb6b62c06a"
|
||||||
|
|
||||||
|
bytes@3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
||||||
|
|
||||||
caller-path@^0.1.0:
|
caller-path@^0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
|
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
|
||||||
|
@ -1337,6 +1364,10 @@ content-type@~1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed"
|
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed"
|
||||||
|
|
||||||
|
content-type@~1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||||
|
|
||||||
convert-source-map@^1.5.0:
|
convert-source-map@^1.5.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5"
|
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5"
|
||||||
|
@ -1566,6 +1597,12 @@ debug@2.6.8, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0, debug@^2.6.0, debug@^2.6.
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
|
debug@2.6.9:
|
||||||
|
version "2.6.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
dependencies:
|
||||||
|
ms "2.0.0"
|
||||||
|
|
||||||
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
|
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||||
|
@ -1721,6 +1758,13 @@ ecc-jsbn@~0.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
jsbn "~0.1.0"
|
jsbn "~0.1.0"
|
||||||
|
|
||||||
|
ecdsa-sig-formatter@1.0.9:
|
||||||
|
version "1.0.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1"
|
||||||
|
dependencies:
|
||||||
|
base64url "^2.0.0"
|
||||||
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
ee-first@1.1.1:
|
ee-first@1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||||
|
@ -2589,7 +2633,7 @@ htmlparser2@~3.3.0:
|
||||||
domutils "1.1"
|
domutils "1.1"
|
||||||
readable-stream "1.0"
|
readable-stream "1.0"
|
||||||
|
|
||||||
http-errors@~1.6.1, http-errors@~1.6.2:
|
http-errors@1.6.2, http-errors@~1.6.1, http-errors@~1.6.2:
|
||||||
version "1.6.2"
|
version "1.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -2630,6 +2674,10 @@ iconv-lite@0.4.13:
|
||||||
version "0.4.13"
|
version "0.4.13"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
|
||||||
|
|
||||||
|
iconv-lite@0.4.19:
|
||||||
|
version "0.4.19"
|
||||||
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
|
||||||
|
|
||||||
iconv-lite@~0.4.13:
|
iconv-lite@~0.4.13:
|
||||||
version "0.4.18"
|
version "0.4.18"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2"
|
||||||
|
@ -3262,6 +3310,21 @@ jsonpointer@^4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
|
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
|
||||||
|
|
||||||
|
jsonwebtoken@^8.1.0:
|
||||||
|
version "8.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.1.0.tgz#c6397cd2e5fd583d65c007a83dc7bb78e6982b83"
|
||||||
|
dependencies:
|
||||||
|
jws "^3.1.4"
|
||||||
|
lodash.includes "^4.3.0"
|
||||||
|
lodash.isboolean "^3.0.3"
|
||||||
|
lodash.isinteger "^4.0.4"
|
||||||
|
lodash.isnumber "^3.0.3"
|
||||||
|
lodash.isplainobject "^4.0.6"
|
||||||
|
lodash.isstring "^4.0.1"
|
||||||
|
lodash.once "^4.0.0"
|
||||||
|
ms "^2.0.0"
|
||||||
|
xtend "^4.0.1"
|
||||||
|
|
||||||
jsprim@^1.2.2:
|
jsprim@^1.2.2:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
|
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
|
||||||
|
@ -3275,6 +3338,23 @@ jsx-ast-utils@^1.0.0, jsx-ast-utils@^1.3.1:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1"
|
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1"
|
||||||
|
|
||||||
|
jwa@^1.1.4:
|
||||||
|
version "1.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5"
|
||||||
|
dependencies:
|
||||||
|
base64url "2.0.0"
|
||||||
|
buffer-equal-constant-time "1.0.1"
|
||||||
|
ecdsa-sig-formatter "1.0.9"
|
||||||
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
jws@^3.1.4:
|
||||||
|
version "3.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2"
|
||||||
|
dependencies:
|
||||||
|
base64url "^2.0.0"
|
||||||
|
jwa "^1.1.4"
|
||||||
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
kind-of@^3.0.2:
|
kind-of@^3.0.2:
|
||||||
version "3.2.2"
|
version "3.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
||||||
|
@ -3406,6 +3486,10 @@ lodash.defaults@^4.0.1:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
|
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
|
||||||
|
|
||||||
|
lodash.includes@^4.3.0:
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||||
|
|
||||||
lodash.isarguments@^3.0.0:
|
lodash.isarguments@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
|
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
|
||||||
|
@ -3414,6 +3498,26 @@ lodash.isarray@^3.0.0:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
|
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
|
||||||
|
|
||||||
|
lodash.isboolean@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
||||||
|
|
||||||
|
lodash.isinteger@^4.0.4:
|
||||||
|
version "4.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
|
||||||
|
|
||||||
|
lodash.isnumber@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
|
||||||
|
|
||||||
|
lodash.isplainobject@^4.0.6:
|
||||||
|
version "4.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||||
|
|
||||||
|
lodash.isstring@^4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||||
|
|
||||||
lodash.keys@^3.0.0:
|
lodash.keys@^3.0.0:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
|
resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
|
||||||
|
@ -3426,6 +3530,10 @@ lodash.memoize@^4.1.2:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||||
|
|
||||||
|
lodash.once@^4.0.0:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||||
|
|
||||||
lodash.pickby@^4.6.0:
|
lodash.pickby@^4.6.0:
|
||||||
version "4.6.0"
|
version "4.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff"
|
resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff"
|
||||||
|
@ -3572,6 +3680,10 @@ mime@^1.3.4, mime@^1.3.6:
|
||||||
version "1.3.6"
|
version "1.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0"
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0"
|
||||||
|
|
||||||
|
mime@^1.4.0:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
|
||||||
|
|
||||||
min-document@^2.19.0:
|
min-document@^2.19.0:
|
||||||
version "2.19.0"
|
version "2.19.0"
|
||||||
resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
|
resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
|
||||||
|
@ -3618,7 +3730,7 @@ ms@0.7.1:
|
||||||
version "0.7.1"
|
version "0.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0, ms@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
|
||||||
|
@ -3672,6 +3784,10 @@ node-fetch@^1.0.1:
|
||||||
encoding "^0.1.11"
|
encoding "^0.1.11"
|
||||||
is-stream "^1.0.1"
|
is-stream "^1.0.1"
|
||||||
|
|
||||||
|
node-forge@^0.7.1:
|
||||||
|
version "0.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300"
|
||||||
|
|
||||||
node-int64@^0.4.0:
|
node-int64@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||||
|
@ -4401,6 +4517,10 @@ qs@6.5.0, qs@^6.4.0:
|
||||||
version "6.5.0"
|
version "6.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.0.tgz#8d04954d364def3efc55b5a0793e1e2c8b1e6e49"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.0.tgz#8d04954d364def3efc55b5a0793e1e2c8b1e6e49"
|
||||||
|
|
||||||
|
qs@6.5.1:
|
||||||
|
version "6.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
|
||||||
|
|
||||||
qs@~6.4.0:
|
qs@~6.4.0:
|
||||||
version "6.4.0"
|
version "6.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
|
||||||
|
@ -4445,6 +4565,15 @@ range-parser@^1.0.3, range-parser@~1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
|
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
|
||||||
|
|
||||||
|
raw-body@2.3.2:
|
||||||
|
version "2.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89"
|
||||||
|
dependencies:
|
||||||
|
bytes "3.0.0"
|
||||||
|
http-errors "1.6.2"
|
||||||
|
iconv-lite "0.4.19"
|
||||||
|
unpipe "1.0.0"
|
||||||
|
|
||||||
rc@^1.1.7:
|
rc@^1.1.7:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
|
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
|
||||||
|
@ -5389,7 +5518,7 @@ uniqs@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
|
resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
|
||||||
|
|
||||||
unpipe@~1.0.0:
|
unpipe@1.0.0, unpipe@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue