Compare commits

...

49 Commits
0.8.7 ... 1.0.6

Author SHA1 Message Date
fengmk2
7b77f09264 Release 1.0.6 2014-08-02 02:13:44 +08:00
fengmk2
7731b44ab4 WTF moment@2.8.0 missing 2014-08-02 02:13:24 +08:00
fengmk2
4bc6998040 Release 1.0.5 2014-08-02 02:04:36 +08:00
fengmk2
0d28fbde54 fix remove tar test cases 2014-08-02 02:03:59 +08:00
fengmk2
660035c1d5 Merge pull request #401 from cnpm/hotfix-unpublish-version
unpublish pkg@version bug hotfix. fixed #400
2014-08-02 01:57:19 +08:00
fengmk2
f21e5a1c07 unpublish pkg@version bug hotfix. fixed #400 2014-08-02 01:56:32 +08:00
fengmk2
4d20d91965 Release 1.0.4 2014-08-01 18:40:45 +08:00
fengmk2
a3e33850fc hotfix #399 use not exists 2014-08-01 18:39:53 +08:00
fengmk2
d888ef3297 Release 1.0.3 2014-08-01 13:31:54 +08:00
fengmk2
ce4ec7e6e9 Merge pull request #397 from cnpm/list-users-packages
add maintaining packages in user page
2014-08-01 13:19:30 +08:00
dead_horse
fe319b06ba Release 1.0.2 2014-08-01 12:05:54 +08:00
dead_horse
d829600ed0 ~_~ fix response message 2014-08-01 12:05:25 +08:00
dead_horse
4dd59cb300 Release 1.0.1 2014-08-01 12:00:33 +08:00
dead_horse
5ff25474c0 Merge pull request #398 from cnpm/fix-auth
hot fix auth error
2014-08-01 11:58:54 +08:00
dead_horse
6d49a859c6 hot fix auth error 2014-08-01 11:58:23 +08:00
dead_horse
094178c3ca add maintaining packages in user page 2014-08-01 10:47:47 +08:00
fengmk2
63a57b906e Release 1.0.0 2014-08-01 09:26:04 +08:00
fengmk2
a09e6b142d Merge pull request #394 from cnpm/list-pravites
add private package list
2014-08-01 08:55:11 +08:00
dead_horse
bca9341e5d add private package list 2014-08-01 01:43:13 +08:00
fengmk2
71b0662d49 Release 0.9.2 2014-07-30 23:15:11 +08:00
fengmk2
b894c0fa2b hotfix save custom user bug 2014-07-30 23:14:53 +08:00
fengmk2
4127fa1f07 Release 0.9.1 2014-07-30 17:20:17 +08:00
dead_horse
5ee2a6e8fd Merge pull request #390 from cnpm/handle-user-service-auth-throw-error
Handle user service auth throw custom error message
2014-07-30 10:16:24 +08:00
fengmk2
8baa34145b Handle user service auth throw custom error message 2014-07-30 10:05:55 +08:00
fengmk2
4acb819356 Merge pull request #389 from cnpm/private-white-list
Private white list
2014-07-30 08:21:43 +08:00
dead_horse
2a84e39aee add test for config private packages 2014-07-30 00:52:44 +08:00
dead_horse
2019d72e36 add config.privatePackages 2014-07-29 23:55:42 +08:00
dead_horse
b9fa7e7f98 add more comments in config/index.js 2014-07-29 23:31:22 +08:00
fengmk2
694795e451 Release 0.9.0 2014-07-29 10:24:56 +08:00
dead_horse
fae67880a5 Merge pull request #388 from cnpm/user-service
Custom user service. fixed #385
2014-07-29 10:17:08 +08:00
fengmk2
0929748cbd listMaintainers only return name and email 2014-07-29 10:06:28 +08:00
fengmk2
a87945c6a0 remove sessions 2014-07-29 01:55:18 +08:00
fengmk2
d7345dcc76 scopes init mv to services/user.js 2014-07-29 01:51:32 +08:00
fengmk2
1a9ea8c6cf show user more profile 2014-07-29 01:42:02 +08:00
fengmk2
8f0ada7bf8 registry show user support custom user service 2014-07-29 00:54:18 +08:00
fengmk2
ddd9c9557d support custom user service for user auth 2014-07-29 00:11:34 +08:00
fengmk2
9b2a846865 remove session middleware 2014-07-28 09:17:42 +08:00
fengmk2
b1b238e20c add DefaultUserService 2014-07-28 09:17:42 +08:00
fengmk2
26031a49d2 Merge pull request #383 from cnpm/issue382-limit-scope
Issue382 limit scope
2014-07-28 09:15:39 +08:00
dead_horse
8bfc8ce71d check scopes in module.getAdapt 2014-07-27 23:32:58 +08:00
dead_horse
dffae785ca test public mode, fix some logic, close #382 2014-07-27 23:28:38 +08:00
dead_horse
078d7ddf3d fix sync scope test 2014-07-27 23:28:38 +08:00
dead_horse
8ec082db89 move scope.js into publishable.js, add forcePublishWithScope 2014-07-27 23:28:38 +08:00
dead_horse
040b727878 config.scopes not exist, means do not support scope 2014-07-27 23:28:38 +08:00
dead_horse
02e1ba63d6 fix scope test cases 2014-07-25 09:39:33 +08:00
dead_horse
a16883618e add test, fix middleware 2014-07-25 00:23:26 +08:00
dead_horse
7bd1c85d4e fix adapt scope 2014-07-25 00:12:01 +08:00
dead_horse
336e6f0da8 fix debug info 2014-07-24 23:55:07 +08:00
dead_horse
d43266670e add assert scope middleware 2014-07-24 23:54:53 +08:00
52 changed files with 2646 additions and 492 deletions

View File

@@ -1,3 +1,5 @@
language: node_js language: node_js
node_js: node_js:
- '0.11' - '0.11'
script: "make test-travis"
after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls"

View File

@@ -1,4 +1,68 @@
1.0.6 / 2014-08-02
==================
* WTF moment@2.8.0 missing
1.0.5 / 2014-08-02
==================
* unpublish pkg@version bug hotfix. fixed #400
1.0.4 / 2014-08-01
==================
* hotfix #399 use not exists
1.0.3 / 2014-08-01
==================
* add maintaining packages in user page
1.0.2 / 2014-08-01
==================
* ~_~ fix auth error response message
1.0.1 / 2014-08-01
==================
* Merge pull request #398 from cnpm/fix-auth
* hot fix auth error
1.0.0 / 2014-08-01
==================
* add private package list
0.9.2 / 2014-07-30
==================
* hotfix save custom user bug
0.9.1 / 2014-07-30
==================
* Handle user service auth throw custom error message
* add test for config private packages
* add config.privatePackages
* add more comments in config/index.js
0.9.0 / 2014-07-29
==================
* scopes init mv to services/user.js
* show user more profile
* registry show user support custom user service
* support custom user service for user auth
* remove session middleware
* add DefaultUserService
* check scopes in module.getAdapt
* test public mode, fix some logic, close #382
* move scope.js into publishable.js, add forcePublishWithScope
* config.scopes not exist, means do not support scope
* add assert scope middleware
0.8.7 / 2014-07-24 0.8.7 / 2014-07-24
================== ==================

View File

@@ -44,11 +44,26 @@ test-cov cov: install pretest
$(TESTS) $(TESTS)
@./node_modules/.bin/cov coverage @./node_modules/.bin/cov coverage
test-travis: install pretest
@NODE_ENV=test node --harmony \
node_modules/.bin/istanbul cover --preserve-comments \
./node_modules/.bin/_mocha \
--report lcovonly \
-- \
--reporter dot \
--timeout $(TIMEOUT) \
--require should \
--require should-http \
--require co-mocha \
--require ./test/init.js \
$(MOCHA_OPTS) \
$(TESTS)
contributors: install contributors: install
@./node_modules/.bin/contributors -f plain -o AUTHORS @./node_modules/.bin/contributors -f plain -o AUTHORS
autod: install autod: install
@./node_modules/.bin/autod -w -e public,view,docs,backup,coverage @./node_modules/.bin/autod -w -e public,view,docs,backup,coverage -k nodemailer
@$(MAKE) install @$(MAKE) install
.PHONY: test .PHONY: test

View File

