Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f48cbb457b | |||
| 8d192dc992 | |||
| b70324aa24 | |||
| 390afaf614 | |||
| 5112322e7d | |||
| 2cb498d500 | |||
| 2bd6e02cdc | |||
| 85423cbc20 | |||
| 1c0d027bd3 | |||
| 5a8a023039 | |||
| 196b059cfb | |||
| 2d930b9c3d | |||
| a5ba3faa49 | |||
| 02ba91f1bb | |||
| bfa917e057 | |||
| 909dd0725a | |||
| 74860f2d16 | |||
| 132ebb4e74 | |||
| 698158cd93 | |||
| 5bfc684f1b | |||
| c944c9b65b | |||
| d61698b894 | |||
| a4d32009ad | |||
| 3007875e35 | |||
| b4aad138fc | |||
| 8df7eb2acb | |||
| 18cab6f861 | |||
| b2071c65d8 | |||
| 402dba096e | |||
| abf0c81de4 | |||
| 613985a17c | |||
| bfc9801699 | |||
| ee705eb979 | |||
| 67b94c7fde | |||
| 77e5d3f4bb | |||
| 30618b8644 | |||
| 57a2613286 | |||
| e15bd89ba2 | |||
| d2ed816f44 | |||
| e51234928b | |||
| 3aa668aea3 | |||
| 870edab78a | |||
| ebc9d9185d | |||
| 093150d4e3 | |||
| de80a6692d | |||
| c28f564a47 | |||
| eb6a09c2bd |
@@ -374,3 +374,8 @@
|
||||
|
||||
[0.6.3]
|
||||
- Make sending invite for new users optional
|
||||
|
||||
[0.6.4]
|
||||
- Add support for display names
|
||||
- Send invite links to admins for user setup
|
||||
- Enforce stronger passwords
|
||||
|
||||
+1
-10
@@ -39,7 +39,7 @@ gulp.task('3rdparty', function () {
|
||||
// JavaScript
|
||||
// --------------
|
||||
|
||||
gulp.task('js', ['js-index', 'js-setup', 'js-update', 'js-error'], function () {});
|
||||
gulp.task('js', ['js-index', 'js-setup', 'js-update'], function () {});
|
||||
|
||||
var oauth = {
|
||||
clientId: argv.clientId || 'cid-webadmin',
|
||||
@@ -80,14 +80,6 @@ gulp.task('js-setup', function () {
|
||||
.pipe(gulp.dest('webadmin/dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-error', function () {
|
||||
gulp.src(['webadmin/src/js/error.js'])
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(uglify())
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('webadmin/dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-update', function () {
|
||||
gulp.src(['webadmin/src/js/update.js'])
|
||||
.pipe(sourcemaps.init())
|
||||
@@ -149,7 +141,6 @@ gulp.task('watch', ['default'], function () {
|
||||
gulp.watch(['webadmin/src/views/*.html'], ['html-views']);
|
||||
gulp.watch(['webadmin/src/templates/*.html'], ['html-templates']);
|
||||
gulp.watch(['webadmin/src/js/update.js'], ['js-update']);
|
||||
gulp.watch(['webadmin/src/js/error.js'], ['js-error']);
|
||||
gulp.watch(['webadmin/src/js/setup.js', 'webadmin/src/js/client.js'], ['js-setup']);
|
||||
gulp.watch(['webadmin/src/js/index.js', 'webadmin/src/js/client.js', 'webadmin/src/js/appstore.js', 'webadmin/src/js/main.js', 'webadmin/src/views/*.js'], ['js-index']);
|
||||
gulp.watch(['webadmin/src/3rdparty/**/*'], ['3rdparty']);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE users ADD COLUMN displayName VARCHAR(512) DEFAULT ""', function (error) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(db, callback) {
|
||||
db.runSql('ALTER TABLE users DROP COLUMN displayName', function (error) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
@@ -18,6 +18,7 @@ CREATE TABLE IF NOT EXISTS users(
|
||||
createdAt VARCHAR(512) NOT NULL,
|
||||
modifiedAt VARCHAR(512) NOT NULL,
|
||||
admin INTEGER NOT NULL,
|
||||
displayName VARCHAR(512) DEFAULT '',
|
||||
PRIMARY KEY(id));
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tokens(
|
||||
|
||||
+29
@@ -22,6 +22,7 @@ exports = module.exports = {
|
||||
|
||||
backup: backup,
|
||||
backupApp: backupApp,
|
||||
listBackups: listBackups,
|
||||
|
||||
getLogs: getLogs,
|
||||
|
||||
@@ -863,3 +864,31 @@ function restoreApp(app, addonsToRestore, backupId, callback) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function listBackups(appId, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
appdb.exists(appId, function (error, exists) {
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
if (!exists) return callback(new AppsError(AppsError.NOT_FOUND));
|
||||
|
||||
// TODO pagination is not implemented in the backend yet
|
||||
backups.getAllPaged(0, 1000, function (error, result) {
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
var appBackups = [];
|
||||
|
||||
result.forEach(function (backup) {
|
||||
appBackups = appBackups.concat(backup.dependsOn.filter(function (d) {
|
||||
return d.indexOf('appbackup_' + appId) === 0;
|
||||
}));
|
||||
});
|
||||
|
||||
// alphabetic should be sufficient
|
||||
appBackups.sort();
|
||||
|
||||
callback(null, appBackups);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
+3
-2
@@ -196,10 +196,11 @@ function setTimeZone(ip, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function activate(username, password, email, ip, callback) {
|
||||
function activate(username, password, email, displayName, ip, callback) {
|
||||
assert.strictEqual(typeof username, 'string');
|
||||
assert.strictEqual(typeof password, 'string');
|
||||
assert.strictEqual(typeof email, 'string');
|
||||
assert.strictEqual(typeof displayName, 'string');
|
||||
assert.strictEqual(typeof ip, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
@@ -207,7 +208,7 @@ function activate(username, password, email, ip, callback) {
|
||||
|
||||
setTimeZone(ip, function () { }); // TODO: get this from user. note that timezone is detected based on the browser location and not the cloudron region
|
||||
|
||||
user.createOwner(username, password, email, function (error, userObject) {
|
||||
user.createOwner(username, password, email, displayName, function (error, userObject) {
|
||||
if (error && error.reason === UserError.ALREADY_EXISTS) return callback(new CloudronError(CloudronError.ALREADY_PROVISIONED));
|
||||
if (error && error.reason === UserError.BAD_USERNAME) return callback(new CloudronError(CloudronError.BAD_USERNAME));
|
||||
if (error && error.reason === UserError.BAD_PASSWORD) return callback(new CloudronError(CloudronError.BAD_PASSWORD));
|
||||
|
||||
+1
-1
@@ -171,7 +171,7 @@ function createSubcontainer(app, name, cmd, options, callback) {
|
||||
Hostname: isolatedNetworkNs ? (semver.gte(targetBoxVersion(app.manifest), '0.0.77') ? app.location : config.appFqdn(app.location)) : null,
|
||||
Tty: isAppContainer,
|
||||
Image: app.manifest.dockerImage,
|
||||
Cmd: (isAppContainer && developmentMode) ? [ '/bin/sleep', 'infinity' ] : cmd,
|
||||
Cmd: (isAppContainer && developmentMode) ? [ '/bin/bash', '-c', 'echo "Development mode. Use cloudron exec to debug. Sleeping" && sleep infinity' ] : cmd,
|
||||
Env: stdEnv.concat(addonEnv).concat(portEnv),
|
||||
ExposedPorts: isAppContainer ? exposedPorts : { },
|
||||
Volumes: { // see also ReadonlyRootfs
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ function start(callback) {
|
||||
cn: entry.id,
|
||||
uid: entry.id,
|
||||
mail: entry.email,
|
||||
displayname: entry.username,
|
||||
displayname: entry.displayName || entry.username,
|
||||
username: entry.username,
|
||||
samaccountname: entry.username, // to support ActiveDirectory clients
|
||||
memberof: groups
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<%if (format === 'text') { %>
|
||||
|
||||
Dear Admin,
|
||||
|
||||
User with name '<%= username %>' (<%= email %>) was added in the Cloudron at <%= fqdn %>.
|
||||
|
||||
You are receiving this email because you are an Admin of the Cloudron at <%= fqdn %>.
|
||||
|
||||
<% if (inviteLink) { %>
|
||||
This user was not invited immediately, he has to get invited manually later, using the "send invite" button in the admin panel.
|
||||
To perform any configuration on behalf of the user, please use this link
|
||||
<%= inviteLink %>
|
||||
It allows to setup a temporary password, which the user will be able to override, once he gets invited.
|
||||
This link will become invalid as soon as the user was invited.
|
||||
<% } %>
|
||||
|
||||
Thank you,
|
||||
User Manager
|
||||
|
||||
<% } else { %>
|
||||
|
||||
<% } %>
|
||||
|
||||
+19
-3
@@ -246,12 +246,28 @@ function sendInvite(user, invitor) {
|
||||
enqueue(mailOptions);
|
||||
}
|
||||
|
||||
function userAdded(user) {
|
||||
function userAdded(user, inviteSent) {
|
||||
assert.strictEqual(typeof user, 'object');
|
||||
assert.strictEqual(typeof inviteSent, 'boolean');
|
||||
|
||||
debug('Sending mail for userAdded');
|
||||
debug('Sending mail for userAdded %s including invite link', inviteSent ? 'not' : '');
|
||||
|
||||
mailUserEventToAdmins(user, 'was added');
|
||||
getAdminEmails(function (error, adminEmails) {
|
||||
if (error) return console.log('Error getting admins', error);
|
||||
|
||||
adminEmails = _.difference(adminEmails, [ user.email ]);
|
||||
|
||||
var inviteLink = inviteSent ? null : config.adminOrigin() + '/api/v1/session/password/setup.html?reset_token=' + user.resetToken;
|
||||
|
||||
var mailOptions = {
|
||||
from: config.get('adminEmail'),
|
||||
to: adminEmails.join(', '),
|
||||
subject: util.format('%s added in Cloudron %s', user.username, config.fqdn()),
|
||||
text: render('user_added.ejs', { fqdn: config.fqdn(), username: user.username, email: user.email, inviteLink: inviteLink, format: 'text' }),
|
||||
};
|
||||
|
||||
enqueue(mailOptions);
|
||||
});
|
||||
}
|
||||
|
||||
function userRemoved(username) {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/* jslint node:true */
|
||||
|
||||
'use strict';
|
||||
|
||||
// From https://www.npmjs.com/package/password-generator
|
||||
|
||||
exports = module.exports = {
|
||||
generate: generate,
|
||||
validate: validate
|
||||
};
|
||||
|
||||
var assert = require('assert'),
|
||||
generatePassword = require('password-generator');
|
||||
|
||||
// http://www.w3resource.com/javascript/form/example4-javascript-form-validation-password.html
|
||||
var gPasswordTestRegExp = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])(?!.*\s).{8,30}$/;
|
||||
|
||||
var UPPERCASE_RE = /([A-Z])/g;
|
||||
var LOWERCASE_RE = /([a-z])/g;
|
||||
var NUMBER_RE = /([\d])/g;
|
||||
var SPECIAL_CHAR_RE = /([\?\-])/g;
|
||||
|
||||
function isStrongEnough(password) {
|
||||
var uc = password.match(UPPERCASE_RE);
|
||||
var lc = password.match(LOWERCASE_RE);
|
||||
var n = password.match(NUMBER_RE);
|
||||
var sc = password.match(SPECIAL_CHAR_RE);
|
||||
|
||||
return uc && lc && n && sc;
|
||||
}
|
||||
|
||||
function generate() {
|
||||
var password = '';
|
||||
|
||||
while (!isStrongEnough(password)) password = generatePassword(8, false, /[\w\d\?\-]/);
|
||||
|
||||
return password;
|
||||
}
|
||||
|
||||
function validate(password) {
|
||||
assert.strictEqual(typeof password, 'string');
|
||||
|
||||
if (!password.match(gPasswordTestRegExp)) return new Error('Password must be 8-30 character with at least one uppercase, one numeric and one special character');
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -15,6 +15,7 @@ exports = module.exports = {
|
||||
updateApp: updateApp,
|
||||
getLogs: getLogs,
|
||||
getLogStream: getLogStream,
|
||||
listBackups: listBackups,
|
||||
|
||||
stopApp: stopApp,
|
||||
startApp: startApp,
|
||||
@@ -373,3 +374,14 @@ function exec(req, res, next) {
|
||||
res.socket.pipe(duplexStream);
|
||||
});
|
||||
}
|
||||
|
||||
function listBackups(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
apps.listBackups(req.params.id, function (error, result) {
|
||||
if (error && error.reason === AppsError.NOT_FOUND) return next(new HttpError(404, 'No such app'));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(200, { backups: result }));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,15 +41,17 @@ function activate(req, res, next) {
|
||||
if (typeof req.body.username !== 'string') return next(new HttpError(400, 'username must be string'));
|
||||
if (typeof req.body.password !== 'string') return next(new HttpError(400, 'password must be string'));
|
||||
if (typeof req.body.email !== 'string') return next(new HttpError(400, 'email must be string'));
|
||||
if ('displayName' in req.body && typeof req.body.displayName !== 'string') return next(new HttpError(400, 'displayName must be string'));
|
||||
|
||||
var username = req.body.username;
|
||||
var password = req.body.password;
|
||||
var email = req.body.email;
|
||||
var displayName = req.body.displayName || '';
|
||||
|
||||
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||
debug('activate: username:%s ip:%s', username, ip);
|
||||
|
||||
cloudron.activate(username, password, email, ip, function (error, info) {
|
||||
cloudron.activate(username, password, email, displayName, ip, function (error, info) {
|
||||
if (error && error.reason === CloudronError.ALREADY_PROVISIONED) return next(new HttpError(409, 'Already setup'));
|
||||
if (error && error.reason === CloudronError.BAD_USERNAME) return next(new HttpError(400, 'Bad username'));
|
||||
if (error && error.reason === CloudronError.BAD_PASSWORD) return next(new HttpError(400, 'Bad password'));
|
||||
|
||||
@@ -56,8 +56,8 @@ var APP_MANIFEST_1 = JSON.parse(fs.readFileSync(__dirname + '/../../../../test-a
|
||||
APP_MANIFEST_1.dockerImage = TEST_IMAGE_REPO + ':' + TEST_IMAGE_TAG;
|
||||
APP_MANIFEST_1.singleUser = true;
|
||||
|
||||
var USERNAME = 'admin', PASSWORD = 'password', EMAIL ='admin@me.com';
|
||||
var USERNAME_1 = 'user', PASSWORD_1 = 'password', EMAIL_1 ='user@me.com';
|
||||
var USERNAME = 'admin', PASSWORD = 'Foobar?1337', EMAIL ='admin@me.com';
|
||||
var USERNAME_1 = 'user', PASSWORD_1 = 'Foobar?1338', EMAIL_1 ='user@me.com';
|
||||
var token = null; // authentication token
|
||||
var token_1 = null;
|
||||
|
||||
@@ -941,14 +941,17 @@ describe('App installation', function () {
|
||||
});
|
||||
|
||||
it('did start the app', function (done) {
|
||||
setTimeout(function () {
|
||||
var count = 0;
|
||||
function checkStartState() {
|
||||
superagent.get('http://localhost:' + appEntry.httpPort + appResult.manifest.healthCheckPath)
|
||||
.end(function (err, res) {
|
||||
expect(!err).to.be.ok();
|
||||
expect(res.statusCode).to.equal(200);
|
||||
done();
|
||||
if (res && res.statusCode === 200) return done();
|
||||
if (++count > 50) return done(new Error('Timedout'));
|
||||
setTimeout(checkStartState, 500);
|
||||
});
|
||||
}, 2000); // give some time for docker to settle
|
||||
}
|
||||
|
||||
checkStartState();
|
||||
});
|
||||
|
||||
it('can uninstall app', function (done) {
|
||||
|
||||
@@ -19,7 +19,7 @@ var appdb = require('../../appdb.js'),
|
||||
|
||||
var SERVER_URL = 'http://localhost:' + config.get('port');
|
||||
|
||||
var USERNAME = 'admin', PASSWORD = 'password', EMAIL ='silly@me.com';
|
||||
var USERNAME = 'admin', PASSWORD = 'Foobar?1337', EMAIL ='silly@me.com';
|
||||
var token = null;
|
||||
|
||||
var server;
|
||||
|
||||
@@ -20,7 +20,7 @@ var async = require('async'),
|
||||
|
||||
var SERVER_URL = 'http://localhost:' + config.get('port');
|
||||
|
||||
var USERNAME = 'admin', PASSWORD = 'password', EMAIL ='silly@me.com';
|
||||
var USERNAME = 'admin', PASSWORD = 'Foobar?1337', EMAIL ='silly@me.com';
|
||||
var token = null; // authentication token
|
||||
|
||||
function cleanup(done) {
|
||||
@@ -392,7 +392,7 @@ describe('Clients', function () {
|
||||
var USER_0 = {
|
||||
userId: uuid.v4(),
|
||||
username: 'someusername',
|
||||
password: 'somepassword',
|
||||
password: 'Strong#$%2345',
|
||||
email: 'some@email.com',
|
||||
admin: true,
|
||||
salt: 'somesalt',
|
||||
|
||||
@@ -18,7 +18,7 @@ var async = require('async'),
|
||||
|
||||
var SERVER_URL = 'http://localhost:' + config.get('port');
|
||||
|
||||
var USERNAME = 'admin', PASSWORD = 'password', EMAIL ='silly@me.com';
|
||||
var USERNAME = 'admin', PASSWORD = 'Foobar?1337', EMAIL ='silly@me.com';
|
||||
var token = null; // authentication token
|
||||
|
||||
var server;
|
||||
@@ -68,7 +68,7 @@ describe('Cloudron', function () {
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: 'someuser', password: 'somepassword', email: 'admin@foo.bar' })
|
||||
.send({ username: 'someuser', password: 'strong#A3asdf', email: 'admin@foo.bar' })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(500);
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
@@ -81,7 +81,7 @@ describe('Cloudron', function () {
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: '', password: 'somepassword', email: 'admin@foo.bar' })
|
||||
.send({ username: '', password: 'ADSFsdf$%436', email: 'admin@foo.bar' })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
@@ -107,7 +107,7 @@ describe('Cloudron', function () {
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: 'someuser', password: 'somepassword', email: '' })
|
||||
.send({ username: 'someuser', password: 'ADSF#asd546', email: '' })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
@@ -115,12 +115,12 @@ describe('Cloudron', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('fails due to empty name', function (done) {
|
||||
it('fails due to wrong displayName type', function (done) {
|
||||
var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: 'someuser', password: '', email: 'admin@foo.bar', name: '' })
|
||||
.send({ username: 'someuser', password: 'ADSF?#asd546', email: 'admin@foo.bar', displayName: 1234 })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
@@ -133,7 +133,7 @@ describe('Cloudron', function () {
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: 'someuser', password: 'somepassword', email: 'invalidemail' })
|
||||
.send({ username: 'someuser', password: 'ADSF#asd546', email: 'invalidemail' })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
@@ -147,7 +147,7 @@ describe('Cloudron', function () {
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: 'someuser', password: 'somepassword', email: 'admin@foo.bar', name: 'tester' })
|
||||
.send({ username: 'someuser', password: 'ADSF#asd546', email: 'admin@foo.bar', displayName: 'tester' })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(201);
|
||||
expect(scope1.isDone()).to.be.ok();
|
||||
@@ -161,7 +161,7 @@ describe('Cloudron', function () {
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: 'someuser', password: 'somepassword', email: 'admin@foo.bar' })
|
||||
.send({ username: 'someuser', password: 'ADSF#asd546', email: 'admin@foo.bar' })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(409);
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
|
||||
@@ -17,7 +17,7 @@ var async = require('async'),
|
||||
|
||||
var SERVER_URL = 'http://localhost:' + config.get('port');
|
||||
|
||||
var USERNAME = 'admin', PASSWORD = 'password', EMAIL ='silly@me.com';
|
||||
var USERNAME = 'admin', PASSWORD = 'Foobar?1337', EMAIL ='silly@me.com';
|
||||
var token = null; // authentication token
|
||||
|
||||
var server;
|
||||
|
||||
@@ -139,13 +139,14 @@ describe('OAuth2', function () {
|
||||
var USER_0 = {
|
||||
id: uuid.v4(),
|
||||
username: 'someusername',
|
||||
password: 'somepassword',
|
||||
password: '@#45Strongpassword',
|
||||
email: 'some@email.com',
|
||||
admin: true,
|
||||
salt: 'somesalt',
|
||||
createdAt: (new Date()).toUTCString(),
|
||||
modifiedAt: (new Date()).toUTCString(),
|
||||
resetToken: hat(256)
|
||||
resetToken: hat(256),
|
||||
displayName: ''
|
||||
};
|
||||
|
||||
var APP_0 = {
|
||||
@@ -291,7 +292,7 @@ describe('OAuth2', function () {
|
||||
appdb.add.bind(null, APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.portBindings, APP_1.accessRestriction, APP_1.oauthProxy),
|
||||
appdb.add.bind(null, APP_2.id, APP_2.appStoreId, APP_2.manifest, APP_2.location, APP_2.portBindings, APP_2.accessRestriction, APP_2.oauthProxy),
|
||||
function (callback) {
|
||||
user.create(USER_0.username, USER_0.password, USER_0.email, true, '', false, function (error, userObject) {
|
||||
user.create(USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, true, '', false, function (error, userObject) {
|
||||
expect(error).to.not.be.ok();
|
||||
|
||||
// update the global objects to reflect the new user id
|
||||
@@ -1239,13 +1240,14 @@ describe('Password', function () {
|
||||
var USER_0 = {
|
||||
userId: uuid.v4(),
|
||||
username: 'someusername',
|
||||
password: 'somepassword',
|
||||
password: 'passWord%1234',
|
||||
email: 'some@email.com',
|
||||
admin: true,
|
||||
salt: 'somesalt',
|
||||
createdAt: (new Date()).toUTCString(),
|
||||
modifiedAt: (new Date()).toUTCString(),
|
||||
resetToken: hat(256)
|
||||
resetToken: hat(256),
|
||||
displayName: ''
|
||||
};
|
||||
|
||||
// make csrf always succeed for testing
|
||||
@@ -1415,7 +1417,7 @@ describe('Password', function () {
|
||||
.get('/?accessToken=token&expiresAt=1234').reply(200, {});
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/session/password/reset')
|
||||
.send({ password: 'somepassword', resetToken: USER_0.resetToken })
|
||||
.send({ password: 'ASF23$%somepassword', resetToken: USER_0.resetToken })
|
||||
.end(function (error, result) {
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
expect(result.statusCode).to.equal(200);
|
||||
|
||||
@@ -22,7 +22,7 @@ var appdb = require('../../appdb.js'),
|
||||
|
||||
var SERVER_URL = 'http://localhost:' + config.get('port');
|
||||
|
||||
var USERNAME = 'admin', PASSWORD = 'password', EMAIL ='silly@me.com';
|
||||
var USERNAME = 'admin', PASSWORD = 'Foobar?1337', EMAIL ='silly@me.com';
|
||||
var token = null;
|
||||
|
||||
var server;
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('SimpleAuth API', function () {
|
||||
var SERVER_URL = 'http://localhost:' + config.get('port');
|
||||
var SIMPLE_AUTH_ORIGIN = 'http://localhost:' + config.get('simpleAuthPort');
|
||||
|
||||
var USERNAME = 'admin', PASSWORD = 'password', EMAIL ='silly@me.com';
|
||||
var USERNAME = 'admin', PASSWORD = 'Foobar?1337', EMAIL ='silly@me.com';
|
||||
|
||||
var APP_0 = {
|
||||
id: 'app0',
|
||||
|
||||
@@ -18,7 +18,7 @@ var config = require('../../config.js'),
|
||||
|
||||
var SERVER_URL = 'http://localhost:' + config.get('port');
|
||||
|
||||
var USERNAME_0 = 'admin', PASSWORD = 'password', EMAIL = 'silly@me.com', EMAIL_0_NEW = 'stupid@me.com';
|
||||
var USERNAME_0 = 'admin', PASSWORD = 'Foobar?1337', EMAIL = 'silly@me.com', EMAIL_0_NEW = 'stupid@me.com';
|
||||
var USERNAME_1 = 'userTheFirst', EMAIL_1 = 'tao@zen.mac';
|
||||
var USERNAME_2 = 'userTheSecond', EMAIL_2 = 'user@foo.bar';
|
||||
var USERNAME_3 = 'userTheThird', EMAIL_3 = 'user3@foo.bar';
|
||||
@@ -553,7 +553,7 @@ describe('User API', function () {
|
||||
it('change password fails due to wrong password', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/password')
|
||||
.query({ access_token: token })
|
||||
.send({ password: 'some wrong password', newPassword: 'newpassword' })
|
||||
.send({ password: 'some wrong password', newPassword: 'MOre#$%34' })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(403);
|
||||
done();
|
||||
@@ -573,7 +573,7 @@ describe('User API', function () {
|
||||
it('change password succeeds', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/password')
|
||||
.query({ access_token: token })
|
||||
.send({ password: PASSWORD, newPassword: 'new_password' })
|
||||
.send({ password: PASSWORD, newPassword: 'MOre#$%34' })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
done();
|
||||
|
||||
+5
-3
@@ -17,7 +17,7 @@ exports = module.exports = {
|
||||
};
|
||||
|
||||
var assert = require('assert'),
|
||||
generatePassword = require('password-generator'),
|
||||
generatePassword = require('../password.js').generate,
|
||||
HttpError = require('connect-lastmile').HttpError,
|
||||
HttpSuccess = require('connect-lastmile').HttpSuccess,
|
||||
user = require('../user.js'),
|
||||
@@ -46,13 +46,15 @@ function createUser(req, res, next) {
|
||||
if (typeof req.body.username !== 'string') return next(new HttpError(400, 'username must be string'));
|
||||
if (typeof req.body.email !== 'string') return next(new HttpError(400, 'email must be string'));
|
||||
if (typeof req.body.invite !== 'boolean') return next(new HttpError(400, 'invite must be boolean'));
|
||||
if ('displayName' in req.body && typeof req.body.displayName !== 'string') return next(new HttpError(400, 'displayName must be string'));
|
||||
|
||||
var username = req.body.username;
|
||||
var password = generatePassword(8, true /* memorable */);
|
||||
var password = generatePassword();
|
||||
var email = req.body.email;
|
||||
var sendInvite = req.body.invite;
|
||||
var displayName = req.body.displayName || '';
|
||||
|
||||
user.create(username, password, email, false /* admin */, req.user /* creator */, sendInvite, function (error, user) {
|
||||
user.create(username, password, email, displayName, false /* admin */, req.user /* creator */, sendInvite, function (error, user) {
|
||||
if (error && error.reason === UserError.BAD_USERNAME) return next(new HttpError(400, 'Invalid username'));
|
||||
if (error && error.reason === UserError.BAD_EMAIL) return next(new HttpError(400, 'Invalid email'));
|
||||
if (error && error.reason === UserError.BAD_PASSWORD) return next(new HttpError(400, 'Invalid password'));
|
||||
|
||||
@@ -144,6 +144,7 @@ function initializeExpressSync() {
|
||||
router.post('/api/v1/apps/:id/update', appsScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.apps.updateApp);
|
||||
router.post('/api/v1/apps/:id/restore', appsScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.apps.restoreApp);
|
||||
router.post('/api/v1/apps/:id/backup', appsScope, routes.user.requireAdmin, routes.apps.backupApp);
|
||||
router.get ('/api/v1/apps/:id/backups', appsScope, routes.user.requireAdmin, routes.apps.listBackups);
|
||||
router.post('/api/v1/apps/:id/stop', appsScope, routes.user.requireAdmin, routes.apps.stopApp);
|
||||
router.post('/api/v1/apps/:id/start', appsScope, routes.user.requireAdmin, routes.apps.startApp);
|
||||
router.get ('/api/v1/apps/:id/logstream', appsScope, routes.user.requireAdmin, routes.apps.getLogStream);
|
||||
|
||||
+2
-1
@@ -53,7 +53,8 @@ function getAllPaged(backupConfig, page, perPage, callback) {
|
||||
var results = data.Contents.map(function (backup) {
|
||||
return {
|
||||
creationTime: backup.LastModified,
|
||||
restoreKey: backup.Key.slice(backupConfig.prefix.length + 1)
|
||||
restoreKey: backup.Key.slice(backupConfig.prefix.length + 1),
|
||||
dependsOn: [] // FIXME empty dependsOn is wrong and version property is missing!!
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -40,7 +40,8 @@ describe('database', function () {
|
||||
salt: 'morton',
|
||||
createdAt: 'sometime back',
|
||||
modifiedAt: 'now',
|
||||
resetToken: hat(256)
|
||||
resetToken: hat(256),
|
||||
displayName: ''
|
||||
};
|
||||
|
||||
var ADMIN_0 = {
|
||||
@@ -52,7 +53,8 @@ describe('database', function () {
|
||||
salt: 'tata',
|
||||
createdAt: 'sometime back',
|
||||
modifiedAt: 'now',
|
||||
resetToken: ''
|
||||
resetToken: '',
|
||||
displayName: 'Herbert Heidelberg'
|
||||
};
|
||||
|
||||
it('can add user', function (done) {
|
||||
@@ -154,10 +156,11 @@ describe('database', function () {
|
||||
});
|
||||
|
||||
it('can update the user', function (done) {
|
||||
userdb.update(USER_0.id, { email: 'some@thing.com' }, function (error) {
|
||||
userdb.update(USER_0.id, { email: 'some@thing.com', displayName: 'Heiter' }, function (error) {
|
||||
expect(error).to.not.be.ok();
|
||||
userdb.get(USER_0.id, function (error, user) {
|
||||
expect(user.email).to.equal('some@thing.com');
|
||||
expect(user.displayName).to.equal('Heiter');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,14 +17,16 @@ var database = require('../database.js'),
|
||||
|
||||
var USER_0 = {
|
||||
username: 'foobar0',
|
||||
password: 'password0',
|
||||
email: 'foo0@bar.com'
|
||||
password: 'Foobar?1234',
|
||||
email: 'foo0@bar.com',
|
||||
displayName: 'Bob bobson'
|
||||
};
|
||||
|
||||
var USER_1 = {
|
||||
username: 'foobar1',
|
||||
password: 'password1',
|
||||
email: 'foo1@bar.com'
|
||||
password: 'Foobar?12345',
|
||||
email: 'foo1@bar.com',
|
||||
displayName: 'Jesus'
|
||||
};
|
||||
|
||||
function setup(done) {
|
||||
@@ -32,8 +34,8 @@ function setup(done) {
|
||||
database.initialize.bind(null),
|
||||
database._clear.bind(null),
|
||||
ldapServer.start.bind(null),
|
||||
user.create.bind(null, USER_0.username, USER_0.password, USER_0.email, true, null, false),
|
||||
user.create.bind(null, USER_1.username, USER_1.password, USER_1.email, false, USER_0, false)
|
||||
user.create.bind(null, USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, true, null, false),
|
||||
user.create.bind(null, USER_1.username, USER_1.password, USER_1.email, USER_0.displayName, false, USER_0, false)
|
||||
], done);
|
||||
}
|
||||
|
||||
|
||||
+51
-10
@@ -17,9 +17,10 @@ var USERNAME = 'nobody';
|
||||
var USERNAME_NEW = 'nobodynew';
|
||||
var EMAIL = 'nobody@no.body';
|
||||
var EMAIL_NEW = 'nobodynew@no.body';
|
||||
var PASSWORD = 'foobar';
|
||||
var NEW_PASSWORD = 'somenewpassword';
|
||||
var PASSWORD = 'sTrOnG#$34134';
|
||||
var NEW_PASSWORD = 'oTHER@#$235';
|
||||
var IS_ADMIN = true;
|
||||
var DISPLAY_NAME = 'Nobody cares';
|
||||
var userObject = null;
|
||||
|
||||
function cleanupUsers(done) {
|
||||
@@ -30,7 +31,7 @@ function cleanupUsers(done) {
|
||||
}
|
||||
|
||||
function createUser(done) {
|
||||
user.create(USERNAME, PASSWORD, EMAIL, IS_ADMIN, null /* invitor */, false, function (error, result) {
|
||||
user.create(USERNAME, PASSWORD, EMAIL, DISPLAY_NAME, IS_ADMIN, null /* invitor */, false, function (error, result) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(result).to.be.ok();
|
||||
|
||||
@@ -74,8 +75,48 @@ describe('User', function () {
|
||||
before(cleanupUsers);
|
||||
after(cleanupUsers);
|
||||
|
||||
it('fails due to short password', function (done) {
|
||||
user.create(USERNAME, 'Fo$%23', EMAIL, DISPLAY_NAME, IS_ADMIN, null, true, function (error, result) {
|
||||
expect(error).to.be.ok();
|
||||
expect(result).to.not.be.ok();
|
||||
expect(error.reason).to.equal(UserError.BAD_PASSWORD);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails due to missing upper case password', function (done) {
|
||||
user.create(USERNAME, 'thisiseightch%$234arslong', EMAIL, DISPLAY_NAME, IS_ADMIN, null, true, function (error, result) {
|
||||
expect(error).to.be.ok();
|
||||
expect(result).to.not.be.ok();
|
||||
expect(error.reason).to.equal(UserError.BAD_PASSWORD);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails due to missing numerics in password', function (done) {
|
||||
user.create(USERNAME, 'foobaRASDF%', EMAIL, DISPLAY_NAME, IS_ADMIN, null, true, function (error, result) {
|
||||
expect(error).to.be.ok();
|
||||
expect(result).to.not.be.ok();
|
||||
expect(error.reason).to.equal(UserError.BAD_PASSWORD);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails due to missing special chars in password', function (done) {
|
||||
user.create(USERNAME, 'foobaRASDF23423', EMAIL, DISPLAY_NAME, IS_ADMIN, null, true, function (error, result) {
|
||||
expect(error).to.be.ok();
|
||||
expect(result).to.not.be.ok();
|
||||
expect(error.reason).to.equal(UserError.BAD_PASSWORD);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds and attempts to send invite', function (done) {
|
||||
user.create(USERNAME, PASSWORD, EMAIL, IS_ADMIN, null /* invitor */, true, function (error, result) {
|
||||
user.create(USERNAME, PASSWORD, EMAIL, DISPLAY_NAME, IS_ADMIN, null /* invitor */, true, function (error, result) {
|
||||
expect(error).not.to.be.ok();
|
||||
expect(result).to.be.ok();
|
||||
expect(result.username).to.equal(USERNAME);
|
||||
@@ -110,7 +151,7 @@ describe('User', function () {
|
||||
});
|
||||
|
||||
it('fails because user exists', function (done) {
|
||||
user.create(USERNAME, PASSWORD, EMAIL, IS_ADMIN, null /* invitor */, false, function (error, result) {
|
||||
user.create(USERNAME, PASSWORD, EMAIL, DISPLAY_NAME, IS_ADMIN, null /* invitor */, false, function (error, result) {
|
||||
expect(error).to.be.ok();
|
||||
expect(result).not.to.be.ok();
|
||||
expect(error.reason).to.equal(UserError.ALREADY_EXISTS);
|
||||
@@ -120,7 +161,7 @@ describe('User', function () {
|
||||
});
|
||||
|
||||
it('fails because password is empty', function (done) {
|
||||
user.create(USERNAME, '', EMAIL, IS_ADMIN, null /* invitor */, false, function (error, result) {
|
||||
user.create(USERNAME, '', EMAIL, DISPLAY_NAME, IS_ADMIN, null /* invitor */, false, function (error, result) {
|
||||
expect(error).to.be.ok();
|
||||
expect(result).not.to.be.ok();
|
||||
expect(error.reason).to.equal(UserError.BAD_PASSWORD);
|
||||
@@ -326,11 +367,11 @@ describe('User', function () {
|
||||
it('make second user admin succeeds', function (done) {
|
||||
var user1 = {
|
||||
username: 'seconduser',
|
||||
password: 'foobar',
|
||||
password: 'ASDFkljsf#$^%2354',
|
||||
email: 'some@thi.ng'
|
||||
};
|
||||
|
||||
user.create(user1.username, user1.password, user1.email, false, { username: USERNAME, email: EMAIL } /* invitor */, false, function (error, result) {
|
||||
user.create(user1.username, user1.password, user1.email, DISPLAY_NAME, false, { username: USERNAME, email: EMAIL } /* invitor */, false, function (error, result) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(result).to.be.ok();
|
||||
|
||||
@@ -368,11 +409,11 @@ describe('User', function () {
|
||||
it('succeeds for two admins', function (done) {
|
||||
var user1 = {
|
||||
username: 'seconduser',
|
||||
password: 'foobar',
|
||||
password: 'Adfasdkjf#$%43',
|
||||
email: 'some@thi.ng'
|
||||
};
|
||||
|
||||
user.create(user1.username, user1.password, user1.email, false, { username: USERNAME, email: EMAIL } /* invitor */, false, function (error, result) {
|
||||
user.create(user1.username, user1.password, user1.email, DISPLAY_NAME, false, { username: USERNAME, email: EMAIL } /* invitor */, false, function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.be.ok();
|
||||
|
||||
|
||||
+21
-17
@@ -31,6 +31,7 @@ var assert = require('assert'),
|
||||
userdb = require('./userdb.js'),
|
||||
tokendb = require('./tokendb.js'),
|
||||
clientdb = require('./clientdb.js'),
|
||||
validatePassword = require('./password.js').validate,
|
||||
util = require('util'),
|
||||
validator = require('validator'),
|
||||
_ = require('underscore');
|
||||
@@ -90,14 +91,6 @@ function validateUsername(username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function validatePassword(password) {
|
||||
assert.strictEqual(typeof password, 'string');
|
||||
|
||||
if (password.length < 5) return new UserError(UserError.BAD_PASSWORD, 'Password must be atleast 5 chars');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateEmail(email) {
|
||||
assert.strictEqual(typeof email, 'string');
|
||||
|
||||
@@ -114,10 +107,17 @@ function validateToken(token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function createUser(username, password, email, admin, invitor, sendInvite, callback) {
|
||||
function validateDisplayName(name) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function createUser(username, password, email, displayName, admin, invitor, sendInvite, callback) {
|
||||
assert.strictEqual(typeof username, 'string');
|
||||
assert.strictEqual(typeof password, 'string');
|
||||
assert.strictEqual(typeof email, 'string');
|
||||
assert.strictEqual(typeof displayName, 'string');
|
||||
assert.strictEqual(typeof admin, 'boolean');
|
||||
assert(invitor || admin);
|
||||
assert.strictEqual(typeof sendInvite, 'boolean');
|
||||
@@ -127,11 +127,14 @@ function createUser(username, password, email, admin, invitor, sendInvite, callb
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validatePassword(password);
|
||||
if (error) return callback(error);
|
||||
if (error) return callback(new UserError(UserError.BAD_PASSWORD, error.message));
|
||||
|
||||
error = validateEmail(email);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateDisplayName(displayName);
|
||||
if (error) return callback(error);
|
||||
|
||||
crypto.randomBytes(CRYPTO_SALT_SIZE, function (error, salt) {
|
||||
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
||||
|
||||
@@ -148,7 +151,8 @@ function createUser(username, password, email, admin, invitor, sendInvite, callb
|
||||
salt: salt.toString('hex'),
|
||||
createdAt: now,
|
||||
modifiedAt: now,
|
||||
resetToken: hat(256)
|
||||
resetToken: hat(256),
|
||||
displayName: displayName
|
||||
};
|
||||
|
||||
userdb.add(user.id, user, function (error) {
|
||||
@@ -157,8 +161,8 @@ function createUser(username, password, email, admin, invitor, sendInvite, callb
|
||||
|
||||
callback(null, user);
|
||||
|
||||
// do not send email for admins (this can only be the case for the owner)
|
||||
if (!admin) mailer.userAdded(user);
|
||||
// WARNING do not send email for admins (this can only be the case for the owner, the first user creation during activation)
|
||||
if (!admin) mailer.userAdded(user, sendInvite);
|
||||
if (sendInvite) mailer.sendInvite(user, invitor);
|
||||
});
|
||||
});
|
||||
@@ -334,7 +338,7 @@ function setPassword(userId, newPassword, callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var error = validatePassword(newPassword);
|
||||
if (error) return callback(error);
|
||||
if (error) return callback(new UserError(UserError.BAD_PASSWORD, error.message));
|
||||
|
||||
userdb.get(userId, function (error, user) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
||||
@@ -377,7 +381,7 @@ function changePassword(username, oldPassword, newPassword, callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var error = validatePassword(newPassword);
|
||||
if (error) return callback(error);
|
||||
if (error) return callback(new UserError(UserError.BAD_PASSWORD, error.message));
|
||||
|
||||
verify(username, oldPassword, function (error, user) {
|
||||
if (error) return callback(error);
|
||||
@@ -386,12 +390,12 @@ function changePassword(username, oldPassword, newPassword, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function createOwner(username, password, email, callback) {
|
||||
function createOwner(username, password, email, displayName, callback) {
|
||||
userdb.count(function (error, count) {
|
||||
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
||||
if (count !== 0) return callback(new UserError(UserError.ALREADY_EXISTS));
|
||||
|
||||
createUser(username, password, email, true /* admin */, null /* invitor */, false /* sendInvite */, callback);
|
||||
createUser(username, password, email, displayName, true /* admin */, null /* invitor */, false /* sendInvite */, callback);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -23,7 +23,7 @@ var assert = require('assert'),
|
||||
debug = require('debug')('box:userdb'),
|
||||
DatabaseError = require('./databaseerror');
|
||||
|
||||
var USERS_FIELDS = [ 'id', 'username', 'email', 'password', 'salt', 'createdAt', 'modifiedAt', 'admin', 'resetToken' ].join(',');
|
||||
var USERS_FIELDS = [ 'id', 'username', 'email', 'password', 'salt', 'createdAt', 'modifiedAt', 'admin', 'resetToken', 'displayName' ].join(',');
|
||||
|
||||
function get(userId, callback) {
|
||||
assert.strictEqual(typeof userId, 'string');
|
||||
@@ -113,10 +113,11 @@ function add(userId, user, callback) {
|
||||
assert.strictEqual(typeof user.createdAt, 'string');
|
||||
assert.strictEqual(typeof user.modifiedAt, 'string');
|
||||
assert.strictEqual(typeof user.resetToken, 'string');
|
||||
assert.strictEqual(typeof user.displayName, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var data = [ userId, user.username, user.password, user.email, user.admin, user.salt, user.createdAt, user.modifiedAt, user.resetToken ];
|
||||
database.query('INSERT INTO users (id, username, password, email, admin, salt, createdAt, modifiedAt, resetToken) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
var data = [ userId, user.username, user.password, user.email, user.admin, user.salt, user.createdAt, user.modifiedAt, user.resetToken, user.displayName ];
|
||||
database.query('INSERT INTO users (id, username, password, email, admin, salt, createdAt, modifiedAt, resetToken, displayName) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
data, function (error, result) {
|
||||
if (error && error.code === 'ER_DUP_ENTRY') return callback(new DatabaseError(DatabaseError.ALREADY_EXISTS, error));
|
||||
if (error || result.affectedRows !== 1) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
|
||||
@@ -199,4 +200,3 @@ function adminCount(callback) {
|
||||
return callback(null, result[0].total);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+12
-1
@@ -33,6 +33,7 @@
|
||||
app.controller('Controller', ['$scope', '$http', function ($scope, $http) {
|
||||
$scope.webServerOriginLink = '/';
|
||||
$scope.errorMessage = '';
|
||||
$scope.statusOk = false;
|
||||
|
||||
// try to fetch at least config.json to get appstore url
|
||||
$http.get('/config.json').success(function(data, status) {
|
||||
@@ -43,6 +44,15 @@
|
||||
else console.error(status, data);
|
||||
});
|
||||
|
||||
// try to fetch the cloudron status
|
||||
$http.get('/api/v1/cloudron/status').success(function(data, status) {
|
||||
if (status !== 200 || typeof data !== 'object') return console.error(status, data);
|
||||
$scope.statusOk = true;
|
||||
}).error(function (data, status) {
|
||||
console.error(status, data);
|
||||
$scope.statusOk = false;
|
||||
});
|
||||
|
||||
var search = window.location.search.slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||
|
||||
$scope.errorCode = search.errorCode || 0;
|
||||
@@ -62,7 +72,8 @@
|
||||
|
||||
<div ng-show="errorCode == 0">
|
||||
<h3> <i class="fa fa-frown-o fa-fw text-danger"></i> Something has gone wrong </h3>
|
||||
Please check your cloudron status <a href="{{ webServerOriginLink }}">here</a>.
|
||||
<span ng-hide="statusOk">Please check your cloudron status <a href="{{ webServerOriginLink }}">here</a>.</span>
|
||||
<span ng-show="statusOk">Please try again reloading the page <a href="/">here</a>.</span>
|
||||
</div>
|
||||
|
||||
<div ng-show="errorCode == 1">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<title> Cloudron </title>
|
||||
|
||||
<link href="/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||
<link id="favicon" href="/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" type="text/css" href="/3rdparty/slick.css"/>
|
||||
@@ -121,7 +121,7 @@
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand navbar-brand-icon" href="#/"><img ng-src="{{ cloudronAvatar }}" width="40" height="40"/></a>
|
||||
<a class="navbar-brand navbar-brand-icon" href="#/"><img ng-src="{{ client.avatar }}" width="40" height="40"/></a>
|
||||
<a class="navbar-brand" href="#/">Cloudron</a>
|
||||
</div>
|
||||
<!-- /.navbar-header -->
|
||||
|
||||
@@ -86,6 +86,9 @@ angular.module('Application').service('Client', ['$http', 'md5', 'Notification',
|
||||
this._clientId = '<%= oauth.clientId %>';
|
||||
this._clientSecret = '<%= oauth.clientSecret %>';
|
||||
this.apiOrigin = '<%= oauth.apiOrigin %>';
|
||||
this.avatar = '';
|
||||
|
||||
this.resetAvatar();
|
||||
|
||||
this.setToken(localStorage.token);
|
||||
}
|
||||
@@ -147,6 +150,15 @@ angular.module('Application').service('Client', ['$http', 'md5', 'Notification',
|
||||
callback(this._config);
|
||||
};
|
||||
|
||||
Client.prototype.resetAvatar = function () {
|
||||
this.avatar = this.apiOrigin + '/api/v1/cloudron/avatar?' + String(Math.random()).slice(2);
|
||||
|
||||
var favicon = $('#favicon');
|
||||
|
||||
console.log('rseet favicon', favicon)
|
||||
if (favicon) favicon.attr('href', this.avatar);
|
||||
};
|
||||
|
||||
Client.prototype.setUserInfo = function (userInfo) {
|
||||
// In order to keep the angular bindings alive, set each property individually
|
||||
this._userInfo.id = userInfo.id;
|
||||
@@ -413,8 +425,8 @@ angular.module('Application').service('Client', ['$http', 'md5', 'Notification',
|
||||
|
||||
Client.prototype.getAppIconUrls = function (app) {
|
||||
return {
|
||||
cloudron: this.apiOrigin + app.iconUrl + '?access_token=' + this._token,
|
||||
store: this._config.apiServerOrigin + '/api/v1/apps/' + app.appStoreId + '/versions/' + app.manifest.version + '/icon'
|
||||
cloudron: app.iconUrl ? (this.apiOrigin + app.iconUrl + '?access_token=' + this._token) : null,
|
||||
store: app.appStoreId ? (this._config.apiServerOrigin + '/api/v1/apps/' + app.appStoreId + '/versions/' + app.manifest.version + '/icon') : null
|
||||
};
|
||||
};
|
||||
|
||||
@@ -437,11 +449,12 @@ angular.module('Application').service('Client', ['$http', 'md5', 'Notification',
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.createAdmin = function (username, password, email, setupToken, callback) {
|
||||
Client.prototype.createAdmin = function (username, password, email, displayName, setupToken, callback) {
|
||||
var payload = {
|
||||
username: username,
|
||||
password: password,
|
||||
email: email
|
||||
email: email,
|
||||
displayName: displayName
|
||||
};
|
||||
|
||||
var that = this;
|
||||
@@ -540,10 +553,11 @@ angular.module('Application').service('Client', ['$http', 'md5', 'Notification',
|
||||
}).error(defaultErrorHandler(callback));
|
||||
};
|
||||
|
||||
Client.prototype.createUser = function (username, email, sendInvite, callback) {
|
||||
Client.prototype.createUser = function (username, email, displayName, sendInvite, callback) {
|
||||
var data = {
|
||||
username: username,
|
||||
email: email,
|
||||
displayName: displayName,
|
||||
invite: !!sendInvite
|
||||
};
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', []);
|
||||
|
||||
app.controller('ErrorController', ['$scope', '$http', function ($scope, $http) {
|
||||
$scope.webServerOriginLink = '/';
|
||||
$scope.errorMessage = '';
|
||||
|
||||
// try to fetch at least config.json to get appstore url
|
||||
$http.get('config.json').success(function(data, status) {
|
||||
if (status !== 200 || typeof data !== 'object') return console.error(status, data);
|
||||
$scope.webServerOriginLink = data.webServerOrigin + '/console.html';
|
||||
}).error(function (data, status) {
|
||||
if (status === 404) console.error('No config.json found');
|
||||
else console.error(status, data);
|
||||
});
|
||||
|
||||
var search = window.location.search.slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||
|
||||
$scope.errorCode = search.errorCode || 0;
|
||||
$scope.errorContext = search.errorContext || '';
|
||||
}]);
|
||||
@@ -4,8 +4,8 @@ angular.module('Application').controller('MainController', ['$scope', '$route',
|
||||
$scope.initialized = false;
|
||||
$scope.user = Client.getUserInfo();
|
||||
$scope.installedApps = Client.getInstalledApps();
|
||||
$scope.cloudronAvatar = Client.apiOrigin + '/api/v1/cloudron/avatar';
|
||||
$scope.config = {};
|
||||
$scope.client = Client;
|
||||
|
||||
$scope.update = {
|
||||
busy: false,
|
||||
|
||||
@@ -43,6 +43,7 @@ app.service('Wizard', [ function () {
|
||||
this.username = '';
|
||||
this.email = '';
|
||||
this.password = '';
|
||||
this.displayName = '';
|
||||
this.setupToken = null;
|
||||
this.provider = null;
|
||||
this.availableAvatars = [{
|
||||
@@ -221,7 +222,7 @@ app.controller('StepController', ['$scope', '$route', '$location', 'Wizard', fun
|
||||
app.controller('FinishController', ['$scope', '$location', 'Wizard', 'Client', function ($scope, $location, Wizard, Client) {
|
||||
$scope.wizard = Wizard;
|
||||
|
||||
Client.createAdmin(Wizard.username, Wizard.password, Wizard.email, Wizard.setupToken, function (error) {
|
||||
Client.createAdmin(Wizard.username, Wizard.password, Wizard.email, Wizard.displayName, Wizard.setupToken, function (error) {
|
||||
if (error) {
|
||||
console.error('Internal error', error);
|
||||
window.location.href = '/error.html';
|
||||
@@ -286,6 +287,7 @@ app.controller('SetupController', ['$scope', '$location', 'Client', 'Wizard', fu
|
||||
}
|
||||
|
||||
Wizard.email = search.email;
|
||||
Wizard.displayName = search.displayName;
|
||||
Wizard.requireEmail = !search.email;
|
||||
Wizard.provider = status.provider;
|
||||
|
||||
|
||||
@@ -38,9 +38,13 @@
|
||||
</div>
|
||||
<div class="form-group" ng-show="appConfigure.app.manifest.singleUser">
|
||||
<label class="control-label">User</label>
|
||||
<p>This is a single user application. Access is granted to <b>{{appConfigure.app.accessRestriction.users[0]}}</b>.</p>
|
||||
<p>
|
||||
This is a single user application.<br/><br/>
|
||||
Access is granted to <b>{{appConfigure.app.accessRestriction.users[0]}}</b>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<!-- Not sure if oauthproxy makes any sense with singleuser apps, it certainly looks strange in the UI, so we hide it for now -->
|
||||
<div class="form-group" ng-hide="appConfigure.app.manifest.singleUser">
|
||||
<label class="control-label" for="oauthProxy">Website Visibility</label>
|
||||
<select class="form-control" id="oauthProxy" ng-model="appConfigure.oauthProxy">
|
||||
<option value="">Visible to all</option>
|
||||
@@ -48,8 +52,6 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="hide">
|
||||
<label class="control-label" for="appConfigureCertificateInput" ng-show="config.isCustomDomain">Certificate (optional)</label>
|
||||
<div class="has-error text-center" ng-show="appConfigure.error.cert && config.isCustomDomain">{{ appConfigure.error.cert }}</div>
|
||||
@@ -243,7 +245,7 @@
|
||||
<div class="grid-item-top">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-center" style="padding-left: 5px; padding-right: 5px;">
|
||||
<img ng-src="{{app.iconUrl}}" fallback-icon="img/appicon_fallback.png" appstore-icon="{{ app.iconUrlStore }}" onerror="imageErrorHandler(this)" class="app-icon"/>
|
||||
<img ng-src="{{app.iconUrl || 'img/appicon_fallback.png'}}" fallback-icon="img/appicon_fallback.png" appstore-icon="{{ app.iconUrlStore }}" onerror="imageErrorHandler(this)" class="app-icon"/>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
@@ -272,12 +274,7 @@
|
||||
<i class="fa fa-wrench scale"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-xs-4 text-center">
|
||||
<!-- we check the version here because the box updater does not know when an app gets updated -->
|
||||
<a href="" ng-click="showUpdate(app)" class="ng-hide animateMe" ng-show="config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version && (app | installSuccess)">
|
||||
<i class="fa fa-arrow-up text-success scale"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-xs-4 text-center"></div>
|
||||
<div class="col-xs-4 text-right">
|
||||
<a href="" ng-click="showUninstall(app)">
|
||||
<i class="fa fa-remove scale"></i>
|
||||
@@ -300,7 +297,7 @@
|
||||
</div>
|
||||
|
||||
<!-- we check the version here because the box updater does not know when an app gets updated -->
|
||||
<div class="app-update-badge" ng-show="config.update.apps[app.id].manifest.version && (app | installSuccess)">
|
||||
<div class="app-update-badge" ng-show="config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version && (app | installSuccess)">
|
||||
<a href="" ng-click="showUpdate(app)" title="Update Available"><i class="fa fa-asterisk fa-2x text-success scale"></i></a>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<h4 class="modal-title">Change your Cloudron Avatar</h4>
|
||||
</div>
|
||||
<div class="modal-body settings-avatar-selector">
|
||||
<img id="previewAvatar" width="128" height="128" ng-src="{{avatarChange.avatar.data || avatarChange.avatar.url || avatar.data || avatar.url}}"/>
|
||||
<img id="previewAvatar" width="128" height="128" ng-src="{{avatarChange.avatar.data || avatarChange.avatar.url || client.avatar}}"/>
|
||||
<input type="file" id="avatarFileInput" style="display: none" accept="image/png"/>
|
||||
|
||||
<br/>
|
||||
@@ -94,7 +94,7 @@
|
||||
<div class="card" style="margin-bottom: 15px;" ng-show="user.admin">
|
||||
<div class="row">
|
||||
<div class="col-xs-4" style="min-width: 150px;">
|
||||
<div class="settings-avatar" ng-click="showChangeAvatar()" style="background-image: url('{{avatar.data || avatar.url}}');">
|
||||
<div class="settings-avatar" ng-click="showChangeAvatar()" style="background-image: url('{{ client.avatar }}');">
|
||||
<div class="overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('Application').controller('SettingsController', ['$scope', '$location', 'Client', function ($scope, $location, Client) {
|
||||
angular.module('Application').controller('SettingsController', ['$scope', '$location', '$rootScope', 'Client', function ($scope, $location, $rootScope, Client) {
|
||||
Client.onReady(function () { if (!Client.getUserInfo().admin) $location.path('/'); });
|
||||
|
||||
$scope.client = Client;
|
||||
$scope.user = Client.getUserInfo();
|
||||
$scope.config = Client.getConfig();
|
||||
$scope.dnsConfig = {};
|
||||
|
||||
$scope.lastBackup = null;
|
||||
$scope.backups = [];
|
||||
$scope.avatar = {
|
||||
data: null,
|
||||
url: null
|
||||
};
|
||||
|
||||
$scope.developerModeChange = {
|
||||
busy: false,
|
||||
@@ -188,11 +185,11 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
||||
if (error) {
|
||||
console.error('Unable to change developer mode.', error);
|
||||
} else {
|
||||
// Do soft reload, since the browser will not update the avatar URLs in the UI
|
||||
window.location.reload();
|
||||
Client.resetAvatar();
|
||||
}
|
||||
|
||||
$scope.avatarChange.busy = false;
|
||||
$('#avatarChangeModal').modal('hide');
|
||||
avatarChangeReset();
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -262,8 +259,6 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
||||
|
||||
Client.onReady(function () {
|
||||
fetchBackups();
|
||||
|
||||
$scope.avatar.url = ($scope.config.isCustomDomain ? '//my.' : '//my-') + $scope.config.fqdn + '/api/v1/cloudron/avatar';
|
||||
});
|
||||
|
||||
// setup all the dialog focus handling
|
||||
|
||||
@@ -28,6 +28,17 @@
|
||||
</div>
|
||||
<input type="email" class="form-control" ng-model="useradd.email" id="inputUserAddEmail" name="email" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': (useradd_form.displayName.$dirty && useradd_form.displayName.$invalid) || (!useradd_form.displayName.$dirty && useradd.error.displayName) }">
|
||||
<label class="control-label" for="inputUserAddDisplayName">Full Name</label>
|
||||
<div class="control-label" ng-show="(!useradd_form.displayName.$dirty && useradd.error.displayName) || (useradd_form.displayname.$dirty && useradd_form.displayName.$invalid) || (!useradd_form.displayName.$dirty && useradd.error.displayName)">
|
||||
<small ng-show="useradd_form.displayName.$error.required">A Name is required</small>
|
||||
<small ng-show="useradd_form.displayName.$error.displayName">This is not a valid Name</small>
|
||||
<small ng-show="!useradd_form.displayName.$dirty && useradd.error.displayName">{{ useradd.error.displayName }}</small>
|
||||
</div>
|
||||
<input type="text" class="form-control" ng-model="useradd.displayName" id="inputUserAddDisplayName" name="email" required>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="useradd.sendInvite" id="inputUserAddSendInvite"> Send invite
|
||||
@@ -138,4 +149,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,6 +21,7 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio
|
||||
error: {},
|
||||
username: '',
|
||||
email: '',
|
||||
displayName: '',
|
||||
sendInvite: true
|
||||
};
|
||||
|
||||
@@ -54,14 +55,16 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio
|
||||
$scope.useradd.alreadyTaken = false;
|
||||
$scope.useradd.error.username = null;
|
||||
$scope.useradd.error.email = null;
|
||||
$scope.useradd.error.displayName = null;
|
||||
|
||||
Client.createUser($scope.useradd.username, $scope.useradd.email, $scope.useradd.sendInvite, function (error) {
|
||||
Client.createUser($scope.useradd.username, $scope.useradd.email, $scope.useradd.displayName, $scope.useradd.sendInvite, function (error) {
|
||||
$scope.useradd.busy = false;
|
||||
|
||||
if (error && error.statusCode === 409) {
|
||||
$scope.useradd.error.username = 'Username or Email already taken';
|
||||
$scope.useradd_form.username.$setPristine();
|
||||
$scope.useradd_form.email.$setPristine();
|
||||
$scope.useradd_form.displayName.$setPristine();
|
||||
$('#inputUserAddUsername').focus();
|
||||
return;
|
||||
}
|
||||
@@ -76,6 +79,12 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio
|
||||
$scope.useradd.error.usernameAttempted = $scope.useradd.username;
|
||||
$scope.useradd_form.username.$setPristine();
|
||||
$('#inputUserAddUsername').focus();
|
||||
} else if (error.message.indexOf('displayName') !== -1) {
|
||||
$scope.useradd.error.displayName = 'Invalid Name';
|
||||
$scope.useradd.error.displayNameAttempted = $scope.useradd.displayName;
|
||||
$scope.useradd_form.displayName.$setPristine();
|
||||
$('#inputUserAddDisplayName').focus();
|
||||
|
||||
} else {
|
||||
console.error('Unable to create user.', error.statusCode, error.message);
|
||||
}
|
||||
@@ -86,6 +95,7 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio
|
||||
$scope.useradd.error = {};
|
||||
$scope.useradd.username = '';
|
||||
$scope.useradd.email = '';
|
||||
$scope.useradd.displayName = '';
|
||||
|
||||
$scope.useradd_form.$setUntouched();
|
||||
$scope.useradd_form.$setPristine();
|
||||
|
||||
Reference in New Issue
Block a user