Compare commits

..

31 Commits

Author SHA1 Message Date
Johannes Zellner a50409bdca Also show errors above input fields for password reset 2017-03-20 16:50:31 +01:00
Johannes Zellner 60a722e6cc Remove superflous quote in html 2017-03-20 16:43:36 +01:00
Johannes Zellner 4d6cafa589 Show form errors on the top during user activation 2017-03-20 15:57:02 +01:00
Johannes Zellner 63e557430b ng-href takes a template string 2017-03-20 15:26:31 +01:00
Johannes Zellner 04acb4423d Add open registration rest api tests 2017-03-20 14:27:47 +01:00
Johannes Zellner ea813acf4c Add open registration default value and test 2017-03-20 14:27:39 +01:00
Johannes Zellner b1198dfdbf Add settingsdb tests for open registration 2017-03-20 14:22:11 +01:00
Johannes Zellner 4342de3747 Show error response on signup 2017-03-20 14:19:52 +01:00
Johannes Zellner ef8bc7e7e9 username must be null or non-empty string 2017-03-20 14:01:12 +01:00
Johannes Zellner e18e401f6b Improve signup form 2017-03-20 14:00:56 +01:00
Johannes Zellner ab998c47e8 Show user signup link if registration is open 2017-03-20 13:52:31 +01:00
Johannes Zellner 9fb830b2e1 add section to toggle open registration in settings view 2017-03-20 12:55:48 +01:00
Johannes Zellner 415c3f90a1 Always send an object with properties 2017-03-20 12:53:21 +01:00
Johannes Zellner 60c8ff7fb1 Add open_registration settings routes 2017-03-20 12:31:53 +01:00
Johannes Zellner 037816313c Remove newline 2017-03-20 12:29:15 +01:00
Johannes Zellner 3d285d1ac6 Better signup styling 2017-03-20 12:04:23 +01:00
Johannes Zellner 135338786f Protect user creation if open registration is not allowed 2017-03-20 12:00:58 +01:00
Johannes Zellner 661f1fce31 Angular uses double curly brackets 2017-03-20 11:58:01 +01:00
Johannes Zellner 03664ef784 Add open registration setting 2017-03-20 11:56:58 +01:00
Johannes Zellner d2111ef2b6 Add user signup ui 2017-03-20 11:52:11 +01:00
Johannes Zellner e0df19c888 Remove unused api wrapper for getAppLogStream() 2017-03-20 10:46:27 +01:00
Girish Ramakrishnan 6a523606ca Revert "Bump version to Nginx IPv6 support."
This reverts commit 5555321cf5.
This reverts commit f087ebbee0.
This reverts commit d04f64d3d4.

Part of #264
2017-03-19 14:25:30 -07:00
Girish Ramakrishnan b6cd40e63c Use latest manifestformat 2017-03-19 14:20:00 -07:00
Girish Ramakrishnan b421866bf5 Remove simpleauth
Simple Auth used to provide auth over HTTP. The original motivation
behind this was this was a simple way to add Cloudron Auth integration.
Back in the day, Cloudron Auth was a requirement for apps but this is
not the case anymore.

This is currently not used by any app and having this might encourage
people to make Cloudron specific un-upstreamable changes.
2017-03-19 01:31:38 -07:00
Girish Ramakrishnan fe06075816 more CHANGES 2017-03-17 13:49:47 -07:00
Girish Ramakrishnan 2b73eb90ec Merge branch 'ipv6' into 'master'
Add IPv6 Support

