Skip to content

Commit

Permalink
Merge pull request #42 from nus-mtp/admin-admins
Browse files Browse the repository at this point in the history
Admin - Admins CRUD
  • Loading branch information
yj-soh committed Feb 2, 2016
2 parents 07e2de0 + fac0380 commit 15ab407
Show file tree
Hide file tree
Showing 12 changed files with 701 additions and 76 deletions.
2 changes: 1 addition & 1 deletion app_server/app/Router.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ server.register(require('hapi-auth-cookie'), function (err) {

server.auth.default({
strategy: 'session',
scope: Authenticator.SCOPE.ADMIN
scope: Authenticator.SCOPE.ADMIN.DEFAULT
});

/* Register controllers */
Expand Down
184 changes: 166 additions & 18 deletions app_server/app/controllers/AdminController.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var Class = AdminController.prototype;

var ROOT_ADMIN_USERNAME = 'root';

Class.registerRoutes = function () {
Class.registerRoutes = function() {
/*
this.server.route({
method: 'POST', path: '/rootAdmin',
Expand All @@ -32,15 +32,48 @@ Class.registerRoutes = function () {
});
*/

this.server.route({
method: 'GET', path: '/{username}',
config: {
auth: {scope: Authenticator.SCOPE.ADMIN.ADMINS}
},
handler: this.getAdminByUsername
});

this.server.route({
method: 'GET', path: '/',
config: {
auth: {scope: Authenticator.SCOPE.ADMIN.ADMINS}
},
handler: this.getListOfAdmins
});

this.server.route({
method: 'POST', path: '/',
config: {
auth: {scope: Authenticator.SCOPE.ADMIN},
auth: {scope: Authenticator.SCOPE.ADMIN.ADMINS},
validate: accountPayloadValidator
},
handler: this.createAdmin
});

this.server.route({
method: 'PUT', path: '/{id}',
config: {
auth: {scope: Authenticator.SCOPE.ADMIN.ADMINS},
validate: updatePayloadValidator
},
handler: this.updateAdmin
});

this.server.route({
method: 'DELETE', path: '/{id}',
config: {
auth: {scope: Authenticator.SCOPE.ADMIN.ADMINS}
},
handler: this.deleteAdmin
});

this.server.route({
method: 'POST', path: '/login',
config: {
Expand All @@ -58,36 +91,122 @@ Class.registerRoutes = function () {
};

/* Routes handlers */
Class.getAdminByUsername = function(request, reply) {
Service.getAdminByUsername(request.params.username)
.then(function(admin) {
if (!admin || admin instanceof Error) {
return reply(Boom.badRequest(
'Unable to get user with id ' + request.params.username
));
}

admin.permissions = unwrapPermissionsFromDB(admin.permissions);
return reply(Utility.clearUserProfile(admin));
});
};

Class.getListOfAdmins = function(request, reply) {
var filters = {
order: request.query.order || 'asc'
};

Service.getListOfAdmins(filters).then(function(admins) {
if (!admins || admins instanceof Error) {
return reply(Boom.badRequest('Unable to get list of admins'));
}

return reply(admins);
});
};

/*
Class.createRootAdmin = function (request, reply) {
Class.createRootAdmin = function(request, reply) {
var generatedPassword = Utility.randomValueBase64(20);
request.payload = {
username: ROOT_ADMIN_USERNAME,
password: generatedPassword
password: generatedPassword,
permissions: [
Authenticator.SCOPE.ADMIN.METRICS,
Authenticator.SCOPE.ADMIN.STREAMS,
Authenticator.SCOPE.ADMIN.USERS,
Authenticator.SCOPE.ADMIN.ADMINS,
Authenticator.SCOPE.ADMIN.SETTINGS
]
};
Class.createAdmin(request, reply);
};
*/

Class.createAdmin = function (request, reply) {
Class.createAdmin = function(request, reply) {
var credentials = {
username: request.payload.username,
password: encrypt(request.payload.password)
password: encrypt(request.payload.password),
email: request.payload.email || null,
permissions: (function () {
request.payload.permissions.push(Authenticator.SCOPE.ADMIN.DEFAULT);
return wrapPermissionsForDB(request.payload.permissions);
}())
};

Service.createNewAdmin(credentials)
.then(function (admin) {
.then(function(admin) {
if (!admin || admin instanceof Error) {
return reply(Boom.badRequest('Unable to create admin ' + credentials));
}

// overwrite with unencrypted password
admin.password = request.payload.password;
admin.permissions = unwrapPermissionsFromDB(admin.permissions);
reply(admin).created();
});
};

Class.login = function (request, reply) {
Class.updateAdmin = function(request, reply) {
var id = request.params.id;
var password = request.payload.password;
var particulars = request.payload;

if (particulars.password) {
particulars.password = encrypt(particulars.password);
} else {
// no password provided, don't change
delete particulars.password;
}

particulars.permissions = wrapPermissionsForDB(particulars.permissions);

return Service.updateAdmin(id, particulars)
.then(function(admin) {
if (!admin || admin instanceof Error) {
return reply(Boom.badRequest('Unable to update admin with id ' + id));
}

admin.permissions = unwrapPermissionsFromDB(admin.permissions);

if (particulars.password) {
// overwrite with unencrypted password
admin.password = password;
return reply(admin);
} else {
return reply(Utility.clearUserProfile(admin));
}
});
};

Class.deleteAdmin = function(request, reply) {
var id = request.params.id;

return Service.deleteAdmin(id)
.then(function(result) {
if (!result) {
return reply(Boom.badRequest('Unable to delete admin with id ' + id));
}

return reply();
});
};

Class.login = function(request, reply) {
var credentials = {
username: request.payload.username,
password: request.payload.password
Expand All @@ -105,14 +224,10 @@ Class.login = function (request, reply) {
userId: admin.userId,
username: admin.username,
password: admin.password,
scope: Authenticator.SCOPE.ADMIN
scope: unwrapPermissionsFromDB(admin.permissions)
};

request.server.app.cache.set(account.userId, account, 0, function (err) {
if (err) {
logger.error(err);
}

return updateCache(request, account, function() {
request.cookieAuth.set(account);

// rewrite with unencrypted password
Expand All @@ -124,26 +239,59 @@ Class.login = function (request, reply) {
});
};

Class.logout = function (request, reply) {
Class.logout = function(request, reply) {
request.cookieAuth.clear();
return reply('Logged out');
};

/* Validator for routes */
var validPermissions = Joi.string().valid([
Authenticator.SCOPE.ADMIN.DEFAULT,
Authenticator.SCOPE.ADMIN.METRICS,
Authenticator.SCOPE.ADMIN.STREAMS,
Authenticator.SCOPE.ADMIN.USERS,
Authenticator.SCOPE.ADMIN.ADMINS,
Authenticator.SCOPE.ADMIN.SETTINGS
]);

var accountPayloadValidator = {
payload: {
username: Joi.string().required(),
password: Joi.string().required()
password: Joi.string().required(),
email: Joi.string().optional(),
permissions: Joi.array().items(validPermissions).unique().default([])
}
};

var updatePayloadValidator = {
payload: {
username: Joi.string().required(),
password: Joi.string().optional(),
email: Joi.string().optional(),
permissions: Joi.array().items(validPermissions).unique().required()
}
};

/* Helpers for everything above */
var encrypt = function (password) {
var encrypt = function(password) {
var salt = bcrypt.genSaltSync(10);
return bcrypt.hashSync(password, salt);
};

exports.register = function (server, options, next) {
var updateCache = function(request, account, callback) {
request.server.app.cache.set(account.userId, account, 0, function (err) {
if (err) {
logger.error(err);
}

return callback();
});
};

var wrapPermissionsForDB = (permissionsArr) => permissionsArr.join(';');
var unwrapPermissionsFromDB = (permissions) => permissions.split(';');

exports.register = function(server, options, next) {
var adminController = new AdminController(server, options);
server.bind(adminController);
adminController.registerRoutes();
Expand Down
19 changes: 18 additions & 1 deletion app_server/app/models/Storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ Class.deleteUserById = function(userId) {
};

/**
* @param {string}
* @param {string} userId
* @param {object} newParticulars
* @param {string} newParticulars.username
* @param {string} newParticulars.password
Expand Down Expand Up @@ -254,6 +254,23 @@ Class.getListOfUsers = function(filters) {
});
};

// TODO: Merge into getListOfUsers() after implementing filters
/**
* @return {Promise<List<Sequelize.object>>} - a list of admins
* {False} on fail
*/
Class.getListOfAdmins = function(filters) {
filters = mapParams(filters);

return this.models.User.findAll({
order: [['username', filters.order]],
where: {permissions: {ne: null}}
}).catch(function(err) {
logger.error('Error in fetching list of users: %j', err);
return false;
});
};

/**
* @param {string} userId - userid of the user who created stream
* @param {object} streamAttributes
Expand Down
11 changes: 9 additions & 2 deletions app_server/app/policies/Authenticator.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ Class.ERRORS = {

Class.SCOPE = {
USER: 'user',
ADMIN: 'admin',
ADMIN: {
DEFAULT: 'admin',
METRICS: 'metrics',
STREAMS: 'streams',
USERS: 'users',
ADMINS: 'admins',
SETTINGS: 'settings'
},
ALL: ['user', 'admin']
};

Expand Down Expand Up @@ -198,7 +205,7 @@ Class.validateAccount = function (server, session) {

if (session.scope === Class.SCOPE.USER) {
return Class.verifyUserToken(user, session.password);
} else if (session.scope === Class.SCOPE.ADMIN) {
} else if (session.scope === Class.SCOPE.ADMIN.DEFAULT) {
return bcrypt.compareAsync(session.password, user.password);
} else {
return new Error(Class.ERRORS.UNKNOWN_SCOPE);
Expand Down
Loading

0 comments on commit 15ab407

Please sign in to comment.