Add auth and blacklist APIs

This commit is contained in:
MICHAEL JACKSON 2017-11-11 12:18:13 -08:00
parent cc70428986
commit 0e1f26849b
35 changed files with 1166 additions and 339 deletions

3
.gitignore vendored
View File

@ -18,3 +18,6 @@ yarn-error.log*
# redis
dump.rdb
# keys
private.key

View File

@ -33,7 +33,7 @@ unpkg is not affiliated with or supported by npm, Inc. in any way. Please do not
### 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

View File

@ -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)
})

View File

@ -47,7 +47,7 @@ class Layout extends React.Component {
componentDidMount() {
this.adjustUnderline()
fetch('/_stats/last-month')
fetch('/_stats?period=last-month')
.then(res => res.json())
.then(stats => this.setState({ stats }))

View File

@ -11,6 +11,7 @@
"dependencies": {
"algoliasearch": "^3.24.3",
"babel-plugin-unpkg-rewrite": "^3.1.0",
"body-parser": "^1.18.2",
"cors": "^2.8.1",
"countries-list": "^1.3.2",
"csso": "^3.1.1",
@ -20,10 +21,12 @@
"gunzip-maybe": "^1.4.0",
"invariant": "^2.2.2",
"isomorphic-fetch": "^2.2.1",
"jsonwebtoken": "^8.1.0",
"mime": "^1.4.0",
"mkdirp": "^0.5.1",
"morgan": "^1.8.1",
"ndjson": "^1.5.0",
"node-forge": "^0.7.1",
"os-tmpdir": "^1.0.2",
"pretty-bytes": "^3",
"prop-types": "^15.5.8",
@ -91,7 +94,8 @@
"<rootDir>/config/polyfills.js"
],
"testPathIgnorePatterns": [
"<rootDir>[/\\\\](build|docs|node_modules|scripts)[/\\\\]"
"<rootDir>[/\\\\](build|docs|node_modules|scripts)[/\\\\]",
"__tests__/utils"
],
"testEnvironment": "node",
"testURL": "http://localhost",

9
public.key Normal file
View File

@ -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-----

120
scripts/add-blacklist.js Normal file
View File

@ -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)

19
scripts/create-token.js Normal file
View File

@ -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)
}
)

118
server/AuthAPI.js Normal file
View File

@ -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
}

71
server/BlacklistAPI.js Normal file
View File

@ -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
}

View File

@ -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"
]
}

View File

@ -1,14 +1,17 @@
const cf = require('./CloudflareAPI')
const db = require('./RedisClient')
const PackageBlacklist = require('./PackageBlacklist').blacklist
const CloudflareAPI = require('./CloudflareAPI')
const BlacklistAPI = require('./BlacklistAPI')
function prunePackages(packagesMap) {
PackageBlacklist.forEach(packageName => {
delete packagesMap[packageName]
})
return packagesMap
return Promise.all(
Object.keys(packagesMap).map(packageName =>
BlacklistAPI.isBlacklisted(packageName).then(blacklisted => {
if (blacklisted) {
delete packagesMap[packageName]
}
})
)
).then(() => packagesMap)
}
function createDayKey(date) {
@ -87,25 +90,25 @@ function sumMaps(maps) {
}
function addDailyMetrics(result) {
return Promise.all(result.timeseries.map(addDailyMetricsToTimeseries)).then(
() => {
result.totals.requests.package = sumMaps(
result.timeseries.map(timeseries => {
return timeseries.requests.package
})
)
return Promise.all(
result.timeseries.map(addDailyMetricsToTimeseries)
).then(() => {
result.totals.requests.package = sumMaps(
result.timeseries.map(timeseries => {
return timeseries.requests.package
})
)
result.totals.bandwidth.package = sumMaps(
result.timeseries.map(timeseries => timeseries.bandwidth.package)
)
result.totals.bandwidth.package = sumMaps(
result.timeseries.map(timeseries => timeseries.bandwidth.package)
)
result.totals.requests.protocol = sumMaps(
result.timeseries.map(timeseries => timeseries.requests.protocol)
)
result.totals.requests.protocol = sumMaps(
result.timeseries.map(timeseries => timeseries.requests.protocol)
)
return result
}
)
return result
})
}
function extractPublicInfo(data) {
@ -140,8 +143,12 @@ function extractPublicInfo(data) {
const DomainNames = ['unpkg.com', 'npmcdn.com']
function fetchStats(since, until) {
return cf.getZones(DomainNames).then(zones => {
return cf.getZoneAnalyticsDashboard(zones, since, until).then(dashboard => {
return CloudflareAPI.getZones(DomainNames).then(zones => {
return CloudflareAPI.getZoneAnalyticsDashboard(
zones,
since,
until
).then(dashboard => {
return {
timeseries: dashboard.timeseries.map(extractPublicInfo),
totals: extractPublicInfo(dashboard.totals)
@ -154,14 +161,9 @@ const oneMinute = 1000 * 60
const oneHour = oneMinute * 60
const oneDay = oneHour * 24
function getStats(since, until, callback) {
let promise = fetchStats(since, until)
if (until - since > oneDay) promise = promise.then(addDailyMetrics)
promise.then(value => {
callback(null, value)
}, callback)
function getStats(since, until) {
const promise = fetchStats(since, until)
return until - since > oneDay ? promise.then(addDailyMetrics) : promise
}
module.exports = {

View File

@ -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()
})
})
})
})
})

View File

@ -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()
})
})
})
})
})
})

