Files
cloudron-box/src/clients.js
T

326 lines
11 KiB
JavaScript
Raw Normal View History

'use strict';
exports = module.exports = {
add: add,
get: get,
del: del,
getAll: getAll,
2016-06-03 14:47:06 +02:00
getByAppIdAndType: getByAppIdAndType,
2018-04-28 17:55:17 -07:00
getTokensByUserId: getTokensByUserId,
delTokensByUserId: delTokensByUserId,
delByAppIdAndType: delByAppIdAndType,
2018-04-28 17:55:17 -07:00
addTokenByUserId: addTokenByUserId,
2016-06-07 15:34:27 +02:00
delToken: delToken,
2016-04-25 10:26:26 -07:00
2018-05-01 13:40:25 -07:00
issueDeveloperToken: issueDeveloperToken,
2017-01-09 15:25:45 -08:00
addDefaultClients: addDefaultClients,
removeTokenPrivateFields: removeTokenPrivateFields,
2016-06-03 15:05:00 +02:00
// client type enums
TYPE_EXTERNAL: 'external',
TYPE_BUILT_IN: 'built-in',
2016-06-03 15:05:00 +02:00
TYPE_OAUTH: 'addon-oauth',
2016-06-08 14:09:06 +02:00
TYPE_PROXY: 'addon-proxy'
};
2018-01-10 14:13:43 -08:00
var apps = require('./apps.js'),
2017-01-09 15:25:45 -08:00
assert = require('assert'),
async = require('async'),
2019-10-22 21:16:00 -07:00
BoxError = require('./boxerror.js'),
clientdb = require('./clientdb.js'),
2018-05-01 13:40:25 -07:00
constants = require('./constants.js'),
2017-01-09 15:25:45 -08:00
debug = require('debug')('box:clients'),
2018-05-01 13:40:25 -07:00
eventlog = require('./eventlog.js'),
2018-06-11 12:38:15 -07:00
hat = require('./hat.js'),
2018-04-26 15:54:53 -07:00
accesscontrol = require('./accesscontrol.js'),
2017-01-09 15:25:45 -08:00
tokendb = require('./tokendb.js'),
2018-05-01 13:40:25 -07:00
users = require('./users.js'),
uuid = require('uuid'),
_ = require('underscore');
2018-08-27 14:30:39 -07:00
function validateClientName(name) {
assert.strictEqual(typeof name, 'string');
2019-10-22 21:16:00 -07:00
if (name.length < 1) return new BoxError(BoxError.BAD_FIELD, 'name must be atleast 1 character', { field: 'name' });
if (name.length > 128) return new BoxError(BoxError.BAD_FIELD, 'name too long', { field: 'name' });
2019-10-22 21:16:00 -07:00
if (/[^a-zA-Z0-9-]/.test(name)) return new BoxError(BoxError.BAD_FIELD, 'name can only contain alphanumerals and dash', { field: 'name' });
return null;
}
2018-08-27 14:50:41 -07:00
function validateTokenName(name) {
assert.strictEqual(typeof name, 'string');
2019-10-22 21:16:00 -07:00
if (name.length > 64) return new BoxError(BoxError.BAD_FIELD, 'name too long', { field: 'name' });
2018-08-27 14:50:41 -07:00
return null;
}
2015-10-15 16:31:45 -07:00
function add(appId, type, redirectURI, scope, callback) {
2015-10-15 15:51:51 -07:00
assert.strictEqual(typeof appId, 'string');
2015-10-15 16:31:45 -07:00
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof redirectURI, 'string');
assert.strictEqual(typeof scope, 'string');
assert.strictEqual(typeof callback, 'function');
2018-06-17 22:29:17 -07:00
var error = accesscontrol.validateScopeString(scope);
2019-10-22 21:16:00 -07:00
if (error) return callback(error);
2018-08-27 14:30:39 -07:00
error = validateClientName(appId);
if (error) return callback(error);
var id = 'cid-' + uuid.v4();
2016-06-17 09:46:07 -05:00
var clientSecret = hat(8 * 128);
2015-10-15 16:31:45 -07:00
clientdb.add(id, appId, type, clientSecret, redirectURI, scope, function (error) {
if (error) return callback(error);
var client = {
id: id,
2015-10-15 15:51:51 -07:00
appId: appId,
2015-10-15 16:31:45 -07:00
type: type,
clientSecret: clientSecret,
redirectURI: redirectURI,
scope: scope
};
callback(null, client);
});
}
function get(id, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof callback, 'function');
clientdb.get(id, function (error, result) {
if (error) return callback(error);
2019-10-22 21:16:00 -07:00
callback(null, result);
});
}
function del(id, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof callback, 'function');
clientdb.del(id, function (error, result) {
if (error) return callback(error);
2019-10-22 21:16:00 -07:00
callback(null, result);
});
}
function getAll(callback) {
2016-06-07 11:17:29 +02:00
assert.strictEqual(typeof callback, 'function');
clientdb.getAll(function (error, results) {
if (error && error.reason === BoxError.NOT_FOUND) return callback(null, []);
if (error) return callback(error);
2016-06-07 11:17:29 +02:00
var tmp = [];
async.each(results, function (record, callback) {
if (record.type === exports.TYPE_EXTERNAL || record.type === exports.TYPE_BUILT_IN) {
2016-06-08 14:09:06 +02:00
// the appId in this case holds the name
2016-06-08 11:36:01 +02:00
record.name = record.appId;
tmp.push(record);
2016-06-07 11:17:29 +02:00
return callback(null);
}
2018-01-10 14:13:43 -08:00
apps.get(record.appId, function (error, result) {
2016-06-07 11:17:29 +02:00
if (error) {
2016-06-07 15:56:22 +02:00
console.error('Failed to get app details for oauth client', record.appId, error);
2016-06-07 11:17:29 +02:00
return callback(null); // ignore error so we continue listing clients
}
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';
2018-02-08 15:07:49 +01:00
record.domain = result.fqdn;
2016-06-07 11:17:29 +02:00
tmp.push(record);
callback(null);
});
}, function (error) {
if (error) return callback(error);
callback(null, tmp);
});
});
}
2016-06-03 14:47:06 +02:00
function getByAppIdAndType(appId, type, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof callback, 'function');
clientdb.getByAppIdAndType(appId, type, function (error, result) {
if (error) return callback(error);
2019-10-22 21:16:00 -07:00
2016-06-03 14:47:06 +02:00
callback(null, result);
});
}
2018-04-28 17:55:17 -07:00
function getTokensByUserId(clientId, userId, callback) {
assert.strictEqual(typeof clientId, 'string');
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof callback, 'function');
2016-06-03 12:58:39 +02:00
tokendb.getByIdentifierAndClientId(userId, clientId, function (error, result) {
if (error && error.reason === BoxError.NOT_FOUND) {
// this can mean either that there are no tokens or the clientId is actually unknown
2016-06-13 13:51:14 +02:00
get(clientId, function (error/*, result*/) {
if (error) return callback(error);
callback(null, []);
});
return;
}
if (error) return callback(error);
callback(null, result || []);
});
}
2018-04-28 17:55:17 -07:00
function delTokensByUserId(clientId, userId, callback) {
assert.strictEqual(typeof clientId, 'string');
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof callback, 'function');
2016-06-03 12:58:39 +02:00
tokendb.delByIdentifierAndClientId(userId, clientId, function (error) {
if (error && error.reason === BoxError.NOT_FOUND) {
// this can mean either that there are no tokens or the clientId is actually unknown
get(clientId, function (error/*, result*/) {
if (error) return callback(error);
callback(null);
});
return;
}
if (error) return callback(error);
callback(null);
});
}
function delByAppIdAndType(appId, type, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof callback, 'function');
2016-09-07 10:42:55 -07:00
getByAppIdAndType(appId, type, function (error, result) {
if (error) return callback(error);
2016-09-07 10:42:55 -07:00
tokendb.delByClientId(result.id, function (error) {
2019-10-24 20:31:45 -07:00
if (error && error.reason !== BoxError.NOT_FOUND) return callback(error);
2016-09-07 10:42:55 -07:00
clientdb.delByAppIdAndType(appId, type, function (error) {
if (error) return callback(error);
callback(null);
});
});
});
}
2018-08-27 14:50:41 -07:00
function addTokenByUserId(clientId, userId, expiresAt, options, callback) {
assert.strictEqual(typeof clientId, 'string');
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof expiresAt, 'number');
2018-08-27 14:50:41 -07:00
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
2018-08-27 14:50:41 -07:00
const name = options.name || '';
let error = validateTokenName(name);
if (error) return callback(error);
get(clientId, function (error, result) {
if (error) return callback(error);
2018-07-26 10:20:19 -07:00
users.get(userId, function (error, user) {
2019-10-24 15:12:58 -07:00
if (error) return callback(error);
2018-08-02 19:07:33 -07:00
accesscontrol.scopesForUser(user, function (error, userScopes) {
2019-10-24 15:12:58 -07:00
if (error) return callback(error);
2019-02-15 13:57:18 -08:00
const scope = accesscontrol.canonicalScopeString(result.scope);
const authorizedScopes = accesscontrol.intersectScopes(userScopes, scope.split(','));
const token = {
id: 'tid-' + uuid.v4(),
accessToken: hat(8 * 32),
identifier: userId,
clientId: result.id,
expires: expiresAt,
scope: authorizedScopes.join(','),
name: name
};
tokendb.add(token, function (error) {
if (error) return callback(error);
2018-08-02 19:07:33 -07:00
callback(null, {
2019-02-15 13:57:18 -08:00
accessToken: token.accessToken,
2018-08-02 19:07:33 -07:00
tokenScopes: authorizedScopes,
identifier: userId,
clientId: result.id,
expires: expiresAt
});
});
2016-06-07 14:47:47 +02:00
});
});
});
}
2016-06-07 15:34:27 +02:00
2018-05-01 13:40:25 -07:00
// this issues a cid-cli token that does not require a password in various routes
2018-08-27 14:50:41 -07:00
function issueDeveloperToken(userObject, auditSource, callback) {
2018-05-01 13:40:25 -07:00
assert.strictEqual(typeof userObject, 'object');
2018-08-27 14:50:41 -07:00
assert.strictEqual(typeof auditSource, 'object');
2018-05-01 13:40:25 -07:00
assert.strictEqual(typeof callback, 'function');
const expiresAt = Date.now() + constants.DEFAULT_TOKEN_EXPIRATION;
2018-08-27 14:50:41 -07:00
addTokenByUserId('cid-cli', userObject.id, expiresAt, {}, function (error, result) {
2018-05-01 13:40:25 -07:00
if (error) return callback(error);
2018-08-27 14:50:41 -07:00
eventlog.add(eventlog.ACTION_USER_LOGIN, auditSource, { userId: userObject.id, user: users.removePrivateFields(userObject) });
2018-05-01 13:40:25 -07:00
callback(null, result);
});
}
2016-06-07 15:34:27 +02:00
function delToken(clientId, tokenId, callback) {
assert.strictEqual(typeof clientId, 'string');
2016-06-07 15:38:30 +02:00
assert.strictEqual(typeof tokenId, 'string');
2016-06-07 15:34:27 +02:00
assert.strictEqual(typeof callback, 'function');
2017-01-09 15:25:45 -08:00
get(clientId, function (error) {
2016-06-07 15:34:27 +02:00
if (error) return callback(error);
tokendb.del(tokenId, function (error) {
if (error) return callback(error);
2016-06-07 15:34:27 +02:00
callback(null);
});
});
}
2017-01-09 15:25:45 -08:00
2018-01-26 22:52:54 -08:00
function addDefaultClients(origin, callback) {
assert.strictEqual(typeof origin, 'string');
2017-01-09 15:25:45 -08:00
assert.strictEqual(typeof callback, 'function');
debug('Adding default clients');
// The domain might have changed, therefor we have to update the record
// id, appId, type, clientSecret, redirectURI, scope
async.series([
2018-04-30 23:03:28 -07:00
clientdb.upsert.bind(null, 'cid-webadmin', 'Settings', 'built-in', 'secret-webadmin', origin, '*'),
clientdb.upsert.bind(null, 'cid-sdk', 'SDK', 'built-in', 'secret-sdk', origin, '*'),
clientdb.upsert.bind(null, 'cid-cli', 'Cloudron Tool', 'built-in', 'secret-cli', origin, '*')
2017-01-09 15:25:45 -08:00
], callback);
}
function removeTokenPrivateFields(token) {
2019-02-15 13:57:18 -08:00
return _.pick(token, 'id', 'identifier', 'clientId', 'scope', 'expires', 'name');
}