diff --git a/src/accesscontrol.js b/src/accesscontrol.js index 86a8dae95..428a9e778 100644 --- a/src/accesscontrol.js +++ b/src/accesscontrol.js @@ -27,6 +27,7 @@ exports = module.exports = { }; var assert = require('assert'), + BoxError = require('./boxerror.js'), DatabaseError = require('./databaseerror.js'), debug = require('debug')('box:accesscontrol'), tokendb = require('./tokendb.js'), @@ -78,13 +79,12 @@ function intersectScopes(allowedScopes, wantedScopes) { function validateScopeString(scope) { assert.strictEqual(typeof scope, 'string'); - if (scope === '') return new Error('Empty scope not allowed'); + if (scope === '') return new BoxError(BoxError.BAD_FIELD, 'Empty scope not allowed', { field: 'scope' }); // NOTE: this function intentionally does not allow '*'. This is only allowed in the db to allow // us not write a migration script every time we add a new scope var allValid = scope.split(',').every(function (s) { return exports.VALID_SCOPES.indexOf(s.split(':')[0]) !== -1; }); - if (!allValid) return new Error('Invalid scope. Available scopes are ' + exports.VALID_SCOPES.join(', ')); - + if (!allValid) return new BoxError(BoxError.BAD_FIELD, 'Invalid scope. Available scopes are ' + exports.VALID_SCOPES.join(', '), { field: 'scope' }); return null; } diff --git a/src/addons.js b/src/addons.js index 5085541cb..94f715903 100644 --- a/src/addons.js +++ b/src/addons.js @@ -39,7 +39,6 @@ var accesscontrol = require('./accesscontrol.js'), BoxError = require('./boxerror.js'), clients = require('./clients.js'), constants = require('./constants.js'), - ClientsError = clients.ClientsError, crypto = require('crypto'), DatabaseError = require('./databaseerror.js'), debug = require('debug')('box:addons'), @@ -816,7 +815,7 @@ function setupOauth(app, options, callback) { var scope = accesscontrol.SCOPE_PROFILE; clients.delByAppIdAndType(appId, clients.TYPE_OAUTH, function (error) { // remove existing creds - if (error && error.reason !== ClientsError.NOT_FOUND) return callback(error); + if (error && error.reason !== BoxError.NOT_FOUND) return callback(error); clients.add(appId, clients.TYPE_OAUTH, redirectURI, scope, function (error, result) { if (error) return callback(error); @@ -844,7 +843,7 @@ function teardownOauth(app, options, callback) { debugApp(app, 'teardownOauth'); clients.delByAppIdAndType(app.id, clients.TYPE_OAUTH, function (error) { - if (error && error.reason !== ClientsError.NOT_FOUND) debug(error); + if (error && error.reason !== BoxError.NOT_FOUND) debug(error); appdb.unsetAddonConfig(app.id, 'oauth', callback); }); diff --git a/src/clients.js b/src/clients.js index 499cf1189..03922d0f2 100644 --- a/src/clients.js +++ b/src/clients.js @@ -1,8 +1,6 @@ 'use strict'; exports = module.exports = { - ClientsError: ClientsError, - add: add, get: get, del: del, @@ -30,6 +28,7 @@ exports = module.exports = { var apps = require('./apps.js'), assert = require('assert'), async = require('async'), + BoxError = require('./boxerror.js'), clientdb = require('./clientdb.js'), constants = require('./constants.js'), DatabaseError = require('./databaseerror.js'), @@ -40,44 +39,16 @@ var apps = require('./apps.js'), tokendb = require('./tokendb.js'), users = require('./users.js'), UsersError = users.UsersError, - util = require('util'), uuid = require('uuid'), _ = require('underscore'); -function ClientsError(reason, errorOrMessage) { - assert.strictEqual(typeof reason, 'string'); - assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined'); - - Error.call(this); - Error.captureStackTrace(this, this.constructor); - - this.name = this.constructor.name; - this.reason = reason; - if (typeof errorOrMessage === 'undefined') { - this.message = reason; - } else if (typeof errorOrMessage === 'string') { - this.message = errorOrMessage; - } else { - this.message = 'Internal error'; - this.nestedError = errorOrMessage; - } -} -util.inherits(ClientsError, Error); -ClientsError.INVALID_SCOPE = 'Invalid scope'; -ClientsError.INVALID_CLIENT = 'Invalid client'; -ClientsError.INVALID_TOKEN = 'Invalid token'; -ClientsError.BAD_FIELD = 'Bad field'; -ClientsError.NOT_FOUND = 'Not found'; -ClientsError.INTERNAL_ERROR = 'Internal Error'; -ClientsError.NOT_ALLOWED = 'Not allowed to remove this client'; - function validateClientName(name) { assert.strictEqual(typeof name, 'string'); - if (name.length < 1) return new ClientsError(ClientsError.BAD_FIELD, 'Name must be atleast 1 character'); - if (name.length > 128) return new ClientsError(ClientsError.BAD_FIELD, 'Name too long'); + 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' }); - if (/[^a-zA-Z0-9-]/.test(name)) return new ClientsError(ClientsError.BAD_FIELD, 'Username can only contain alphanumerals and dash'); + if (/[^a-zA-Z0-9-]/.test(name)) return new BoxError(BoxError.BAD_FIELD, 'name can only contain alphanumerals and dash', { field: 'name' }); return null; } @@ -85,7 +56,7 @@ function validateClientName(name) { function validateTokenName(name) { assert.strictEqual(typeof name, 'string'); - if (name.length > 64) return new ClientsError(ClientsError.BAD_FIELD, 'Name too long'); + if (name.length > 64) return new BoxError(BoxError.BAD_FIELD, 'name too long', { field: 'name' }); return null; } @@ -98,7 +69,7 @@ function add(appId, type, redirectURI, scope, callback) { assert.strictEqual(typeof callback, 'function'); var error = accesscontrol.validateScopeString(scope); - if (error) return callback(new ClientsError(ClientsError.INVALID_SCOPE, error.message)); + if (error) return callback(error); error = validateClientName(appId); if (error) return callback(error); @@ -127,8 +98,9 @@ function get(id, callback) { assert.strictEqual(typeof callback, 'function'); clientdb.get(id, function (error, result) { - if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new ClientsError(ClientsError.NOT_FOUND, 'No such client')); - if (error) return callback(error); + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new BoxError(BoxError.NOT_FOUND, 'No such client')); + if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); + callback(null, result); }); } @@ -138,8 +110,9 @@ function del(id, callback) { assert.strictEqual(typeof callback, 'function'); clientdb.del(id, function (error, result) { - if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new ClientsError(ClientsError.NOT_FOUND, 'No such client')); - if (error) return callback(error); + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new BoxError(BoxError.NOT_FOUND, 'No such client')); + if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); + callback(null, result); }); } @@ -149,7 +122,7 @@ function getAll(callback) { clientdb.getAll(function (error, results) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, []); - if (error) return callback(error); + if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); var tmp = []; async.each(results, function (record, callback) { @@ -190,8 +163,9 @@ function getByAppIdAndType(appId, type, callback) { assert.strictEqual(typeof callback, 'function'); clientdb.getByAppIdAndType(appId, type, function (error, result) { - if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new ClientsError(ClientsError.NOT_FOUND, 'No such client')); - if (error) return callback(error); + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new BoxError(BoxError.NOT_FOUND, 'No such client')); + if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); + callback(null, result); }); } @@ -243,10 +217,10 @@ function delByAppIdAndType(appId, type, callback) { if (error) return callback(error); tokendb.delByClientId(result.id, function (error) { - if (error && error.reason !== DatabaseError.NOT_FOUND) return callback(new ClientsError(ClientsError.INTERNAL_ERROR, error)); + if (error && error.reason !== DatabaseError.NOT_FOUND) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); clientdb.delByAppIdAndType(appId, type, function (error) { - if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new ClientsError(ClientsError.NOT_FOUND, 'No such client')); + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new BoxError(BoxError.NOT_FOUND, 'No such client')); if (error) return callback(error); callback(null); @@ -270,11 +244,11 @@ function addTokenByUserId(clientId, userId, expiresAt, options, callback) { if (error) return callback(error); users.get(userId, function (error, user) { - if (error && error.reason === UsersError.NOT_FOUND) return callback(new ClientsError(ClientsError.NOT_FOUND, 'No such user')); - if (error) return callback(new ClientsError(ClientsError.INTERNAL_ERROR, error)); + if (error && error.reason === UsersError.NOT_FOUND) return callback(new BoxError(BoxError.NOT_FOUND, 'No such user')); + if (error) return callback(new BoxError(BoxError.INTERNAL_ERROR, error)); accesscontrol.scopesForUser(user, function (error, userScopes) { - if (error) return callback(new ClientsError(ClientsError.INTERNAL_ERROR, error)); + if (error) return callback(new BoxError(BoxError.INTERNAL_ERROR, error)); const scope = accesscontrol.canonicalScopeString(result.scope); const authorizedScopes = accesscontrol.intersectScopes(userScopes, scope.split(',')); @@ -290,7 +264,7 @@ function addTokenByUserId(clientId, userId, expiresAt, options, callback) { }; tokendb.add(token, function (error) { - if (error) return callback(new ClientsError(ClientsError.INTERNAL_ERROR, error)); + if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); callback(null, { accessToken: token.accessToken, @@ -331,8 +305,8 @@ function delToken(clientId, tokenId, callback) { if (error) return callback(error); tokendb.del(tokenId, function (error) { - if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new ClientsError(ClientsError.INVALID_TOKEN, 'Invalid token')); - if (error) return callback(new ClientsError(ClientsError.INTERNAL_ERROR, error)); + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new BoxError(BoxError.NOT_FOUND, 'Invalid token')); + if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); callback(null); }); diff --git a/src/routes/accesscontrol.js b/src/routes/accesscontrol.js index 2936c7560..940a83523 100644 --- a/src/routes/accesscontrol.js +++ b/src/routes/accesscontrol.js @@ -12,9 +12,9 @@ var accesscontrol = require('../accesscontrol.js'), assert = require('assert'), BasicStrategy = require('passport-http').BasicStrategy, BearerStrategy = require('passport-http-bearer').Strategy, + BoxError = require('../boxerror.js'), clients = require('../clients.js'), ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy, - ClientsError = clients.ClientsError, HttpError = require('connect-lastmile').HttpError, LocalStrategy = require('passport-local').Strategy, passport = require('passport'), @@ -62,7 +62,7 @@ function initialize(callback) { // Used to authenticate a OAuth2 client which uses clientId and clientSecret in the Authorization header passport.use(new BasicStrategy(function (clientId, clientSecret, callback) { clients.get(clientId, function (error, client) { - if (error && error.reason === ClientsError.NOT_FOUND) return callback(null, false); + if (error && error.reason === BoxError.NOT_FOUND) return callback(null, false); if (error) return callback(error); if (client.clientSecret !== clientSecret) return callback(null, false); callback(null, client); @@ -72,7 +72,7 @@ function initialize(callback) { // Used to authenticate a OAuth2 client which uses clientId and clientSecret in the request body (client_id, client_secret) passport.use(new ClientPasswordStrategy(function (clientId, clientSecret, callback) { clients.get(clientId, function(error, client) { - if (error && error.reason === ClientsError.NOT_FOUND) return callback(null, false); + if (error && error.reason === BoxError.NOT_FOUND) return callback(null, false); if (error) { return callback(error); } if (client.clientSecret !== clientSecret) { return callback(null, false); } callback(null, client); diff --git a/src/routes/clients.js b/src/routes/clients.js index a64d72a65..112518284 100644 --- a/src/routes/clients.js +++ b/src/routes/clients.js @@ -12,13 +12,25 @@ exports = module.exports = { }; var assert = require('assert'), + BoxError = require('../boxerror.js'), clients = require('../clients.js'), - ClientsError = clients.ClientsError, constants = require('../constants.js'), HttpError = require('connect-lastmile').HttpError, HttpSuccess = require('connect-lastmile').HttpSuccess, validUrl = require('valid-url'); +function toHttpError(error) { + switch (error.reason) { + case BoxError.NOT_FOUND: + return new HttpError(404, error); + case BoxError.BAD_FIELD: + return new HttpError(400, error); + case BoxError.INTERNAL_ERROR: + default: + return new HttpError(500, error); + } +} + function add(req, res, next) { var data = req.body; @@ -29,9 +41,8 @@ function add(req, res, next) { if (!validUrl.isWebUri(data.redirectURI)) return next(new HttpError(400, 'redirectURI must be a valid uri')); clients.add(data.appId, clients.TYPE_EXTERNAL, data.redirectURI, data.scope, function (error, result) { - if (error && error.reason === ClientsError.INVALID_SCOPE) return next(new HttpError(400, error.message)); - if (error && error.reason === ClientsError.BAD_FIELD) return next(new HttpError(400, error.message)); - if (error) return next(new HttpError(500, error)); + if (error) return next(toHttpError(error)); + next(new HttpSuccess(201, result)); }); } @@ -40,8 +51,8 @@ function get(req, res, next) { assert.strictEqual(typeof req.params.clientId, 'string'); clients.get(req.params.clientId, function (error, result) { - if (error && error.reason === ClientsError.NOT_FOUND) return next(new HttpError(404, error.message)); - if (error) return next(new HttpError(500, error)); + if (error) return next(toHttpError(error)); + next(new HttpSuccess(200, result)); }); } @@ -50,16 +61,14 @@ function del(req, res, next) { assert.strictEqual(typeof req.params.clientId, 'string'); clients.get(req.params.clientId, function (error, result) { - if (error && error.reason === ClientsError.NOT_FOUND) return next(new HttpError(404, error.message)); - if (error) return next(new HttpError(500, error)); + if (error) return next(toHttpError(error)); // we do not allow to use the REST API to delete addon clients if (result.type !== clients.TYPE_EXTERNAL) return next(new HttpError(405, 'Deleting app addon clients is not allowed.')); clients.del(req.params.clientId, function (error, result) { - if (error && error.reason === ClientsError.NOT_FOUND) return next(new HttpError(404, error.message)); - if (error && error.reason === ClientsError.NOT_ALLOWED) return next(new HttpError(405, error.message)); - if (error) return next(new HttpError(500, error)); + if (error) return next(toHttpError(error)); + next(new HttpSuccess(204, result)); }); }); @@ -67,7 +76,8 @@ function del(req, res, next) { function getAll(req, res, next) { clients.getAll(function (error, result) { - if (error && error.reason !== ClientsError.NOT_FOUND) return next(new HttpError(500, error)); + if (error) return next(toHttpError(error)); + next(new HttpSuccess(200, { clients: result })); }); } @@ -83,8 +93,8 @@ function addToken(req, res, next) { if ('name' in req.body && typeof req.body.name !== 'string') return next(new HttpError(400, 'name must be a string')); clients.addTokenByUserId(req.params.clientId, req.user.id, expiresAt, { name: req.body.name || '' }, function (error, result) { - if (error && error.reason === ClientsError.NOT_FOUND) return next(new HttpError(404, error.message)); - if (error) return next(new HttpError(500, error)); + if (error) return next(toHttpError(error)); + next(new HttpSuccess(201, { token: result })); }); } @@ -94,8 +104,7 @@ function getTokens(req, res, next) { assert.strictEqual(typeof req.user, 'object'); clients.getTokensByUserId(req.params.clientId, req.user.id, function (error, result) { - if (error && error.reason === ClientsError.NOT_FOUND) return next(new HttpError(404, error.message)); - if (error) return next(new HttpError(500, error)); + if (error) return next(toHttpError(error)); result = result.map(clients.removeTokenPrivateFields); @@ -108,8 +117,8 @@ function delTokens(req, res, next) { assert.strictEqual(typeof req.user, 'object'); clients.delTokensByUserId(req.params.clientId, req.user.id, function (error) { - if (error && error.reason === ClientsError.NOT_FOUND) return next(new HttpError(404, error.message)); - if (error) return next(new HttpError(500, error)); + if (error) return next(toHttpError(error)); + next(new HttpSuccess(204)); }); } @@ -120,9 +129,7 @@ function delToken(req, res, next) { assert.strictEqual(typeof req.user, 'object'); clients.delToken(req.params.clientId, req.params.tokenId, function (error) { - if (error && error.reason === ClientsError.NOT_FOUND) return next(new HttpError(404, error.message)); - if (error && error.reason === ClientsError.INVALID_TOKEN) return next(new HttpError(404, error.message)); - if (error) return next(new HttpError(500, error)); + if (error) return next(toHttpError(error)); next(new HttpSuccess(204)); }); diff --git a/src/routes/oauth2.js b/src/routes/oauth2.js index 83a4b026e..65cd51c9c 100644 --- a/src/routes/oauth2.js +++ b/src/routes/oauth2.js @@ -22,8 +22,8 @@ exports = module.exports = { var apps = require('../apps.js'), assert = require('assert'), authcodedb = require('../authcodedb.js'), + BoxError = require('../boxerror.js'), clients = require('../clients'), - ClientsError = clients.ClientsError, constants = require('../constants.js'), DatabaseError = require('../databaseerror.js'), debug = require('debug')('box:routes/oauth2'), @@ -463,7 +463,7 @@ function authorization() { debug('authorization: client %s with callback to %s.', clientId, redirectURI); clients.get(clientId, function (error, client) { - if (error && error.reason === ClientsError.NOT_FOUND) return callback(null, false); + if (error && error.reason === BoxError.NOT_FOUND) return callback(null, false); if (error) return callback(error); // ignore the origin passed into form the client, but use the one from the clientdb