View File

@ -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()
})
})
})
})
})
})

View File

@ -0,0 +1,7 @@
const BlacklistAPI = require('../../BlacklistAPI')
function withBlacklist(blacklist, callback) {
return Promise.all(blacklist.map(BlacklistAPI.addPackage)).then(callback)
}
module.exports = withBlacklist

View File

@ -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

View File

@ -0,0 +1,7 @@
const AuthAPI = require('../../AuthAPI')
function withToken(scopes, callback) {
AuthAPI.createToken(scopes).then(callback)
}
module.exports = withToken

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,5 @@
function showAuth(req, res) {
res.send({ auth: req.user })
}
module.exports = showAuth

View File

@ -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

View File

@ -0,0 +1,7 @@
const AuthAPI = require('../AuthAPI')
function showPublicKey(req, res) {
res.type('text').send(AuthAPI.getPublicKey())
}
module.exports = showPublicKey

View File

@ -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

View File

@ -1,23 +1,21 @@
const fs = require('fs')
const path = require('path')
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const morgan = require('morgan')
const checkBlacklist = require('./middleware/checkBlacklist')
const packageURL = require('./middleware/packageURL')
const fetchFile = require('./middleware/fetchFile')
const parseURL = require('./middleware/parseURL')
const requireAuth = require('./middleware/requireAuth')
const serveFile = require('./middleware/serveFile')
const userToken = require('./middleware/userToken')
morgan.token('fwd', function(req) {
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) {
console.error(err.stack)
@ -47,7 +45,6 @@ function createServer() {
}
app.use(errorHandler)
app.use(cors())
app.use(
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.use('/_stats', createStatsServer())
app.get('/_publicKey', require('./actions/showPublicKey'))
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(
'/',
packageURL,
checkBlacklist(PackageBlacklist),
fetchFile,
serveFile
)
app.use('/', parseURL, checkBlacklist, fetchFile, serveFile)
return app
}

View File

@ -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()
})
})
})

View File

@ -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

View File