@@ -1,9 +1,22 @@
cnpmjs.org cnpmjs.org
======= =======
[![Build Status](https://secure.travis-ci.org/cnpm/cnpmjs.org.svg)](http://travis-ci.org/cnpm/cnpmjs.org) [![Dependency Status](https://gemnasium.com/cnpm/cnpmjs.org.svg)](https://gemnasium.com/cnpm/cnpmjs.org) [![NPM version][npm-image]][npm-url]
[![build status][travis-image]][travis-url]
[![Test coverage][coveralls-image]][coveralls-url]
[![Gittip][gittip-image]][gittip-url]
[![David deps][david-image]][david-url]
[![NPM](https://nodei.co/npm/cnpmjs.org.svg?downloads=true&stars=true)](https://nodei.co/npm/cnpmjs.org/) [npm-image]: https://img.shields.io/npm/v/cnpmjs.org.svg?style=flat
[npm-url]: https://npmjs.org/package/cnpmjs.org
[travis-image]: https://img.shields.io/travis/cnpm/cnpmjs.org.svg?style=flat
[travis-url]: https://travis-ci.org/cnpm/cnpmjs.org
[coveralls-image]: https://img.shields.io/coveralls/cnpm/cnpmjs.org.svg?style=flat
[coveralls-url]: https://coveralls.io/r/cnpm/cnpmjs.org?branch=master
[gittip-image]: https://img.shields.io/gittip/fengmk2.svg?style=flat
[gittip-url]: https://www.gittip.com/fengmk2/
[david-image]: https://img.shields.io/david/cnpm/cnpmjs.org.svg?style=flat
[david-url]: https://david-dm.org/cnpm/cnpmjs.org
![logo](https://raw.github.com/cnpm/cnpmjs.org/master/logo.png) ![logo](https://raw.github.com/cnpm/cnpmjs.org/master/logo.png)

View File

@@ -35,8 +35,8 @@ var logger = module.exports = Logger({
}); });
var to = []; var to = [];
for (var name in config.admins) { for (var user in config.admins) {
to.push(config.admins[name]); to.push(config.admins[user]);
} }
function errorFormater(err) { function errorFormater(err) {

View File

@@ -1,34 +0,0 @@
/*!
* cnpmjs.org - common/session.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
/**
* Module dependencies.
*/
var middlewares = require('koa-middlewares');
var config = require('../config');
var key = 'AuthSession';
var cookie = { path: '/', httpOnly: true, maxAge: 3600000 * 24 * 365, signed: false };
var options = {
key: key,
cookie: cookie,
defer: true,
rolling: false
};
if (!config.debug) {
options.store = config.sessionStore || middlewares.RedisStore(config.redis);
}
module.exports = middlewares.session(options);

View File

@@ -27,16 +27,88 @@ var version = require('../package.json').version;
var root = path.dirname(__dirname); var root = path.dirname(__dirname);
var config = { var config = {
version: version, version: version,
/**
* Cluster mode
*/
enableCluster: false,
numCPUs: os.cpus().length,
/*
* server configure
*/
registryPort: 7001, registryPort: 7001,
webPort: 7002, webPort: 7002,
bindingHost: '127.0.0.1', // only binding on 127.0.0.1 for local access bindingHost: '127.0.0.1', // only binding on 127.0.0.1 for local access
enableCluster: false,
numCPUs: os.cpus().length, // debug mode
debug: true, // if debug // if in debug mode, some middleware like limit wont load
// logger module will print to stdout
debug: true,
// session secret
sessionSecret: 'cnpmjs.org test session secret',
// max request json body size
jsonLimit: '10mb',
// log dir name
logdir: path.join(root, '.tmp', 'logs'), logdir: path.join(root, '.tmp', 'logs'),
// update file template dir
uploadDir: path.join(root, '.dist'),
// web page viewCache
viewCache: false, viewCache: false,
// mysql config
// config for koa-limit middleware
// for limit download rates
limit: {
enable: false,
token: 'koa-limit:download',
limit: 1000,
interval: 1000 * 60 * 60 * 24,
whiteList: [],
blackList: [],
message: 'request frequency limited, any question, please contact fengmk2@gmail.com',
},
enableCompress: false, // enable gzip response or not
// default system admins
admins: {
// name: email
fengmk2: 'fengmk2@gmail.com',
admin: 'admin@cnpmjs.org',
dead_horse: 'dead_horse@qq.com',
cnpmjstest10: 'cnpmjstest10@cnpmjs.org',
},
// email notification for errors
mail: {
appname: 'cnpmjs.org',
sender: 'cnpmjs.org mail sender <adderss@gmail.com>',
host: 'smtp.gmail.com',
port: 465,
user: 'address@gmail.com',
pass: 'your password',
ssl: true,
debug: false
},
logoURL: '//ww4.sinaimg.cn/large/69c1d4acgw1ebfly5kjlij208202oglr.jpg', // cnpm logo image url
customReadmeFile: '', // you can use your custom readme file instead the cnpm one
customFooter: '', // you can add copyright and site total script html here
npmClientName: 'cnpm', // use `${name} install package`
packagePageContributorSearch: true, // package page contributor link to search, default is true
// max handle number of package.json `dependencies` property
maxDependencies: 200,
// backup filepath prefix
backupFilePrefix: '/cnpm/backup/',
/**
* mysql config
*/
mysqlServers: [ mysqlServers: [
{ {
host: '127.0.0.1', host: '127.0.0.1',
@@ -49,74 +121,75 @@ var config = {
mysqlMaxConnections: 4, mysqlMaxConnections: 4,
mysqlQueryTimeout: 5000, mysqlQueryTimeout: 5000,
sessionSecret: 'cnpmjs.org test session secret',
redis: { // redis config
// host: 'pub-redis-19533.us-east-1-4.3.ec2.garantiadata.com', // use for koa-limit module as storage
// port: 19533, redis: null,
// pass: 'cnpmjs_dev'
}, // package tarball store in qn by default
jsonLimit: '10mb', // max request json body size
uploadDir: path.join(root, '.dist'),
// qiniu cdn: http://www.qiniu.com/, it free for dev. // qiniu cdn: http://www.qiniu.com/, it free for dev.
qn: { qn: {
// accessKey: "iN7NgwM31j4-BZacMjPrOQBs34UG1maYCAQmhdCV",
// secretKey: "6QTOr2Jg1gcZEWDQXKOGZh5PziC2MCV5KsntT70j",
// bucket: "qtestbucket",
// domain: "http://qtestbucket.qiniudn.com",
accessKey: "5UyUq-l6jsWqZMU6tuQ85Msehrs3Dr58G-mCZ9rE", accessKey: "5UyUq-l6jsWqZMU6tuQ85Msehrs3Dr58G-mCZ9rE",
secretKey: "YaRsPKiYm4nGUt8mdz2QxeV5Q_yaUzVxagRuWTfM", secretKey: "YaRsPKiYm4nGUt8mdz2QxeV5Q_yaUzVxagRuWTfM",
bucket: "qiniu-sdk-test", bucket: "qiniu-sdk-test",
domain: "http://qiniu-sdk-test.qiniudn.com", domain: "http://qiniu-sdk-test.qiniudn.com",
}, },
mail: { // registry url name
appname: 'cnpmjs.org', registryHost: 'r.cnpmjs.org',
sender: 'cnpmjs.org mail sender <adderss@gmail.com>',
host: 'smtp.gmail.com',
port: 465,
user: 'address@gmail.com',
pass: 'your password',
ssl: true,
debug: false
},
/**
* registry mode config
*/
// enable private mode, only admin can publish, other use just can sync package from source npm
enablePrivate: true,
// registry scopes, if don't set, means do not support scopes
scopes: [
'@cnpm',
'@cnpmtest'
],
// redirect @cnpm/private-package => private-package
// forward compatbility for update from lower version cnpmjs.org
adaptScope: true,
// force user publish with scope
// but admins still can publish without scope
forcePublishWithScope: true,
// some registry already have some private packages in global scope
// but we want to treat them as scoped private packages,
// so you can use this white list.
privatePackages: ['private-package'],
/**
* sync configs
*/
// sync dist config
// sync node.js dist from nodejs.org
noticeSyncDistError: true, noticeSyncDistError: true,
disturl: 'http://nodejs.org/dist', disturl: 'http://nodejs.org/dist',
syncDist: false, syncDist: false,
logoURL: '//ww4.sinaimg.cn/large/69c1d4acgw1ebfly5kjlij208202oglr.jpg',
registryHost: 'r.cnpmjs.org',
// customReadmeFile: __dirname + '/web_readme.md',
customReadmeFile: '', // you can use your custom readme file instead the cnpm one
customFooter: '', // you can add copyright and site total script html here
npmClientName: 'cnpm', // use `${name} install package`
packagePageContributorSearch: true, // package page contributor link to search, default is true
sourceNpmRegistry: 'http://registry.npmjs.org',
enablePrivate: true, // enable private mode, only admin can publish, other use just can sync package from source npm
admins: {
// name: email
fengmk2: 'fengmk2@gmail.com',
admin: 'admin@cnpmjs.org',
dead_horse: 'dead_horse@qq.com',
cnpmjstest10: 'cnpmjstest10@cnpmjs.org',
},
syncByInstall: true,
backupFilePrefix: '/cnpm/backup/', // backup filepath prefix
syncModel: 'none', // 'none', 'all', 'exist'
syncConcurrency: 1,
syncInterval: '10m', // sync interval, default is 10 minutes
maxDependencies: 200, // max handle number of package.json `dependencies` property
limit: { // sync source
enable: false, sourceNpmRegistry: 'http://registry.npmjs.org',
token: 'koa-limit:download',
limit: 1000, // if install return 404, try to sync from source registry
interval: 1000 * 60 * 60 * 24, syncByInstall: true,
whiteList: [],
blackList: [], // sync mode select
message: 'request frequency limited, any question, please contact fengmk2@gmail.com', // none: do not sync any module
}, // exist: only sync exist modules
enableCompress: false, // enable gzip response or not // all: sync all modules
defaultScope: '', // default scope name syncModel: 'none', // 'none', 'all', 'exist'
syncConcurrency: 1,
// sync interval, default is 10 minutes
syncInterval: '10m',
}; };
// load config/config.js, everything in config.js will cover the same key in index.js // load config/config.js, everything in config.js will cover the same key in index.js

View File

@@ -39,6 +39,7 @@ var ModuleDeps = require('../../proxy/module_deps');
var ModuleStar = require('../../proxy/module_star'); var ModuleStar = require('../../proxy/module_star');
var ModuleUnpublished = require('../../proxy/module_unpublished'); var ModuleUnpublished = require('../../proxy/module_unpublished');
var packageService = require('../../services/package'); var packageService = require('../../services/package');
var UserService = require('../../services/user');
var downloadAsReadStream = require('../utils').downloadAsReadStream; var downloadAsReadStream = require('../utils').downloadAsReadStream;
/** /**
@@ -440,7 +441,7 @@ exports.addPackageAndDist = function *(next) {
var pkg = this.request.body; var pkg = this.request.body;
var username = this.user.name; var username = this.user.name;
var name = this.params.name; var name = this.params.name || this.params[0];
var filename = Object.keys(pkg._attachments || {})[0]; var filename = Object.keys(pkg._attachments || {})[0];
var version = Object.keys(pkg.versions || {})[0]; var version = Object.keys(pkg.versions || {})[0];
if (!version || !filename) { if (!version || !filename) {
@@ -454,6 +455,34 @@ exports.addPackageAndDist = function *(next) {
var attachment = pkg._attachments[filename]; var attachment = pkg._attachments[filename];
var versionPackage = pkg.versions[version]; var versionPackage = pkg.versions[version];
// should never happened in normal request
if (!versionPackage.maintainers) {
this.status = 400;
this.body = {
error: 'maintainers error',
reason: 'request body need maintainers'
};
return;
}
// notice that admins can not publish to all modules
// (but admins can add self to maintainers first)
// make sure user in auth is in maintainers
// should never happened in normal request
var m = versionPackage.maintainers.filter(function (maintainer) {
return maintainer.name === username;
});
if (!m.length) {
this.status = 403;
this.body = {
error: 'maintainers error',
reason: username + ' does not in maintainer list'
};
return;
}
versionPackage._publish_on_cnpm = true; versionPackage._publish_on_cnpm = true;
var distTags = pkg['dist-tags'] || {}; var distTags = pkg['dist-tags'] || {};
var tags = []; // tag, version var tags = []; // tag, version
@@ -572,7 +601,9 @@ exports.addPackageAndDist = function *(next) {
// PUT /:name/-rev/:rev // PUT /:name/-rev/:rev
exports.updateOrRemove = function* (next) { exports.updateOrRemove = function* (next) {
debug('updateOrRemove module %s, %s, %j', this.url, this.params.name, this.request.body); var name = this.params.name || this.params[0];
debug('updateOrRemove module %s, %s, %j', this.url, name, this.request.body);
var body = this.request.body; var body = this.request.body;
if (body.versions) { if (body.versions) {
yield* exports.removeWithVersions.call(this, next); yield* exports.removeWithVersions.call(this, next);
@@ -612,14 +643,53 @@ exports.updateMaintainers = function* (next) {
return; return;
} }
var r = yield *packageService.updateMaintainers(name, usernames); if (config.customUserService) {
// ensure new authors are vaild
var maintainers = yield* packageService.listMaintainerNamesOnly(name);
var map = {};
var newNames = [];
for (var i = 0; i < maintainers.length; i++) {
map[maintainers[i]] = 1;
}
for (var i = 0; i < usernames.length; i++) {
var username = usernames[i];
if (map[username] !== 1) {
newNames.push(username);
}
}
if (newNames.length > 0) {
var users = yield* UserService.list(newNames);
var map = {};
for (var i = 0; i < users.length; i++) {
var user = users[i];
map[user.login] = 1;
}
var invailds = [];
for (var i = 0; i < newNames.length; i++) {
var username = newNames[i];
if (map[username] !== 1) {
invailds.push(username);
}
}
if (invailds.length > 0) {
this.status = 403;
this.body = {
error: 'invalid user name',
reason: 'User: ' + invailds.join(', ') + ' not exists'
};
return;
}
}
}
var r = yield* packageService.updateMaintainers(name, usernames);
debug('result: %j', r); debug('result: %j', r);
this.status = 201; this.status = 201;
this.body = { this.body = {
ok: true, ok: true,
id: name, id: name,
rev: this.params.rev, rev: this.params.rev || this.params[1],
}; };
}; };
@@ -677,7 +747,7 @@ exports.removeWithVersions = function* (next) {
debug('remove versions: %j, remain versions: %j', removeVersions, remainVersions); debug('remove versions: %j, remain versions: %j', removeVersions, remainVersions);
// step 4: remove all the versions which need to remove // step 4: remove all the versions which need to remove
yield Module.removeByNameAndVersions(name, removeVersions); // let removeTar do remove versions from module table
var tags = yield Module.listTags(name); var tags = yield Module.listTags(name);
var removeTags = []; var removeTags = [];
@@ -715,20 +785,24 @@ exports.removeTar = function* (next) {
var name = this.params.name || this.params[0]; var name = this.params.name || this.params[0];
var filename = this.params.filename || this.params[1]; var filename = this.params.filename || this.params[1];
var id = Number(this.params.rev || this.params[2]); var id = Number(this.params.rev || this.params[2]);
debug('remove tarball with filename: %s, id: %s', filename, id); // cnpmjs.org-2.0.0.tgz
var version = filename.split(name + '-')[1];
if (version) {
// 2.0.0.tgz
version = version.substring(0, version.lastIndexOf('.tgz'));
}
if (!version) {
return yield* next;
}
debug('remove tarball with filename: %s, version: %s, revert to => rev id: %s', filename, version, id);
var username = this.user.name; var username = this.user.name;
if (isNaN(id)) { if (isNaN(id)) {
return yield* next; return yield* next;
} }
var mod = yield Module.getById(id);
if (!mod || mod.name !== name) {
return yield* next;
}
var isMaintainer = yield* packageService.isMaintainer(name, username); var isMaintainer = yield* packageService.isMaintainer(name, username);
if (!isMaintainer && !this.user.isAdmin) { if (!isMaintainer && !this.user.isAdmin) {
this.status = 403; this.status = 403;
this.body = { this.body = {
@@ -737,9 +811,33 @@ exports.removeTar = function* (next) {
}; };
return; return;
} }
var key = mod.package.dist && mod.package.dist.key;
var rs = yield [
Module.getById(id),
Module.get(name, version),
];
var revertTo = rs[0];
var mod = rs[1]; // module need to delete
if (!mod || mod.name !== name) {
return yield* next;
}
var key = mod.package && mod.package.dist && mod.package.dist.key;
key = key || common.getCDNKey(mod.name, filename); key = key || common.getCDNKey(mod.name, filename);
yield nfs.remove(key);
if (revertTo && revertTo.package) {
debug('removing key: %s from nfs, revert to %s@%s', key, revertTo.name, revertTo.package.version);
} else {
debug('removing key: %s from nfs, no revert mod', key);
}
try {
yield nfs.remove(key);
} catch (err) {
logger.error(err);
}
// remove version from table
yield Module.removeByNameAndVersions(name, [version]);
debug('removed %s@%s', name, version);
this.body = { ok: true }; this.body = { ok: true };
}; };
@@ -894,7 +992,7 @@ exports.updateTag = function* () {
// check permission // check permission
var isMaintainer = yield* packageService.isMaintainer(name, this.user.name); var isMaintainer = yield* packageService.isMaintainer(name, this.user.name);
if (!isMaintainer) { if (!isMaintainer && !this.user.isAdmin) {
this.status = 403; this.status = 403;
this.body = { this.body = {
error: 'forbidden user', error: 'forbidden user',

View File

@@ -18,14 +18,31 @@
var debug = require('debug')('cnpmjs.org:controllers:registry:user'); var debug = require('debug')('cnpmjs.org:controllers:registry:user');
var utility = require('utility'); var utility = require('utility');
var crypto = require('crypto'); var crypto = require('crypto');
var UserService = require('../../services/user');
var User = require('../../proxy/user'); var User = require('../../proxy/user');
var config = require('../../config'); var config = require('../../config');
var common = require('../../lib/common');
exports.show = function *(next) { exports.show = function* (next) {
var name = this.params.name; var name = this.params.name;
var user = yield User.get(name); var isAdmin = common.isAdmin(name);
var scopes = config.scopes || [];
if (config.customUserService) {
var customUser = yield* UserService.get(name);
if (customUser) {
isAdmin = !!customUser.site_admin;
scopes = customUser.scopes;
var data = {
user: customUser
};
yield* User.saveCustomUser(data);
}
}
var user = yield* User.get(name);
if (!user) { if (!user) {
return yield *next; return yield* next;
} }
var data = user.json; var data = user.json;
@@ -40,12 +57,32 @@ exports.show = function *(next) {
date: user.gmt_modified, date: user.gmt_modified,
}; };
} }
if (data.login) {
// custom user format
// convert to npm user format
data = {
_id: 'org.couchdb.user:' + user.name,
_rev: user.rev,
name: user.name,
email: user.email,
type: 'user',
roles: [],
date: user.gmt_modified,
avatar: data.avatar_url,
fullname: data.name || data.login,
homepage: data.html_url,
};
}
data._cnpm_meta = { data._cnpm_meta = {
id: user.id, id: user.id,
npm_user: user.npm_user, npm_user: user.npm_user === 1,
custom_user: user.npm_user === 2,
gmt_create: user.gmt_create, gmt_create: user.gmt_create,
gmt_modified: user.gmt_modified, gmt_modified: user.gmt_modified,
admin: !!config.admins[user.name], admin: isAdmin,
scopes: scopes,
}; };
this.body = data; this.body = data;
@@ -85,20 +122,20 @@ function ensurePasswordSalt(user, body) {
// 'content-length': '258', // 'content-length': '258',
// connection: 'keep-alive' } // connection: 'keep-alive' }
// { name: 'mk2', // { name: 'mk2',
// salt: '18d8d51936478446a5466d4fb1633b80f3838b4caaa03649a885ac722cd6', // salt: '12351936478446a5466d4fb1633b80f3838b4caaa03649a885ac722cd6',
// password_sha: '8f4408912a6db1d96b132a90856d99db029cef3d', // password_sha: '123408912a6db1d96b132a90856d99db029cef3d',
// email: 'fengmk2@gmail.com', // email: 'fengmk2@gmail.com',
// _id: 'org.couchdb.user:mk2', // _id: 'org.couchdb.user:mk2',
// type: 'user', // type: 'user',
// roles: [], // roles: [],
// date: '2014-03-15T02:39:25.696Z' } // date: '2014-03-15T02:39:25.696Z' }
exports.add = function *() { exports.add = function* () {
var name = this.params.name; var name = this.params.name;
var body = this.request.body || {}; var body = this.request.body || {};
var user = { var user = {
name: body.name, name: body.name,
salt: body.salt, // salt: body.salt,
password_sha: body.password_sha, // password_sha: body.password_sha,
email: body.email, email: body.email,
ip: this.ip || '0.0.0.0', ip: this.ip || '0.0.0.0',
// roles: body.roles || [], // roles: body.roles || [],
@@ -106,7 +143,7 @@ exports.add = function *() {
ensurePasswordSalt(user, body); ensurePasswordSalt(user, body);
if (!user.name || !user.salt || !user.password_sha || !user.email) { if (!body.password || !user.name || !user.salt || !user.password_sha || !user.email) {
this.status = 422; this.status = 422;
this.body = { this.body = {
error: 'paramError', error: 'paramError',
@@ -114,7 +151,47 @@ exports.add = function *() {
}; };
return; return;
} }
debug('add user: %j', user);
debug('add user: %j', body);
var loginedUser;
try {
loginedUser = yield UserService.auth(body.name, body.password);
} catch (err) {
this.status = err.status || 500;
this.body = {
error: err.name,
reason: err.message
};
return;
}
if (loginedUser) {
var rev = Date.now() + '-' + loginedUser.login;
if (config.customUserService) {
// make sure sync user meta to cnpm database
var data = user;
data.rev = rev;
data.user = loginedUser;
yield* User.saveCustomUser(data);
}
this.status = 201;
this.body = {
ok: true,
id: 'org.couchdb.user:' + loginedUser.login,
rev: rev,
};
return;
}
if (config.customUserService) {
// user login fail, not allow to add new user
this.status = 401;
this.body = {
error: 'unauthorized',
reason: 'Login fail, please check your login name and password'
};
return;
}
var existUser = yield User.get(name); var existUser = yield User.get(name);
if (existUser) { if (existUser) {
@@ -136,24 +213,7 @@ exports.add = function *() {
}; };
}; };
exports.authSession = function *() { // logined before update, no need to auth user again
// body: {"name":"foo","password":"****"}
var body = this.request.body || {};
var name = body.name;
var password = body.password;
var user = yield User.auth(name, password);
debug('authSession %s: %j', name, user);
if (!user) {
this.status = 401;
this.body = {ok: false, name: null, roles: []};
return;
}
var session = yield *this.session;
session.name = user.name;
this.body = {ok: true, name: user.name, roles: []};
};
exports.update = function *(next) { exports.update = function *(next) {
var name = this.params.name; var name = this.params.name;
var rev = this.params.rev; var rev = this.params.rev;
@@ -175,17 +235,19 @@ exports.update = function *(next) {
var body = this.request.body || {}; var body = this.request.body || {};
var user = { var user = {
name: body.name, name: body.name,
salt: body.salt, // salt: body.salt,
password_sha: body.password_sha, // password_sha: body.password_sha,
email: body.email, email: body.email,
ip: this.ip || '0.0.0.0', ip: this.ip || '0.0.0.0',
rev: body.rev || body._rev, rev: body.rev || body._rev,
// roles: body.roles || [], // roles: body.roles || [],
}; };
debug('update user %j', body);
ensurePasswordSalt(user, body); ensurePasswordSalt(user, body);
if (!user.name || !user.salt || !user.password_sha || !user.email) { if (!body.password || !user.name || !user.salt || !user.password_sha || !user.email) {
this.status = 422; this.status = 422;
this.body = { this.body = {
error: 'paramError', error: 'paramError',

View File

@@ -242,6 +242,14 @@ exports.displaySync = function* (next) {
}); });
}; };
exports.listPrivates = function* () {
var packages = yield Module.listPrivates();
yield this.render('private', {
title: 'private packages',
packages: packages
});
};
function setLicense(pkg) { function setLicense(pkg) {
var license; var license;
license = pkg.license || pkg.licenses || pkg.licence || pkg.licences; license = pkg.license || pkg.licenses || pkg.licence || pkg.licences;

View File

@@ -1,4 +1,4 @@
/*! /**!
* cnpmjs.org - controllers/web/package.js * cnpmjs.org - controllers/web/package.js
* *
* Copyright(c) cnpmjs.org and other contributors. * Copyright(c) cnpmjs.org and other contributors.
@@ -6,6 +6,7 @@
* *
* Authors: * Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me) * dead_horse <dead_horse@qq.com> (http://deadhorse.me)
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/ */
'use strict'; 'use strict';
@@ -13,26 +14,68 @@
/** /**
* Module dependencies. * Module dependencies.
*/ */
var config = require('../../config');
var Module = require('../../proxy/module'); var Module = require('../../proxy/module');
var User = require('../../proxy/user'); var User = require('../../proxy/user');
var UserService = require('../../services/user');
var common = require('../../lib/common');
exports.display = function *(next) { exports.display = function* (next) {
var name = this.params.name; var name = this.params.name;
var isAdmin = common.isAdmin(name);
var scopes = config.scopes || [];
if (config.customUserService) {
var customUser = yield* UserService.get(name);
if (customUser) {
isAdmin = !!customUser.site_admin;
scopes = customUser.scopes;
var data = {
user: customUser
};
yield* User.saveCustomUser(data);
}
}
var r = yield [Module.listByAuthor(name), User.get(name)]; var r = yield [Module.listByAuthor(name), User.get(name)];
var packages = r[0]; var packages = r[0] || [];
var user = r[1]; var user = r[1];
if (!user && !packages.length) { if (!user && !packages.length) {
return yield* next; return yield* next;
} }
user = {
user = user || {};
var data = {
name: name, name: name,
email: user && user.email email: user.email,
json: user.json || {}
}; };
if (data.json.login) {
// custom user format
// convert to npm user format
var json = data.json;
data.json = {
_id: 'org.couchdb.user:' + user.name,
_rev: user.rev,
name: user.name,
email: user.email,
type: 'user',
roles: [],
date: user.gmt_modified,
avatar: json.avatar_url,
fullname: json.name || json.login,
homepage: json.html_url,
};
}
yield this.render('profile', { yield this.render('profile', {
title: 'User - ' + name, title: 'User - ' + name,
packages: packages || [], packages: packages,
user: user user: data,
lastModified: user && user.gmt_modified,
isAdmin: isAdmin,
scopes: scopes
}); });
}; };

View File

@@ -177,8 +177,3 @@ Release [History](/history).
## npm and cnpm relation ## npm and cnpm relation
![npm&cnpm](https://docs.google.com/drawings/d/12QeQfGalqjsB77mRnf5Iq5oSXHCIUTvZTwECMonqCmw/pub?w=383&h=284) ![npm&cnpm](https://docs.google.com/drawings/d/12QeQfGalqjsB77mRnf5Iq5oSXHCIUTvZTwECMonqCmw/pub?w=383&h=284)
## 捐赠 Donate
如果您觉得 [cnpmjs.org](/) 对您有帮助,欢迎请作者一杯咖啡.
[![Donate](https://img.alipay.com/sys/personalprod/style/mc/btn-index.png)](https://me.alipay.com/imk2)

View File

@@ -15,46 +15,50 @@
*/ */
var debug = require('debug')('cnpmjs.org:middleware:auth'); var debug = require('debug')('cnpmjs.org:middleware:auth');
var User = require('../proxy/user'); var UserService = require('../services/user');
var config = require('../config');
var common = require('../lib/common'); /**
* Parse the request authorization
* get the real user
*/
module.exports = function (options) { module.exports = function (options) {
return function *auth(next) { return function* auth(next) {
var session = yield *this.session;
debug('%s, %s, %j', this.url, this.sessionId, session);
this.user = {}; this.user = {};
if (session.name) {
this.user.name = session.name;
this.user.isAdmin = common.isAdmin(session.name);
debug('auth exists user: %j, headers: %j', this.user, this.header);
return yield *next;
}
var authorization = (this.get('authorization') || '').split(' ')[1] || ''; var authorization = (this.get('authorization') || '').split(' ')[1] || '';
authorization = authorization.trim(); authorization = authorization.trim();
debug('%s %s with %j', this.method, this.url, authorization);
if (!authorization) { if (!authorization) {
return yield *next; return yield* next;
} }
authorization = new Buffer(authorization, 'base64').toString().split(':'); authorization = new Buffer(authorization, 'base64').toString().split(':');
if (authorization.length !== 2) { if (authorization.length !== 2) {
return yield *next; return yield* next;
} }
var username = authorization[0]; var username = authorization[0];
var password = authorization[1]; var password = authorization[1];
var row = yield User.auth(username, password); var row;
if (!row) { try {
debug('auth fail user: %j, headers: %j', row, this.header); row = yield* UserService.auth(username, password);
return yield *next; } catch (err) {
// do not response error here
// many request do not need login
this.user.error = err;
} }
this.user.name = row.name; if (!row) {
this.user.isAdmin = common.isAdmin(row.name); debug('auth fail user: %j, headers: %j', row, this.header);
return yield* next;
}
this.user.name = row.login;
this.user.isAdmin = row.site_admin;
this.user.scopes = row.scopes;
debug('auth pass user: %j, headers: %j', this.user, this.header); debug('auth pass user: %j, headers: %j', this.user, this.header);
yield *next; yield* next;
}; };
}; };

View File

@@ -15,7 +15,17 @@
*/ */
module.exports = function *login(next) { module.exports = function *login(next) {
if (this.user.error) {
this.status = this.user.error.status || 500;
this.body = {
error: this.user.error.name,
reason: this.user.error.message
};
return;
}
if (!this.user.name) { if (!this.user.name) {
this.status = 401; this.status = 401;
this.body = { this.body = {
error: 'unauthorized', error: 'unauthorized',

View File

@@ -14,11 +14,14 @@
* Module dependencies. * Module dependencies.
*/ */
var util = require('util');
var config = require('../config'); var config = require('../config');
var debug = require('debug')('cnpmjs.org:middlewares/publishable');
module.exports = function *publishable(next) { module.exports = function *publishable(next) {
// private mode, only admin user can publish
if (config.enablePrivate && !this.user.isAdmin) { if (config.enablePrivate && !this.user.isAdmin) {
// private mode, only admin user can publish
this.status = 403; this.status = 403;
this.body = { this.body = {
error: 'no_perms', error: 'no_perms',
@@ -26,5 +29,75 @@ module.exports = function *publishable(next) {
}; };
return; return;
} }
yield *next;
// public mode, all user have permission to publish
// but if `config.scopes` exist, only can publish with scopes in `config.scope`
// if `config.forcePublishWithScope` set to true, only admins can publish without scope
var name = this.params.name || this.params[0];
// check if is private package list in config
if (config.privatePackages && config.privatePackages.indexOf(name) !== -1) {
return yield* next;
}
// scope
if (name[0] === '@') {
if (checkScope(name, this)) {
return yield* next;
}
return;
}
// none-scope
if (checkNoneScope(this)) {
return yield* next;
}
}; };
/**
* check module's scope legal
*/
function checkScope(name, ctx) {
if (!ctx.user.scopes || !ctx.user.scopes.length) {
ctx.status = 404;
return false;
}
var scope = name.split('/')[0];
if (ctx.user.scopes.indexOf(scope) === -1) {
debug('assert scope %s error', name);
ctx.status = 400;
ctx.body = {
error: 'invalid scope',
reason: util.format('scope %s not match legal scopes %j', scope, ctx.user.scopes)
};
return false;
}
return true;
}
/**
* check if user have permission to publish without scope
*/
function checkNoneScope(ctx) {
if (!config.scopes
|| !config.scopes.length
|| !config.forcePublishWithScope) {
return true;
}
// only admins can publish or unpublish non-scope modules
if (ctx.user.isAdmin) {
return true;
}
ctx.status = 403;
ctx.body = {
error: 'no_perms',
reason: 'only allow publish with ' + ctx.user.scopes.join(',') + ' scope(s)'
};
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "cnpmjs.org", "name": "cnpmjs.org",
"version": "0.8.7", "version": "1.0.6",
"description": "Private npm registry and web for Enterprise, base on MySQL and Simple Store Service", "description": "Private npm registry and web for Enterprise, base on MySQL and Simple Store Service",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@@ -12,7 +12,7 @@
"dependencies": { "dependencies": {
"bytes": "1.0.0", "bytes": "1.0.0",
"cheerio": "0.17.0", "cheerio": "0.17.0",
"co": "3.0.6", "co": "3.1.0",
"co-defer": "0.1.0", "co-defer": "0.1.0",
"co-gather": "0.0.1", "co-gather": "0.0.1",
"co-read": "0.1.0", "co-read": "0.1.0",
@@ -27,7 +27,7 @@
"graceful": "0.1.0", "graceful": "0.1.0",
"gravatar": "1.0.6", "gravatar": "1.0.6",
"humanize-number": "0.0.2", "humanize-number": "0.0.2",
"koa": "0.8.1", "koa": "0.8.2",
"koa-limit": "1.0.2", "koa-limit": "1.0.2",
"koa-markdown": "0.0.3", "koa-markdown": "0.0.3",
"koa-middlewares": "1.2.0", "koa-middlewares": "1.2.0",
@@ -35,7 +35,7 @@
"mime": "1.2.11", "mime": "1.2.11",
"mini-logger": "0.3.0", "mini-logger": "0.3.0",
"mkdirp": "0.5.0", "mkdirp": "0.5.0",
"moment": "2.7.0", "moment": "~2.8.1",
"ms": "0.6.2", "ms": "0.6.2",
"multiline": "0.3.4", "multiline": "0.3.4",
"mysql": "2.4.1", "mysql": "2.4.1",
@@ -43,8 +43,8 @@
"qn": "0.2.2", "qn": "0.2.2",
"ready": "0.1.1", "ready": "0.1.1",
"redis": "0.11.0", "redis": "0.11.0",
"semver": "2.3.1", "semver": "3.0.1",
"thunkify-wrap": "1.0.1", "thunkify-wrap": "1.0.2",
"utility": "0.1.16" "utility": "0.1.16"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -9,6 +9,7 @@
*/ */
'use strict'; 'use strict';
/* jshint -W032 */
/** /**
* Module dependencies. * Module dependencies.

View File

@@ -9,6 +9,8 @@
*/ */
'use strict'; 'use strict';
/* jshint -W032 */
/** /**
* Module dependencies. * Module dependencies.

View File

@@ -9,6 +9,7 @@
*/ */
'use strict'; 'use strict';
/* jshint -W032 */
/** /**
* Module dependencies. * Module dependencies.
@@ -494,63 +495,6 @@ exports.removeByNameAndVersions = function (name, versions, callback) {
mysql.query(DELETE_MODULE_BY_NAME_AND_VERSIONS_SQL, [name, versions], callback); mysql.query(DELETE_MODULE_BY_NAME_AND_VERSIONS_SQL, [name, versions], callback);
}; };
var LIST_BY_AUTH_SQLS = [];
LIST_BY_AUTH_SQLS.push(multiline(function () {;/*
SELECT
distinct(name) AS name
FROM
module
WHERE
author=?
ORDER BY
publish_time DESC
LIMIT
100;
*/}));
LIST_BY_AUTH_SQLS.push(multiline(function () {;/*
SELECT
module_id
FROM
tag
WHERE
tag="latest" AND name IN (?);
*/}));
LIST_BY_AUTH_SQLS.push(multiline(function () {;/*
SELECT
name, description
FROM
module
WHERE
id IN (?)
ORDER BY
publish_time DESC;
*/}));
exports.listByAuthor = function (author, callback) {
var ep = eventproxy.create();
ep.fail(callback);
mysql.query(LIST_BY_AUTH_SQLS[0], [author], ep.done(function (rows) {
if (!rows || rows.length === 0) {
return callback(null, []);
}
ep.emit('names', rows.map(function (r) {
return r.name;
}));
}));
ep.on('names', function (names) {
mysql.query(LIST_BY_AUTH_SQLS[1], [names], ep.done(function (rows) {
if (!rows || rows.length === 0) {
return callback(null, []);
}
ep.emit('ids', rows.map(function (r) {
return r.module_id;
}));
}));
});
ep.on('ids', function (ids) {
mysql.query(LIST_BY_AUTH_SQLS[2], [ids], callback);
});
};
var SEARCH_MODULES_SQL = multiline(function () {;/* var SEARCH_MODULES_SQL = multiline(function () {;/*
SELECT SELECT
module_id module_id
@@ -677,15 +621,128 @@ exports.removeTagsByNames = function* (moduleName, tagNames) {
return yield mysql.query(DELETE_TAGS_BY_NAMES_SQL, [moduleName, tagNames]); return yield mysql.query(DELETE_TAGS_BY_NAMES_SQL, [moduleName, tagNames]);
}; };
/**
* forward compatbility for update from lower version cnpmjs.org
* redirect @scope/name => name
*/
exports.getAdaptName = function* (name) { exports.getAdaptName = function* (name) {
if (!config.defaultScope || name.indexOf(config.defaultScope + '/') !== 0) { if (!config.scopes
|| !config.scopes.length
|| !config.adaptScope) {
return; return;
} }
name = name.split('/')[1];
// only private module can adapt var tmp = name.split('/');
var scope = tmp[0];
name = tmp[1];
if (config.scopes.indexOf(scope) === -1) {
return;
}
var pkg = yield exports.getByTag(name, 'latest'); var pkg = yield exports.getByTag(name, 'latest');
// only private module can adapt
if (pkg && pkg.package._publish_on_cnpm) { if (pkg && pkg.package._publish_on_cnpm) {
return name; return name;
} }
return; return;
}; };
exports.listPrivates = function* () {
var scopes = config.scopes;
if (!scopes || !scopes.length) {
return [];
}
var privatePackages = config.privatePackages || [];
var args = [];
var sql = 'SELECT module_id AS id FROM tag WHERE tag="latest" AND (';
var wheres = [];
scopes.forEach(function (scope) {
wheres.push('name LIKE ?');
args.push(scope + '%');
});
if (privatePackages.length) {
wheres.push('name in (?)');
args.push(privatePackages);
}
sql = sql + wheres.join(' OR ') + ')';
var ids = yield mysql.query(sql, args);
ids = ids.map(function (row) {
return row.id;
});
if (!ids.length) {
return [];
}
return yield mysql.query(QUERY_MODULES_BY_ID_SQL, [ids]);
};
var LIST_BY_AUTH_SQLS = [];
LIST_BY_AUTH_SQLS.push(multiline(function () {;/*
SELECT
distinct(name) AS name
FROM
module
WHERE
author=?
ORDER BY
publish_time DESC
LIMIT
100;
*/}));
LIST_BY_AUTH_SQLS.push(multiline(function () {;/*
SELECT
name
FROM
module_maintainer
WHERE
user = ?
*/}));
LIST_BY_AUTH_SQLS.push(multiline(function () {;/*
SELECT
module_id
FROM
tag
WHERE
tag="latest" AND name IN (?);
*/}));
LIST_BY_AUTH_SQLS.push(multiline(function () {;/*
SELECT
name, description
FROM
module
WHERE
id IN (?)
ORDER BY
publish_time DESC;
*/}));
exports.listByAuthor = function* (author, callback) {
var names = yield [
mysql.query(LIST_BY_AUTH_SQLS[0], [author]),
mysql.query(LIST_BY_AUTH_SQLS[1], [author])
];
names = names[0].concat(names[1]).map(function (n) {
return n.name;
}).sort();
if (!names.length) {
return [];
}
var ids = yield mysql.query(LIST_BY_AUTH_SQLS[2], [names]);
if (!ids.length) {
return [];
}
ids = ids.map(function (i) {
return i.module_id;
});
return yield mysql.query(LIST_BY_AUTH_SQLS[3], [ids]);
};

View File

@@ -9,6 +9,7 @@
*/ */
"use strict"; "use strict";
/* jshint -W032 */
/** /**
* Module dependencies. * Module dependencies.

View File

@@ -9,6 +9,7 @@
*/ */
'use strict'; 'use strict';
/* jshint -W032 */
/** /**
* Module dependencies. * Module dependencies.
@@ -26,7 +27,7 @@ exports.create = function (data, callback) {
name: data.name, name: data.name,
username: data.username, username: data.username,
log: '' log: ''
} };
mysql.query(INSERT_LOG_SQL, [args], function (err, result) { mysql.query(INSERT_LOG_SQL, [args], function (err, result) {
if (err) { if (err) {
return callback(err); return callback(err);

View File

@@ -9,6 +9,7 @@
*/ */
'use strict'; 'use strict';
/* jshint -W032 */
/** /**
* Module dependencies. * Module dependencies.

View File

@@ -9,6 +9,7 @@
*/ */
'use strict'; 'use strict';
/* jshint -W032 */
/** /**
* Module dependencies. * Module dependencies.

View File

@@ -351,13 +351,13 @@ SyncModuleWorker.prototype._sync = function* (name, pkg) {
maintainers = [maintainers]; maintainers = [maintainers];
} }
maintainers.forEach(function (m) { maintainers.forEach(pushName);
if (m.name) { }
npmUsernames[m.name.toLowerCase()] = 1; function pushName(m) {
} if (m.name) {
}); npmUsernames[m.name.toLowerCase()] = 1;
}
} }
// get the missing star users // get the missing star users
var starUsers = pkg.users || {}; var starUsers = pkg.users || {};
for (var k in starUsers) { for (var k in starUsers) {

View File

@@ -9,6 +9,7 @@
*/ */
'use strict'; 'use strict';
/* jshint -W032 */
/** /**
* Module dependencies. * Module dependencies.

View File

@@ -9,6 +9,7 @@
*/ */
'use strict'; 'use strict';
/* jshint -W032 */
/** /**
* Module dependencies. * Module dependencies.
@@ -20,52 +21,10 @@ var config = require('../config');
var mysql = require('../common/mysql'); var mysql = require('../common/mysql');
var multiline = require('multiline'); var multiline = require('multiline');
var SELECT_USER_SQL = multiline(function () {;/*
SELECT
id, rev, name, email, salt, password_sha, ip,
roles, json, npm_user, gmt_create, gmt_modified
FROM
user
WHERE
name=?;
*/});
exports.get = function (name, callback) {
mysql.queryOne(SELECT_USER_SQL, [name], function (err, row) {
if (row) {
try {
row.roles = row.roles ? JSON.parse(row.roles) : [];
} catch (e) {
row.roles = [];
}
try {
row.json = row.json ? JSON.parse(row.json) : null;
} catch (e) {
row.json = null;
}
}
callback(err, row);
});
};
function passwordSha(password, salt) { function passwordSha(password, salt) {
return utility.sha1(password + salt); return utility.sha1(password + salt);
} }
exports.auth = function (name, password, callback) {
exports.get(name, function (err, row) {
if (err || !row) {
return callback(err, row);
}
var sha = passwordSha(password, row.salt);
if (row.password_sha !== sha) {
row = null;
}
callback(null, row);
});
};
var INSERT_USER_SQL = 'INSERT INTO user SET ?'; var INSERT_USER_SQL = 'INSERT INTO user SET ?';
exports.add = function (user, callback) { exports.add = function (user, callback) {
var roles = user.roles || []; var roles = user.roles || [];
@@ -143,7 +102,42 @@ thunkify(exports);
exports.passwordSha = passwordSha; exports.passwordSha = passwordSha;
exports.saveNpmUser = function *(user) { var SELECT_USER_SQL = 'SELECT \
id, rev, name, email, salt, password_sha, ip, \
roles, json, npm_user, gmt_create, gmt_modified \
FROM \
user \
WHERE \
name=?;';
exports.get = function* (name) {
var row = yield mysql.queryOne(SELECT_USER_SQL, [name]);
if (row) {
try {
row.roles = row.roles ? JSON.parse(row.roles) : [];
} catch (e) {
row.roles = [];
}
try {
row.json = row.json ? JSON.parse(row.json) : null;
} catch (e) {
row.json = null;
}
}
return row;
};
exports.auth = function* (name, password) {
var row = yield* exports.get(name);
if (row) {
var sha = passwordSha(password, row.salt);
if (row.password_sha !== sha) {
row = null;
}
}
return row;
};
exports.saveNpmUser = function* (user) {
var sql = 'SELECT id, json FROM user WHERE name=?;'; var sql = 'SELECT id, json FROM user WHERE name=?;';
var row = yield mysql.queryOne(sql, [user.name]); var row = yield mysql.queryOne(sql, [user.name]);
if (!row) { if (!row) {
@@ -156,17 +150,47 @@ exports.saveNpmUser = function *(user) {
} }
}; };
var LIST_BY_NAMES_SQL = multiline(function () {;/* exports.saveCustomUser = function* (data) {
SELECT var sql = 'SELECT id, json FROM user WHERE name=?;';
id, name, email, json var row = yield mysql.queryOne(sql, [data.user.login]);
FROM var salt = data.salt || '0';
user var password_sha = data.password_sha || '0';
WHERE var ip = data.ip || '0';
name in (?); var rev = rev || '1-' + data.user.login;
*/}); var json = JSON.stringify(data.user);
exports.listByNames = function *(names) { if (!row) {
sql = 'INSERT INTO user(npm_user, json, rev, name, email, salt, password_sha, ip, gmt_create, gmt_modified) \
VALUES(2, ?, ?, ?, ?, ?, ?, ?, now(), now());';
yield mysql.query(sql, [
json, rev, data.user.login, data.user.email,
salt, password_sha, ip
]);
} else {
sql = 'UPDATE user SET json=?, rev=?, salt=?, password_sha=?, ip=? WHERE id=?;';
yield mysql.query(sql, [
json, rev,
salt, password_sha, ip,
row.id
]);
}
};
var LIST_BY_NAMES_SQL = 'SELECT \
id, name, email, json \
FROM \
user \
WHERE \
name in (?);';
exports.listByNames = function* (names) {
if (names.length === 0) { if (names.length === 0) {
return []; return [];
} }
return yield mysql.query(LIST_BY_NAMES_SQL, [names]); return yield mysql.query(LIST_BY_NAMES_SQL, [names]);
}; };
var SEARCH_SQL = 'SELECT id, name, email, json FROM user WHERE name LIKE ? LIMIT ?;';
exports.search = function* (query, options) {
var limit = options.limit;
query = query + '%';
return yield mysql.query(SEARCH_SQL, [query, limit]);
};

View File

@@ -51,28 +51,28 @@ function routes(app) {
app.get('/:name', syncByInstall, mod.show); app.get('/:name', syncByInstall, mod.show);
app.get('/:name/:version', syncByInstall, mod.get); app.get('/:name/:version', syncByInstall, mod.get);
// try to add module // try to add module
// app.put(/^\/(@[\w\-\.]+\/[\w\-\.]+)$/, login, publishable, mod.addPackageAndDist); app.put(/^\/(@[\w\-\.]+\/[\w\-\.]+)$/, login, publishable, mod.addPackageAndDist);
app.put('/:name', login, publishable, mod.addPackageAndDist); app.put('/:name', login, publishable, mod.addPackageAndDist);
// sync from source npm // sync from source npm
app.put('/:name/sync', sync.sync); app.put('/:name/sync', sync.sync);
app.get('/:name/sync/log/:id', sync.getSyncLog); app.get('/:name/sync/log/:id', sync.getSyncLog);
app.put(/^\/(@[\w\-\.]+\/[\w\-\.]+)\/([\w\-\.]+)/, login, mod.updateTag); app.put(/^\/(@[\w\-\.]+\/[\w\-\.]+)\/([\w\-\.]+)$/, login, mod.updateTag);
app.put('/:name/:tag', login, mod.updateTag); app.put('/:name/:tag', login, mod.updateTag);
// need limit by ip // need limit by ip
app.get(/^\/(@[\w\-\.]+\/[\w\-\.]+)\/download\/(@[\w\-\.]+\/[\w\-\.]+)/, limit, mod.download); app.get(/^\/(@[\w\-\.]+\/[\w\-\.]+)\/download\/(@[\w\-\.]+\/[\w\-\.]+)$/, limit, mod.download);
app.get('/:name/download/:filename', limit, mod.download); app.get('/:name/download/:filename', limit, mod.download);
// delete tarball // delete tarball
app.delete(/^\/(@[\w\-\.]+\/[\w\-\.]+)\/download\/(@[\w\-\.]+\/[\w\-\.]+)\/\-rev\/([\w\-\.]+)/, app.delete(/^\/(@[\w\-\.]+\/[\w\-\.]+)\/download\/(@[\w\-\.]+\/[\w\-\.]+)\/\-rev\/([\w\-\.]+)$/,
login, publishable, mod.removeTar); login, publishable, mod.removeTar);
app.delete('/:name/download/:filename/-rev/:rev', login, publishable, mod.removeTar); app.delete('/:name/download/:filename/-rev/:rev', login, publishable, mod.removeTar);
// update module, unpublish will PUT this // update module, unpublish will PUT this
app.put(/^\/(@[\w\-\.]+\/[\w\-\.]+)\/\-rev\/([\w\-\.]+)/, login, publishable, mod.updateOrRemove); app.put(/^\/(@[\w\-\.]+\/[\w\-\.]+)\/\-rev\/([\w\-\.]+)$/, login, publishable, mod.updateOrRemove);
app.delete(/^\/(@[\w\-\.]+\/[\w\-\.]+)\/\-rev\/([\w\-\.]+)/, login, publishable, mod.removeAll); app.delete(/^\/(@[\w\-\.]+\/[\w\-\.]+)\/\-rev\/([\w\-\.]+)$/, login, publishable, mod.removeAll);
app.put('/:name/-rev/:rev', login, publishable, mod.updateOrRemove); app.put('/:name/-rev/:rev', login, publishable, mod.updateOrRemove);
app.delete('/:name/-rev/:rev', login, publishable, mod.removeAll); app.delete('/:name/-rev/:rev', login, publishable, mod.removeAll);
@@ -81,8 +81,6 @@ function routes(app) {
app.put('/-/user/org.couchdb.user::name', user.add); app.put('/-/user/org.couchdb.user::name', user.add);
app.get('/-/user/org.couchdb.user::name', user.show); app.get('/-/user/org.couchdb.user::name', user.show);
app.put('/-/user/org.couchdb.user::name/-rev/:rev', login, user.update); app.put('/-/user/org.couchdb.user::name/-rev/:rev', login, user.update);
// _session
app.post('/_session', user.authSession);
} }
module.exports = routes; module.exports = routes;

View File

@@ -31,15 +31,15 @@ function routes(app) {
app.get('/package/:name', pkg.display); app.get('/package/:name', pkg.display);
app.get('/package/:name/:version', pkg.display); app.get('/package/:name/:version', pkg.display);
app.get('/privates', pkg.listPrivates);
app.get(/\/browse\/keyword\/(@[\w\-\.]+\/[\w\-\.]+)$/, pkg.search); app.get(/\/browse\/keyword\/(@[\w\-\.]+\/[\w\-\.]+)$/, pkg.search);
app.get('/browse/keyword/:word', pkg.search); app.get('/browse/keyword/:word', pkg.search);
app.get('/~:name', user.display); app.get('/~:name', user.display);
app.get(/\/sync\/(@[\w\-\.]+\/[\w\-\.]+)$/, pkg.displaySync);
app.get('/sync/:name', pkg.displaySync); app.get('/sync/:name', pkg.displaySync);
app.put(/\/sync\/(@[\w\-\.]+\/[\w\-\.]+)$/, sync.sync);
app.put('/sync/:name', sync.sync); app.put('/sync/:name', sync.sync);
// params: [$name, $id] // params: [$name, $id]

View File

@@ -22,7 +22,6 @@ var middlewares = require('koa-middlewares');
var routes = require('../routes/registry'); var routes = require('../routes/registry');
var logger = require('../common/logger'); var logger = require('../common/logger');
var config = require('../config'); var config = require('../config');
var session = require('../common/session');
var auth = require('../middleware/auth'); var auth = require('../middleware/auth');
var staticCache = require('../middleware/static'); var staticCache = require('../middleware/static');
var notFound = require('../middleware/registry_not_found'); var notFound = require('../middleware/registry_not_found');
@@ -34,7 +33,6 @@ app.use(staticCache);
app.keys = ['todokey', config.sessionSecret]; app.keys = ['todokey', config.sessionSecret];
app.proxy = true; app.proxy = true;
app.use(session);
app.use(middlewares.bodyParser({jsonLimit: config.jsonLimit})); app.use(middlewares.bodyParser({jsonLimit: config.jsonLimit}));
app.use(auth()); app.use(auth());
app.use(notFound); app.use(notFound);
@@ -57,6 +55,7 @@ routes(app);
*/ */
app.on('error', function (err, ctx) { app.on('error', function (err, ctx) {
// console.log(err.stack)
err.url = err.url || ctx.request.url; err.url = err.url || ctx.request.url;
logger.error(err); logger.error(err);
}); });

View File

@@ -21,7 +21,6 @@ var fs = require('fs');
var koa = require('koa'); var koa = require('koa');
var middlewares = require('koa-middlewares'); var middlewares = require('koa-middlewares');
var markdown = require('koa-markdown'); var markdown = require('koa-markdown');
var session = require('../common/session');
var opensearch = require('../middleware/opensearch'); var opensearch = require('../middleware/opensearch');
var notFound = require('../middleware/web_not_found'); var notFound = require('../middleware/web_not_found');
var staticCache = require('../middleware/static'); var staticCache = require('../middleware/static');
@@ -41,7 +40,6 @@ app.use(staticCache);
app.use(opensearch); app.use(opensearch);
app.keys = ['todokey', config.sessionSecret]; app.keys = ['todokey', config.sessionSecret];
app.proxy = true; app.proxy = true;
app.use(session);
app.use(middlewares.bodyParser()); app.use(middlewares.bodyParser());
app.use(auth()); app.use(auth());
app.use(notFound); app.use(notFound);
@@ -107,6 +105,7 @@ routes(app);
app.on('error', function (err, ctx) { app.on('error', function (err, ctx) {
err.url = err.url || ctx.request.url; err.url = err.url || ctx.request.url;
console.log(err.stack);
logger.error(err); logger.error(err);
}); });

View File

@@ -0,0 +1,139 @@
/**!
* cnpmjs.org - services/default_user_service.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var gravatar = require('gravatar');
var User = require('../proxy/user');
var isAdmin = require('../lib/common').isAdmin;
var config = require('../config');
// User: https://github.com/cnpm/cnpmjs.org/wiki/Use-Your-Own-User-Authorization#user-data-structure
// {
// "login": "fengmk2",
// "email": "fengmk2@gmail.com",
// "name": "Yuan Feng",
// "html_url": "http://fengmk2.github.com",
// "avatar_url": "https://avatars3.githubusercontent.com/u/156269?s=460",
// "im_url": "",
// "site_admin": false,
// "scopes": ["@org1", "@org2"]
// }
module.exports = DefaultUserService;
function convertToUser(row) {
var user = {
login: row.name,
email: row.email,
name: row.name,
html_url: 'http://cnpmjs.org/~' + row.name,
avatar_url: '',
im_url: '',
site_admin: isAdmin(row.name),
scopes: config.scopes
};
if (row.json) {
var data = row.json;
if (data.login) {
// custom user
user = data;
} else {
// npm user
if (data.avatar) {
user.avatar_url = data.avatar;
}
if (data.fullname) {
user.name = data.fullname;
}
if (data.homepage) {
user.html_url = data.homepage;
}
if (data.twitter) {
user.im_url = 'https://twitter.com/' + data.twitter;
}
}
}
if (!user.avatar_url) {
user.avatar_url = gravatar.url(user.email, {s: '50', d: 'retro'}, true);
}
return user;
}
function DefaultUserService() {}
var proto = DefaultUserService.prototype;
/**
* Auth user with login name and password
* @param {String} login login name
* @param {String} password login password
* @return {User}
*/
proto.auth = function* (login, password) {
var row = yield* User.auth(login, password);
if (!row) {
return null;
}
return convertToUser(row);
};
/**
* Get user by login name
* @param {String} login login name
* @return {User}
*/
proto.get = function* (login) {
var row = yield* User.get(login);
if (!row) {
return null;
}
return convertToUser(row);
};
/**
* List users
* @param {Array<String>} logins login names
* @return {Array<User>}
*/
proto.list = function* (logins) {
var rows = yield* User.listByNames(logins);
var users = [];
rows.forEach(function (row) {
users.push(convertToUser(row));
});
return users;
};
/**
* Search users
* @param {String} query query keyword
* @param {Object} [options] optional query params
* - {Number} limit match users count, default is `20`
* @return {Array<User>}
*/
proto.search = function* (query, options) {
options = options || {};
options.limit = parseInt(options.limit);
if (!options.limit || options.limit < 0) {
options.limit = 20;
}
var rows = yield* User.search(query, options);
var users = [];
rows.forEach(function (row) {
users.push(convertToUser(row));
});
return users;
};

View File

@@ -32,6 +32,10 @@ exports.listMaintainers = function* (name) {
}); });
}; };
exports.listMaintainerNamesOnly = function* (name) {
return yield* ModuleMaintainer.get(name);
};
exports.updateMaintainers = function* (name, usernames) { exports.updateMaintainers = function* (name, usernames) {
var rs = yield [ var rs = yield [
ModuleMaintainer.update(name, usernames), ModuleMaintainer.update(name, usernames),
@@ -41,10 +45,21 @@ exports.updateMaintainers = function* (name, usernames) {
}; };
exports.isMaintainer = function* (name, username) { exports.isMaintainer = function* (name, username) {
var maintainers = yield* ModuleMaintainer.get(name); var rs = yield [
ModuleMaintainer.get(name),
Module.getLatest(name)
];
var maintainers = rs[0];
var latestMod = rs[1];
if (latestMod && !latestMod.package._publish_on_cnpm) {
// no one can update public package maintainers
// public package only sync from source npm registry
return false;
}
if (maintainers.length === 0) { if (maintainers.length === 0) {
// if not found maintainers, try to get from latest module package info // if not found maintainers, try to get from latest module package info
var latestMod = yield Module.getLatest(name);
var ms = latestMod && latestMod.package && latestMod.package.maintainers; var ms = latestMod && latestMod.package && latestMod.package.maintainers;
if (ms && ms.length > 0) { if (ms && ms.length > 0) {
maintainers = ms.map(function (user) { maintainers = ms.map(function (user) {

56
services/user.js Normal file
View File

@@ -0,0 +1,56 @@
/**!
* cnpmjs.org - services/user.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var config = require('../config');
if (!config.userService) {
var DefaultUserService = require('./default_user_service');
config.userService = new DefaultUserService();
config.customUserService = false;
} else {
config.customUserService = true;
}
config.scopes = config.scopes || [];
function convertUser(user) {
if (!user) {
return null;
}
user.scopes = user.scopes || [];
if (user.scopes.length === 0 && config.scopes.length > 0) {
user.scopes = config.scopes.slice();
}
return user;
}
exports.auth = function* (login, password) {
var user = yield* config.userService.auth(login, password);
return convertUser(user);
};
exports.get = function* (login) {
var user = yield* config.userService.get(login);
return convertUser(user);
};
exports.list = function* (logins) {
var users = yield* config.userService.list(logins);
return users.map(convertUser);
};
exports.search = function* (query, options) {
var users = yield* config.userService.search(query, options);
return users.map(convertUser);
};

View File

@@ -420,6 +420,7 @@ describe('controllers/registry/module.test.js', function () {
it('should 403 when not maintainer update in public mode', function (done) { it('should 403 when not maintainer update in public mode', function (done) {
mm(config, 'enablePrivate', false); mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', false);
request(app) request(app)
.put('/mk2testmodule/-rev/1') .put('/mk2testmodule/-rev/1')
.send({ .send({
@@ -491,6 +492,7 @@ describe('controllers/registry/module.test.js', function () {
it('should try to add return 400 when not module user and only next module exists', it('should try to add return 400 when not module user and only next module exists',
function (done) { function (done) {
mm(config, 'enablePrivate', false); mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', false);
request(app) request(app)
.put('/' + pkg.name) .put('/' + pkg.name)
.set('authorization', baseauthOther) .set('authorization', baseauthOther)

View File

@@ -0,0 +1,105 @@
/*!
* cnpmjs.org - test/controllers/registry/module/public_mode.test.js
* Copyright(c) 2014 dead_horse <dead_horse@qq.com>
* MIT Licensed
*/
'use strict';
/**
* Module dependencies.
*/
var should = require('should');
var request = require('supertest');
var mm = require('mm');
var config = require('../../../../config');
var app = require('../../../../servers/registry');
var utils = require('../../../utils');
describe('controllers/registry/module/config_private_packages.test.js', function () {
beforeEach(function () {
mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', true);
mm(config, 'privatePackages', ['private-package']);
});
after(mm.restore);
it('should publish with tgz base64, addPackageAndDist()', function (done) {
var pkg = utils.getPackage('private-package', '0.0.1', utils.otherUser);
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(201, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('ok', 'rev');
res.body.ok.should.equal(true);
pkg = utils.getPackage('private-package', '0.0.1', utils.otherUser);
// upload again should 403
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(403, function (err, res) {
should.not.exist(err);
res.body.should.eql({
error: 'forbidden',
reason: 'cannot modify pre-existing version: 0.0.1'
});
done();
});
});
});
it('should other user publish 403', function (done) {
var pkg = utils.getPackage('private-package', '0.0.2', utils.secondUser);
request(app)
.put('/' + pkg.name)
.set('authorization', utils.secondUserAuth)
.send(pkg)
.expect(/forbidden user/)
.expect(403, done);
});
it('should admin publish 403', function (done) {
var pkg = utils.getPackage('private-package', '0.0.2', utils.admin);
request(app)
.put('/' + pkg.name)
.set('authorization', utils.adminAuth)
.send(pkg)
.expect(/forbidden user/)
.expect(403, done);
});
it('should add again new maintainers', function (done) {
request(app)
.put('/private-package/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest101',
email: 'cnpmjstest101@cnpmjs.org'
}, {
name: 'fengmk2',
email: 'fengmk2@cnpmjs.org'
}]
})
.set('authorization', utils.otherUserAuth)
.expect(201)
.expect('content-type', 'application/json; charset=utf-8', done);
});
it('should remove maintainers', function (done) {
request(app)
.put('/private-package/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest101',
email: 'cnpmjstest101@cnpmjs.org'
}]
})
.set('authorization', utils.otherUserAuth)
.expect(201)
.expect('content-type', 'application/json; charset=utf-8', done);
});
});

View File

@@ -0,0 +1,100 @@
/**!
* cnpmjs.org - test/controllers/registry/module/maintainer.test.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var should = require('should');
var request = require('supertest');
var mm = require('mm');
var config = require('../../../../config');
var app = require('../../../../servers/registry');
var utils = require('../../../utils');
describe('controllers/registry/module/maintainer.test.js', function () {
var pkgname = '@cnpm/test-package-maintainer';
var pkgURL = '/@' + encodeURIComponent(pkgname.substring(1));
before(function (done) {
app = app.listen(0, function () {
// add scope package
var pkg = utils.getPackage(pkgname, '0.0.1', utils.admin);
request(app)
.put(pkgURL)
.set('authorization', utils.adminAuth)
.send(pkg)
.expect(201, done);
});
});
beforeEach(function () {
mm(config, 'scopes', ['@cnpm', '@cnpmtest']);
});
afterEach(mm.restore);
it('should add new maintainer without custom user service', function (done) {
mm(config, 'customUserService', false);
request(app)
.put('/@cnpm/test-package-maintainer/-rev/1')
.set('authorization', utils.adminAuth)
.send({
maintainers: [
{ name: 'new-maintainer', email: 'new-maintainer@cnpmjs.org' },
{ name: utils.admin, email: utils.admin + '@cnpmjs.org' },
]
})
.expect(201, done);
});
describe('config.customUserService = true', function () {
it('should add new maintainer fail when user not exists', function (done) {
mm(config, 'customUserService', true);
request(app)
.put('/@cnpm/test-package-maintainer/-rev/1')
.set('authorization', utils.adminAuth)
.send({
maintainers: [
{ name: 'new-maintainer-not-exists', email: 'new-maintainer@cnpmjs.org' },
{ name: 'new-maintainer-not-exists2', email: 'new-maintainer@cnpmjs.org' },
{ name: utils.admin, email: utils.admin + '@cnpmjs.org' },
]
})
.expect({
error: 'invalid user name',
reason: 'User: new-maintainer-not-exists, new-maintainer-not-exists2 not exists'
})
.expect(403, done);
});
it('should add new maintainer success when user all exists', function (done) {
mm(config, 'customUserService', true);
request(app)
.put('/@cnpm/test-package-maintainer/-rev/1')
.set('authorization', utils.adminAuth)
.send({
maintainers: [
{ name: 'cnpmjstest101', email: 'cnpmjstest101@cnpmjs.org' },
{ name: 'cnpmjstest102', email: 'cnpmjstest102@cnpmjs.org' },
{ name: utils.admin, email: utils.admin + '@cnpmjs.org' },
]
})
.expect({
ok: true,
id: '@cnpm/test-package-maintainer',
rev: '1'
})
.expect(201, done);
});
});
});

View File

@@ -0,0 +1,804 @@
/*!
* cnpmjs.org - test/controllers/registry/module/public_mode.test.js
* Copyright(c) 2014 dead_horse <dead_horse@qq.com>
* MIT Licensed
*/
'use strict';
/**
* Module dependencies.
*/
var should = require('should');
var request = require('supertest');
var path = require('path');
var mm = require('mm');
var pedding = require('pedding');
var config = require('../../../../config');
var app = require('../../../../servers/registry');
var utils = require('../../../utils');
var Module = require('../../../../proxy/module');
var fixtures = path.join(__dirname, '..', '..', '..', 'fixtures');
describe('controllers/registry/module/public_module.test.js', function () {
beforeEach(function () {
mm(config, 'enablePrivate', false);
});
before(function (done) {
mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', false);
app = app.listen(0, function () {
done = pedding(2, done);
// name: publictestmodule
var pkg = utils.getPackage('publictestmodule', '0.0.1', utils.otherUser);
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(201, function (err) {
should.not.exist(err);
pkg = utils.getPackage('publictestmodule', '0.0.2', utils.otherUser);
// publish 0.0.2
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(201, done);
});
// publicputmodule@0.1.9
var testpkg = utils.getPackage('publicputmodule', '0.1.9', utils.otherUser);
request(app)
.put('/' + testpkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(201, done);
});
});
afterEach(mm.restore);
describe('PUT /:name/-rev/id updateMaintainers() in public mode', function () {
beforeEach(function () {
mm(config, 'forcePublishWithScope', false);
});
before(function (done) {
mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', false);
request(app)
.put('/publictestmodule/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest101',
email: 'fengmk2@cnpmjs.org'
}]
})
.set('authorization', utils.otherUserAuth)
.expect({"ok":true,"id":"publictestmodule","rev":"1"}, done);
});
it('should add new maintainers', function (done) {
request(app)
.put('/publictestmodule/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest10',
email: 'fengmk2@cnpmjs.org'
}, {
name: 'cnpmjstest101',
email: 'fengmk2@cnpmjs.org'
}]
})
.set('authorization', utils.otherUserAuth)
.expect(201)
.expect({
ok: true, id: 'publictestmodule', rev: '1'
}, function (err) {
should.not.exist(err);
done = pedding(2, done);
// check maintainers update
request(app)
.get('/publictestmodule')
.expect(200, function (err, res) {
should.not.exist(err);
var pkg = res.body;
pkg.maintainers.should.length(2);
pkg.maintainers.should.eql(pkg.versions['0.0.1'].maintainers);
pkg.maintainers.sort(function (a, b) {
return a.name > b.name ? 1 : -1;
});
pkg.maintainers.should.eql([
{ name: 'cnpmjstest10', email: 'fengmk2@gmail.com' },
{ name: 'cnpmjstest101', email: 'fengmk2@gmail.com' },
]);
done();
});
// /pkg/0.0.1
request(app)
.get('/publictestmodule/0.0.1')
.expect(200, function (err, res) {
should.not.exist(err);
var pkg = res.body;
pkg.maintainers.should.length(2);
pkg.maintainers.sort(function (a, b) {
return a.name > b.name ? 1 : -1;
});
pkg.maintainers.should.eql([
{ name: 'cnpmjstest10', email: 'fengmk2@gmail.com' },
{ name: 'cnpmjstest101', email: 'fengmk2@gmail.com' },
]);
done();
});
});
});
it('should add again new maintainers', function (done) {
request(app)
.put('/publictestmodule/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest101',
email: 'cnpmjstest101@cnpmjs.org'
}, {
name: 'fengmk2',
email: 'fengmk2@cnpmjs.org'
}]
})
.set('authorization', utils.otherUserAuth)
.expect(201)
.expect('content-type', 'application/json; charset=utf-8', done);
});
it('should add new maintainers by admin', function (done) {
request(app)
.put('/publictestmodule/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest101',
email: 'cnpmjstest101@cnpmjs.org'
}, {
name: 'fengmk2',
email: 'fengmk2@cnpmjs.org'
}]
})
.set('authorization', utils.adminAuth)
.expect(201)
.expect('content-type', 'application/json; charset=utf-8', done);
});
it('should rm maintainers', function (done) {
request(app)
.put('/publictestmodule/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest101',
email: 'cnpmjstest101@cnpmjs.org'
}]
})
.set('authorization', utils.otherUserAuth)
.expect(201)
.expect('content-type', 'application/json; charset=utf-8', done);
});
it('should rm again maintainers', function (done) {
request(app)
.put('/publictestmodule/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest101',
email: 'cnpmjstest101@cnpmjs.org'
}]
})
.set('authorization', utils.otherUserAuth)
.expect(201)
.expect({
id: 'publictestmodule',
rev: '1',
ok: true
}, done);
});
it('should rm all maintainers forbidden 403', function (done) {
request(app)
.put('/publictestmodule/-rev/1')
.send({
maintainers: []
})
.set('authorization', utils.otherUserAuth)
.expect(403)
.expect({error: 'invalid operation', reason: 'Can not remove all maintainers'})
.expect('content-type', 'application/json; charset=utf-8', done);
});
it('should 403 when not maintainer update', function (done) {
request(app)
.put('/publictestmodule/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org'
}]
})
.set('authorization', utils.secondUserAuth)
.expect(403)
.expect({
error: 'forbidden user',
reason: 'cnpmjstest102 not authorized to modify publictestmodule'
}, done);
});
describe('forcePublishWithScope = true', function () {
beforeEach(function () {
mm(config, 'forcePublishWithScope', true);
});
before(function (done) {
mm(config, 'forcePublishWithScope', true);
mm(config, 'enablePrivate', false);
var pkg = utils.getPackage('@cnpm/publictestmodule', '0.0.1', utils.otherUser);
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(201, function (err, res) {
should.not.exist(err);
pkg = utils.getPackage(pkg.name, '0.0.2', utils.otherUser);
// publish 0.0.2
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(201, done);
});
});
it('should 403 add maintainers without scope', function (done) {
request(app)
.put('/publictestmodule/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest101',
email: 'cnpmjstest101@cnpmjs.org'
}, {
name: 'fengmk2',
email: 'fengmk2@cnpmjs.org'
}]
})
.set('authorization', utils.otherUserAuth)
.expect(403, done);
});
it('should add maintainers ok with scope', function (done) {
request(app)
.put('/@cnpm/publictestmodule/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest101',
email: 'cnpmjstest101@cnpmjs.org'
}, {
name: 'fengmk2',
email: 'fengmk2@cnpmjs.org'
}]
})
.set('authorization', utils.otherUserAuth)
.expect( { ok: true, id: '@cnpm/publictestmodule', rev: '1' })
.expect(201, done);
});
});
});
describe('PUT /:name publish new flow addPackageAndDist()', function () {
beforeEach(function () {
mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', false);
});
it('should publish with tgz base64, addPackageAndDist()', function (done) {
var pkg = utils.getPackage('publicpublishmodule', '0.0.2', utils.otherUser);
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(201, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('ok', 'rev');
res.body.ok.should.equal(true);
pkg = utils.getPackage('publicpublishmodule', '0.0.2', utils.otherUser);
// upload again should 403
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(403, function (err, res) {
should.not.exist(err);
res.body.should.eql({
error: 'forbidden',
reason: 'cannot modify pre-existing version: 0.0.2'
});
done();
});
});
});
it('should other user pulbish 403', function (done) {
var pkg = utils.getPackage('publicpublishmodule', '0.0.3', utils.secondUser);
request(app)
.put('/' + pkg.name)
.set('authorization', utils.secondUserAuth)
.send(pkg)
.expect(403, done);
});
it('should admin pulbish 403', function (done) {
var pkg = utils.getPackage('publicpublishmodule', '0.0.3', utils.admin);
request(app)
.put('/' + pkg.name)
.set('authorization', utils.adminAuth)
.send(pkg)
.expect(403, done);
});
it('should publish with scope, addPackageAndDist()', function (done) {
mm(config, 'forcePublishWithScope', false);
var pkg = utils.getPackage('@cnpm/publicpublishmodule', '0.0.2', utils.otherUser);
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(201, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('ok', 'rev');
res.body.ok.should.equal(true);
// upload again should 403
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(403, function (err, res) {
should.not.exist(err);
res.body.should.eql({
error: 'forbidden',
reason: 'cannot modify pre-existing version: 0.0.2'
});
done();
});
});
});
describe('forcePublishWithScope = true', function () {
it('should publish without scope 403, addPackageAndDist()', function (done) {
mm(config, 'forcePublishWithScope', false);
var pkg = utils.getPackage('publicpublishmodule', '0.0.2');
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(403, done);
});
it('should admin publish without scope ok, addPackageAndDist()', function (done) {
mm(config, 'forcePublishWithScope', false);
var pkg = utils.getPackage('publicpublishmodule1', '0.0.4', utils.admin);
request(app)
.put('/' + pkg.name)
.set('authorization', utils.adminAuth)
.send(pkg)
.expect(201, done);
});
});
});
describe('PUT /:name/-rev/:rev removeWithVersions', function () {
var withoutScopeRev;
before(function (done) {
mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', false);
var pkg = utils.getPackage('publicremovemodule', '0.0.1', utils.otherUser);
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(201, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('ok', 'rev');
res.body.ok.should.equal(true);
pkg = utils.getPackage('publicremovemodule', '0.0.2', utils.otherUser);
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(201, function (err, res) {
should.not.exist(err);
withoutScopeRev = res.body.rev;
done();
});
});
});
it('should remove with version ok', function (done) {
mm.empty(Module, 'removeByNameAndVersions');
mm.empty(Module, 'removeTagsByIds');
request(app)
.put('/publicremovemodule/-rev/' + withoutScopeRev)
.set('authorization', utils.otherUserAuth)
.send({
versions: {
'0.0.1': {}
}
})
.expect(201, done);
});
it('should no auth user remove 403', function (done) {
mm.empty(Module, 'removeByNameAndVersions');
mm.empty(Module, 'removeTagsByIds');
request(app)
.put('/publicremovemodule/-rev/' + withoutScopeRev)
.set('authorization', utils.secondUserAuth)
.send({
versions: {
'0.0.1': {}
}
})
.expect(403, done);
});
it('should admin remove ok', function (done) {
mm.empty(Module, 'removeByNameAndVersions');
mm.empty(Module, 'removeTagsByIds');
request(app)
.put('/publicremovemodule/-rev/' + withoutScopeRev)
.set('authorization', utils.adminAuth)
.send({
versions: {
'0.0.1': {}
}
})
.expect(201, done);
});
describe('forcePublishWithScope = true', function () {
var withScopeRev;
before(function (done) {
mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', true);
var pkg = utils.getPackage('@cnpm/publicremovemodule', '0.0.1', utils.otherUser);
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(201, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('ok', 'rev');
res.body.ok.should.equal(true);
pkg = utils.getPackage('@cnpm/publicremovemodule', '0.0.2', utils.otherUser);
request(app)
.put('/' + pkg.name)
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(201, function (err, res) {
should.not.exist(err);
withScopeRev = res.body.rev;
done();
});
});
});
it('should remove without scope 403', function (done) {
mm(config, 'forcePublishWithScope', true);
mm.empty(Module, 'removeByNameAndVersions');
mm.empty(Module, 'removeTagsByIds');
request(app)
.put('/publicremovemodule/-rev/' + withoutScopeRev)
.set('authorization', utils.otherUserAuth)
.send({
versions: {
'0.0.1': {}
}
})
.expect(403, done);
});
it('should admin remove without scope ok', function (done) {
mm(config, 'forcePublishWithScope', true);
mm.empty(Module, 'removeByNameAndVersions');
mm.empty(Module, 'removeTagsByIds');
request(app)
.put('/publicremovemodule/-rev/' + withoutScopeRev)
.set('authorization', utils.adminAuth)
.send({
versions: {
'0.0.1': {}
}
})
.expect(201, done);
});
it('should remove with scope ok', function (done) {
mm(config, 'forcePublishWithScope', true);
mm.empty(Module, 'removeByNameAndVersions');
mm.empty(Module, 'removeTagsByIds');
request(app)
.put('/@cnpm/publicremovemodule/-rev/' + withScopeRev)
.set('authorization', utils.otherUserAuth)
.send({
versions: {
'0.0.1': {}
}
})
.expect(201, done);
});
it('should admin remove with scope ok', function (done) {
mm(config, 'forcePublishWithScope', true);
mm.empty(Module, 'removeByNameAndVersions');
mm.empty(Module, 'removeTagsByIds');
request(app)
.put('/@cnpm/publicremovemodule/-rev/' + withScopeRev)
.set('authorization', utils.adminAuth)
.send({
versions: {
'0.0.1': {}
}
})
.expect(201, done);
});
});
});
describe('DELETE /:name/download/:filename/-rev/:rev', function () {
var withoutScopeRev;
beforeEach(function () {
mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', false);
});
beforeEach(function (done) {
mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', false);
var pkg = utils.getPackage('public-test-delete-download-module', '0.1.9', utils.otherUser);
request(app)
.put('/' + pkg.name)
.set('content-type', 'application/json')
.set('authorization', utils.otherUserAuth)
.send(pkg)
.end(function (err, res) {
should.not.exist(err);
if (res.body.rev) {
withoutScopeRev = res.body.rev;
}
done();
});
});
it('should delete 403 when auth error', function (done) {
request(app)
.del('/public-test-delete-download-module/download/public-test-delete-download-module-0.1.9.tgz/-rev/' + withoutScopeRev)
.set('authorization', utils.secondUserAuth)
.expect(403, done);
});
it('should delete file ok', function (done) {
request(app)
.del('/public-test-delete-download-module/download/public-test-delete-download-module-0.1.9.tgz/-rev/' + withoutScopeRev)
.set('authorization', utils.otherUserAuth)
.expect(200, done);
});
it('should admin delete file ok', function (done) {
request(app)
.del('/public-test-delete-download-module/download/public-test-delete-download-module-0.1.9.tgz/-rev/' + withoutScopeRev)
.set('authorization', utils.adminAuth)
.expect(200, done);
});
describe('forcePublishWithScope = true', function () {
var withScopeRev;
beforeEach(function () {
mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', true);
});
beforeEach(function (done) {
mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', true);
var pkg = utils.getPackage('@cnpm/public-test-delete-download-module', '0.1.9', utils.otherUser);
request(app)
.put('/' + pkg.name)
.set('content-type', 'application/json')
.set('authorization', utils.otherUserAuth)
.send(pkg)
.end(function (err, res) {
should.not.exist(err);
if (res.body.rev) {
withScopeRev = res.body.rev;
}
done();
});
});
it('should delete file without scope 403', function (done) {
request(app)
.del('/public-test-delete-download-module/download/public-test-delete-download-module-0.1.9.tgz/-rev/' + withoutScopeRev)
.set('authorization', utils.otherUserAuth)
.expect(403, done);
});
it('should admin delete file without scope ok', function (done) {
request(app)
.del('/public-test-delete-download-module/download/public-test-delete-download-module-0.1.9.tgz/-rev/' + withoutScopeRev)
.set('authorization', utils.adminAuth)
.expect(200, done);
});
it('should delete file with scope ok', function (done) {
request(app)
.del('/@cnpm/public-test-delete-download-module/download/@cnpm/public-test-delete-download-module-0.1.9.tgz/-rev/' + withScopeRev)
.set('authorization', utils.otherUserAuth)
.expect(200, done);
});
it('should admin delete file with scope ok', function (done) {
request(app)
.del('/@cnpm/public-test-delete-download-module/download/@cnpm/public-test-delete-download-module-0.1.9.tgz/-rev/' + withScopeRev)
.set('authorization', utils.adminAuth)
.expect(200, done);
});
});
});
describe('PUT /:name/:tag updateTag()', function () {
it('should create new tag ok', function (done) {
request(app)
.put('/publictestmodule/newtag')
.set('content-type', 'application/json')
.set('authorization', utils.otherUserAuth)
.send('"0.0.1"')
.expect(201)
.expect({"ok":true}, done);
});
it('shold update tag not maintainer 403', function (done) {
request(app)
.put('/publictestmodule/newtag')
.set('content-type', 'application/json')
.set('authorization', utils.secondUserAuth)
.send('"0.0.1"')
.expect(403, done);
});
it('should admin update tag ok', function (done) {
request(app)
.put('/publictestmodule/newtag')
.set('content-type', 'application/json')
.set('authorization', utils.adminAuth)
.send('"0.0.1"')
.expect(201, done);
});
});
describe('DELETE /:name/-rev/:rev', function () {
describe('remove all modules by name', function () {
beforeEach(function () {
mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', false);
});
before(function (done) {
mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', false);
var pkg = utils.getPackage('public-remove-all-module', '0.0.1', utils.otherUser);
request(app)
.put('/public-remove-all-module')
.set('content-type', 'application/json')
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(201, done);
});
it('shold fail when user not maintainer', function (done) {
request(app)
.del('/public-remove-all-module/-rev/1')
.set('authorization', utils.secondUserAuth)
.expect(403, function (err, res) {
should.not.exist(err);
res.body.should.eql({
error: 'forbidden user',
reason: 'cnpmjstest102 not authorized to modify public-remove-all-module'
});
done();
});
});
it('shold maintainer remove ok', function (done) {
mm.empty(Module, 'removeByName');
mm.empty(Module, 'removeTags');
request(app)
.del('/public-remove-all-module/-rev/1')
.set('authorization', utils.otherUserAuth)
.expect(200, function (err, res) {
should.not.exist(err);
should.not.exist(res.headers['set-cookie']);
done();
});
});
it('shold admin remove ok', function (done) {
mm.empty(Module, 'removeByName');
mm.empty(Module, 'removeTags');
request(app)
.del('/public-remove-all-module/-rev/1')
.set('authorization', utils.adminAuth)
.expect(200, function (err, res) {
should.not.exist(err);
should.not.exist(res.headers['set-cookie']);
done();
});
});
describe('forcePublishWithScope = true', function () {
before(function (done) {
mm(config, 'enablePrivate', false);
mm(config, 'forcePublishWithScope', true);
var pkg = utils.getPackage('@cnpm/public-remove-all-module', '0.0.1', utils.otherUser);
request(app)
.put('/@cnpm/public-remove-all-module')
.set('content-type', 'application/json')
.set('authorization', utils.otherUserAuth)
.send(pkg)
.expect(201, done);
});
it('should fail when user remove module without scope', function (done) {
mm(config, 'forcePublishWithScope', true);
request(app)
.del('/public-remove-all-module/-rev/1')
.set('authorization', utils.otherUserAuth)
.expect(403, done);
});
it('shold admin remove module without scope ok', function (done) {
mm(config, 'forcePublishWithScope', true);
mm.empty(Module, 'removeByName');
mm.empty(Module, 'removeTags');
request(app)
.del('/public-remove-all-module/-rev/1')
.set('authorization', utils.adminAuth)
.expect(200, done);
});
it('shold maintainer remove ok', function (done) {
mm(config, 'forcePublishWithScope', true);
mm.empty(Module, 'removeByName');
mm.empty(Module, 'removeTags');
request(app)
.del('/@cnpm/public-remove-all-module/-rev/1')
.set('authorization', utils.otherUserAuth)
.expect(200, function (err, res) {
should.not.exist(err);
should.not.exist(res.headers['set-cookie']);
done();
});
});
it('shold admin remove ok', function (done) {
mm(config, 'forcePublishWithScope', true);
mm.empty(Module, 'removeByName');
mm.empty(Module, 'removeTags');
request(app)
.del('/@cnpm/public-remove-all-module/-rev/1')
.set('authorization', utils.adminAuth)
.expect(200, function (err, res) {
should.not.exist(err);
should.not.exist(res.headers['set-cookie']);
done();
});
});
});
});
});
});

