diff --git a/app.js b/app.js index 3e16f54..3d16876 100644 --- a/app.js +++ b/app.js @@ -25,6 +25,8 @@ if (cluster.isMaster) { var app = express(); var params = require('express-params'); var expressDomain = require('express-domain-middleware'); + var server; + params.extend(app); var AppError = require('./core/AppError'); @@ -36,7 +38,7 @@ if (cluster.isMaster) { app.set('view engine', 'jade'); }); -// Set statics dirs (not handlers) + // Set statics dirs (not handlers) app.use('/js/lib', express.static('public/lib')); app.use('/js', express.static('public/js')); app.use('/css', express.static('public/css')); @@ -80,7 +82,7 @@ if (cluster.isMaster) { res.json(err.status, err.getData()); }); - var server = app.listen(config.port); + server = app.listen(config.port); console.log('Express started on port ' + config.port); server.on('connection', function(socket) { diff --git a/controller/main/mainController.js b/controller/main/mainController.js index 1a4d58f..17ee704 100644 --- a/controller/main/mainController.js +++ b/controller/main/mainController.js @@ -28,8 +28,23 @@ MainController.prototype.index = function (core, next) { }; MainController.prototype.notFound = function (core, next) { - var data = { script: 'main/main', style: 'main/style' }; - core.responseHtmlFromTemplate('main:notFound', data, next, 404); + var data = { + script: 'main/main', + style: 'main/main', + title: 'Страница не найдена', + message: 'Данной страницы не существует.' + }; + core.responseHtmlFromTemplate('main:error', data, next, 404); +}; + +MainController.prototype.forbidden = function (core, next) { + var data = { + script: 'main/main', + style: 'main/main', + title: 'Ошибка доступа', + message: 'Доступ к данной странице запрещен.' + }; + core.responseHtmlFromTemplate('main:error', data, next, 403); }; module.exports = MainController; diff --git a/controller/main/tpl/error.jade b/controller/main/tpl/error.jade new file mode 100755 index 0000000..39bc339 --- /dev/null +++ b/controller/main/tpl/error.jade @@ -0,0 +1,7 @@ +extends block/base + +block title + =title + +block content + =message diff --git a/controller/main/tpl/notFound.jade b/controller/main/tpl/notFound.jade deleted file mode 100755 index 9987ee0..0000000 --- a/controller/main/tpl/notFound.jade +++ /dev/null @@ -1,7 +0,0 @@ -extends block/base - -block title - | Страница не найдена - -block content - | К сожалению, данной страницы не существует. diff --git a/controller/picture/pictureController.js b/controller/picture/pictureController.js index fc78686..4ccd01e 100644 --- a/controller/picture/pictureController.js +++ b/controller/picture/pictureController.js @@ -10,57 +10,87 @@ var AppError = require('../../core/AppError'); var PictureController = function () {}; PictureController.prototype.upload = function (core, next) { - var file = core.files.picture; var pictureId = core.post.pictureId; - core.pictureManager.upload(file, pictureId, function (err, data) { - if (err) next(new AppError(err)); - core.responseJson(data); + core.pictureManager.hasAccess('upload', { picture: pictureId }, function (err, hasAccess) { + if (err) return next(new AppError(err)); + if (!hasAccess) return core.jsonForbidden(); + + var file = core.files.picture; + core.pictureManager.upload(file, pictureId, function (err, data) { + if (err) next(new AppError(err)); + core.responseJson(data); + }); }); }; PictureController.prototype.addPage = function (core, next) { - var data = { script: 'picture/addPicture', style: 'main/main' }; - core.responseHtmlFromTemplate('picture:addPicture', data, next); + core.pictureManager.hasAccess('add', null, function (err, hasAccess) { + if (err) return next(new AppError(err)); + if (!hasAccess) return core.forbidden(); + + var data = { script: 'picture/addPicture', style: 'main/main' }; + core.responseHtmlFromTemplate('picture:addPicture', data, next); + }); }; PictureController.prototype.editPage = function (core, next) { var picture = core.req.picture; + core.pictureManager.hasAccess('edit', { picture: picture }, function (err, hasAccess) { + if (err) return next(new AppError(err)); + if (!hasAccess) return core.forbidden(); - var data = { - script: 'picture/editPicture', - style: 'main/main', - id: picture.id, - title: picture.title, - description: picture.description - }; - core.responseHtmlFromTemplate('picture:editPicture', data, next); + var data = { + script: 'picture/editPicture', + style: 'main/main', + id: picture.id, + title: picture.title, + description: picture.description + }; + core.responseHtmlFromTemplate('picture:editPicture', data, next); + }); }; PictureController.prototype.add = function (core, next) { - var data = core.post; - data.userId = core.userManager.currentUser.id; - - core.pictureManager.add(data, function (err) { + core.pictureManager.hasAccess('add', null, function (err, hasAccess) { if (err) return next(new AppError(err)); - core.responseJson(); + if (!hasAccess) return core.jsonForbidden(); + + var data = core.post; + data.userId = core.userManager.currentUser.id; + + core.pictureManager.add(data, function (err) { + if (err) return next(new AppError(err)); + core.responseJson(); + }); }); }; PictureController.prototype.edit = function (core, next) { - var data = core.post; - data.userId = core.userManager.currentUser.id; - - core.pictureManager.edit(core.req.picture, data, function (err) { + var picture = core.req.picture; + core.pictureManager.hasAccess('edit', { picture: picture }, function (err, hasAccess) { if (err) return next(new AppError(err)); - core.responseJson(); + if (!hasAccess) return core.jsonForbidden(); + + var data = core.post; + data.userId = core.userManager.currentUser.id; + core.pictureManager.edit(picture, data, function (err) { + if (err) return next(new AppError(err)); + core.responseJson(); + }); }); }; PictureController.prototype.del = function (core, next) { - core.pictureManager.del(core.req.picture, function (err) { + var picture = core.req.picture; + core.pictureManager.hasAccess('delete', { picture: picture }, function (err, hasAccess) { if (err) return next(new AppError(err)); - core.responseJson(); + if (!hasAccess) return core.jsonForbidden(); + + core.pictureManager.del(picture, function (err) { + if (err) return next(new AppError(err)); + core.responseJson(); + }); }); }; diff --git a/controller/user/js/controller/signIn.coffee b/controller/user/js/controller/signIn.coffee index 9d5544f..cfe5689 100644 --- a/controller/user/js/controller/signIn.coffee +++ b/controller/user/js/controller/signIn.coffee @@ -21,7 +21,7 @@ window.app.controller 'SignInCtrl', ['$scope', '$http', ($scope, $http)-> $http.post('/signOut', $scope.user) .success (response)-> return console.error response.errorMessage if response.error - location.reload() + location.href = '/' .error (response)-> console.error response ] diff --git a/controller/user/js/controller/signUp.coffee b/controller/user/js/controller/signUp.coffee index a2f1870..74ac828 100644 --- a/controller/user/js/controller/signUp.coffee +++ b/controller/user/js/controller/signUp.coffee @@ -22,7 +22,7 @@ window.app.controller 'SignUpCtrl', ['$scope', '$http', (s, $http)-> $http.post('/signUp', s.user) .success (response)-> return console.error response.errorMessage if response.error - location.reload() + window.history.back(); .error (response)-> console.error response ] diff --git a/controller/user/userController.js b/controller/user/userController.js index 5f3df71..1a5698a 100644 --- a/controller/user/userController.js +++ b/controller/user/userController.js @@ -11,30 +11,53 @@ var AppError = require('../../core/AppError'); var UserController = function () {}; UserController.prototype.signUpPage = function (core, next) { - var data = { script: 'user/signUp', style: 'main/main' }; - core.responseHtmlFromTemplate('user:signUp', data, next); + core.userManager.hasAccess('signUp', null, function (err, hasAccess) { + if (err) return next(new AppError(err)); + if (!hasAccess) return core.forbidden(); + + var data = { script: 'user/signUp', style: 'main/main' }; + core.responseHtmlFromTemplate('user:signUp', data, next); + }); }; UserController.prototype.signUp = function (core, next) { - core.userManager.signUp(core.post, function (err) { + core.userManager.hasAccess('signUp', null, function (err, hasAccess) { if (err) return next(new AppError(err)); - core.responseJson(); + if (!hasAccess) return core.jsonForbidden(); + + core.userManager.signUp(core.post, function (err) { + if (err) return next(new AppError(err)); + core.responseJson(); + }); }); }; UserController.prototype.signIn = function (core, next) { - var login = core.post.login; - var password = core.post.password; - core.userManager.signIn(login, password, function (err) { + core.userManager.hasAccess('signIn', null, function (err, hasAccess) { if (err) return next(new AppError(err)); - core.responseJson(); + if (!hasAccess) return core.jsonForbidden(); + + var login = core.post.login; + var password = core.post.password; + core.userManager.signIn(login, password, function (err) { + if (err) return next(new AppError(err)); + core.responseJson(); + }); }); }; UserController.prototype.signOut = function (core, next) { - core.userManager.signOut(function (err) { + core.userManager.hasAccess('signOut', null, function (err, hasAccess) { if (err) return next(new AppError(err)); - core.responseJson(); + if (!hasAccess) return core.jsonForbidden(); + + if (!core.userManager.isAuthorized()) { + return core.forbidden(); + } + core.userManager.signOut(function (err) { + if (err) return next(new AppError(err)); + core.responseJson(); + }); }); }; diff --git a/core/BaseManager.js b/core/BaseManager.js index 2329932..04316ab 100644 --- a/core/BaseManager.js +++ b/core/BaseManager.js @@ -7,6 +7,7 @@ var util = require('util'); var _ = require('underscore'); +var AccessManager = require('hm-access-manager'); var AppError = require('../core/AppError'); var BaseClass = require('../core/BaseClass'); @@ -16,33 +17,29 @@ var BaseManager = function (core, table, Entity) { BaseClass.call(this, core); this.mysql = this.mysql.assign(table); this.Entity = Entity.bind(null, this); + this.accessManager = new AccessManager(); }; util.inherits(BaseManager, BaseClass); BaseManager.prototype.getById = function (id, next) { + var self = this; this.mysql.one(null, { id: id }, function (err, data) { if (err) return next(new AppError(err)); if (!data) return next(null, null); - - try { - next(null, new this.Entity(data, true)); - } catch (err) { next(new AppError(err)); } - - }.bind(this)); -}; + next(null, new self.Entity(data, true)); + }); +};// BaseManager.prototype.getByFields = function (fields, next) { + var self = this; this.mysql.one(null, fields, function (err, data) { if (err) return next(new AppError(err)); if (!data) return next(null, null); + return next(null, new self.Entity(data)); - try { - return next(null, new this.Entity(data)); - } catch (err) { next(new AppError(err)); } - - }.bind(this)); + }); }; BaseManager.prototype.getByField = function (field, value, next) { @@ -51,5 +48,9 @@ BaseManager.prototype.getByField = function (field, value, next) { this.getByFields(fields, next); }; +BaseManager.prototype.hasAccess = function (action, args, next) { + this.accessManager.hasAccess(action, args, next); +}; + module.exports = BaseManager; diff --git a/core/Core.js b/core/Core.js index 8f8f45b..ea50168 100644 --- a/core/Core.js +++ b/core/Core.js @@ -73,6 +73,10 @@ Core.prototype.responseHtml = function (html, code) { this.res.send(code, html); }; +Core.prototype.getCurrentUser = function () { + return this.userManager.currentUser; +}; + /** * @param {Object} data * @param {String} template @@ -90,6 +94,18 @@ Core.prototype.notFound = function (next) { mainController.notFound(this, next); }; +Core.prototype.forbidden = function (next) { + mainController.forbidden(this, next); +}; + +Core.prototype.jsonForbidden = function () { + this.responseJson({ + result: 0, + errorCode: 0, + errorMessage: 'Forbidden' + }, 403); +}; + Core.prototype.render = function (template, data, next) { var tmp = template.split(':'); template = 'controller/' + tmp[0] + '/tpl/' + tmp[1] + '.jade'; diff --git a/core/Mysql.js b/core/Mysql.js index 2733282..40ef01b 100644 --- a/core/Mysql.js +++ b/core/Mysql.js @@ -85,26 +85,21 @@ Mysql.prototype.one = function (table, columns, where, next) { * @param {Function} next */ Mysql.prototype.insert = function (table, fields, next) { - try { - var query = 'insert into' + mysql.escapeId(table); + var query = 'insert into' + mysql.escapeId(table); - query += '(' + _.map(fields, function (value, field) { - if (!_.isString(field)) throw new AppError('Field must be a string'); - return mysql.escapeId(field); - }).join(', ') + ')'; + query += '(' + _.map(fields, function (value, field) { + if (!_.isString(field)) throw new AppError('Field must be a string'); + return mysql.escapeId(field); + }).join(', ') + ')'; - query += ' values(' + _.map(fields, function (value) { - return mysql.escape(value); - }).join(', ') + ')'; + query += ' values(' + _.map(fields, function (value) { + return mysql.escape(value); + }).join(', ') + ')'; - this.query(query, function (err, response) { - if (err) return next(new AppError(err)); - next(null, response.insertId); - }); - - } catch (err) { - next(new AppError(err)); - } + this.query(query, function (err, response) { + if (err) return next(new AppError(err)); + next(null, response.insertId); + }); }; Mysql.prototype.resetCache = function () { @@ -120,14 +115,10 @@ Mysql.prototype.resetCache = function () { * @param {Function} next */ Mysql.prototype.update = function (table, where, values, next) { - try { - var query = 'update ' + mysql.escapeId(table); - query += ' set ' + this._getWhereString(values, ','); - query += ' where ' + this._getWhereString(where); - this.query(query, next); - } catch (err) { - next(new AppError(err)); - } + var query = 'update ' + mysql.escapeId(table); + query += ' set ' + this._getWhereString(values, ','); + query += ' where ' + this._getWhereString(where); + this.query(query, next); }; /** @@ -138,49 +129,39 @@ Mysql.prototype.update = function (table, where, values, next) { * @param {Function} next */ Mysql.prototype.del = function (table, where, next) { - try { - var query = 'delete from ' + mysql.escapeId(table); - query += ' where ' + this._getWhereString(where); - this.query(query, next); - - } catch (err) { - next(new AppError(err)); - } + var query = 'delete from ' + mysql.escapeId(table); + query += ' where ' + this._getWhereString(where); + this.query(query, next); }; Mysql.prototype._select = function (table, columns, where, next) { - try { - table = mysql.escapeId(table); - if (where) { - where = ' where ' + this._getWhereString(where); - } else { - where = ''; - } + table = mysql.escapeId(table); + if (where) { + where = ' where ' + this._getWhereString(where); + } else { + where = ''; + } - if (columns === null) { - columns = '*'; - } else { - columns = columns.forEach(function (column) { - return mysql.escapeId(column); - }).join(', '); - } + if (columns === null) { + columns = '*'; + } else { + columns = columns.forEach(function (column) { + return mysql.escapeId(column); + }).join(', '); + } - var query = 'select ' + columns + ' from ' + table + where; + var query = 'select ' + columns + ' from ' + table + where; - if (this.cache[query]) { - return next(null, this.cache[query]); - } - else { - this.query(query, function (err, rows) { - if (err) return new AppError(err); - this.cache[query] = rows; - next(null, rows); - }.bind(this)); - } - - } catch (err) { - next(new AppError(err)); + if (this.cache[query]) { + return next(null, this.cache[query]); + } + else { + this.query(query, function (err, rows) { + if (err) return new AppError(err); + this.cache[query] = rows; + next(null, rows); + }.bind(this)); } }; diff --git a/model/PictureManager.js b/model/PictureManager.js index 879dcf0..15ece4f 100644 --- a/model/PictureManager.js +++ b/model/PictureManager.js @@ -16,10 +16,68 @@ var Picture = require('./Picture'); var PictureManager = function (core) { BaseManager.call(this, core, 'picture', Picture); + this._initAccessHandlers(); }; util.inherits(PictureManager, BaseManager); +PictureManager.prototype._initAccessHandlers = function () { + var self = this; + + var initUserArgs = function (args) { + if (!args.user) return; + args.isOwnPicture = args.user.id === args.picture.userId; + args.isPointerUser = args.user.hasRole('pointer'); + }; + + this.accessManager.prepareHandle(function (action, args, next) { + if (!args) args = {}; + + args.user = self.core.getCurrentUser(); + if (args.user) { + args.isSuperUser = args.user.inRoles('admin', 'moder'); + } + + if (!args.picture) return next(null, args); + + if (args.picture instanceof Picture) { + initUserArgs(args); + return next(null, args); + } + + self.getById(args.picture, function (err, picture) { + if (err) return next(err); + + args.picture = picture; + initUserArgs(args); + next(null, args); + }); + }); + + this.accessManager.handle('view', function (action, args, next) { + next(null, true); + }); + + this.accessManager.handle('upload', function (handler, args, next) { + if (!args.user) return next(null, false); + if (!args.picture) return next(null, true); + if (args.isOwnPicture || args.isSuperUser) return next(null, true); + return next(null, false); + }.bind(this)); + + this.accessManager.handle('add', function (handler, args, next) { + if (!args.user) return next(null, false); + if (args.isPointerUser || args.isSuperUser) return next(null, true); + return next(null, false); + }); + + this.accessManager.handle(['edit', 'delete'], function (handler, args, next) { + if (!args.user) return next(null, false); + if (args.isOwnPicture || args.isSuperUser) return next(null, true); + return next(null, false); + }); +}; + PictureManager.prototype._getTmpPath = function (filename) { return __dirname + '/../tmp/img/' + filename; }; diff --git a/model/User.js b/model/User.js index f64fd50..4f777cf 100644 --- a/model/User.js +++ b/model/User.js @@ -6,6 +6,7 @@ 'use strict'; var util = require('util'); +var _ = require('underscore'); var BaseEntity = require('../core/BaseEntity'); @@ -19,13 +20,28 @@ var User = function (manager, data) { this.login = data.login; this.password = data.password; this.email = data.email; - this.role = data.role; + this.roles = data.roles ? data.roles.split('|') : 'user'; this.name = data.name; this.secondName = data.second_name; }; util.inherits(User, BaseEntity); +User.prototype.hasRole = function (role) { + return this.roles.indexOf(role) !== -1; +}; + +User.prototype.inRoles = function () { + if (arguments[0] instanceof Array) { + return this.inRoles.apply(this, arguments[0]); + } + + var self = this; + var roles = Array.prototype.slice.call(arguments); + return !!_.find(roles, function (role) { + return self.hasRole(role); + }); +}; User.prototype.getMysqlData = function () { return { @@ -34,7 +50,7 @@ User.prototype.getMysqlData = function () { login: this.login, password: this.password, email: this.email, - role: this.role, + roles: this.roles, name: this.name, second_name: this.secondName }; diff --git a/model/UserManager.js b/model/UserManager.js index 86676a2..64cd08c 100644 --- a/model/UserManager.js +++ b/model/UserManager.js @@ -15,11 +15,43 @@ var User = require('./User'); var UserManager = function (core) { BaseManager.call(this, core, 'user', User); + this._initAccessHandlers(); }; util.inherits(UserManager, BaseManager); +UserManager.prototype._initAccessHandlers = function () { + var self = this; + this.accessManager.prepareHandle(function (action, args, next) { + if (!args) args = {}; + args.currentUser = self.core.getCurrentUser(); + if (args.user) { + args.isSuperUser = args.currentUser.inRoles('admin', 'moder'); + } + if (!args.user) args.user = args.currentUser; + next(null, args); + }); + + this.accessManager.handle(['signIn', 'signUp'], function (action, args, next) { + next(null, !args.currentUser); + }); + + this.accessManager.handle('signOut', function (action, args, next) { + next(null, args.currentUser); + }); + + this.accessManager.handle(['edit', 'delete'], function (action, args, next) { + if (!args.currentUser) return next(null, false); + if (args.isSuperUser || args.currentUser.id === args.user.id) { + return next(null, true); + } + return next(null, false); + }); +}; + UserManager.prototype.initialize = function (next) { + var self = this; + var token = this.session.token; if (!token) return next(); @@ -27,21 +59,21 @@ UserManager.prototype.initialize = function (next) { if (err) return next(new AppError(err)); if (!userData) { - this.session.token = null; - this.currentUser = null; + self.session.token = null; + self.currentUser = null; return next(null); } - this.currentUser = new this.Entity(userData); + self.currentUser = new self.Entity(userData); next(null); - }.bind(this)); + }); }; -UserManager.prototype.getUserByLogin = function (login, next) { +UserManager.prototype.getByLogin = function (login, next) { this.getByField('login', login, next); }; -UserManager.prototype.getUserByEmail = function (email, next) { +UserManager.prototype.getByEmail = function (email, next) { this.getByField('email', email, next); }; @@ -50,10 +82,7 @@ UserManager.prototype.isAuthorized = function () { }; UserManager.prototype.signIn = function (login, password, next) { - if (this.isAuthorized()) { - return next(new AppError('User already login', 1)); - } - + var self = this; password = this._getHashedPassword(password); var data = { login: login, @@ -61,39 +90,34 @@ UserManager.prototype.signIn = function (login, password, next) { }; this.mysql.one(data, function (err, userData) { if (err) return next(new AppError(err)); - - if (!userData) return next(new AppError('Wrong login or password', 2)); + if (!userData) return next(new AppError('Wrong login or password', 1)); var id = userData.id; - this._createToken(function (err, token) { + self._createToken(function (err, token) { if (err) return next(new AppError(err)); - this.session.token = token; - this.mysql.update({ id: id }, { token: token }, next); - }.bind(this)); - }.bind(this)); + self.session.token = token; + self.mysql.update({ id: id }, { token: token }, next); + }); + }); }; UserManager.prototype.signUp = function (data, next) { - if (this.isAuthorized()) return next(new AppError('User already login', 1)); - + var self = this; var user = new this.Entity(data); user.password = this._getHashedPassword(user.password); this._checkUserOnExists(user.login, user.email, function (err) { if (err) return next(new AppError(err)); - - this._createToken(function (err, token) { + self._createToken(function (err, token) { if (err) return next(new AppError(err)); user.token = token; - - this.mysql.insert(user.getMysqlData(), function (err) { + self.mysql.insert(user.getMysqlData(), function (err) { if (err) return next(new AppError(err)); - - this.signIn(user.login, data.password, next); - }.bind(this)); - }.bind(this)); - }.bind(this)); + self.signIn(user.login, data.password, next); + }); + }); + }); }; UserManager.prototype.signOut = function (next) { @@ -102,16 +126,17 @@ UserManager.prototype.signOut = function (next) { }; UserManager.prototype._checkUserOnExists = function (login, email, next) { - this.getUserByLogin(login, function (err, userData) { + var self = this; + this.getByLogin(login, function (err, userData) { if (err) return next(new AppError(err)); if (userData) next(new AppError('User with this login already exists', 2)); - this.getUserByEmail(email, function (err, userData) { + self.getByEmail(email, function (err, userData) { if (err) return next(new AppError(err)); if (userData) return next(new AppError('User with this email already exists', 3)); next(); }); - }.bind(this)); + }); }; UserManager.prototype._createToken = function (next) { diff --git a/package.json b/package.json index 40d659a..9eba11a 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "mysql": "~2.0.0-rc1", "connect-multiparty": "1.0.2", "express-params": "0.0.3", - "express-domain-middleware": "0.1.0" + "express-domain-middleware": "0.1.0", + "hm-access-manager": "0.0.1" } }