2013-10-18 16:17:53 -05: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')
|
|
|
|
, basic_auth = Middleware.basic_auth
|
|
|
|
, validate_name = Middleware.validate_name
|
|
|
|
, media = Middleware.media
|
|
|
|
, expect_json = Middleware.expect_json
|
2013-05-31 01:26:11 -05:00
|
|
|
|
2013-06-07 20:16:28 -05:00
|
|
|
module.exports = function(config_hash) {
|
|
|
|
var config = new Config(config_hash);
|
|
|
|
var storage = new Storage(config);
|
|
|
|
|
|
|
|
var can = function(action) {
|
2013-06-14 02:10:50 -05:00
|
|
|
return function(req, res, next) {
|
2013-06-07 20:16:28 -05:00
|
|
|
if (config['allow_'+action](req.params.package, req.remoteUser)) {
|
|
|
|
next();
|
|
|
|
} else {
|
2013-10-11 00:32:59 -05:00
|
|
|
if (!req.remoteUser) {
|
2013-10-05 09:49:08 -05:00
|
|
|
next(new UError({
|
|
|
|
status: 403,
|
|
|
|
msg: "can't "+action+" restricted package without auth, did you forget 'npm set always-auth true'?",
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
next(new UError({
|
|
|
|
status: 403,
|
|
|
|
msg: 'user '+req.remoteUser+' not allowed to '+action+' it'
|
|
|
|
}));
|
|
|
|
}
|
2013-06-07 20:16:28 -05:00
|
|
|
}
|
2013-06-14 02:10:50 -05:00
|
|
|
};
|
2013-06-07 20:16:28 -05:00
|
|
|
};
|
2013-05-31 17:57:28 -05:00
|
|
|
|
2013-05-31 01:26:11 -05:00
|
|
|
var app = express();
|
2013-10-11 04:49:00 -05:00
|
|
|
|
|
|
|
app.use(function(req, res, next) {
|
|
|
|
var calls = 0;
|
|
|
|
res.report_error = function(err) {
|
|
|
|
calls++;
|
|
|
|
if (err.status && err.status >= 400 && err.status < 600) {
|
|
|
|
if (calls == 1) {
|
|
|
|
res.status(err.status);
|
|
|
|
res.send({error: err.msg || err.message || 'unknown error'});
|
|
|
|
}
|
|
|
|
} else {
|
2013-10-18 16:17:53 -05:00
|
|
|
Logger.logger.error({err: err}, 'unexpected error: @{!err.message}\n@{err.stack}')
|
2013-10-11 04:49:00 -05:00
|
|
|
if (calls == 1) {
|
|
|
|
res.status(500);
|
|
|
|
res.send({error: 'internal server error'});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
2013-10-11 00:32:59 -05:00
|
|
|
app.use(Middleware.log_and_etagify);
|
2013-06-14 02:10:50 -05:00
|
|
|
app.use(basic_auth(function(user, pass) {
|
|
|
|
return config.authenticate(user, pass);
|
2013-09-28 11:46:55 -05:00
|
|
|
}));
|
2013-10-06 02:26:05 -05:00
|
|
|
app.use(express.json({strict: false}));
|
2013-06-07 20:16:28 -05:00
|
|
|
|
2013-06-20 12:10:33 -05:00
|
|
|
// TODO: npm DO NOT support compression :(
|
|
|
|
app.use(express.compress());
|
|
|
|
|
2013-05-31 01:26:11 -05:00
|
|
|
app.param('package', validate_name);
|
2013-05-31 17:57:28 -05:00
|
|
|
app.param('filename', validate_name);
|
2013-05-31 01:26:11 -05:00
|
|
|
|
|
|
|
/* app.get('/', function(req, res) {
|
|
|
|
res.send({
|
|
|
|
error: 'unimplemented'
|
|
|
|
});
|
|
|
|
});*/
|
|
|
|
|
2013-06-07 20:16:28 -05:00
|
|
|
/* app.get('/-/all', function(req, res) {
|
|
|
|
var https = require('https');
|
|
|
|
var JSONStream = require('JSONStream');
|
|
|
|
var request = require('request')({
|
|
|
|
url: 'https://registry.npmjs.org/-/all',
|
|
|
|
ca: require('./npmsslkeys'),
|
|
|
|
})
|
|
|
|
.pipe(JSONStream.parse('*'))
|
|
|
|
.on('data', function(d) {
|
|
|
|
console.log(d);
|
|
|
|
});
|
|
|
|
});*/
|
|
|
|
|
2013-06-13 09:21:14 -05:00
|
|
|
// TODO: anonymous user?
|
|
|
|
app.get('/:package/:version?', can('access'), function(req, res, next) {
|
2013-05-31 01:26:11 -05:00
|
|
|
storage.get_package(req.params.package, function(err, info) {
|
2013-06-07 20:16:28 -05:00
|
|
|
if (err) return next(err);
|
2013-06-18 13:14:55 -05:00
|
|
|
info = utils.filter_tarball_urls(info, req, config);
|
2013-06-13 09:21:14 -05:00
|
|
|
|
2013-10-02 13:26:20 -05:00
|
|
|
var version = req.params.version;
|
|
|
|
if (!version) {
|
|
|
|
return res.send(info);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info.versions[version] != null) {
|
|
|
|
return res.send(info.versions[version]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info['dist-tags'] != null) {
|
|
|
|
if (info['dist-tags'][version] != null) {
|
|
|
|
version = info['dist-tags'][version];
|
|
|
|
if (info.versions[version] != null) {
|
|
|
|
return res.send(info.versions[version]);
|
|
|
|
}
|
2013-06-13 09:21:14 -05:00
|
|
|
}
|
|
|
|
}
|
2013-10-02 13:26:20 -05:00
|
|
|
|
|
|
|
return next(new UError({
|
|
|
|
status: 404,
|
|
|
|
msg: 'version not found: ' + req.params.version
|
|
|
|
}));
|
2013-05-31 17:57:28 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2013-06-07 20:16:28 -05:00
|
|
|
app.get('/:package/-/:filename', can('access'), function(req, res, next) {
|
2013-06-20 08:07:34 -05:00
|
|
|
var stream = storage.get_tarball(req.params.package, req.params.filename);
|
|
|
|
stream.on('error', function(err) {
|
2013-10-11 04:49:00 -05:00
|
|
|
return res.report_error(err);
|
2013-05-31 01:26:11 -05:00
|
|
|
});
|
2013-06-20 08:07:34 -05:00
|
|
|
res.header('content-type', 'application/octet-stream');
|
|
|
|
stream.pipe(res);
|
2013-05-31 01:26:11 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
//app.get('/*', function(req, res) {
|
|
|
|
// proxy.request(req, res);
|
|
|
|
//});
|
|
|
|
|
|
|
|
// 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-06-07 20:16:28 -05:00
|
|
|
res.send({"ok":true,"name":"somebody","roles":[]});
|
|
|
|
});
|
|
|
|
|
|
|
|
app.get('/-/user/:argument', function(req, res, next) {
|
|
|
|
// can't put 'org.couchdb.user' in route address for some reason
|
|
|
|
if (req.params.argument.split(':')[0] !== 'org.couchdb.user') return next('route');
|
|
|
|
res.status(200);
|
|
|
|
return res.send({
|
2013-06-14 02:56:02 -05:00
|
|
|
ok: 'you are authenticated as "' + req.user + '"',
|
2013-06-07 20:16:28 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
app.put('/-/user/:argument', function(req, res, next) {
|
2013-06-14 02:56:02 -05:00
|
|
|
// can't put 'org.couchdb.user' in route address for some reason
|
|
|
|
if (req.params.argument.split(':')[0] !== 'org.couchdb.user') return next('route');
|
|
|
|
res.status(409);
|
|
|
|
return res.send({
|
|
|
|
error: 'registration is not implemented',
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
app.put('/-/user/:argument/-rev/*', function(req, res, next) {
|
2013-06-07 20:16:28 -05:00
|
|
|
// can't put 'org.couchdb.user' in route address for some reason
|
|
|
|
if (req.params.argument.split(':')[0] !== 'org.couchdb.user') return next('route');
|
|
|
|
res.status(201);
|
|
|
|
return res.send({
|
2013-06-14 02:56:02 -05:00
|
|
|
ok: 'you are authenticated as "' + req.user + '"',
|
2013-06-07 20:16:28 -05:00
|
|
|
});
|
2013-05-31 01:26:11 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
// publishing a package
|
2013-10-11 00:32:12 -05:00
|
|
|
app.put('/:package/:_rev?/:revision?', can('publish'), media('application/json'), expect_json, function(req, res, next) {
|
|
|
|
if (req.params._rev != null && req.params._rev != '-rev') return next('route');
|
2013-05-31 01:26:11 -05:00
|
|
|
var name = req.params.package;
|
2013-10-02 13:01:18 -05:00
|
|
|
|
|
|
|
if (Object.keys(req.body).length == 1 && typeof(req.body.users) === 'object') {
|
|
|
|
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-05-31 17:57:28 -05:00
|
|
|
try {
|
|
|
|
var metadata = utils.validate_metadata(req.body, name);
|
|
|
|
} catch(err) {
|
2013-06-07 20:16:28 -05:00
|
|
|
return next(new UError({
|
2013-05-31 17:57:28 -05:00
|
|
|
status: 422,
|
|
|
|
msg: 'bad incoming package data',
|
2013-06-07 20:16:28 -05:00
|
|
|
}));
|
2013-05-31 01:26:11 -05:00
|
|
|
}
|
|
|
|
|
2013-10-11 00:32:12 -05:00
|
|
|
if (req.params._rev) {
|
|
|
|
return next(new UError({
|
|
|
|
status: 404,
|
|
|
|
msg: 'unimplemented yet, work in progress',
|
|
|
|
}));
|
|
|
|
/* storage.change_package(name, metadata, req.params.revision, function(err) {
|
|
|
|
if (err) return next(err);
|
|
|
|
res.status(201);
|
|
|
|
return res.send({
|
|
|
|
ok: 'package changed'
|
|
|
|
});
|
|
|
|
});*/
|
|
|
|
} else {
|
|
|
|
storage.add_package(name, metadata, function(err) {
|
|
|
|
if (err) return next(err);
|
|
|
|
res.status(201);
|
|
|
|
return res.send({
|
|
|
|
ok: 'created new package'
|
|
|
|
});
|
2013-05-31 17:57:28 -05:00
|
|
|
});
|
2013-10-11 00:32:12 -05:00
|
|
|
}
|
2013-05-31 01:26:11 -05:00
|
|
|
});
|
|
|
|
|
2013-10-06 03:27:50 -05:00
|
|
|
// unpublishing an entire package
|
|
|
|
app.delete('/:package/-rev/*', can('publish'), function(req, res, next) {
|
|
|
|
storage.remove_package(req.params.package, function(err) {
|
|
|
|
if (err) return next(err);
|
|
|
|
res.status(201);
|
|
|
|
return res.send({
|
|
|
|
ok: 'package removed'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2013-05-31 01:26:11 -05:00
|
|
|
// uploading package tarball
|
2013-06-07 20:16:28 -05:00
|
|
|
app.put('/:package/-/:filename/*', can('publish'), media('application/octet-stream'), function(req, res, next) {
|
2013-05-31 17:57:28 -05:00
|
|
|
var name = req.params.package;
|
|
|
|
|
2013-06-20 08:07:34 -05:00
|
|
|
var stream = storage.add_tarball(name, req.params.filename);
|
|
|
|
req.pipe(stream);
|
2013-09-24 01:28:26 -05:00
|
|
|
|
|
|
|
// checking if end event came before closing
|
|
|
|
var complete = false;
|
|
|
|
req.on('end', function() {
|
|
|
|
complete = true;
|
2013-09-27 06:31:28 -05:00
|
|
|
stream.done();
|
2013-09-24 01:28:26 -05:00
|
|
|
});
|
|
|
|
req.on('close', function() {
|
|
|
|
if (!complete) {
|
2013-09-27 06:31:28 -05:00
|
|
|
stream.abort();
|
2013-09-24 01:28:26 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2013-06-20 08:07:34 -05:00
|
|
|
stream.on('error', function(err) {
|
2013-10-11 04:49:00 -05:00
|
|
|
return res.report_error(err);
|
2013-06-20 08:07:34 -05:00
|
|
|
});
|
2013-09-27 06:31:28 -05:00
|
|
|
stream.on('success', function() {
|
2013-05-31 17:57:28 -05:00
|
|
|
res.status(201);
|
|
|
|
return res.send({
|
|
|
|
ok: 'tarball uploaded successfully'
|
|
|
|
});
|
2013-05-31 01:26:11 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// adding a version
|
2013-06-07 20:16:28 -05:00
|
|
|
app.put('/:package/:version/-tag/:tag', can('publish'), media('application/json'), expect_json, function(req, res, next) {
|
2013-05-31 01:26:11 -05:00
|
|
|
var name = req.params.package;
|
2013-05-31 17:57:28 -05:00
|
|
|
var version = req.params.version;
|
|
|
|
var tag = req.params.tag;
|
2013-05-31 01:26:11 -05:00
|
|
|
|
2013-05-31 17:57:28 -05:00
|
|
|
storage.add_version(name, version, req.body, tag, function(err) {
|
2013-05-31 01:26:11 -05:00
|
|
|
if (err) return next(err);
|
2013-05-31 17:57:28 -05:00
|
|
|
res.status(201);
|
|
|
|
return res.send({
|
|
|
|
ok: 'package published'
|
|
|
|
});
|
2013-05-31 01:26:11 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2013-05-31 17:57:28 -05:00
|
|
|
app.use(app.router);
|
|
|
|
app.use(function(err, req, res, next) {
|
2013-10-11 04:49:00 -05:00
|
|
|
res.report_error(err);
|
2013-05-31 17:57:28 -05:00
|
|
|
});
|
|
|
|
|
2013-05-31 01:26:11 -05:00
|
|
|
return app;
|
|
|
|
};
|
|
|
|
|