View File

@@ -47,8 +47,25 @@ describe('controllers/registry/module/scope_package.test.js', function () {
}); });
}); });
beforeEach(function () {
mm(config, 'scopes', ['@cnpm', '@cnpmtest']);
});
afterEach(mm.restore); afterEach(mm.restore);
it('should get 404 when do not support scope', function (done) {
mm(config, 'scopes', []);
request(app)
.get('/@invalid/test')
.expect(404, done);
});
it('should get 400 when scope not match', function (done) {
request(app)
.get('/@invalid/test')
.expect(404, done);
});
it('should get scope package info: /@scope%2Fname', function (done) { it('should get scope package info: /@scope%2Fname', function (done) {
request(app) request(app)
.get(pkgURL) .get(pkgURL)
@@ -132,7 +149,7 @@ describe('controllers/registry/module/scope_package.test.js', function () {
.expect(302, done); .expect(302, done);
}); });
describe('support defaultScope', function () { describe('support adaptScope', function () {
before(function (done) { before(function (done) {
var pkg = utils.getPackage('test-default-scope-package', '0.0.1', utils.admin); var pkg = utils.getPackage('test-default-scope-package', '0.0.1', utils.admin);
request(app) request(app)
@@ -143,7 +160,7 @@ describe('controllers/registry/module/scope_package.test.js', function () {
}); });
describe('/@:scope/:name', function () { describe('/@:scope/:name', function () {
it('should adapt /@cnpm/test-default-scope-package => /test-default-scope-package', function (done) { it('should adapt /@cnpm/test-default-scope-package => /test-default-scope-package', function (done) {
mm(config, 'defaultScope', '@cnpm'); mm(config, 'adaptScope', true);
request(app) request(app)
.get('/@cnpm/test-default-scope-package') .get('/@cnpm/test-default-scope-package')
.expect(200, function (err, res) { .expect(200, function (err, res) {
@@ -161,29 +178,47 @@ describe('controllers/registry/module/scope_package.test.js', function () {
}); });
}); });
it('should not adapt /@cnpm123/test-default-scope-package', function (done) { it('should adapt /@cnpmtest/test-default-scope-package => /test-default-scope-package', function (done) {
mm(config, 'defaultScope', '@cnpm'); mm(config, 'adaptScope', true);
request(app) request(app)
.get('/@cnpm123/test-default-scope-package') .get('/@cnpmtest/test-default-scope-package')
.expect(404, done); .expect(200, function (err, res) {
should.not.exist(err);
var pkg = res.body;
pkg._id.should.equal('@cnpmtest/test-default-scope-package');
pkg.name.should.equal('@cnpmtest/test-default-scope-package');
pkg.versions.should.have.keys('0.0.1');
pkg['dist-tags'].latest.should.equal('0.0.1');
pkg.versions['0.0.1'].name.should.equal('@cnpmtest/test-default-scope-package');
pkg.versions['0.0.1']._id.should.equal('@cnpmtest/test-default-scope-package@0.0.1');
pkg.versions['0.0.1'].dist.tarball
.should.containEql('/test-default-scope-package/download/test-default-scope-package-0.0.1.tgz');
done();
});
}); });
it('should not adapt when defaultScope is empty', function (done) { it('should not adapt when adaptScope is false', function (done) {
mm(config, 'defaultScope', ''); mm(config, 'adaptScope', false);
request(app) request(app)
.get('/@cnpm/test-default-scope-package') .get('/@cnpm/test-default-scope-package')
.expect(404, done); .expect(404, done);
}); });
it('should 404 when pkg not exists', function (done) { it('should 404 when pkg not exists', function (done) {
mm(config, 'defaultScope', '@cnpm'); mm(config, 'adaptScope', true);
request(app) request(app)
.get('/@cnpm/test-default-scope-package-not-exists') .get('/@cnpm/test-default-scope-package-not-exists')
.expect(404, done); .expect(404, done);
}); });
it('should 404 when scope not match', function (done) { it('should show() 404 when adapt package is not private package', function (done) {
mm(config, 'defaultScope', '@cnpm123'); var getByTag = Module.getByTag;
mm(Module, 'getByTag', function* (name, tag) {
var pkg = yield getByTag.call(Module, name, tag);
pkg && delete pkg.package._publish_on_cnpm;
return pkg;
});
mm(config, 'adaptScope', true);
request(app) request(app)
.get('/@cnpm/test-default-scope-package') .get('/@cnpm/test-default-scope-package')
.expect(404, done); .expect(404, done);
@@ -192,7 +227,7 @@ describe('controllers/registry/module/scope_package.test.js', function () {
describe('/@:scope/:name/:tag', function () { describe('/@:scope/:name/:tag', function () {
it('should adapt /@cnpm/test-default-scope-package/latest => /test-default-scope-package/latest', function (done) { it('should adapt /@cnpm/test-default-scope-package/latest => /test-default-scope-package/latest', function (done) {
mm(config, 'defaultScope', '@cnpm'); mm(config, 'adaptScope', true);
request(app) request(app)
.get('/@cnpm/test-default-scope-package/latest') .get('/@cnpm/test-default-scope-package/latest')
.expect(200, function (err, res) { .expect(200, function (err, res) {
@@ -206,54 +241,27 @@ describe('controllers/registry/module/scope_package.test.js', function () {
}); });
}); });
it('should not adapt /@cnpm123/test-default-scope-package/latest', function (done) { it('should not adapt when adaptScope is false', function (done) {
mm(config, 'defaultScope', '@cnpm'); mm(config, 'adaptScope', false);
request(app)
.get('/@cnpm123/test-default-scope-package/latest')
.expect(404, done);
});
it('should not adapt when defaultScope is empty', function (done) {
mm(config, 'defaultScope', '');
request(app) request(app)
.get('/@cnpm/test-default-scope-package/latest') .get('/@cnpm/test-default-scope-package/latest')
.expect(404, done); .expect(404, done);
}); });
it('should 404 when pkg not exists', function (done) { it('should 404 when pkg not exists', function (done) {
mm(config, 'defaultScope', '@cnpm'); mm(config, 'adaptScope', true);
request(app) request(app)
.get('/@cnpm/test-default-scope-package-not-exists/latest') .get('/@cnpm/test-default-scope-package-not-exists/latest')
.expect(404, done); .expect(404, done);
}); });
it('should 404 when pkg version not exists', function (done) { it('should 404 when pkg version not exists', function (done) {
mm(config, 'defaultScope', '@cnpm'); mm(config, 'adaptScope', true);
request(app) request(app)
.get('/@cnpm/test-default-scope-package-not-exists/1.0.0') .get('/@cnpm/test-default-scope-package-not-exists/1.0.0')
.expect(404, done); .expect(404, done);
}); });
it('should 404 when scope not match', function (done) {
mm(config, 'defaultScope', '@cnpm123');
request(app)
.get('/@cnpm/test-default-scope-package/latest')
.expect(404, done);
});
it('should show() 404 when adapt package is not private package', function (done) {
var getByTag = Module.getByTag;
mm(Module, 'getByTag', function* (name, tag) {
var pkg = yield getByTag.call(Module, name, tag);
pkg && delete pkg.package._publish_on_cnpm;
return pkg;
});
mm(config, 'defaultScope', '@cnpm');
request(app)
.get('/@cnpm/test-default-scope-package')
.expect(404, done);
});
it('should get() 404 when adapt package is not private package', function (done) { it('should get() 404 when adapt package is not private package', function (done) {
var getByTag = Module.getByTag; var getByTag = Module.getByTag;
mm(Module, 'getByTag', function* (name, tag) { mm(Module, 'getByTag', function* (name, tag) {
@@ -261,7 +269,7 @@ describe('controllers/registry/module/scope_package.test.js', function () {
pkg && delete pkg.package._publish_on_cnpm; pkg && delete pkg.package._publish_on_cnpm;
return pkg; return pkg;
}); });
mm(config, 'defaultScope', '@cnpm'); mm(config, 'adaptScope', true);
request(app) request(app)
.get('/@cnpm/test-default-scope-package/latest') .get('/@cnpm/test-default-scope-package/latest')
.expect(404, done); .expect(404, done);

View File

@@ -20,6 +20,8 @@ var mm = require('mm');
var app = require('../../../servers/registry'); var app = require('../../../servers/registry');
var user = require('../../../proxy/user'); var user = require('../../../proxy/user');
var mysql = require('../../../common/mysql'); var mysql = require('../../../common/mysql');
var config = require('../../../config');
var UserService = require('../../../services/user');
describe('controllers/registry/user.test.js', function () { describe('controllers/registry/user.test.js', function () {
before(function (done) { before(function (done) {
@@ -84,91 +86,51 @@ describe('controllers/registry/user.test.js', function () {
}); });
it('should 409 when already exist', function (done) { it('should 409 when already exist', function (done) {
mm.data(user, 'get', {name: 'name'}); mm(user, 'get', function* () {
return {name: 'name'};
});
request(app) request(app)
.put('/-/user/org.couchdb.user:name') .put('/-/user/org.couchdb.user:name')
.send({ .send({
name: 'name', name: 'name',
salt: 'salt', password: 'password',
password_sha: 'password_sha',
email: 'email' email: 'email'
}) })
.expect(409, done); .expect(409, done);
}); });
it('should 500 when user.get error', function (done) { it('should 500 when user.get error', function (done) {
mm.error(user, 'get', 'mock error'); mm(user, 'get', function* () {
throw new Error('mock User.get error');
});
request(app) request(app)
.put('/-/user/org.couchdb.user:name') .put('/-/user/org.couchdb.user:name')
.send({ .send({
name: 'name', name: 'name',
salt: 'salt', password: 'password',
password_sha: 'password_sha',
email: 'email' email: 'email'
}) })
.expect(500, done); .expect(500, done);
}); });
it('should 201 when user.add ok', function (done) { it('should 201 when user.add ok', function (done) {
mm.empty(user, 'get'); mm(user, 'get', function* () {
mm.data(user, 'add', {rev: '1-123'}); return null;
});
mm(user, 'add', function* () {
return {rev: '1-123'};
});
request(app) request(app)
.put('/-/user/org.couchdb.user:name') .put('/-/user/org.couchdb.user:name')
.send({ .send({
name: 'name', name: 'name',
salt: 'salt', password: 'password',
password_sha: 'password_sha',
email: 'email' email: 'email'
}) })
.expect(201, done); .expect(201, done);
}); });
}); });
describe('POST /_session', function () {
it('should 500 auth error by user.auth', function (done) {
mm.error(user, 'auth', 'mock error');
request(app)
.post('/_session')
.send({
name: 'name',
password: '123'
})
.expect(500, done);
});
it('should 401 auth fail by user.auth', function (done) {
mm.empty(user, 'auth');
request(app)
.post('/_session')
.send({
name: 'name',
password: '123'
})
.expect(401, done);
});
it('should 200 auth pass by user.auth', function (done) {
mm.data(user, 'auth', {name: 'name'});
request(app)
.post('/_session')
.send({
name: 'name',
password: '123'
})
.expect(200)
.expect({
ok: true,
name: 'name',
roles: []
}, function (err, res) {
should.not.exist(err);
should.exist(res.headers['set-cookie']);
res.headers['set-cookie'].join(';').should.containEql('AuthSession=');
done();
});
});
});
describe('PUT /-/user/:name/-rev/:rev', function () { describe('PUT /-/user/:name/-rev/:rev', function () {
it('should 404 when without a name', function (done) { it('should 404 when without a name', function (done) {
request(app) request(app)
@@ -223,4 +185,184 @@ describe('controllers/registry/user.test.js', function () {
.expect(201, done); .expect(201, done);
}); });
}); });
describe('config.customUserSerivce = true', function () {
beforeEach(function () {
mm(config, 'customUserService', true);
});
it('should 422 when password missing', function (done) {
request(app)
.put('/-/user/org.couchdb.user:cnpmjstest10-not-exists')
.send({
name: 'cnpmjstest10-not-exists',
password: '',
email: 'cnpmjstest10@cnpmjs.org'
})
.expect({
error: 'paramError',
reason: 'params missing, name, email or password missing.'
})
.expect(422, done);
});
it('should 201 login success', function (done) {
request(app)
.put('/-/user/org.couchdb.user:cnpmjstest10')
.send({
name: 'cnpmjstest10',
password: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org'
})
.expect(201, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('ok', 'id', 'rev');
res.body.id.should.equal('org.couchdb.user:cnpmjstest10');
res.body.rev.should.match(/\d+\-cnpmjstest10/);
res.body.ok.should.equal(true);
done();
});
});
it('should 401 login fail', function (done) {
request(app)
.put('/-/user/org.couchdb.user:cnpmjstest10-not-exists')
.send({
name: 'cnpmjstest10-not-exists',
password: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org'
})
.expect({
error: 'unauthorized',
reason: 'Login fail, please check your login name and password'
})
.expect(401, done);
});
});
describe('config.customUserService = true', function () {
beforeEach(function () {
mm(config, 'customUserService', true);
});
afterEach(mm.restore);
it('should show custom user info: admin', function (done) {
mm(UserService, 'get', function* () {
return {
login: 'mock_custom_user',
email: 'mock_custom_user@cnpmjs.org',
name: 'mock_custom_user fullname',
avatar_url: 'avatar_url',
html_url: 'html_url',
im_url: '',
site_admin: true,
scopes: ['@test-user-scope']
};
});
request(app)
.get('/-/user/org.couchdb.user:mock_custom_user')
.expect(200, function (err, res) {
should.not.exist(err);
var user = res.body;
delete user._cnpm_meta.gmt_create;
delete user._cnpm_meta.gmt_modified;
delete user._cnpm_meta.id;
delete user.date;
user.should.eql({
_id: 'org.couchdb.user:mock_custom_user',
_rev: '1-mock_custom_user',
name: 'mock_custom_user',
email: 'mock_custom_user@cnpmjs.org',
type: 'user',
roles: [],
// date: '2014-07-28T16:46:36.000Z',
avatar: 'avatar_url',
fullname: 'mock_custom_user fullname',
homepage: 'html_url',
_cnpm_meta:
{
// id: 4,
npm_user: false,
custom_user: true,
// gmt_create: '2014-07-28T16:46:36.000Z',
// gmt_modified: '2014-07-28T16:46:36.000Z',
admin: true,
scopes: [ '@test-user-scope' ] }
});
done();
});
});
it('should show custom user info: not admin', function (done) {
mm(UserService, 'get', function* () {
return {
login: 'mock_custom_not_admin_user',
email: 'mock_custom_not_admin_user@cnpmjs.org',
name: 'mock_custom_not_admin_user fullname',
avatar_url: 'avatar_url',
html_url: 'html_url',
im_url: '',
site_admin: false,
scopes: ['@test-user-scope']
};
});
request(app)
.get('/-/user/org.couchdb.user:mock_custom_not_admin_user')
.expect(200, function (err, res) {
should.not.exist(err);
var user = res.body;
delete user._cnpm_meta.gmt_create;
delete user._cnpm_meta.gmt_modified;
delete user._cnpm_meta.id;
delete user.date;
user.should.eql({
_id: 'org.couchdb.user:mock_custom_not_admin_user',
_rev: '1-mock_custom_not_admin_user',
name: 'mock_custom_not_admin_user',
email: 'mock_custom_not_admin_user@cnpmjs.org',
type: 'user',
roles: [],
// date: '2014-07-28T16:46:36.000Z',
avatar: 'avatar_url',
fullname: 'mock_custom_not_admin_user fullname',
homepage: 'html_url',
_cnpm_meta:
{
// id: 5,
npm_user: false,
custom_user: true,
// gmt_create: '2014-07-28T16:46:36.000Z',
// gmt_modified: '2014-07-28T16:46:36.000Z',
admin: false,
scopes: [ '@test-user-scope' ] }
});
done();
});
});
it('should show error json when userSerive.auth throw error', function (done) {
mm(UserService, 'auth', function* () {
var err = new Error('mock user service auth error, please visit http://ooxx.net/user to sigup first');
err.name = 'UserSeriveAuthError';
err.status = 401;
throw err;
});
request(app)
.put('/-/user/org.couchdb.user:cnpmjstest10')
.send({
name: 'cnpmjstest10',
password: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org'
})
.expect({
error: 'UserSeriveAuthError',
reason: 'mock user service auth error, please visit http://ooxx.net/user to sigup first'
})
.expect(401, done);
});
});
}); });

View File

@@ -43,14 +43,14 @@ describe('controllers/sync.test.js', function () {
it('should sync as publish success', function (done) { it('should sync as publish success', function (done) {
request(registryApp) request(registryApp)
.del('/utility/-rev/123') .del('/pedding/-rev/123')
.set('authorization', baseauth) .set('authorization', baseauth)
.end(function (err, res) { .end(function (err, res) {
should.not.exist(err); should.not.exist(err);
mm.data(Npm, 'get', require(path.join(fixtures, 'utility.json'))); mm.data(Npm, 'get', require(path.join(fixtures, 'utility.json')));
request(registryApp) request(registryApp)
.put('/utility/sync?publish=true&nodeps=true') .put('/pedding/sync?publish=true&nodeps=true')
.set('authorization', baseauth) .set('authorization', baseauth)
.end(function (err, res) { .end(function (err, res) {
should.not.exist(err); should.not.exist(err);
@@ -75,7 +75,7 @@ describe('controllers/sync.test.js', function () {
it('should sync through web success', function (done) { it('should sync through web success', function (done) {
mm.data(Npm, 'get', require(path.join(fixtures, 'utility.json'))); mm.data(Npm, 'get', require(path.join(fixtures, 'utility.json')));
request(webApp) request(webApp)
.put('/sync/utility') .put('/sync/pedding')
.end(function (err, res) { .end(function (err, res) {
should.not.exist(err); should.not.exist(err);
res.body.should.have.keys('ok', 'logId'); res.body.should.have.keys('ok', 'logId');
@@ -87,7 +87,7 @@ describe('controllers/sync.test.js', function () {
it('should sync through registry success', function (done) { it('should sync through registry success', function (done) {
mm.data(Npm, 'get', require(path.join(fixtures, 'utility.json'))); mm.data(Npm, 'get', require(path.join(fixtures, 'utility.json')));
request(registryApp) request(registryApp)
.put('/utility/sync') .put('/pedding/sync')
.set('authorization', baseauth) .set('authorization', baseauth)
.end(function (err, res) { .end(function (err, res) {
should.not.exist(err); should.not.exist(err);
@@ -100,7 +100,7 @@ describe('controllers/sync.test.js', function () {
it('should get sync log', function (done) { it('should get sync log', function (done) {
done = pedding(2, done); done = pedding(2, done);
request(registryApp) request(registryApp)
.get('/utility/sync/log/' + logIdRegistry) .get('/pedding/sync/log/' + logIdRegistry)
.end(function (err, res) { .end(function (err, res) {
should.not.exist(err); should.not.exist(err);
res.body.should.have.keys('ok', 'log'); res.body.should.have.keys('ok', 'log');
@@ -108,7 +108,7 @@ describe('controllers/sync.test.js', function () {
}); });
request(webApp) request(webApp)
.get('/sync/utility/log/' + logIdWeb) .get('/sync/pedding/log/' + logIdWeb)
.end(function (err, res) { .end(function (err, res) {
should.not.exist(err); should.not.exist(err);
res.body.should.have.keys('ok', 'log'); res.body.should.have.keys('ok', 'log');
@@ -121,10 +121,6 @@ describe('controllers/sync.test.js', function () {
it('should sync scope package not found', function (done) { it('should sync scope package not found', function (done) {
request(webApp) request(webApp)
.put('/sync/@cnpm/not-exists-package') .put('/sync/@cnpm/not-exists-package')
.expect({
"ok":false,
"reason":"can not found @cnpm/not-exists-package in the source registry"
})
.expect(404, done); .expect(404, done);
}); });
}); });

View File

@@ -25,6 +25,7 @@ var registry = require('../../../servers/registry');
var pkg = require('../../../controllers/web/package'); var pkg = require('../../../controllers/web/package');
var SyncModuleWorker = require('../../../proxy/sync_module_worker'); var SyncModuleWorker = require('../../../proxy/sync_module_worker');
var utils = require('../../utils'); var utils = require('../../utils');
var config = require('../../../config');
var fixtures = path.join(path.dirname(path.dirname(__dirname)), 'fixtures'); var fixtures = path.join(path.dirname(path.dirname(__dirname)), 'fixtures');
@@ -237,4 +238,21 @@ describe('controllers/web/package.test.js', function () {
.expect(/This package has been unpublished\./, done); .expect(/This package has been unpublished\./, done);
}); });
}); });
describe('GET /privates', function () {
it('should response no private packages', function (done) {
mm(config, 'scopes', []);
request(app)
.get('/privates')
.expect(/Can not found private package/)
.expect(200, done);
});
it('should response no private packages', function (done) {
request(app)
.get('/privates')
.expect(/Private packages in this registry/)
.expect(200, done);
});
});
}); });

View File

@@ -50,6 +50,10 @@ describe('controllers/web/package/scope_package.test.js', function () {
web = web.listen(0, done); web = web.listen(0, done);
}); });
beforeEach(function () {
mm(config, 'scopes', ['@cnpm', '@cnpmtest']);
});
afterEach(mm.restore); afterEach(mm.restore);
it('should show scope package info page: /@scope%2Fname', function (done) { it('should show scope package info page: /@scope%2Fname', function (done) {
@@ -113,7 +117,7 @@ describe('controllers/web/package/scope_package.test.js', function () {
.expect(302, done); .expect(302, done);
}); });
describe('support default scope', function () { describe('support adapt scope', function () {
before(function (done) { before(function (done) {
var pkg = utils.getPackage('test-default-web-scope-package', '0.0.1', utils.admin); var pkg = utils.getPackage('test-default-web-scope-package', '0.0.1', utils.admin);
request(registry) request(registry)
@@ -124,7 +128,7 @@ describe('controllers/web/package/scope_package.test.js', function () {
}); });
it('should adapt /@cnpm/test-default-web-scope-package => /test-default-web-scope-package', function (done) { it('should adapt /@cnpm/test-default-web-scope-package => /test-default-web-scope-package', function (done) {
mm(config, 'defaultScope', '@cnpm'); mm(config, 'adaptScope', true);
request(web) request(web)
.get('/package/@cnpm/test-default-web-scope-package') .get('/package/@cnpm/test-default-web-scope-package')
.expect(200, function (err, res) { .expect(200, function (err, res) {
@@ -137,28 +141,21 @@ describe('controllers/web/package/scope_package.test.js', function () {
}); });
it('should not adapt /@cnpm123/test-default-web-scope-package', function (done) { it('should not adapt /@cnpm123/test-default-web-scope-package', function (done) {
mm(config, 'defaultScope', '@cnpm'); mm(config, 'adaptScope', true);
request(web) request(web)
.get('/package/@cnpm123/test-default-web-scope-package') .get('/package/@cnpm123/test-default-web-scope-package')
.expect(404, done); .expect(404, done);
}); });
it('should not adapt', function (done) { it('should not adapt', function (done) {
mm(config, 'defaultScope', ''); mm(config, 'adaptScope', false);
request(web)
.get('/package/@cnpm/test-default-web-scope-package')
.expect(404, done);
});
it('should 404 when scope not match', function (done) {
mm(config, 'defaultScope', '@cnpm123');
request(web) request(web)
.get('/package/@cnpm/test-default-web-scope-package') .get('/package/@cnpm/test-default-web-scope-package')
.expect(404, done); .expect(404, done);
}); });
it('should 404 when pkg not exists', function (done) { it('should 404 when pkg not exists', function (done) {
mm(config, 'defaultScope', '@cnpm'); mm(config, 'adaptScope', true);
request(web) request(web)
.get('/package/@cnpm/test-default-web-scope-package-not-exists') .get('/package/@cnpm/test-default-web-scope-package-not-exists')
.expect(404, done); .expect(404, done);
@@ -171,7 +168,7 @@ describe('controllers/web/package/scope_package.test.js', function () {
pkg && delete pkg.package._publish_on_cnpm; pkg && delete pkg.package._publish_on_cnpm;
return pkg; return pkg;
}); });
mm(config, 'defaultScope', '@cnpm'); mm(config, 'adaptScope', true);
request(web) request(web)
.get('/package/@cnpm/test-default-web-scope-package') .get('/package/@cnpm/test-default-web-scope-package')
.expect(404, done); .expect(404, done);

View File

@@ -30,7 +30,7 @@ describe('controllers/web/user.test.js', function () {
.expect(200) .expect(200)
.expect('content-type', 'text/html; charset=utf-8') .expect('content-type', 'text/html; charset=utf-8')
.expect(/<div id="profile">/) .expect(/<div id="profile">/)
.expect(/Packages by /, done); .expect(/Packages by/, done);
}); });
it('should get 404', function (done) { it('should get 404', function (done) {

View File

@@ -14,4 +14,4 @@
* Module dependencies. * Module dependencies.
*/ */
var varname = require('modulename'); var koa = require('koa');

View File

@@ -20,6 +20,7 @@ var User = require('../proxy/user');
var usernames = [ var usernames = [
'cnpmjstest101', 'cnpmjstest101',
'cnpmjstest102',
'cnpmjstest10' 'cnpmjstest10'
]; ];

View File

@@ -20,6 +20,8 @@ var request = require('supertest');
var app = require('../../servers/registry'); var app = require('../../servers/registry');
var mm = require('mm'); var mm = require('mm');
var mysql = require('../../common/mysql'); var mysql = require('../../common/mysql');
var config = require('../../config');
var UserService = require('../../services/user');
describe('middleware/auth.test.js', function () { describe('middleware/auth.test.js', function () {
before(function (done) { before(function (done) {
@@ -60,5 +62,29 @@ describe('middleware/auth.test.js', function () {
.set('authorization', 'basic ' + new Buffer('cnpmjstest10:cnpmjstest10').toString('base64')) .set('authorization', 'basic ' + new Buffer('cnpmjstest10:cnpmjstest10').toString('base64'))
.expect(500, done); .expect(500, done);
}); });
describe('config.customUserService = true', function () {
beforeEach(function () {
mm(config, 'customUserService', true);
});
it('should 401 when user service auth throw error', function (done) {
mm(UserService, 'auth', function* () {
var err = new Error('mock user service auth error, please visit http://ooxx.net/user to sigup first');
err.name = 'UserSeriveAuthError';
err.status = 401;
throw err;
});
request(app)
.put('/-/user/org.couchdb.user:cnpmjstest10/-rev/1')
.set('authorization', 'basic ' + new Buffer('cnpmjstest10:cnpmjstest10').toString('base64'))
.expect({
error: 'UserSeriveAuthError',
reason: 'mock user service auth error, please visit http://ooxx.net/user to sigup first'
})
.expect(401, done);
});
});
}); });
}); });

View File

@@ -20,6 +20,7 @@ var fs = require('fs');
var path = require('path'); var path = require('path');
var mysql = require('../../common/mysql'); var mysql = require('../../common/mysql');
var Module = require('../../proxy/module'); var Module = require('../../proxy/module');
var config = require('../../config');
var fixtures = path.join(path.dirname(__dirname), 'fixtures'); var fixtures = path.join(path.dirname(__dirname), 'fixtures');
@@ -144,13 +145,10 @@ describe('proxy/module.test.js', function () {
}); });
describe('listByAuthor()', function () { describe('listByAuthor()', function () {
it('should return author recent modules', function (done) { it('should return author recent modules', function* () {
Module.listByAuthor('fengmk2', function (err, rows) { var rows = yield Module.listByAuthor('fengmk2');
should.not.exist(err); rows.forEach(function (r) {
rows.forEach(function (r) { r.should.have.keys('name', 'description');
r.should.have.keys('name', 'description');
});
done();
}); });
}); });
}); });
@@ -173,4 +171,26 @@ describe('proxy/module.test.js', function () {
yield* Module.removeTagsByNames('foo', ['latest', '1.0']); yield* Module.removeTagsByNames('foo', ['latest', '1.0']);
}); });
}); });
describe('listPrivates()', function () {
it('should response [] if scopes not present', function* () {
mm(config, 'scopes', []);
var modules = yield Module.listPrivates();
modules.should.eql([]);
});
it('should response [] if private modules not present', function* () {
mm(config, 'privatePackages', []);
mm(config, 'scopes', ['@not-exist']);
var modules = yield Module.listPrivates();
modules.should.eql([]);
});
it('should work', function* () {
var modules = yield Module.listPrivates();
modules.forEach(function (m) {
m.should.have.keys(['name', 'description']);
})
});
});
}); });

View File

@@ -51,59 +51,51 @@ describe('proxy/user.test.js', function () {
describe('get()', function () { describe('get()', function () {
before(initUser); before(initUser);
it('should get user ok', function (done) { it('should get user ok', function* () {
user.get('mockuser', function (err, data) { var data = yield* user.get('mockuser');
should.not.exist(err); data.should.have.keys('id', 'rev', 'name', 'email', 'salt',
data.should.have.keys('id', 'rev', 'name', 'email', 'salt', 'json', 'npm_user',
'json', 'npm_user', 'password_sha', 'ip', 'roles', 'gmt_create', 'gmt_modified');
'password_sha', 'ip', 'roles', 'gmt_create', 'gmt_modified');
done();
});
}); });
it('should get error when mysql error', function (done) { it('should get error when mysql error', function* () {
mm.error(mysql, 'query', 'mock error'); mm.error(mysql, 'query', 'mock error');
user.get('mockuser', function (err) { try {
yield* user.get('mockuser');
new Error('should not run this');
} catch (err) {
err.message.should.equal('mock error'); err.message.should.equal('mock error');
done(); }
});
}); });
}); });
describe('auth()', function () { describe('auth()', function () {
before(initUser); before(initUser);
it('should auth user ok', function (done) { it('should auth user ok', function* () {
user.auth(mockUser.name, mockUser.password, function (err, data) { var data = yield* user.auth(mockUser.name, mockUser.password);
should.not.exist(err); data.should.have.keys('id', 'rev', 'name', 'email', 'salt',
data.should.have.keys('id', 'rev', 'name', 'email', 'salt', 'json', 'npm_user',
'json', 'npm_user', 'password_sha', 'ip', 'roles', 'gmt_create', 'gmt_modified');
'password_sha', 'ip', 'roles', 'gmt_create', 'gmt_modified');
done();
});
}); });
it('should auth user fail when user not exist', function (done) { it('should auth user fail when user not exist', function* () {
user.auth('notexistmockuser', '123', function (err, data) { var data = yield* user.auth('notexistmockuser', '123');
should.not.exist(err); should.not.exist(data);
should.not.exist(data);
done();
});
}); });
it('should auth fail when password error', function (done) { it('should auth fail when password error', function* () {
user.auth(mockUser.name, '123', function (err, data) { var data = yield* user.auth(mockUser.name, '123');
should.not.exist(err); should.not.exist(data);
should.not.exist(data);
done();
});
}); });
it('should auth error when mysql error', function (done) { it('should auth error when mysql error', function* () {
mm.error(mysql, 'query', 'mock error'); mm.error(mysql, 'query', 'mock error');
user.auth(mockUser.name, '123', function (err, data) { try {
yield* user.auth(mockUser.name, '123');
new Error('should not run this');
} catch (err) {
err.message.should.equal('mock error'); err.message.should.equal('mock error');
done(); }
});
}); });
}); });

View File

@@ -0,0 +1,151 @@
/**!
* cnpmjs.org - test/services/default_user_service.test.js
*
* Copyright(c) fengmk2 and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var should = require('should');
var mm = require('mm');
var npm = require('../../proxy/npm');
var User = require('../../proxy/user');
var DefaultUserService = require('../../services/default_user_service');
var config = require('../../config');
describe('services/default_user_service.test.js', function () {
var userService = new DefaultUserService();
before(function* () {
var user = yield* npm.getUser('fengmk2');
if (!user) {
return;
}
user.fullname = 'Yuan Feng';
yield* User.saveNpmUser(user);
});
beforeEach(function () {
mm(config, 'scopes', ['@cnpm', '@cnpmtest']);
});
afterEach(mm.restore);
describe('auth()', function () {
it('should return user when auth success', function* () {
var user = yield* userService.auth('cnpmjstest10', 'cnpmjstest10');
should.exist(user);
user.should.eql({
login: 'cnpmjstest10',
email: 'fengmk2@gmail.com',
name: 'cnpmjstest10',
html_url: 'http://cnpmjs.org/~cnpmjstest10',
avatar_url: 'https://secure.gravatar.com/avatar/95b9d41231617a05ced5604d242c9670?s=50&d=retro',
im_url: '',
site_admin: true,
scopes: ['@cnpm', '@cnpmtest'],
});
});
it('should return null when auth fail', function* () {
var user = yield* userService.auth('cnpmjstest10', 'wrong');
should.not.exist(user);
});
});
describe('get()', function () {
it('should get a cnpm admin user by login name', function* () {
var user = yield* userService.get('cnpmjstest10');
should.exist(user);
user.should.eql({
login: 'cnpmjstest10',
email: 'fengmk2@gmail.com',
name: 'cnpmjstest10',
html_url: 'http://cnpmjs.org/~cnpmjstest10',
avatar_url: 'https://secure.gravatar.com/avatar/95b9d41231617a05ced5604d242c9670?s=50&d=retro',
im_url: '',
site_admin: true,
scopes: ['@cnpm', '@cnpmtest'],
});
});
it('should get a cnpm normal user by login name', function* () {
var user = yield* userService.get('cnpmjstest101');
should.exist(user);
user.should.eql({
login: 'cnpmjstest101',
email: 'fengmk2@gmail.com',
name: 'cnpmjstest101',
html_url: 'http://cnpmjs.org/~cnpmjstest101',
avatar_url: 'https://secure.gravatar.com/avatar/95b9d41231617a05ced5604d242c9670?s=50&d=retro',
im_url: '',
site_admin: false,
scopes: ['@cnpm', '@cnpmtest'],
});
});
it('should get a npm sync user by login name', function* () {
var user = yield* userService.get('fengmk2');
should.exist(user);
user.should.eql({
login: 'fengmk2',
email: 'fengmk2@gmail.com',
name: 'Yuan Feng',
html_url: 'http://fengmk2.github.com',
avatar_url: 'https://secure.gravatar.com/avatar/95b9d41231617a05ced5604d242c9670?s=50&d=retro',
im_url: 'https://twitter.com/fengmk2',
site_admin: true,
scopes: ['@cnpm', '@cnpmtest'],
});
});
it('should get null when user not exists', function* () {
var user = yield* userService.get('not-exists');
should.not.exist(user);
});
});
describe('list()', function () {
it('should return all exists users', function* () {
var users = yield* userService.list(['cnpmjstest10', 'fengmk2', 'cnpmjstest101']);
users.should.length(3);
});
it('should return some exists users', function* () {
var users = yield* userService.list(['cnpmjstest10', 'fengmk2123', 'cnpmjstest101']);
users.should.length(2);
});
it('should return []', function* () {
var users = yield* userService.list([]);
users.should.length(0);
var users = yield* userService.list(['not1', 'not2']);
users.should.length(0);
});
});
describe('search()', function () {
it('should return login name matched users', function* () {
var users = yield* userService.search('cnpm');
users.length.should.above(2);
});
it('should return limit 1 user', function* () {
var users = yield* userService.search('cnpm', {limit: 1});
users.should.length(1);
});
it('should return []', function* () {
var users = yield* userService.search('not-cnpm');
users.should.length(0);
});
});
});

View File

@@ -25,6 +25,9 @@ exports.adminAuth = 'Basic ' + new Buffer(admin + ':' + admin).toString('base64'
var otherUser = exports.otherUser = 'cnpmjstest101'; var otherUser = exports.otherUser = 'cnpmjstest101';
exports.otherUserAuth = 'Basic ' + new Buffer(otherUser + ':' + otherUser).toString('base64'); exports.otherUserAuth = 'Basic ' + new Buffer(otherUser + ':' + otherUser).toString('base64');
var secondUser = exports.secondUser = 'cnpmjstest102';
exports.secondUserAuth = 'Basic ' + new Buffer(secondUser + ':' + secondUser).toString('base64');
var _pkg = fs.readFileSync(path.join(fixtures, 'package_and_tgz.json')); var _pkg = fs.readFileSync(path.join(fixtures, 'package_and_tgz.json'));
exports.getPackage = function (name, version, user) { exports.getPackage = function (name, version, user) {

33
view/web/private.html Normal file
View File

@@ -0,0 +1,33 @@
<style>
#private .package {
padding: 10px;
font-size: 18px;
border-bottom: 1px solid #ddd;
}
#private .alert a {
font-size: 20px;
}
</style>
<div id="private">
<% if (!packages.length) { %>
<div class="alert alert-warning">
Can not found private package
</div>
<% } else {%>
<h1>
Private packages in this registry
</h1>
<hr />
<% for (var i = 0; i < packages.length; i++) {
var item = packages[i];
%>
<div class="package">
<a href="/package/<%= item.name %>" class="package-name"><%= item.name %></a>
<span class="package-description"><%= item.description %></span>
</div>
<% } %>
<% } %>
</div>

View File

@@ -7,14 +7,49 @@
</style> </style>
<div id="profile"> <div id="profile">
<h1> <h1>
Packages by <span style="color:#09f;"><%= user.name %></span> Packages by
<% if (user.json.homepage) { %>
<span class="user">
<a class="username" target="_blank" href="<%= user.json.homepage %>">
<% if (user.json.avatar) { %>
<img src="<%= user.json.avatar %>" class="avatar">
<% } %>
<%= user.name %>
</a>
</span>
<% } else { %>
<span style="color:#09f;"><%= user.name %></span>
<% } %>
<% if (user.json.fullname) { %>
<small>(<%= user.json.fullname %>)</small>
<% } %>
<% if (user.email) { %> <% if (user.email) { %>
<small>(<a href="mailto:<%= user.email %>"><%= user.email %></a>)</small> <small>(<a href="mailto:<%= user.email %>"><%= user.email %></a>)</small>
<% } %> <% } %>
</h1> </h1>
<table class="downloads">
<tbody>
<tr>
<th>Name</th>
<th>Last modified</th>
<th>Is admin</th>
<th>Publish scopes</th>
</tr>
<tr>
<td>@<%= user.json.fullname || user.name %></td>
<td><%- lastModified %></td>
<td><%- isAdmin %></td>
<td>
<%- scopes.map(function (scope) {
return '<a href="/browse/keyword/' + scope + '" target="_blank">' + scope + '</a>';
}).join(' , ') %>
</td>
</tr>
</tbody>
</table>
<hr /> <hr />
<% if (!packages.length) { %> <% if (!packages.length) { %>
<div class="alert alert-warning">Can not found package by <%= user.name %>.</div> <div class="alert alert-warning">Can not found any package by <%= user.name %>.</div>
<% } %> <% } %>
<% for (var i = 0; i < packages.length; i++) { %> <% for (var i = 0; i < packages.length; i++) { %>
<div class="package"> <div class="package">