verdaccio/lib/index.js

424 lines
11 KiB
JavaScript
Raw Normal View History

2013-10-18 23:17:53 +02:00
var express = require('express')
, cookies = require('cookies')
, utils = require('./utils')
, Storage = require('./storage')
, Config = require('./config')
, UError = require('./error').UserError
, Middleware = require('./middleware')
, Logger = require('./logger')
, Cats = require('./status-cats')
2013-10-18 23:17:53 +02:00
, basic_auth = Middleware.basic_auth
, validate_name = Middleware.validate_name
, media = Middleware.media
, expect_json = Middleware.expect_json
2014-05-06 23:34:48 +02:00
, Handlebars = require('handlebars')
2014-05-07 00:40:21 +02:00
, fs = require('fs')
, localList = require('./local-list')
, _ = require('underscore');
2013-05-31 08:26:11 +02:00
2014-02-01 09:08:48 +01:00
function match(regexp) {
return function(req, res, next, value, name) {
if (regexp.exec(value)) {
next()
} else {
next('route')
}
}
}
2013-06-08 03:16:28 +02:00
module.exports = function(config_hash) {
2013-10-26 14:18:36 +02:00
var config = new Config(config_hash)
, storage = new Storage(config)
2013-06-08 03:16:28 +02:00
var can = function(action) {
return function(req, res, next) {
2013-06-08 03:16:28 +02:00
if (config['allow_'+action](req.params.package, req.remoteUser)) {
2013-10-26 14:18:36 +02:00
next()
2013-06-08 03:16:28 +02:00
} else {
if (!req.remoteUser) {
if (req.remoteUserError) {
2014-02-23 18:20:50 +01:00
var msg = "can't "+action+' restricted package, ' + req.remoteUserError
} else {
var msg = "can't "+action+" restricted package without auth, did you forget 'npm set always-auth true'?"
}
2013-10-05 16:49:08 +02:00
next(new UError({
status: 403,
msg: msg,
2013-10-26 14:18:36 +02:00
}))
2013-10-05 16:49:08 +02:00
} else {
next(new UError({
status: 403,
msg: 'user '+req.remoteUser+' not allowed to '+action+' it'
2013-10-26 14:18:36 +02:00
}))
2013-10-05 16:49:08 +02:00
}
2013-06-08 03:16:28 +02:00
}
2013-10-26 14:18:36 +02:00
}
}
2013-06-01 00:57:28 +02:00
2013-10-26 14:18:36 +02:00
var app = express()
// run in production mode by default, just in case
// it shouldn't make any difference anyway
app.set('env', process.env.NODE_ENV || 'production')
2013-12-05 13:27:23 +01:00
function error_reporting_middleware(req, res, next) {
2013-10-26 14:18:36 +02:00
var calls = 0
2013-12-05 13:27:23 +01:00
res.report_error = res.report_error || function(err) {
2013-10-26 14:18:36 +02:00
calls++
if (err.status && err.status >= 400 && err.status < 600) {
if (calls == 1) {
2013-10-26 14:18:36 +02:00
res.status(err.status)
res.send({error: err.msg || err.message || 'unknown error'})
}
} else {
2013-10-18 23:17:53 +02:00
Logger.logger.error({err: err}, 'unexpected error: @{!err.message}\n@{err.stack}')
if (!res.status || !res.send) {
Logger.logger.error('this is an error in express.js, please report this')
res.destroy()
}
if (calls == 1) {
2013-10-26 14:18:36 +02:00
res.status(500)
res.send({error: 'internal server error'})
}
}
}
2013-10-26 14:18:36 +02:00
next()
2013-12-05 13:27:23 +01:00
}
2013-12-05 13:27:23 +01:00
app.use(error_reporting_middleware)
2013-10-26 14:18:36 +02:00
app.use(Middleware.log_and_etagify)
app.use(function(req, res, next) {
res.setHeader('X-Powered-By', config.user_agent)
next()
})
app.use(Cats.middleware)
app.use(basic_auth(function(user, pass) {
2013-10-26 14:18:36 +02:00
return config.authenticate(user, pass)
}))
app.use(express.json({strict: false, limit: config.max_body_size || '10mb'}))
2013-10-26 14:18:36 +02:00
app.use(express.compress())
2013-12-09 04:59:31 +01:00
app.use(Middleware.anti_loop(config))
2014-02-01 09:08:48 +01:00
// validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble
2013-10-26 14:18:36 +02:00
app.param('package', validate_name)
app.param('filename', validate_name)
2014-02-01 09:08:48 +01:00
app.param('tag', validate_name)
app.param('version', validate_name)
app.param('revision', validate_name)
// these can't be safely put into express url for some reason
app.param('_rev', match(/^-rev$/))
app.param('org_couchdb_user', match(/^org\.couchdb\.user:/))
2013-05-31 08:26:11 +02:00
2013-06-08 03:16:28 +02:00
/* app.get('/-/all', function(req, res) {
2013-10-26 14:18:36 +02:00
var https = require('https')
var JSONStream = require('JSONStream')
2013-06-08 03:16:28 +02:00
var request = require('request')({
url: 'https://registry.npmjs.org/-/all',
})
.pipe(JSONStream.parse('*'))
.on('data', function(d) {
2013-10-26 14:18:36 +02:00
console.log(d)
})
})*/
2014-05-07 17:29:47 +02:00
2014-05-07 00:04:03 +02:00
var template = Handlebars.compile(fs.readFileSync(require.resolve('./GUI/index.handlebars'), 'utf8'));
2014-05-06 23:34:48 +02:00
app.get('/', can('access'), function(req, res, next) {
res.setHeader('Content-Type', 'text/html');
2014-05-07 00:40:21 +02:00
2014-05-07 17:10:59 +02:00
storage.get_local(function(err, packages) {
res.send(template({
2014-05-07 17:29:47 +02:00
name: config.title || "Sinopia",
2014-05-07 17:10:59 +02:00
packages: packages,
baseUrl: req.protocol + '://' + req.get('host') + '/'
}));
});
2014-05-06 23:34:48 +02:00
});
2013-06-08 03:16:28 +02:00
2013-06-13 16:21:14 +02:00
// TODO: anonymous user?
app.get('/:package/:version?', can('access'), function(req, res, next) {
2013-12-09 04:58:25 +01:00
storage.get_package(req.params.package, {req: req}, function(err, info) {
2013-10-26 14:18:36 +02:00
if (err) return next(err)
info = utils.filter_tarball_urls(info, req, config)
2013-06-13 16:21:14 +02:00
2013-10-26 14:18:36 +02:00
var version = req.params.version
, t
if (!version) {
2013-10-26 14:18:36 +02:00
return res.send(info)
}
if ((t = utils.get_version(info, version)) != null) {
return res.send(t)
}
if (info['dist-tags'] != null) {
if (info['dist-tags'][version] != null) {
2013-10-26 14:18:36 +02:00
version = info['dist-tags'][version]
if ((t = utils.get_version(info, version)) != null) {
return res.send(t)
}
2013-06-13 16:21:14 +02:00
}
}
return next(new UError({
status: 404,
msg: 'version not found: ' + req.params.version
2013-10-26 14:18:36 +02:00
}))
})
})
2013-06-01 00:57:28 +02:00
2013-06-08 03:16:28 +02:00
app.get('/:package/-/:filename', can('access'), function(req, res, next) {
2013-10-26 14:18:36 +02:00
var stream = storage.get_tarball(req.params.package, req.params.filename)
stream.on('content-length', function(v) {
res.header('Content-Length', v)
})
2013-06-20 15:07:34 +02:00
stream.on('error', function(err) {
2013-10-26 14:18:36 +02:00
return res.report_error(err)
})
res.header('Content-Type', 'application/octet-stream')
2013-10-26 14:18:36 +02:00
stream.pipe(res)
})
2013-05-31 08:26:11 +02:00
//app.get('/*', function(req, res) {
2013-10-26 14:18:36 +02:00
// proxy.request(req, res)
//})
2013-05-31 08:26:11 +02:00
// placeholder 'cause npm require to be authenticated to publish
// we do not do any real authentication yet
app.post('/_session', cookies.express(), function(req, res) {
res.cookies.set('AuthSession', String(Math.random()), {
// npmjs.org sets 10h expire
expires: new Date(Date.now() + 10*60*60*1000)
2013-10-26 14:18:36 +02:00
})
2014-02-23 18:20:50 +01:00
res.send({'ok':true,'name':'somebody','roles':[]})
2013-10-26 14:18:36 +02:00
})
2013-06-08 03:16:28 +02:00
2014-02-01 09:08:48 +01:00
app.get('/-/user/:org_couchdb_user', function(req, res, next) {
2013-10-26 14:18:36 +02:00
res.status(200)
2013-06-08 03:16:28 +02:00
return res.send({
2013-12-27 12:29:23 +01:00
ok: 'you are authenticated as "' + req.remoteUser + '"',
2013-10-26 14:18:36 +02:00
})
})
2013-06-08 03:16:28 +02:00
2014-05-07 17:11:48 +02:00
app.put('/-/user/:org_couchdb_user', function(req, res, next) {
res.status(409)
return res.send({
error: 'registration is not implemented',
})
})
2014-02-01 09:08:48 +01:00
app.put('/-/user/:org_couchdb_user/-rev/*', function(req, res, next) {
2013-12-06 18:46:51 +01:00
if (req.remoteUser == null) {
res.status(403)
return res.send({
error: 'bad username/password, access denied',
})
}
2013-10-26 14:18:36 +02:00
res.status(201)
2013-06-08 03:16:28 +02:00
return res.send({
2013-12-06 18:46:51 +01:00
ok: 'you are authenticated as "' + req.remoteUser + '"',
2013-10-26 14:18:36 +02:00
})
})
2013-05-31 08:26:11 +02:00
2013-12-27 14:06:30 +01:00
// tagging a package
app.put('/:package/:tag', can('publish'), media('application/json'), function(req, res, next) {
if (typeof(req.body) !== 'string') return next('route')
var tags = {}
tags[req.params.tag] = req.body
storage.add_tags(req.params.package, tags, function(err) {
2013-12-27 14:06:30 +01:00
if (err) return next(err)
res.status(201)
return res.send({
ok: 'package tagged'
})
})
})
2013-05-31 08:26:11 +02:00
// publishing a package
app.put('/:package/:_rev?/:revision?', can('publish'), media('application/json'), expect_json, function(req, res, next) {
var name = req.params.package
if (Object.keys(req.body).length == 1 && utils.is_object(req.body.users)) {
return next(new UError({
// 501 status is more meaningful, but npm doesn't show error message for 5xx
status: 404,
msg: 'npm star|unstar calls are not implemented',
}))
}
2013-06-01 00:57:28 +02:00
try {
var metadata = utils.validate_metadata(req.body, name)
2013-06-01 00:57:28 +02:00
} catch(err) {
2013-06-08 03:16:28 +02:00
return next(new UError({
2013-06-01 00:57:28 +02:00
status: 422,
msg: 'bad incoming package data',
}))
2013-05-31 08:26:11 +02:00
}
if (req.params._rev) {
storage.change_package(name, metadata, req.params.revision, function(err) {
after_change(err, 'package changed')
})
} else {
storage.add_package(name, metadata, function(err) {
after_change(err, 'created new package')
})
}
function after_change(err, ok_message) {
2013-12-29 01:58:48 +01:00
// old npm behaviour
if (metadata._attachments == null) {
if (err) return next(err)
2013-10-26 14:18:36 +02:00
res.status(201)
return res.send({
ok: ok_message
})
}
// npm-registry-client 0.3+ embeds tarball into the json upload
// https://github.com/isaacs/npm-registry-client/commit/e9fbeb8b67f249394f735c74ef11fe4720d46ca0
// issue #31, dealing with it here:
if (typeof(metadata._attachments) != 'object'
|| Object.keys(metadata._attachments).length != 1
|| typeof(metadata.versions) != 'object'
|| Object.keys(metadata.versions).length != 1) {
// npm is doing something strange again
// if this happens in normal circumstances, report it as a bug
return next(new UError({
status: 400,
msg: 'unsupported registry call',
}))
}
if (err && err.status != 409) return next(err)
// at this point document is either created or existed before
var t1 = Object.keys(metadata._attachments)[0]
create_tarball(t1, metadata._attachments[t1], function(err) {
2013-12-29 07:41:31 +01:00
if (err) return next(err)
var t2 = Object.keys(metadata.versions)[0]
create_version(t2, metadata.versions[t2], function(err) {
2013-12-29 07:41:31 +01:00
if (err) return next(err)
2013-12-29 01:58:48 +01:00
add_tags(metadata['dist-tags'], function(err) {
if (err) return next(err)
res.status(201)
return res.send({
ok: ok_message
})
})
})
})
}
function create_tarball(filename, data, cb) {
var stream = storage.add_tarball(name, filename)
stream.on('error', function(err) {
cb(err)
})
stream.on('success', function() {
cb()
})
// this is dumb and memory-consuming, but what choices do we have?
stream.end(new Buffer(data.data, 'base64'))
stream.done()
}
function create_version(version, data, cb) {
storage.add_version(name, version, data, null, cb)
}
function add_tags(tags, cb) {
storage.add_tags(name, tags, cb)
}
})
2013-05-31 08:26:11 +02:00
// unpublishing an entire package
app.delete('/:package/-rev/*', can('publish'), function(req, res, next) {
storage.remove_package(req.params.package, function(err) {
2013-10-26 14:18:36 +02:00
if (err) return next(err)
res.status(201)
return res.send({
ok: 'package removed'
2013-10-26 14:18:36 +02:00
})
})
})
// removing a tarball
app.delete('/:package/-/:filename/-rev/:revision', can('publish'), function(req, res, next) {
storage.remove_tarball(req.params.package, req.params.filename, req.params.revision, function(err) {
2013-10-26 14:18:36 +02:00
if (err) return next(err)
res.status(201)
return res.send({
ok: 'tarball removed'
2013-10-26 14:18:36 +02:00
})
})
})
2013-05-31 08:26:11 +02:00
// uploading package tarball
2013-06-08 03:16:28 +02:00
app.put('/:package/-/:filename/*', can('publish'), media('application/octet-stream'), function(req, res, next) {
2013-10-26 14:18:36 +02:00
var name = req.params.package
2013-06-01 00:57:28 +02:00
2013-10-26 14:18:36 +02:00
var stream = storage.add_tarball(name, req.params.filename)
req.pipe(stream)
2013-09-24 08:28:26 +02:00
// checking if end event came before closing
2013-10-26 14:18:36 +02:00
var complete = false
2013-09-24 08:28:26 +02:00
req.on('end', function() {
2013-10-26 14:18:36 +02:00
complete = true
stream.done()
})
2013-09-24 08:28:26 +02:00
req.on('close', function() {
if (!complete) {
2013-10-26 14:18:36 +02:00
stream.abort()
2013-09-24 08:28:26 +02:00
}
2013-10-26 14:18:36 +02:00
})
2013-09-24 08:28:26 +02:00
2013-06-20 15:07:34 +02:00
stream.on('error', function(err) {
2013-10-26 14:18:36 +02:00
return res.report_error(err)
})
2013-09-27 13:31:28 +02:00
stream.on('success', function() {
2013-10-26 14:18:36 +02:00
res.status(201)
2013-06-01 00:57:28 +02:00
return res.send({
ok: 'tarball uploaded successfully'
2013-10-26 14:18:36 +02:00
})
})
})
2013-05-31 08:26:11 +02:00
// adding a version
2013-06-08 03:16:28 +02:00
app.put('/:package/:version/-tag/:tag', can('publish'), media('application/json'), expect_json, function(req, res, next) {
2013-10-26 14:18:36 +02:00
var name = req.params.package
, version = req.params.version
, tag = req.params.tag
2013-05-31 08:26:11 +02:00
2013-06-01 00:57:28 +02:00
storage.add_version(name, version, req.body, tag, function(err) {
2013-10-26 14:18:36 +02:00
if (err) return next(err)
res.status(201)
2013-06-01 00:57:28 +02:00
return res.send({
ok: 'package published'
2013-10-26 14:18:36 +02:00
})
})
})
2013-05-31 08:26:11 +02:00
2013-10-26 14:18:36 +02:00
app.use(app.router)
2013-06-01 00:57:28 +02:00
app.use(function(err, req, res, next) {
2013-12-05 13:27:23 +01:00
if (typeof(res.report_error) !== 'function') {
// in case of very early error this middleware may not be loaded before error is generated
// fixing that
error_reporting_middleware(req, res, function(){})
}
2013-10-26 14:18:36 +02:00
res.report_error(err)
})
2013-06-01 00:57:28 +02:00
2013-10-26 14:18:36 +02:00
return app
}
2013-05-31 08:26:11 +02:00