I want to set roles for a new user.
I tried updating roles array in metadata during signup but I get an error. If I remove the roles metadata new user is created just fine.
db.signUp(userId, 'pass', {
metadata: {
email: 'robi434n#boywonder.com',
birthday: '1932-03-27T00:00:00.000Z',
likes: ['acrobatics', 'short pants', 'sidekickin\'']
roles: ['basic']
}
}, function (err, response) {
if (err) {
if (err.name === 'conflict') {
console.log('batman" already exists, choose another username')
// "batman" already exists, choose another username
} else if (err.name === 'forbidden') {
console.log('invalid username')
// invalid username
} else {
console.log('sign up error')
// HTTP error, cosmic rays, etc.
}
} else {
console.log('user signed up')
// login()
}
})
So I figured it out first modify the pouchdb-authentication index.js code to accept roles.
var signUp = pouchdbUtils.toPromise(function (username, password,roles, opts, callback) {
var db = this;
if (typeof callback === 'undefined') {
callback = typeof opts === 'undefined' ? (typeof password === 'undefined' ?
username : password) : opts;
opts = {};
}
if (['http', 'https'].indexOf(db.type()) === -1) {
return callback(new AuthError('This plugin only works for the http/https adapter. ' +
'So you should use new PouchDB("http://mysi3te.org:5984/mydb") instead.'));
} else if (!username) {
return callback(new AuthError('You must provide a username'));
} else if (!password) {
return callback(new AuthError('You must provide a password'));
}
var userId = 'org.couchdb.user:' + username;
var user = {
name: username,
password: password,
roles: roles,
type: 'user',
_id: userId,
};
updateUser(db, user, opts, callback);
});
Then you can send the roles in the sign up. I'm sending basic below
signUp()
function signUp () {
db.signUp(userId, 'pass', ['basic'], {
metadata: {
email: 'robi434n#boywonder.com',
birthday: '1932-03-27T00:00:00.000Z',
likes: ['acrobatics', 'short pants', 'sidekickin\'']
}
}, function (err, response) {
if (err) {
if (err.name === 'conflict') {
console.log('batman" already exists, choose another username')
// "batman" already exists, choose another username
} else if (err.name === 'forbidden') {
console.log('invalid username', err)
// invalid username
} else {
console.log('sign up error', err)
// HTTP error, cosmic rays, etc.
}
} else {
console.log('user signed up', err)
login()
}
})
}
now you have to go to couchdb _user database _design/_auth document modify
else if (newDoc.roles.length > 0 ) {\n
set this to
else if (newDoc.roles.length > 0 && newDoc.roles[0] !== 'basic' ) {\n
Now you will have basic in your session and can add more roles my adjusting the code a bit. This allows me to set member role permissions easily to limit access to other databases. Or a simpler solution i found and tested is to add a new design doc to your database with the following code. It will only allow users that are logged in to access your database
{
"_id": "_design/usersOnly",
"_rev": "17-6fb7e6c0ccfca8b2e56738ad63e26107",
"language": "javascript",
"validate_doc_update": "\n function(newDoc, oldDoc, userCtx){ \n // check if user is logged in \n if(!userCtx.name){ throw({forbidden : 'No way.. login man!'});} \n //reqired fields to update \n function require(field){ var message = field + ' is required'; if(!newDoc[field]){ throw({'forbidden':message}) }} require('name'); }"
}
The validate function design documentation uses the _users design document as the primary example:
Example: The _design/_auth ddoc from _users database uses a validation function to ensure that documents contain some required fields and are only modified by a user with the _admin role:
...
} else if (newDoc.roles.length > 0) {
throw({forbidden: 'Only _admin may set roles'});
}
...
In fauxton (for example) you can login as the couchdb admin user and go to _users database and change the design (at your own peril), for example:
} else if (newDoc.roles.length > 0 && !("basic" === newRoles[0] && newDoc.roles.length === 1)) {
throw({forbidden: 'Only _admin may set roles'});
}
saving it as you would any other document. (I say peril, since subtle accidents in this design document can potentially allow unauthorized users to drastically raise their permissions.)
With such a change, new users are allowed to be created with the basic role by couchdb, so your client code works if it sets roles correctly. In pouchdb-authenticate this seems to be with opt.roles and not opt.metadata.roles, i.e.:
db.signUp(userId, 'pass', {
metadata: {
email: 'robi434n#boywonder.com',
birthday: '1932-03-27T00:00:00.000Z',
likes: ['acrobatics', 'short pants', 'sidekickin\'']
},
roles: ['basic'] }, ... )
Related
I have been working on API authentication using passport js. Now i have to write APIs that can be accessible by only some privileged type of users.(some APIs are accessible to admin only.Some for Vendors). In user model i have specified role of each user(if the user is admin,vendor or customer).
I have solved similar problem in drop-wizard using "drop-wizard-auth".
If the user does not have the privilege to access the API it should show error-403
Please share link or advise that can solve my problem.
I'm using Postgresql(for info)
Strategy is custom-bearer-token
Try this solution:
In model users, you have "user_type" field which receive below integers.
// model for user
module.exports = (sequelize, DataTypes) => {
let users = sequelize.define ('users', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, field: 'id' },
name: { type: DataTypes.STRING (50), field: 'name' },
..
user_type: { type: DataTypes.INTEGER, field: 'user_type' } // 0 for admin, 1 for vendor, 2 for customer
},
});
return users;
};
The below function will be passport.js file which checks that this user has the correct role to hit this endpoint or not.
passport.isAuthorized = ( userType ) => {
return (req, res, next) => {
if (userType == 0) { // mean its admin
if (req.user.user_type == 0) { // user value stored in req through deserialize fucntion
return next(); // user has correct admin role
} else {
return next({ error: 'Please need admin level access to hit endpoint'
});
}
} else if (userType == 1) { // mean its vendor
if (req.user.user_type == 1) {
return next(); // user has correct vendor role
} else {
return next({ error: 'Please need vendor level access to hit endpoint'
}
} else if (userType == 2) { // mean its custumer
if (req.user.user_type == 2) {
return next(); // user has correct custumer role
} else {
return next({ error: 'Please need customer level access to hit endpoint'
}
}
return next({ error: 'something!went wrong.' });
};
};
In routes file, you just need to add these middlewares.
app.post('baseURL/get-user-list',
passport.isAuthenticated, // check user is authorized or not
passport.isAuthorized(1), // check user role, if you want to check for admin pass 0, for vendor pass 1, for customer pass 2
controllerUser.getUser
);
Im trying to add reset password feature.User enters username,email and full name,when these values match data from database ,password is changed,otherwise an error is shown.
This is what im doing-
app.post("/reset",function(req,res){
User.findByUsername(req.body.username).then(function(sanitizedUser){
if (sanitizedUser){
sanitizedUser.setPassword(req.body.password, function(){
sanitizedUser.save();
req.flash("success","password resetted");
res.redirect("/login");
});
} else {
req.flash("error","User doesnt exist");
res.redirect("/reset");
}
},function(err){
console.log(err);res.redirect("/");
});
});
But i want to compare more than just the username,i want to compare email and name of the user too.And when i add-
User.find({username:req.body.username},{email:req.body.email},{name:req.body.name})and enter some wrong data,the page just keeps reloading rather than showing an error.
What changes should i make to do that?Please help.
Im using express,nodejs,mongodb
You have a redirect loop.
In the case of !sanitizedUser you redirect to the same page res.redirect('/reset').
This is causing your page to keep reloading.
You should change the line:
res.redirect('/reset')
to
res.status(500).send('Some useful error message')
if(req.body.username && req.body.email && req.body.name && req.body.password){
//add logic to hash password below
let hashedPassword = req.body.password;
Model.findOneAndUpdate(
{ username : req.body.username, email : req.body.email, name : req.body.name },
{ "$set" : { password : hashedPassword } },
//if below option "new" is set to true, call back will return new document else it will return old document beforeupdate
{ new : true },
//call back
function(err, person){
if(err){
//err found
res.send(err);
}
else{
// no error and also person exists
res.send('Password successfully updated..');
}
}
);
}
else{
res.send('Please provide all details..');
}
I am writing an API that takes data and stores it into DB, also edits it and deletes it, the add/delete works fine, but when I update I want to be able to update only certain attributes, and if the user doesn't send an attribute I want the code to keep the data already in the DB. Instead if I don't send an attribute it's overwritten by empty data.
Here's an example:
router.post('/update', function (req, res) {
var first_name = req.body.first_name,
last_name = req.body.last_name,
email = req.body.email,
phone_number = req.body.phone_number,
clas = req.body.clas,
subject = req.body.subject,
teacher_id = req.body.teacher_id;
req.assert('teacher_id', 'Invalid teacher_id').notEmpty();
var errors = req.validationErrors();
if (errors) {
res.json(400, {success: false, message: "please enter your teacher_id "});
return;
}
Teacher.findOne({_id: teacher_id}, function (err, teacher) {
if (err) {
console.log(err);
} else {
teacher.first_name = first_name != null || first_name
!= undefined ? first_name : teacher.first_name;
teacher.last_name = last_name != null || last_name
!= undefined ? last_name : teacher.last_name;
teacher.email = email != null || email
!= undefined ? email : teacher.email;
teacher.phone_number = phone_number != null || phone_number
!= undefined ? phone_number : teacher.pickup_points;
teacher.clas = clas != null || clas
!= undefined ? clas : teacher.clas;
teacher.subject = subject != null && subject
!= undefined ? subject : teacher.subject;
teacher.save(function (err, teacher) {
if (err) {
console.log(err);
} else {
res.json({success: true, message: "teacher successfully updated"});
}
});
}
});
});
This may not be the only way, but I use the Lodash library to make this kind of thing easy. I also use the 'param' route to populate the request with my object for me to save callbacks.
const _= require("lodash ")
router.param("teacherId", function(id, req,res,next){
Teacher.findOne({_id : id}, function(e, teacher){
//add error handling of your choice
req.teacher = teacher;
next();
})
})
router.put("teacher/:teacherId", function(req,res){
var updates = _.pick(req.body, ["name","age"]; // whitelist allowed attribute names to change
var teacher = _.merge(req.teacher, updates);
teacher.save(function(e){
// handle your response.
})
})
/* Edit */
Also, please note that in this solution, I'm assuming the use of (reasonably standard) REST-formatted routes. If you want to keep using router.post('/update'... that's fine, but you won't be able to separate out the teacher query like I did.
The advantage of the way I did it is that anytime you want to find a thing first (e.g. viewing a teacher, deleting a teacher, etc), you have the logic for doing so in one place, and don't have to repeat it in other handlers.
This method is a little different from what you are doing but I'd recommend this way to you so that you can keep your document models in one place and call them whenever you need them. Validations part can be handled using a separate module which is for mongoose.
The way you are updating it expects other variables to be assigned as it is, like I am fetching the document using findone then objectname.value = newobjectname.value
A simple way to solve this would be using findoneandupdate.
modelname.findOneAndUpdate({
primarykey: primarykey
}, {
$set: {
nameindocument: valuetobereplace
//you can add here values that you would like to change
}
}, {
new: true //new:true here helps to get updated object in return
}, function(err, doc) {
if (err) {
console.error("Error:" + err);
} else {
}
});
This code pulls field values from a form, and I can see the values being passed properly when I debug in the console. However, due to the extra object added with my first value, Meteor throws a sanitize error...any ideas?
Template.sendInvitationModal.events({
'submit form': function submitForm(event, template) {
event.preventDefault();
var firstName = template.find("[name='firstName']").value,
lastName = template.find("[name='lastName']").value,
email = template.find("[name='emailAddress']").value,
store = template.find("[name='store'] option:selected").value,
position = template.find("[name='position'] option:selected").value,
roles = template.find("[name='roles'] option:selected").value;
debugger;
if (email && roles && position !== "") {
Meteor.call("sendInvitation", {
firstName: firstName,
lastName: lastName,
email: email,
store: store,
position: position,
roles: roles
}, function (error, response) {
if (error) {
toastr["warning"]( error.reason);
} else {
$("#send-invitation-modal").modal('hide');
$('.modal-backdrop').hide();
toastr["success"]( "Invitation sent!" );
}
});
} else {
toastr["warning"]( "Please set an email and at least one user type!" );
}}});
This is the value being passed with firstName when calling "sendInvitation"
firstName = "Richard", template = B…e.TemplateInstance {view: B…e.View, data: Object, firstNode: div#send-invitation-modal.modal.fade.in, lastNode: div#send-invitation-modal.modal.fade.in, _allSubsReadyDep: T…r.Dependency…}
And then Meteor throws a sanitize error. Any suggestions?
We managed (thanks Ryan Glover for your assistance) to find a solution by simply converting the variables assignment from the values pulled out of the form into an object instead. Solved it! So the correct code is...
Template.sendInvitationModal.events({
'submit form': function submitForm(event, template) {
event.preventDefault();
var data = {
firstName: template.find("[name='firstName']").value,
lastName: template.find("[name='lastName']").value,
email: template.find("[name='emailAddress']").value,
store: template.find("[name='store'] option:selected").value,
position: template.find("[name='position'] option:selected").value,
roles: template.find("[name='roles'] option:selected").value
};
if (data.email && data.roles && data.position !== "") {
Meteor.call("sendInvitation", data, function (error, response) {
if (error) {
toastr["warning"]( error.reason);
} else {
$("#send-invitation-modal").modal('hide');
$('.modal-backdrop').hide();
toastr["success"]( "Invitation sent!" );
}
});
} else {
toastr["warning"]( "Please set an email and at least one user type!" );
}
}
});
I have this code :
user.findOne( { 'email' : email }, function( err, User )
{
if ( err )
{
return done(err);
}
if ( !User )
{
return done(null, false, { error : "User not found"});
}
if ( !User.hasOwnProperty('local') || !User.local.hasOwnProperty('password') )
{
console.log("here: " + User.hasOwnProperty('local')); // displays here: false
}
if ( !User.validPass(password) )
{
return done(null, false, { error : "Incorrect Password"});
}
return done(null, User);
});
Since the app supports other kinds of authentication, I have a user model that has nested object called local which looks like
local : { password : "USERS_PASSWORD" }
So during login I want to check whether the user has provided a password but I encountered this interesting problem.
My test object looks like this:
{ _id: 5569ac206afebed8d2d9e11e,
email: 'test#example.com',
phno: '1234567890',
gender: 'female',
dob: Wed May 20 2015 05:30:00 GMT+0530 (IST),
name: 'Test Account',
__v: 0,
local: { password: '$2a$07$gytktl7BsmhM8mkuh6JVc3Bs/my7Jz9D0KBcDuKh01S' } }
but console.log("here: " + User.hasOwnProperty('local')); prints here: false
Where did I go wrong?
It's because the document object you get back from mongoose doesn't access the properties directly. It uses the prototype chain hence hasOwnProperty returning false (I am simplifying this greatly).
You can do one of two things: use toObject() to convert it to a plain object and then your checks will work as is:
var userPOJO = User.toObject();
if ( !(userPOJO.hasOwnProperty('local') && userPOJO.local.hasOwnProperty('password')) ) {...}
OR you can just check for values directly:
if ( !(User.local && User.local.password) ) {...}
Since neither properties can have a falsy value it should work for testing if they are populated.
EDIT: Another check I forgot to mention is to use Mongoose's built in get method:
if (!User.get('local.password')) {...}
If you only need the data and not the other Mongoose magic such as .save(), .remove() etc then the most simple way would be to use .lean():
user.findOne( { 'email' : email }, function( err, User ).lean()
{
if ( err )
{
return done(err);
}
if ( !User )
{
return done(null, false, { error : "User not found"});
}
if ( !User.hasOwnProperty('local') || !User.local.hasOwnProperty('password') )
{
console.log("here: " + User.hasOwnProperty('local')); // Should now be "here: true"
}
if ( !User.validPass(password) )
{
return done(null, false, { error : "Incorrect Password"});
}
return done(null, User);
});
You can also detach the returned JSON from MongoDB Schema - JSONuser = JSON.parse(JSON.stringify(User)) - and then use JSONuser freely getting, changing or adding, any of its properties.