See merge request !3
2017-03-17 19:55:30 +00:00
Jonah Aragon 5555321cf5 Bump version to Nginx IPv6 support. 2017-03-17 19:43:54 +00:00
Jonah Aragon f087ebbee0 Add listen [::]:80; for IPv6 redirects. 2017-03-17 19:13:18 +00:00
Jonah Aragon d04f64d3d4 Add IPv6 listen directives 2017-03-17 19:12:25 +00:00
Girish Ramakrishnan 777a5a0929 Add 0.106.0 changes 2017-03-17 10:23:17 -07:00
Girish Ramakrishnan 6c297f890e Bump mail container 2017-03-17 10:23:17 -07:00
30 changed files with 3232 additions and 890 deletions
+5
View File
@@ -800,3 +800,8 @@
* Fix issue where pre-installed apps were not installed correctly
* Fix issue where new cloudrons could not be activated
[0.106.0]
* (mail) Fix email forwarding to external domains
* (mail) Set maximum email size to 25MB
* Remove SimpleAuth addon
+1 -5
View File
@@ -13,8 +13,7 @@ var appHealthMonitor = require('./src/apphealthmonitor.js'),
async = require('async'),
config = require('./src/config.js'),
ldap = require('./src/ldap.js'),
server = require('./src/server.js'),
simpleauth = require('./src/simpleauth.js');
server = require('./src/server.js');
console.log();
console.log('==========================================');
@@ -33,7 +32,6 @@ console.log();
async.series([
server.start,
ldap.start,
simpleauth.start,
appHealthMonitor.start,
], function (error) {
if (error) {
@@ -48,13 +46,11 @@ var NOOP_CALLBACK = function () { };
process.on('SIGINT', function () {
server.stop(NOOP_CALLBACK);
ldap.stop(NOOP_CALLBACK);
simpleauth.stop(NOOP_CALLBACK);
setTimeout(process.exit.bind(process), 3000);
});
process.on('SIGTERM', function () {
server.stop(NOOP_CALLBACK);
ldap.stop(NOOP_CALLBACK);
simpleauth.stop(NOOP_CALLBACK);
setTimeout(process.exit.bind(process), 3000);
});
-64
View File
@@ -318,67 +318,3 @@ cloudron exec
> swaks --server "${MAIL_SMTP_SERVER}" -p "${MAIL_SMTP_PORT}" --from "${MAIL_SMTP_USERNAME}@${MAIL_DOMAIN}" --body "Test mail from cloudron app at $(hostname -f)" --auth-user "${MAIL_SMTP_USERNAME}" --auth-password "${MAIL_SMTP_PASSWORD}"
```
## simpleauth
Simple Auth can be used for authenticating users with a HTTP request. This method of authentication is targeted
at applications, which for whatever reason can't use the ldap addon.
The response contains an `accessToken` which can then be used to access the [Cloudron API](/references/api.html).
Exported environment variables:
```
SIMPLE_AUTH_SERVER= # the simple auth HTTP server
SIMPLE_AUTH_PORT= # the simple auth server port
SIMPLE_AUTH_URL= # the simple auth server URL. same as "http://SIMPLE_AUTH_SERVER:SIMPLE_AUTH_PORT
SIMPLE_AUTH_CLIENT_ID # a client id for identifying the request originator with the auth server
```
This addons provides two REST APIs:
**POST /api/v1/login**
Request JSON body:
```
{
"username": "<username> or <email>",
"password": "<password>"
}
```
Response 200 with JSON body:
```
{
"accessToken": "<accessToken>",
"user": {
"id": "<userId>",
"username": "<username>",
"email": "<email>",
"admin": <admin boolean>,
"displayName": "<display name>"
}
}
```
**GET /api/v1/logout**
Request params:
```
?access_token=<accessToken>
```
Response 200 with JSON body:
```
{}
```
For debugging, [cloudron exec](https://www.npmjs.com/package/cloudron) can be used to run the `curl` tool within the context of the app:
```
cloudron exec
> USERNAME=<enter username>
> PASSWORD=<enter password>
> PAYLOAD="{\"clientId\":\"${SIMPLE_AUTH_CLIENT_ID}\", \"username\":\"${USERNAME}\", \"password\":\"${PASSWORD}\"}"
> curl -H "Content-Type: application/json" -X POST -d "${PAYLOAD}" "${SIMPLE_AUTH_ORIGIN}/api/v1/login"
```
+2 -2
View File
@@ -62,7 +62,7 @@ curl -H "Content-Type: application/json" -H "Authorization: Bearer <token>" http
## OAuth
OAuth authentication is meant to be used by apps. An app can get an OAuth token using the
[oauth](addons.html#oauth) or [simpleauth](addons.html#simpleauth) addon.
[oauth](addons.html#oauth) addon.
Tokens obtained via OAuth have a restricted scope wherein they can only access the user's profile.
This restriction is so that apps cannot make undesired changes to the user's Cloudron.
@@ -844,7 +844,7 @@ Response (200):
* user.remove
* user.update
`source` contains information on the originator of the action. For example, for user.login, this contains the IP address, the appId and the authType (ldap or simpleauth or oauth).
`source` contains information on the originator of the action. For example, for user.login, this contains the IP address, the appId and the authType (ldap or oauth).
`data` contains information on the event itself. For example, for user.login, this contains the userId that logged in. For app.install, it contains the manifest and location of the app that was installed.
-1
View File
@@ -29,7 +29,6 @@ Cloudron provides multiple authentication strategies.
* OAuth 2.0 provided by the [OAuth addon](/references/addons.html#oauth)
* LDAP provided by the [LDAP addon](/references/addons.html#ldap)
* Simple Auth provided by [Simple Auth addon](/references/addons.html#simpleauth)
# Choosing a strategy
+2902 -4
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -17,7 +17,7 @@
"aws-sdk": "^2.1.46",
"body-parser": "^1.13.1",
"checksum": "^0.1.1",
"cloudron-manifestformat": "^2.7.0",
"cloudron-manifestformat": "^2.8.0",
"connect-ensure-login": "^0.1.1",
"connect-lastmile": "^0.1.0",
"connect-timeout": "^1.5.0",
-51
View File
@@ -106,12 +106,6 @@ var KNOWN_ADDONS = {
teardown: NOOP,
backup: NOOP,
restore: NOOP
},
simpleauth: {
setup: setupSimpleAuth,
teardown: teardownSimpleAuth,
backup: NOOP,
restore: setupSimpleAuth
}
};
@@ -286,51 +280,6 @@ function teardownOauth(app, options, callback) {
});
}
function setupSimpleAuth(app, options, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
if (!app.sso) return callback(null);
var appId = app.id;
var scope = 'profile';
clients.delByAppIdAndType(app.id, clients.TYPE_SIMPLE_AUTH, function (error) { // remove existing creds
if (error && error.reason !== ClientsError.NOT_FOUND) return callback(error);
clients.add(appId, clients.TYPE_SIMPLE_AUTH, '', scope, function (error, result) {
if (error) return callback(error);
var env = [
'SIMPLE_AUTH_SERVER=172.18.0.1',
'SIMPLE_AUTH_PORT=' + config.get('simpleAuthPort'),
'SIMPLE_AUTH_URL=http://172.18.0.1:' + config.get('simpleAuthPort'), // obsolete, remove
'SIMPLE_AUTH_ORIGIN=http://172.18.0.1:' + config.get('simpleAuthPort'),
'SIMPLE_AUTH_CLIENT_ID=' + result.id
];
debugApp(app, 'Setting simple auth addon config to %j', env);
appdb.setAddonConfig(appId, 'simpleauth', env, callback);
});
});
}
function teardownSimpleAuth(app, options, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
debugApp(app, 'teardownSimpleAuth');
clients.delByAppIdAndType(app.id, clients.TYPE_SIMPLE_AUTH, function (error) {
if (error && error.reason !== ClientsError.NOT_FOUND) debug(error);
appdb.unsetAddonConfig(app.id, 'simpleauth', callback);
});
}
function setupEmail(app, options, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof options, 'object');
+1 -2
View File
@@ -152,7 +152,6 @@ function validatePortBindings(portBindings, tcpPorts) {
config.get('sysadminPort'), /* sysadmin app server (lo) */
config.get('smtpPort'), /* internal smtp port (lo) */
config.get('ldapPort'), /* ldap server (lo) */
config.get('simpleAuthPort'), /* simple auth server (lo) */
3306, /* mysql (lo) */
4190, /* managesieve */
8000 /* graphite (lo) */
@@ -528,7 +527,7 @@ function install(data, auditSource, callback) {
if ('sso' in data && !('optionalSso' in manifest)) return callback(new AppsError(AppsError.BAD_FIELD, 'sso can only be specified for apps with optionalSso'));
// if sso was unspecified, enable it by default if possible
if (sso === null) sso = !!manifest.addons['simpleauth'] || !!manifest.addons['ldap'] || !!manifest.addons['oauth'];
if (sso === null) sso = !!manifest.addons['ldap'] || !!manifest.addons['oauth'];
if (altDomain !== null && !validator.isFQDN(altDomain)) return callback(new AppsError(AppsError.BAD_FIELD, 'Invalid alt domain'));
-2
View File
@@ -32,7 +32,6 @@ exports = module.exports = {
TYPE_EXTERNAL: 'external',
TYPE_BUILT_IN: 'built-in',
TYPE_OAUTH: 'addon-oauth',
TYPE_SIMPLE_AUTH: 'addon-simpleauth',
TYPE_PROXY: 'addon-proxy'
};
@@ -192,7 +191,6 @@ function getAll(callback) {
if (record.type === exports.TYPE_PROXY) record.name = result.manifest.title + ' Website Proxy';
if (record.type === exports.TYPE_OAUTH) record.name = result.manifest.title + ' OAuth';
if (record.type === exports.TYPE_SIMPLE_AUTH) record.name = result.manifest.title + ' Simple Auth';
record.location = result.location;
-1
View File
@@ -84,7 +84,6 @@ function initConfig() {
data.smtpPort = 2525; // // this value comes from mail container
data.sysadminPort = 3001;
data.ldapPort = 3002;
data.simpleAuthPort = 3004;
data.provider = 'caas';
data.appBundle = [ ];
+1 -1
View File
@@ -17,7 +17,7 @@ exports = module.exports = {
'postgresql': { repo: 'cloudron/postgresql', tag: 'cloudron/postgresql:0.16.0' },
'mongodb': { repo: 'cloudron/mongodb', tag: 'cloudron/mongodb:0.12.0' },
'redis': { repo: 'cloudron/redis', tag: 'cloudron/redis:0.11.0' },
'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:0.30.2' },
'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:0.30.3' },
'graphite': { repo: 'cloudron/graphite', tag: 'cloudron/graphite:0.11.0' }
}
};
+48
View File
@@ -0,0 +1,48 @@
<% include header %>
<!-- tester -->
<script>
'use strict';
// very basic angular app
var app = angular.module('Application', []);
app.controller('Controller', ['$scope', function ($scope) {
$scope.success = <%= success %>;
$scope.error = '<%= error %>';
}]);
</script>
<div class="container" ng-app="Application" ng-controller="Controller" ng-cloak>
<div class="row">
<div class="col-md-12 text-center">
<br/>
<h4 ng-hide="success">Hello there, welcome to <%= cloudronName %>.</h4>
<h2 ng-hide="success">Sign up with your email address.</h2>
<h3 ng-show="success">You have received an email invitation to this Cloudron to finish the signup.</h3>
<br/><br/>
</div>
</div>
<div class="row">
<div class="col-md-6 col-md-offset-3" ng-show="!success">
<form action="/api/v1/session/account/create" method="post" name="createForm" autocomplete="off" role="form" novalidate>
<input type="password" style="display: none;">
<input type="hidden" name="_csrf" value="<%= csrf %>"/>
<div class="form-group" ng-class="{ 'has-error': (createForm.email.$dirty && createForm.email.$invalid) || (!createForm.email.$dirty && error) }">
<label class="control-label" for="inputEmail">Email</label>
<input type="email" class="form-control" id="inputEmail" ng-model="email" name="email" autofocus required>
<div class="control-label" ng-show="(createForm.email.$dirty && createForm.email.$invalid) || (!createForm.email.$dirty && error)">
<small ng-show="createForm.email.$dirty && createForm.email.$invalid">Must be a valid email address</small>
<small ng-show="!createForm.email.$dirty && error">{{ error }}</small>
</div>
</div>
<input class="btn btn-primary btn-outline pull-right" type="submit" value="Create" ng-disabled="createForm.$invalid"/>
</form>
</div>
</div>
</div>
<% include footer %>
+4 -4
View File
@@ -32,19 +32,19 @@ app.controller('Controller', ['$scope', function ($scope) {
<center><p class="has-error"><%= error %></p></center>
<% if (user && user.username) { %>
<div class="form-group"">
<div class="form-group">
<label class="control-label">Username</label>
<input type="text" class="form-control" ng-model="username" name="username" readonly required>
</div>
<% } else { %>
<div class="form-group" ng-class="{ 'has-error': (setupForm.username.$dirty && setupForm.username.$invalid) }">
<label class="control-label">Username</label>
<input type="text" class="form-control" ng-model="username" name="username" required autofocus>
<div class="control-label" ng-show="setupForm.username.$dirty && setupForm.username.$invalid">
<small ng-show="setupForm.username.$error.minlength">The username is too short</small>
<small ng-show="setupForm.username.$error.maxlength">The username is too long</small>
<small ng-show="setupForm.username.$dirty && setupForm.username.$invalid">Not a valid username</small>
</div>
<input type="text" class="form-control" ng-model="username" name="username" required autofocus>
</div>
<% } %>
@@ -55,18 +55,18 @@ app.controller('Controller', ['$scope', function ($scope) {
<div class="form-group" ng-class="{ 'has-error': (setupForm.password.$dirty && setupForm.password.$invalid) }">
<label class="control-label">New Password</label>
<input type="password" class="form-control" ng-model="password" name="password" ng-pattern="/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])(?!.*\s).{8,30}$/" required>
<div class="control-label" ng-show="setupForm.password.$dirty && setupForm.password.$invalid">
<small ng-show="setupForm.password.$dirty && setupForm.password.$invalid">Password must be 8-30 character with at least one uppercase, one numeric and one special character</small>
</div>
<input type="password" class="form-control" ng-model="password" name="password" ng-pattern="/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])(?!.*\s).{8,30}$/" required>
</div>
<div class="form-group" ng-class="{ 'has-error': (setupForm.passwordRepeat.$dirty && (password !== passwordRepeat)) }">
<label class="control-label">Repeat Password</label>
<input type="password" class="form-control" ng-model="passwordRepeat" name="passwordRepeat" required>
<div class="control-label" ng-show="setupForm.passwordRepeat.$dirty && (password !== passwordRepeat)">
<small ng-show="setupForm.passwordRepeat.$dirty && (password !== passwordRepeat)">Passwords don't match</small>
</div>
<input type="password" class="form-control" ng-model="passwordRepeat" name="passwordRepeat" required>
</div>
<input class="btn btn-primary btn-outline pull-right" type="submit" value="Create" ng-disabled="setupForm.$invalid || password !== passwordRepeat"/>
+2 -2
View File
@@ -26,17 +26,17 @@ app.controller('Controller', [function () {}]);
<div class="form-group" ng-class="{ 'has-error': resetForm.password.$dirty && resetForm.password.$invalid }">
<label class="control-label" for="inputPassword">New Password</label>
<input type="password" class="form-control" id="inputPassword" ng-model="password" name="password" ng-pattern="/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])(?!.*\s).{8,30}$/" autofocus required>
<div class="control-label" ng-show="resetForm.password.$dirty && resetForm.password.$invalid">
<small ng-show="resetForm.password.$dirty && resetForm.password.$invalid">Password must be 8-30 character with at least one uppercase, one numeric and one special character</small>
</div>
<input type="password" class="form-control" id="inputPassword" ng-model="password" name="password" ng-pattern="/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])(?!.*\s).{8,30}$/" autofocus required>
</div>
<div class="form-group" ng-class="{ 'has-error': resetForm.passwordRepeat.$dirty && (password !== passwordRepeat) }">
<label class="control-label" for="inputPasswordRepeat">Repeat Password</label>
<input type="password" class="form-control" id="inputPasswordRepeat" ng-model="passwordRepeat" name="passwordRepeat" required>
<div class="control-label" ng-show="resetForm.passwordRepeat.$dirty && (password !== passwordRepeat)">
<small ng-show="resetForm.passwordRepeat.$dirty && (password !== passwordRepeat)">Passwords don't match</small>
</div>
<input type="password" class="form-control" id="inputPasswordRepeat" ng-model="passwordRepeat" name="passwordRepeat" required>
</div>
<input class="btn btn-primary btn-outline pull-right" type="submit" value="Create" ng-disabled="resetForm.$invalid || password !== passwordRepeat"/>
</form>
+84 -3
View File
@@ -11,6 +11,7 @@ var appdb = require('../appdb'),
DatabaseError = require('../databaseerror'),
debug = require('debug')('box:routes/oauth2'),
eventlog = require('../eventlog.js'),
generatePassword = require('../password.js').generate,
hat = require('hat'),
HttpError = require('connect-lastmile').HttpError,
middleware = require('../middleware/index.js'),
@@ -233,7 +234,6 @@ function loginForm(req, res) {
switch (result.type) {
case clients.TYPE_BUILT_IN: return renderBuiltIn();
case clients.TYPE_EXTERNAL: return render(result.appId, '/api/v1/cloudron/avatar');
case clients.TYPE_SIMPLE_AUTH: return sendError(req, res, 'Unknown OAuth client');
default: break;
}
@@ -358,6 +358,87 @@ function accountSetup(req, res, next) {
});
}
// -> POST /api/v1/session/account/setup
function accountSetup(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
if (typeof req.body.resetToken !== 'string') return next(new HttpError(400, 'Missing resetToken'));
if (typeof req.body.password !== 'string') return next(new HttpError(400, 'Missing password'));
if (typeof req.body.username !== 'string') return next(new HttpError(400, 'Missing username'));
if (typeof req.body.displayName !== 'string') return next(new HttpError(400, 'Missing displayName'));
debug('accountSetup: with token %s.', req.body.resetToken);
user.getByResetToken(req.body.resetToken, function (error, userObject) {
if (error) return sendError(req, res, 'Invalid Reset Token');
var data = _.pick(req.body, 'username', 'displayName');
user.update(userObject.id, data, auditSource(req), function (error) {
if (error && error.reason === UserError.ALREADY_EXISTS) return renderAccountSetupSite(res, req, userObject, 'Username already exists');
if (error && error.reason === UserError.BAD_FIELD) return renderAccountSetupSite(res, req, userObject, error.message);
if (error && error.reason === UserError.NOT_FOUND) return renderAccountSetupSite(res, req, userObject, 'No such user');
if (error) return next(new HttpError(500, error));
userObject.username = req.body.username;
userObject.displayName = req.body.displayName;
// setPassword clears the resetToken
user.setPassword(userObject.id, req.body.password, function (error, result) {
if (error && error.reason === UserError.BAD_FIELD) return renderAccountSetupSite(res, req, userObject, error.message);
if (error) return next(new HttpError(500, error));
res.redirect(util.format('%s?accessToken=%s&expiresAt=%s', config.adminOrigin(), result.token, result.expiresAt));
});
});
});
}
function renderAccountCreateSite(res, req, error, success) {
renderTemplate(res, 'account_create', {
error: error,
success: !!success,
csrf: req.csrfToken(),
title: 'Account Create'
});
}
// -> GET /api/v1/session/account/create.html
function accountCreateSite(req, res, next) {
settings.getOpenRegistration(function (error, enabled) {
if (error) return next(new HttpError(500, error));
if (!enabled) return sendError(req, res, 'User creation is not allowed on this Cloudron');
renderAccountCreateSite(res, req, '', '');
});
}
// -> POST /api/v1/session/account/create
function accountCreate(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
if (typeof req.body.email !== 'string') return next(new HttpError(400, 'Missing email'));
debug('accountCreate: with email %s.', req.body.email);
settings.getOpenRegistration(function (error, enabled) {
if (error) return next(new HttpError(500, error));
if (!enabled) return sendError(req, res, 'User signup is not allowed on this Cloudron');
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null;
var auditSource = { ip: ip, username: req.body.email, userId: null };
user.create(null, generatePassword(), req.body.email, '', auditSource, { sendInvite: true }, function (error, result) {
if (error && error.reason === UserError.ALREADY_EXISTS) return renderAccountCreateSite(res, req, 'User with this email address already exists');
if (error) return sendError(req, res, 'Internal Error');
debug('accountCreate: success for email %s now with id %s', req.body.remail, result.id);
renderAccountCreateSite(res, req, '', true);
});
});
}
// -> GET /api/v1/session/password/reset.html
function passwordResetSite(req, res, next) {
if (!req.query.reset_token) return next(new HttpError(400, 'Missing reset_token'));
@@ -450,8 +531,6 @@ var authorization = [
if (type === clients.TYPE_EXTERNAL || type === clients.TYPE_BUILT_IN) {
eventlog.add(eventlog.ACTION_USER_LOGIN, auditSource(req, req.oauth2.client.appId), { userId: req.oauth2.user.id });
return next();
} else if (type === clients.TYPE_SIMPLE_AUTH) {
return sendError(req, res, 'Unknown OAuth client.');
}
appdb.get(req.oauth2.client.appId, function (error, appObject) {
@@ -558,6 +637,8 @@ exports = module.exports = {
passwordReset: passwordReset,
accountSetupSite: accountSetupSite,
accountSetup: accountSetup,
accountCreateSite: accountCreateSite,
accountCreate: accountCreate,
authorization: authorization,
token: token,
validateRequestedScopes: validateRequestedScopes,
+24
View File
@@ -27,6 +27,9 @@ exports = module.exports = {
getAppstoreConfig: getAppstoreConfig,
setAppstoreConfig: setAppstoreConfig,
getOpenRegistration: getOpenRegistration,
setOpenRegistration: setOpenRegistration,
setFallbackCertificate: setFallbackCertificate,
setAdminCertificate: setAdminCertificate
};
@@ -234,6 +237,27 @@ function setAppstoreConfig(req, res, next) {
});
}
function getOpenRegistration(req, res, next) {
settings.getOpenRegistration(function (error, enabled) {
if (error) return next(new HttpError(500, error));
next(new HttpSuccess(200, { enabled: enabled }));
});
}
function setOpenRegistration(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
if (typeof req.body.enabled !== 'boolean') return next(new HttpError(400, 'enabled is required'));
settings.setOpenRegistration(req.body.enabled, function (error) {
if (error && error.reason === SettingsError.BAD_FIELD) return next(new HttpError(400, error.message));
if (error) return next(new HttpError(500, error));
next(new HttpSuccess(200));
});
}
// default fallback cert
function setFallbackCertificate(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
+1 -4
View File
@@ -30,7 +30,6 @@ var appdb = require('../../appdb.js'),
safe = require('safetydance'),
server = require('../../server.js'),
settings = require('../../settings.js'),
simpleauth = require('../../simpleauth.js'),
superagent = require('superagent'),
taskmanager = require('../../taskmanager.js'),
tokendb = require('../../tokendb.js'),
@@ -42,7 +41,7 @@ var SERVER_URL = 'http://localhost:' + config.get('port');
// Test image information
var TEST_IMAGE_REPO = 'cloudron/test';
var TEST_IMAGE_TAG = '19.0.1';
var TEST_IMAGE_TAG = '20.0.0';
var TEST_IMAGE = TEST_IMAGE_REPO + ':' + TEST_IMAGE_TAG;
// var TEST_IMAGE_ID = child_process.execSync('docker inspect --format={{.Id}} ' + TEST_IMAGE).toString('utf8').trim();
@@ -174,7 +173,6 @@ function startBox(done) {
server.start.bind(server),
ldap.start,
simpleauth.start,
function (callback) {
var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
@@ -257,7 +255,6 @@ function stopBox(done) {
appdb._clear,
server.stop,
ldap.stop,
simpleauth.stop,
config._reset
], done);
}
-59
View File
@@ -273,16 +273,6 @@ describe('OAuth2', function () {
scope: 'profile'
};
// simple auth client
var CLIENT_8 = {
id: 'cid-client8',
appId: APP_2.id,
type: clients.TYPE_SIMPLE_AUTH,
clientSecret: 'secret8',
redirectURI: 'http://redirect8',
scope: 'profile'
};
// app with accessRestriction allowing group
var CLIENT_9 = {
id: 'cid-client9',
@@ -311,7 +301,6 @@ describe('OAuth2', function () {
clientdb.add.bind(null, CLIENT_5.id, CLIENT_5.appId, CLIENT_5.type, CLIENT_5.clientSecret, CLIENT_5.redirectURI, CLIENT_5.scope),
clientdb.add.bind(null, CLIENT_6.id, CLIENT_6.appId, CLIENT_6.type, CLIENT_6.clientSecret, CLIENT_6.redirectURI, CLIENT_6.scope),
clientdb.add.bind(null, CLIENT_7.id, CLIENT_7.appId, CLIENT_7.type, CLIENT_7.clientSecret, CLIENT_7.redirectURI, CLIENT_7.scope),
clientdb.add.bind(null, CLIENT_8.id, CLIENT_8.appId, CLIENT_8.type, CLIENT_8.clientSecret, CLIENT_8.redirectURI, CLIENT_8.scope),
clientdb.add.bind(null, CLIENT_9.id, CLIENT_9.appId, CLIENT_9.type, CLIENT_9.clientSecret, CLIENT_9.redirectURI, CLIENT_9.scope),
appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.portBindings, APP_0),
appdb.add.bind(null, APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.portBindings, APP_1),
@@ -557,24 +546,6 @@ describe('OAuth2', function () {
});
});
});
it('fails when using simple auth credentials', function (done) {
var url = SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=' + CLIENT_8.redirectURI + '&client_id=' + CLIENT_8.id + '&response_type=code';
request.get(url, { jar: true }, function (error, response, body) {
expect(error).to.not.be.ok();
expect(response.statusCode).to.eql(200);
expect(body).to.eql('<script>window.location.href = "/api/v1/session/login?returnTo=' + CLIENT_8.redirectURI + '";</script>');
request.get(SERVER_URL + '/api/v1/session/login?returnTo=' + CLIENT_8.redirectURI, { jar: true, followRedirect: false }, function (error, response, body) {
expect(error).to.not.be.ok();
expect(response.statusCode).to.eql(200);
expect(body.indexOf('<!-- error tester -->')).to.not.equal(-1);
expect(body.indexOf('Unknown OAuth client')).to.not.equal(-1);
done();
});
});
});
});
describe('loginForm submit', function () {
@@ -796,21 +767,6 @@ describe('OAuth2', function () {
});
});
it('fails for grant type code due to simple auth credentials', function (done) {
startAuthorizationFlow(CLIENT_7, 'code', function (jar) {
var url = SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=' + CLIENT_8.redirectURI + '&client_id=' + CLIENT_8.id + '&response_type=code';
request.get(url, { jar: jar, followRedirect: false }, function (error, response, body) {
expect(error).to.not.be.ok();
expect(response.statusCode).to.eql(200);
expect(body.indexOf('<!-- error tester -->')).to.not.equal(-1);
expect(body.indexOf('Unknown OAuth client.')).to.not.equal(-1);
done();
});
});
});
it('succeeds for grant type code with accessRestriction', function (done) {
startAuthorizationFlow(CLIENT_7, 'code', function (jar) {
var url = SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=' + CLIENT_7.redirectURI + '&client_id=' + CLIENT_7.id + '&response_type=code';
@@ -858,21 +814,6 @@ describe('OAuth2', function () {
});
});
it('fails for grant type token due to simple auth credentials', function (done) {
startAuthorizationFlow(CLIENT_7, 'token', function (jar) {
var url = SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=' + CLIENT_8.redirectURI + '&client_id=' + CLIENT_8.id + '&response_type=token';
request.get(url, { jar: jar, followRedirect: false }, function (error, response, body) {
expect(error).to.not.be.ok();
expect(response.statusCode).to.eql(200);
expect(body.indexOf('<!-- error tester -->')).to.not.equal(-1);
expect(body.indexOf('Unknown OAuth client.')).to.not.equal(-1);
done();
});
});
});
it('succeeds for grant type token', function (done) {
startAuthorizationFlow(CLIENT_7, 'token', function (jar) {
var url = SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=' + CLIENT_7.redirectURI + '&client_id=' + CLIENT_7.id + '&response_type=token';
+41
View File
@@ -770,4 +770,45 @@ describe('Settings API', function () {
});
});
});
describe('open_registration', function () {
it('get open_registration succeeds without being set', function (done) {
superagent.get(SERVER_URL + '/api/v1/settings/open_registration')
.query({ access_token: token })
.end(function (err, res) {
expect(res.statusCode).to.equal(200);
expect(res.body.enabled).to.equal(false);
done();
});
});
it('cannot set without data', function (done) {
superagent.post(SERVER_URL + '/api/v1/settings/open_registration')
.query({ access_token: token })
.end(function (err, res) {
expect(res.statusCode).to.equal(400);
done();
});
});
it('set succeeds', function (done) {
superagent.post(SERVER_URL + '/api/v1/settings/open_registration')
.query({ access_token: token })
.send({ enabled: true })
.end(function (err, res) {
expect(res.statusCode).to.equal(200);
done();
});
});
it('get succeeds', function (done) {
superagent.get(SERVER_URL + '/api/v1/settings/open_registration')
.query({ access_token: token })
.end(function (err, res) {
expect(res.statusCode).to.equal(200);
expect(res.body.enabled).to.equal(true);
done();
});
});
});
});
-512
View File
@@ -1,512 +0,0 @@
/* global it:false */
/* global describe:false */
/* global before:false */
/* global after:false */
'use strict';
var appdb = require('../../appdb.js'),
async = require('async'),
clientdb = require('../../clientdb.js'),
clients = require('../../clients.js'),
config = require('../../config.js'),
database = require('../../database.js'),
expect = require('expect.js'),
superagent = require('superagent'),
server = require('../../server.js'),
simpleauth = require('../../simpleauth.js'),
nock = require('nock'),
settings = require('../../settings.js');
describe('SimpleAuth API', function () {
var SERVER_URL = 'http://localhost:' + config.get('port');
var SIMPLE_AUTH_ORIGIN = 'http://localhost:' + config.get('simpleAuthPort');
var USERNAME = 'superaDMin', PASSWORD = 'Foobar?1337', EMAIL ='silly@ME.com';
var APP_0 = {
id: 'app0',
appStoreId: '',
manifest: { version: '0.1.0', addons: { } },
location: 'test0',
portBindings: {},
accessRestriction: { users: [ 'foobar', 'someone'] },
memoryLimit: 0,
altDomain: null
};
var APP_1 = {
id: 'app1',
appStoreId: '',
manifest: { version: '0.1.0', addons: { } },
location: 'test1',
portBindings: {},
accessRestriction: { users: [ 'foobar', 'someone' ] },
memoryLimit: 0,
altDomain: null
};
var APP_2 = {
id: 'app2',
appStoreId: '',
manifest: { version: '0.1.0', addons: { } },
location: 'test2',
portBindings: {},
accessRestriction: null,
memoryLimit: 0,
altDomain: null
};
var APP_3 = {
id: 'app3',
appStoreId: '',
manifest: { version: '0.1.0', addons: { } },
location: 'test3',
portBindings: {},
accessRestriction: { groups: [ 'someothergroup', 'admin', 'anothergroup' ] },
memoryLimit: 0,
altDomain: null
};
var CLIENT_0 = {
id: 'someclientid',
appId: 'someappid',
type: clients.TYPE_SIMPLE_AUTH,
clientSecret: 'someclientsecret',
redirectURI: '',
scope: 'user,profile'
};
var CLIENT_1 = {
id: 'someclientid1',
appId: APP_0.id,
type: clients.TYPE_SIMPLE_AUTH,
clientSecret: 'someclientsecret1',
redirectURI: '',
scope: 'user,profile'
};
var CLIENT_2 = {
id: 'someclientid2',
appId: APP_1.id,
type: clients.TYPE_SIMPLE_AUTH,
clientSecret: 'someclientsecret2',
redirectURI: '',
scope: 'user,profile'
};
var CLIENT_3 = {
id: 'someclientid3',
appId: APP_2.id,
type: clients.TYPE_SIMPLE_AUTH,
clientSecret: 'someclientsecret3',
redirectURI: '',
scope: 'user,profile'
};
var CLIENT_4 = {
id: 'someclientid4',
appId: APP_2.id,
type: clients.TYPE_OAUTH,
clientSecret: 'someclientsecret4',
redirectURI: '',
scope: 'user,profile'
};
var CLIENT_5 = {
id: 'someclientid5',
appId: APP_3.id,
type: clients.TYPE_SIMPLE_AUTH,
clientSecret: 'someclientsecret5',
redirectURI: '',
scope: 'user,profile'
};
before(function (done) {
async.series([
server.start.bind(server),
simpleauth.start.bind(simpleauth),
database._clear,
function createAdmin(callback) {
var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {});
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok();
expect(result.statusCode).to.eql(201);
expect(scope1.isDone()).to.be.ok();
expect(scope2.isDone()).to.be.ok();
superagent.get(SERVER_URL + '/api/v1/profile').query({ access_token: result.body.token}).end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.eql(200);
APP_1.accessRestriction.users.push(result.body.id);
callback();
});
});
},
clientdb.add.bind(null, CLIENT_0.id, CLIENT_0.appId, CLIENT_0.type, CLIENT_0.clientSecret, CLIENT_0.redirectURI, CLIENT_0.scope),
clientdb.add.bind(null, CLIENT_1.id, CLIENT_1.appId, CLIENT_1.type, CLIENT_1.clientSecret, CLIENT_1.redirectURI, CLIENT_1.scope),
clientdb.add.bind(null, CLIENT_2.id, CLIENT_2.appId, CLIENT_2.type, CLIENT_2.clientSecret, CLIENT_2.redirectURI, CLIENT_2.scope),
clientdb.add.bind(null, CLIENT_3.id, CLIENT_3.appId, CLIENT_3.type, CLIENT_3.clientSecret, CLIENT_3.redirectURI, CLIENT_3.scope),
clientdb.add.bind(null, CLIENT_4.id, CLIENT_4.appId, CLIENT_4.type, CLIENT_4.clientSecret, CLIENT_4.redirectURI, CLIENT_4.scope),
clientdb.add.bind(null, CLIENT_5.id, CLIENT_5.appId, CLIENT_5.type, CLIENT_5.clientSecret, CLIENT_5.redirectURI, CLIENT_5.scope),
appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.portBindings, APP_0),
appdb.add.bind(null, APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.portBindings, APP_1),
appdb.add.bind(null, APP_2.id, APP_2.appStoreId, APP_2.manifest, APP_2.location, APP_2.portBindings, APP_2),
appdb.add.bind(null, APP_3.id, APP_3.appStoreId, APP_3.manifest, APP_3.location, APP_3.portBindings, APP_3),
settings.setBackupConfig.bind(null, { provider: 'caas', token: 'BACKUP_TOKEN', bucket: 'Bucket', prefix: 'Prefix' })
], done);
});
after(function (done) {
async.series([
database._clear,
simpleauth.stop.bind(simpleauth),
server.stop.bind(server)
], done);
});
describe('login', function () {
it('cannot login without clientId', function (done) {
var body = {};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(result.statusCode).to.equal(400);
done();
});
});
it('cannot login without username', function (done) {
var body = {
clientId: 'someclientid'
};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(result.statusCode).to.equal(400);
done();
});
});
it('cannot login without password', function (done) {
var body = {
clientId: 'someclientid',
username: USERNAME
};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(result.statusCode).to.equal(400);
done();
});
});
it('cannot login with unkown clientId', function (done) {
var body = {
clientId: CLIENT_0.id+CLIENT_0.id,
username: USERNAME,
password: PASSWORD
};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('cannot login with unkown user', function (done) {
var body = {
clientId: CLIENT_0.id,
username: USERNAME+USERNAME,
password: PASSWORD
};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('cannot login with empty password', function (done) {
var body = {
clientId: CLIENT_0.id,
username: USERNAME,
password: ''
};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('cannot login with wrong password', function (done) {
var body = {
clientId: CLIENT_0.id,
username: USERNAME,
password: PASSWORD+PASSWORD
};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('fails for unkown app', function (done) {
var body = {
clientId: CLIENT_0.id,
username: USERNAME,
password: PASSWORD
};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('fails for disallowed app', function (done) {
var body = {
clientId: CLIENT_1.id,
username: USERNAME,
password: PASSWORD
};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('succeeds for allowed app', function (done) {
var body = {
clientId: CLIENT_2.id,
username: USERNAME,
password: PASSWORD
};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(200);
expect(result.body.accessToken).to.be.a('string');
expect(result.body.user).to.be.an('object');
expect(result.body.user.id).to.be.a('string');
expect(result.body.user.username).to.be.a('string');
expect(result.body.user.email).to.be.a('string');
expect(result.body.user.displayName).to.be.a('string');
expect(result.body.user.admin).to.be.a('boolean');
superagent.get(SERVER_URL + '/api/v1/profile')
.query({ access_token: result.body.accessToken })
.end(function (error, result) {
expect(error).to.be(null);
expect(result.body).to.be.an('object');
expect(result.body.username).to.eql(USERNAME.toLowerCase());
expect(result.body.email).to.eql(EMAIL.toLowerCase());
done();
});
});
});
it('succeeds for allowed app with email', function (done) {
var body = {
clientId: CLIENT_2.id,
username: EMAIL,
password: PASSWORD
};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(200);
expect(result.body.accessToken).to.be.a('string');
expect(result.body.user).to.be.an('object');
expect(result.body.user.id).to.be.a('string');
expect(result.body.user.username).to.be.a('string');
expect(result.body.user.email).to.be.a('string');
expect(result.body.user.displayName).to.be.a('string');
expect(result.body.user.admin).to.be.a('boolean');
superagent.get(SERVER_URL + '/api/v1/profile')
.query({ access_token: result.body.accessToken })
.end(function (error, result) {
expect(error).to.be(null);
expect(result.body).to.be.an('object');
expect(result.body.username).to.eql(USERNAME.toLowerCase());
expect(result.body.email).to.eql(EMAIL.toLowerCase());
done();
});
});
});
it('succeeds for app without accessRestriction', function (done) {
var body = {
clientId: CLIENT_3.id,
username: USERNAME,
password: PASSWORD
};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(200);
expect(result.body.accessToken).to.be.a('string');
expect(result.body.user).to.be.an('object');
expect(result.body.user.id).to.be.a('string');
expect(result.body.user.username).to.be.a('string');
expect(result.body.user.email).to.be.a('string');
expect(result.body.user.displayName).to.be.a('string');
expect(result.body.user.admin).to.be.a('boolean');
superagent.get(SERVER_URL + '/api/v1/profile')
.query({ access_token: result.body.accessToken })
.end(function (error, result) {
expect(error).to.be(null);
expect(result.body).to.be.an('object');
expect(result.body.username).to.eql(USERNAME.toLowerCase());
expect(result.body.email).to.eql(EMAIL.toLowerCase());
done();
});
});
});
it('succeeds for app with group accessRestriction', function (done) {
var body = {
clientId: CLIENT_5.id,
username: USERNAME,
password: PASSWORD
};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(200);
expect(result.body.accessToken).to.be.a('string');
expect(result.body.user).to.be.an('object');
expect(result.body.user.id).to.be.a('string');
expect(result.body.user.username).to.be.a('string');
expect(result.body.user.email).to.be.a('string');
expect(result.body.user.displayName).to.be.a('string');
expect(result.body.user.admin).to.be.a('boolean');
superagent.get(SERVER_URL + '/api/v1/profile')
.query({ access_token: result.body.accessToken })
.end(function (error, result) {
expect(error).to.be(null);
expect(result.body).to.be.an('object');
expect(result.body.username).to.eql(USERNAME.toLowerCase());
expect(result.body.email).to.eql(EMAIL.toLowerCase());
done();
});
});
});
it('fails for wrong client credentials', function (done) {
var body = {
clientId: CLIENT_4.id,
username: USERNAME,
password: PASSWORD
};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
});
describe('logout', function () {
var accessToken;
before(function (done) {
var body = {
clientId: CLIENT_3.id,
username: USERNAME,
password: PASSWORD
};
superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body)
.end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(200);
accessToken = result.body.accessToken;
done();
});
});
it('fails without access_token', function (done) {
superagent.get(SIMPLE_AUTH_ORIGIN + '/api/v1/logout')
.end(function (error, result) {
expect(result.statusCode).to.equal(400);
done();
});
});
it('fails with unkonwn access_token', function (done) {
superagent.get(SIMPLE_AUTH_ORIGIN + '/api/v1/logout')
.query({ access_token: accessToken+accessToken })
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('succeeds', function (done) {
superagent.get(SIMPLE_AUTH_ORIGIN + '/api/v1/logout')
.query({ access_token: accessToken })
.end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(200);
superagent.get(SERVER_URL + '/api/v1/profile')
.query({ access_token: accessToken })
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
});
});
});
+4 -1
View File
@@ -148,6 +148,8 @@ function initializeExpressSync() {
router.post('/api/v1/session/password/reset', csrf, routes.oauth2.passwordReset);
router.get ('/api/v1/session/account/setup.html', csrf, routes.oauth2.accountSetupSite);
router.post('/api/v1/session/account/setup', csrf, routes.oauth2.accountSetup);
router.get ('/api/v1/session/account/create.html', csrf, routes.oauth2.accountCreateSite);
router.post('/api/v1/session/account/create', csrf, routes.oauth2.accountCreate);
// oauth2 routes
router.get ('/api/v1/oauth/dialog/authorize', routes.oauth2.authorization);
@@ -194,7 +196,6 @@ function initializeExpressSync() {
router.get ('/api/v1/settings/backup_config', settingsScope, routes.user.requireAdmin, routes.settings.getBackupConfig);
router.post('/api/v1/settings/backup_config', settingsScope, routes.user.requireAdmin, routes.settings.setBackupConfig);
router.post('/api/v1/settings/certificate', settingsScope, routes.user.requireAdmin, routes.settings.setFallbackCertificate);
router.post('/api/v1/settings/admin_certificate', settingsScope, routes.user.requireAdmin, routes.settings.setAdminCertificate);
router.get ('/api/v1/settings/time_zone', settingsScope, routes.user.requireAdmin, routes.settings.getTimeZone);
router.post('/api/v1/settings/time_zone', settingsScope, routes.user.requireAdmin, routes.settings.setTimeZone);
@@ -202,6 +203,8 @@ function initializeExpressSync() {
router.post('/api/v1/settings/appstore_config', settingsScope, routes.user.requireAdmin, routes.settings.setAppstoreConfig);
router.get ('/api/v1/settings/mail_config', settingsScope, routes.user.requireAdmin, routes.settings.getMailConfig);
router.post('/api/v1/settings/mail_config', settingsScope, routes.user.requireAdmin, routes.settings.setMailConfig);
router.get ('/api/v1/settings/open_registration', settingsScope, routes.user.requireAdmin, routes.settings.getOpenRegistration);
router.post('/api/v1/settings/open_registration', settingsScope, routes.user.requireAdmin, routes.settings.setOpenRegistration);
// eventlog route
router.get('/api/v1/eventlog', settingsScope, routes.user.requireAdmin, routes.eventlog.get);
+30
View File
@@ -44,6 +44,9 @@ exports = module.exports = {
getMailConfig: getMailConfig,
setMailConfig: setMailConfig,
getOpenRegistration: getOpenRegistration,
setOpenRegistration: setOpenRegistration,
getDefaultSync: getDefaultSync,
getAll: getAll,
@@ -58,6 +61,7 @@ exports = module.exports = {
UPDATE_CONFIG_KEY: 'update_config',
APPSTORE_CONFIG_KEY: 'appstore_config',
MAIL_CONFIG_KEY: 'mail_config',
OPEN_REGISTRATION_KEY: 'open_registration',
events: null
};
@@ -102,6 +106,7 @@ var gDefaults = (function () {
result[exports.UPDATE_CONFIG_KEY] = { prerelease: false };
result[exports.APPSTORE_CONFIG_KEY] = {};
result[exports.MAIL_CONFIG_KEY] = { enabled: false };
result[exports.OPEN_REGISTRATION_KEY] = false;
return result;
})();
@@ -645,6 +650,31 @@ function setMailConfig(mailConfig, callback) {
});
}
function getOpenRegistration(callback) {
assert.strictEqual(typeof callback, 'function');
settingsdb.get(exports.OPEN_REGISTRATION_KEY, function (error, value) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, gDefaults[exports.OPEN_REGISTRATION_KEY]);
if (error) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error));
// settingsdb holds string values only
callback(null, !!value);
});
}
function setOpenRegistration(enabled, callback) {
assert.strictEqual(typeof enabled, 'boolean');
assert.strictEqual(typeof callback, 'function');
settingsdb.set(exports.OPEN_REGISTRATION_KEY, enabled ? 'enabled' : '', function (error) {
if (error) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error));
exports.events.emit(exports.OPEN_REGISTRATION_KEY, enabled);
return callback(null);
});
}
function getAppstoreConfig(callback) {
assert.strictEqual(typeof callback, 'function');
-163
View File
@@ -1,163 +0,0 @@
'use strict';
exports = module.exports = {
start: start,
stop: stop
};
var apps = require('./apps.js'),
AppsError = apps.AppsError,
assert = require('assert'),
clients = require('./clients.js'),
ClientsError = clients.ClientsError,
config = require('./config.js'),
constants = require('./constants.js'),
DatabaseError = require('./databaseerror.js'),
debug = require('debug')('box:src/simpleauth'),
eventlog = require('./eventlog.js'),
express = require('express'),
http = require('http'),
HttpError = require('connect-lastmile').HttpError,
HttpSuccess = require('connect-lastmile').HttpSuccess,
middleware = require('./middleware'),
tokendb = require('./tokendb.js'),
user = require('./user.js'),
UserError = require('./user.js').UserError;
var gHttpServer = null;
function loginLogic(clientId, username, password, callback) {
assert.strictEqual(typeof clientId, 'string');
assert.strictEqual(typeof username, 'string');
assert.strictEqual(typeof password, 'string');
assert.strictEqual(typeof callback, 'function');
debug('login: client %s and user %s', clientId, username);
clients.get(clientId, function (error, clientObject) {
if (error) return callback(error);
// only allow simple auth clients
if (clientObject.type !== clients.TYPE_SIMPLE_AUTH) return callback(new ClientsError(ClientsError.INVALID_CLIENT));
var authFunction = (username.indexOf('@') === -1) ? user.verifyWithUsername : user.verifyWithEmail;
authFunction(username, password, function (error, userObject) {
if (error) return callback(error);
apps.get(clientObject.appId, function (error, appObject) {
if (error) return callback(error);
apps.hasAccessTo(appObject, userObject, function (error, access) {
if (error) return callback(error);
if (!access) return callback(new AppsError(AppsError.ACCESS_DENIED));
var accessToken = tokendb.generateToken();
var expires = Date.now() + constants.DEFAULT_TOKEN_EXPIRATION;
tokendb.add(accessToken, userObject.id, clientId, expires, clientObject.scope, function (error) {
if (error) return callback(error);
debug('login: new access token for client %s and user %s: %s', clientId, username, accessToken);
callback(null, { accessToken: accessToken, user: userObject });
});
});
});
});
});
}
function logoutLogic(accessToken, callback) {
assert.strictEqual(typeof accessToken, 'string');
assert.strictEqual(typeof callback, 'function');
debug('logout: %s', accessToken);
tokendb.del(accessToken, function (error) {
if (error) return callback(error);
callback(null);
});
}
function login(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
if (typeof req.body.clientId !== 'string') return next(new HttpError(400, 'clientId is required'));
if (typeof req.body.username !== 'string') return next(new HttpError(400, 'username is required'));
if (typeof req.body.password !== 'string') return next(new HttpError(400, 'password is required'));
loginLogic(req.body.clientId, req.body.username, req.body.password, function (error, result) {
if (error && error.reason === ClientsError.NOT_FOUND) return next(new HttpError(401, 'Unknown client'));
if (error && error.reason === ClientsError.INVALID_CLIENT) return next(new HttpError(401, 'Unknown client'));
if (error && error.reason === UserError.NOT_FOUND) return next(new HttpError(401, 'Forbidden'));
if (error && error.reason === AppsError.NOT_FOUND) return next(new HttpError(401, 'Unknown app'));
if (error && error.reason === UserError.WRONG_PASSWORD) return next(new HttpError(401, 'Forbidden'));
if (error && error.reason === AppsError.ACCESS_DENIED) return next(new HttpError(401, 'Forbidden'));
if (error) return next(new HttpError(500, error));
eventlog.add(eventlog.ACTION_USER_LOGIN, { authType: 'simpleauth', clientId: req.body.clientId }, { userId: result.user.id });
var tmp = {
accessToken: result.accessToken,
user: {
id: result.user.id,
username: result.user.username,
email: result.user.email,
admin: !!result.user.admin,
displayName: result.user.displayName
}
};
next(new HttpSuccess(200, tmp));
});
}
function logout(req, res, next) {
assert.strictEqual(typeof req.query, 'object');
if (typeof req.query.access_token !== 'string') return next(new HttpError(400, 'access_token in query required'));
logoutLogic(req.query.access_token, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return next(new HttpError(401, 'Forbidden'));
if (error) return next(new HttpError(500, error));
next(new HttpSuccess(200, {}));
});
}
function initializeExpressSync() {
var app = express();
var httpServer = http.createServer(app);
httpServer.on('error', console.error);
var json = middleware.json({ strict: true, limit: '100kb' });
var router = new express.Router();
// basic auth
router.post('/api/v1/login', login);
router.get ('/api/v1/logout', logout);
if (process.env.BOX_ENV !== 'test') app.use(middleware.morgan('SimpleAuth :method :url :status :response-time ms - :res[content-length]', { immediate: false }));
app
.use(middleware.timeout(10000))
.use(json)
.use(router)
.use(middleware.lastMile());
return httpServer;
}
function start(callback) {
assert.strictEqual(typeof callback, 'function');
gHttpServer = initializeExpressSync();
gHttpServer.listen(config.get('simpleAuthPort'), '0.0.0.0', callback);
}
function stop(callback) {
assert.strictEqual(typeof callback, 'function');
if (gHttpServer) gHttpServer.close(callback);
}
+23
View File
@@ -188,5 +188,28 @@ describe('Settings', function () {
done();
});
});
it('can get open registration default value', function (done) {
settings.getOpenRegistration(function (error, enabled) {
expect(error).to.be(null);
expect(enabled).to.equal(false);
done();
});
});
it('can set open registration', function (done) {
settings.setOpenRegistration(true, function (error) {
expect(error).to.be(null);
done();
});
});
it('can get open registration', function (done) {
settings.getOpenRegistration(function (error, enabled) {
expect(error).to.be(null);
expect(enabled).to.equal(true);
done();
});
});
});
});
+14 -6
View File
@@ -1,7 +1,6 @@
'use strict';
/* global angular */
/* global EventSource */
angular.module('Application').service('Client', ['$http', 'md5', 'Notification', function ($http, md5, Notification) {
var client = null;
@@ -440,6 +439,20 @@ angular.module('Application').service('Client', ['$http', 'md5', 'Notification',
}).error(defaultErrorHandler(callback));
};
Client.prototype.setOpenRegistration = function (enabled, callback) {
post('/api/v1/settings/open_registration', { enabled: enabled }).success(function(data, status) {
if (status !== 200) return callback(new ClientError(status, data));
callback(null);
}).error(defaultErrorHandler(callback));
};
Client.prototype.getOpenRegistration = function (callback) {
get('/api/v1/settings/open_registration').success(function(data, status) {
if (status !== 200) return callback(new ClientError(status, data));
callback(null, data.enabled);
}).error(defaultErrorHandler(callback));
};
Client.prototype.getEmailStatus = function (callback) {
get('/api/v1/settings/email_status').success(function(data, status) {
if (status !== 200) return callback(new ClientError(status, data));
@@ -613,11 +626,6 @@ angular.module('Application').service('Client', ['$http', 'md5', 'Notification',
else return callback(new Error('App not found'));
};
Client.prototype.getAppLogStream = function (appId) {
var source = new EventSource(client.apiOrigin + '/api/v1/apps/' + appId + '/logstream');
return source;
};
Client.prototype.getAppIconUrls = function (app) {
return {
cloudron: app.iconUrl ? (this.apiOrigin + app.iconUrl + '?access_token=' + token) : null,
+1 -1
View File
@@ -188,7 +188,7 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
$scope.appConfigure.accessRestriction = app.accessRestriction || { users: [], groups: [] };
$scope.appConfigure.memoryLimit = app.memoryLimit || app.manifest.memoryLimit || (256 * 1024 * 1024);
$scope.appConfigure.xFrameOptions = app.xFrameOptions.indexOf('ALLOW-FROM') === 0 ? app.xFrameOptions.split(' ')[1] : '';
$scope.appConfigure.customAuth = !(app.manifest.addons['simpleauth'] || app.manifest.addons['ldap'] || app.manifest.addons['oauth']);
$scope.appConfigure.customAuth = !(app.manifest.addons['ldap'] || app.manifest.addons['oauth']);
// create ticks starting from manifest memory limit
$scope.appConfigure.memoryTicks = [
+1 -1
View File
@@ -118,7 +118,7 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
var manifest = app.manifest;
$scope.appInstall.optionalSso = !!manifest.optionalSso;
$scope.appInstall.customAuth = !(manifest.addons['simpleauth'] || manifest.addons['ldap'] || manifest.addons['oauth']);
$scope.appInstall.customAuth = !(manifest.addons['ldap'] || manifest.addons['oauth']);
$scope.appInstall.accessRestrictionOption = 'any';
// set default ports
+22
View File
@@ -450,5 +450,27 @@
</div>
</div>
<div class="section-header">
<div class="text-left">
<h3>User Registration</h3>
</div>
</div>
<div class="card" style="margin-bottom: 15px;">
<div class="row">
<div class="col-md-12">
<p>
By default the Cloudron only allows admins to invite other users.
You may enable user registration, allowing users to signup without such an invite.
</p>
<p ng-show="openRegistrationEnabled">
The user signup link is: <a ng-href="{{ signupLink }}" target="_blank">{{ signupLink }}</a>
</p>
<br/>
<button class="btn btn-primary pull-right" ng-click="toggleOpenRegistration()">{{ openRegistrationEnabled ? 'Disable user registration' : 'Enable user registration' }}</button>
</div>
</div>
</div>
<!-- Offset the footer -->
<br/><br/>
+20
View File
@@ -6,6 +6,8 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
$scope.client = Client;
$scope.user = Client.getUserInfo();
$scope.config = Client.getConfig();
$scope.openRegistrationEnabled = false;
$scope.signupLink = '';
$scope.backupConfig = {};
$scope.dnsConfig = {};
$scope.outboundPort25 = {};
@@ -507,6 +509,16 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
});
}
function getOpenRegistration() {
Client.getOpenRegistration(function (error, enabled) {
if (error) return console.error(error);
$scope.openRegistrationEnabled = enabled;
$scope.signupLink = window.location.origin + '/api/v1/session/account/create.html';
});
}
function showExpectedDnsRecords(callback) {
callback = callback || function (error) { if (error) console.error(error); };
@@ -613,12 +625,20 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
}
};
$scope.toggleOpenRegistration = function () {
Client.setOpenRegistration(!$scope.openRegistrationEnabled, function (error) {
if (error) return console.error(error);
$scope.openRegistrationEnabled = !$scope.openRegistrationEnabled;
});
};
Client.onReady(function () {
fetchBackups();
getMailConfig();
getBackupConfig();
getDnsConfig();
getAutoupdatePattern();
getOpenRegistration();
if ($scope.config.provider === 'caas') {
getPlans();