Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a50409bdca | |||
| 60a722e6cc | |||
| 4d6cafa589 | |||
| 63e557430b | |||
| 04acb4423d | |||
| ea813acf4c | |||
| b1198dfdbf | |||
| 4342de3747 | |||
| ef8bc7e7e9 | |||
| e18e401f6b | |||
| ab998c47e8 | |||
| 9fb830b2e1 | |||
| 415c3f90a1 | |||
| 60c8ff7fb1 | |||
| 037816313c | |||
| 3d285d1ac6 | |||
| 135338786f | |||
| 661f1fce31 | |||
| 03664ef784 | |||
| d2111ef2b6 | |||
| e0df19c888 | |||
| 6a523606ca | |||
| b6cd40e63c | |||
| b421866bf5 | |||
| fe06075816 | |||
| 2b73eb90ec | |||
| 5555321cf5 | |||
| f087ebbee0 | |||
| d04f64d3d4 | |||
| 777a5a0929 | |||
| 6c297f890e |
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Generated
+2902
-4
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -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",
|
||||
|
||||
@@ -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
@@ -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'));
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 = [ ];
|
||||
|
||||
|
||||
@@ -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' }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 %>
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user