Add auth and blacklist APIs
This commit is contained in:
@ -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
|
||||
|
@ -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
|
40
server/middleware/requireAuth.js
Normal file
40
server/middleware/requireAuth.js
Normal 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
|
41
server/middleware/userToken.js
Normal file
41
server/middleware/userToken.js
Normal 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
|
@ -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')
|
Reference in New Issue
Block a user