Wednesday, 4 February 2015

keystone more deeper -- supperAdmin

This is a challenge job, because I can't change source code of keystone. (If you can't understand why, sorry, this post does not fix for you.)


1, I tried to do this with mongoose scheme:

User.schema.pre('save', function (next) {
    ... check if current user is a supper admin user.
}

However, I failed, I can't get current user here.

2, I tried to add express middleware, to spy "/keystone/users" accessing. This link is user management page. middlewares can get request and request has reference of current user.

So if current user is not a supper admin user I can throw error to block the accessing.

As we discussed in previous post http://baiduhix.blogspot.co.uk/2015/02/keystone-more-deeper-2.html, mount.js manages all routes. Two major routes 'controllers' are here. However we can't change route.js. So, we can only try the second routes 'controller'.

However I put this code in your project/routes/index.js :

app.use(function(req,res) { console.log("**xxxxxxx-xxxx" + req.user); });

This line of code was not called !!!!!!!!!!! will explain the reason later.

3, Then I tried to add middleware in keystone.js:

keystone.app.use('/keystone/users', function(req,res,next) { console.log("**********--------------" + req.user); next();});

Before keystone.start();

This line of code was called, but req.user is empty!!!!!!!!!!

Analysis:

Express middlewares runs by following the seqeuence of registering. if earlier middlewares didn't call "next()" later middlewares are dead middlewares.

"2" failed because it is registered too late,
"3" failed because it is registered too early,


For "2":

In mount.js:
if (!this.get('headless')) {
this.routes(app);
}

// Configure application routes
if ('function' === typeof this.get('routes')) {
this.get('routes')(app);
}

routes.js is regestered first. And below code regestered the same url which we interest with:

app.all('/keystone/:list/:page([0-9]{1,5})?', initList(true), require('../../routes/views/list'));

After checked the code of routes/views/list.js, this middleware didn't call "next()".

That is why "2" didn't get a chance to run.


For "3"

It is too early, because session setting up routines are in  mount.js




Solution:

Add a middleware to "keystone/users" not too early not too late:

In mount.js:

// Pre-route middleware
this._pre.routes.forEach(function(fn) {
console.log("_pre.routes foreach---------------" + fn);
try {
app.use(fn);
}
catch(e) {
if (keystone.get('logger')) {
console.log('Invalid pre-route middleware provided');
}
throw e;
}
});

_pre.routes just has one function which is the first function of :your project/routes/middleware.js.

BTW, I don't know how this function links to _pre.routes.

So, what I changed were:

exports.initLocals = function(req, res, next) {
var locals = res.locals;
locals.navLinks = [
{ label: 'Home', key: 'home', href: '/' }
];
locals.user = req.user;
if(req.path == "/keystone/users"){
if(req.user.isSupperUser){
next();
}else{
var err = new Error('need supper user');
next(err);
}
}else{
next();
}
};

User management page will only accessable for superuser. (a better code implement is introduced at end of this post)

I also need change models/User.js:

User.add({
name: { type: Types.Name, required: true, index: true },
email: { type: Types.Email, initial: true, required: true, index: true },
password: { type: Types.Password, initial: true, required: true }
}, 'Permissions', {
isAdmin: { type: Boolean, label: 'Can access Keystone', index: true },
isSupperUser: { type: Boolean, label: 'Is Supper User', index: true }
});

After that you can't access user management page anymore, because you haven't set any user as superuser.

You can change a user to superuser in database manually, or add a new user by adding a new file: updates/0.0.2-admins.js:

exports.create = {
User: [
{ 'name.first': 'Adminxx', 'name.last': 'User', email: 'userxx@keystonejs.com', password: 'admin', isAdmin: true,isSupperUser:true }
]
};




A better initLocals implementation:


exports.initLocals = function(req, res, next) {

var locals = res.locals;

locals.navLinks = [
{ label: 'Home', key: 'home', href: '/' }
];

locals.user = req.user;

var indexOfUsersURL = req.path.indexOf("/keystone/users");
//solution for :  blocking common users accessing all user management pages including list and item pages.
// if(indexOfUsersURL == 0){
// if(req.user.isSupperUser){
// next();
// }else{
// var err = new Error('need supper user');
// next(err);
// }
// }else{
// next();
// }

//solution for : non super user can only change her/his account.
if(indexOfUsersURL == 0 && req.path.length > "/keystone/users/".length){
if(req.user.isSupperUser){ //super user  
next();
}else if(req.path == "/keystone/users/" + req.user._id){// for current user
next();
}else{
var err = new Error('need supper user');
next(err);
}
}else{
next();
}
};



500 page:

If user accessed a limited page, the system will redirect to 500 page.

Keystonejs offers a default 500 page, without any configurations.

If you want to customize the page you should follow: http://keystonejs.com/docs/getting-started/#routesviews-settingup




































































































No comments:

Post a comment