@ -1,15 +1,26 @@
function checkBlacklist(blacklist) {
return function(req, res, next) {
// Do not allow packages that have been blacklisted.
if (blacklist.includes(req.packageName)) {
res
.status(403)
.type('text')
.send(`Package "${req.packageName}" is blacklisted`)
} else {
next()
const BlacklistAPI = require('../BlacklistAPI')
function checkBlacklist(req, res, next) {
BlacklistAPI.containsPackage(req.packageName).then(
blacklisted => {
// Disallow packages that have been blacklisted.
if (blacklisted) {
res
.status(403)
.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

View File

@ -1,4 +1,4 @@
const validateNPMPackageName = require('validate-npm-package-name')
const validateNpmPackageName = require('validate-npm-package-name')
const parsePackageURL = require('../utils/parsePackageURL')
const createSearch = require('./utils/createSearch')
@ -29,7 +29,7 @@ function sanitizeQuery(query) {
/**
* Parse and validate the URL.
*/
function packageURL(req, res, next) {
function parseURL(req, res, next) {
// Redirect /_meta/path to /path?meta.
if (req.path.match(/^\/_meta\//)) {
req.query.meta = ''
@ -46,28 +46,30 @@ function packageURL(req, res, next) {
// Redirect requests with unknown query params to their equivalents
// with only known params so they can be served from the cache. This
// 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)))
}
const url = parsePackageURL(req.url)
// Do not allow invalid URLs.
if (url == null)
// Disallow invalid URLs.
if (url == null) {
return res
.status(403)
.type('text')
.send(`Invalid URL: ${req.url}`)
}
const nameErrors = validateNPMPackageName(url.packageName).errors
const nameErrors = validateNpmPackageName(url.packageName).errors
// Do not allow invalid package names.
if (nameErrors)
// Disallow invalid package names.
if (nameErrors) {
const reason = nameErrors.join(', ')
return res
.status(403)
.type('text')
.send(
`Invalid package name: ${url.packageName} (${nameErrors.join(', ')})`
)
.send(`Invalid package name "${url.packageName}" (${reason})`)
}
req.packageName = url.packageName
req.packageVersion = url.packageVersion
@ -80,4 +82,4 @@ function packageURL(req, res, next) {
next()
}
module.exports = packageURL
module.exports = parseURL

View File

@ -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

View File

@ -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

View File

@ -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', () => {
expect(getFileContentType('AUTHORS')).toBe('text/plain')

View File

@ -1,10 +1,10 @@
const parsePackageURL = require('./parsePackageURL')
const parsePackageURL = require('../parsePackageURL')
describe('parsePackageURL', () => {
it('parses plain packages', () => {
expect(parsePackageURL('/history@1.0.0/umd/history.min.js')).toEqual({
pathname: '/history@1.0.0/umd/history.min.js',
search: null,
search: '',
query: {},
packageName: 'history',
packageVersion: '1.0.0',
@ -15,7 +15,7 @@ describe('parsePackageURL', () => {
it('parses plain packages with a hyphen in the name', () => {
expect(parsePackageURL('/query-string@5.0.0/index.js')).toEqual({
pathname: '/query-string@5.0.0/index.js',
search: null,
search: '',
query: {},
packageName: 'query-string',
packageVersion: '5.0.0',
@ -26,7 +26,7 @@ describe('parsePackageURL', () => {
it('parses plain packages with no version specified', () => {
expect(parsePackageURL('/query-string/index.js')).toEqual({
pathname: '/query-string/index.js',
search: null,
search: '',
query: {},
packageName: 'query-string',
packageVersion: 'latest',
@ -37,7 +37,7 @@ describe('parsePackageURL', () => {
it('parses plain packages with version spec', () => {
expect(parsePackageURL('/query-string@>=4.0.0/index.js')).toEqual({
pathname: '/query-string@>=4.0.0/index.js',
search: null,
search: '',
query: {},
packageName: 'query-string',
packageVersion: '>=4.0.0',
@ -48,7 +48,7 @@ describe('parsePackageURL', () => {
it('parses scoped packages', () => {
expect(parsePackageURL('/@angular/router@4.3.3/src/index.d.ts')).toEqual({
pathname: '/@angular/router@4.3.3/src/index.d.ts',
search: null,
search: '',
query: {},
packageName: '@angular/router',
packageVersion: '4.3.3',
@ -59,7 +59,7 @@ describe('parsePackageURL', () => {
it('parses package names with a period in them', () => {
expect(parsePackageURL('/index.js')).toEqual({
pathname: '/index.js',
search: null,
search: '',
query: {},
packageName: 'index.js',
packageVersion: 'latest',

135
yarn.lock
View File

@ -919,6 +919,10 @@ base64-js@^1.0.2:
version "1.2.1"
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:
version "1.1.0"
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"
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:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
@ -1013,6 +1032,10 @@ bser@1.0.2:
dependencies:
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:
version "4.9.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
@ -1037,6 +1060,10 @@ bytes@2.5.0:
version "2.5.0"
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:
version "0.1.0"
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"
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:
version "1.5.0"
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:
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:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@ -1721,6 +1758,13 @@ ecc-jsbn@~0.1.1:
dependencies:
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:
version "1.1.1"
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"
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"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
dependencies:
@ -2630,6 +2674,10 @@ iconv-lite@0.4.13:
version "0.4.13"
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:
version "0.4.18"
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"
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:
version "1.4.1"
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"
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:
version "3.2.2"
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"
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:
version "3.1.0"
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"
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:
version "3.1.2"
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"
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:
version "4.6.0"
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"
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:
version "2.19.0"
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"
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"
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"
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:
version "0.4.0"
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"
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:
version "6.4.0"
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"
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:
version "1.2.1"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
@ -5389,7 +5518,7 @@ uniqs@^2.0.0:
version "2.0.0"
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"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"