I have an express application where administrators should receive an instant notification when a new user registers into the database. Note that I am using Passport to log users in via the Google strategy:
passport.use(new GoogleStrategy({
clientID : configAuth.googleAuth.clientID,
clientSecret: configAuth.googleAuth.clientSecret,
callbackURL : configAuth.googleAuth.callbackURL,
},
function(token, refreshToken, profile, done){
process.nextTick(function(){
// try to find a user based on their google id
User.findOne({ 'google.id' : profile.id }, function(err, user){
if(err)
return done(err)
if(user){
// if a user is found, log them in
return done(null, user)
} else {
// if the user isn't in our database, create a new user
var newUser = new User({
'google.id': profile.id,
'google.token': token,
'google.name': profile.displayName,
'google.email': profile.emails[0].value, //pull the first email
'google.active': 0,
'google.level': 0
})
// save the user
newUser.save(function(err){
if(err)
throw err
return done(null, newUser)
})
// Give the new user the role of 'inactive'
acl.addUserRoles(newUser.id, 'inactive', function(err){})
// Email a notice to the new user that they have to be accepted by an administrator.
...
// Send an email to all "admin-level" users to activate this user.
acl.roleUsers('admin', function(err, users){
// Loop through all admin users
users.forEach(function(value){
// Find each individual admin user by _id
User.findOne({ '_id' : value }, function(err, user){
if(err)
return done(err)
// Send this admin an internal mail (IMail)
var iMail = new IMail()
iMail.body = newUser.google.name + ' needs approval.',
// Send each administrator a notification that a new user needs approval
iMail.recipients.push(user.id)
iMail.save(function(err){
if(err)
return err
console.log("IMail was sent to notify admin " + user.google.name + " to approve " + newUser.google.name + " as a system user.")
})
})
})
})
}
})
})
}))
Within the
iMail.save(function(err){
...
})
block, I know that some type of event needs to be fired which would somehow alert each admin who is currently logged in. Unfortunately, this is where my understanding breaks down at this point. How do I immediately notify an admin of this new notification? I'd like it to be like Gmail, for example, when the Inbox automatically displays (1) when a new email is sent.
As a side note, I've read up on the Publisher/Subscriber pattern (https://joshbedo.github.io/JS-Design-Patterns/) and think this might set me on the right track, I'm just not sure exactly how to implement it. A little push in the right direction from somebody would be appreciated.
#Maximuf Kingzy is right you need to send a notification to admin users and since you want them to get this notification when online then you can use sockets to send them but it can be tough to integrate it inside express sometimes.
One other option :
You can save notifications in database and create an express route that retrieve those notifications. when administrator logs in you just have to retrive those notifications. Or if you want a taste of real-time without real-time you can use setInterval to retrieve those notifications every X seconds and then show them to the administrator user.
Express really simple route (I mean really simple):
app.get('/imail', (req, res) => {
Imail.find({}, (err, imails) => {
if (err) {
return res.status(400).end();
}
res.json(imails);
});
});
And in browser :
setInterval(() => {
fetch('http://yourserver:serverport/imail').then((response) => {
const contentType = response.headers.get("content-type");
if (contentType && contentType.indexOf("application/json") !== -1) {
return response.json().then(json => {
return response.ok ? json : Promise.reject(json);
});
} else {
return response.text().then(text => {
return response.ok ? text : Promise.reject(text);
});
}
}).then((imails) => {
// do what you want with imails
}).catch((err) => {
// error
});
}, 15000);
Related
I'm having a big issue that I'm not sure is just a code design issue or I'm trying to do something that makes no sense.
I've implemented a local sign up using passport for authentication and JWT for route access and permissions. I send the token to the front end and save it to LocalStorage using AngularJS.
Everything there works and makes sense. But with twitter, I can't figure out how to implement the same strategy for getting the user logged in.
I've set up everything on the Twitter side, use passport for login and registering. But I don't see how it is possible to send the token to the front end because I can only use a GET request to receive the information from the Twitter API.
I redirect to Twitter login, redirect to the callback URL. But what comes after that? I have some relevant code that just returns the JWT. What I'm asking is how can I pass that to Angular in the best way possible?
Routes:
app.get('/login/twitter', passport.authenticate('twitter'));
app.get('/login/twitter/callback', function(req, res) {
passport.authenticate('twitter' , function(err, user, info) {
if(err) {
res.json({
'message': err
});
}
var token;
token = user.generateJwt();
res.status(200);
res.json({
"token" : token
});
})(req, res);
});
Passport:
passport.use(new TwitterStrategy({consumerKey: auth.twitterAuth.consumerKey, consumerSecret: auth.twitterAuth.consumerSecret, callbackURL: auth.twitterAuth.callbackURL}, function(req, key, keySecret, profile, done) {
User.findOne({'twitter.id' : profile.id}, function(err, user) {
if(err) {
return done(err);
}
if(user) {
if(!user.twitter.token) {
user.twitter.token = key;
user.twitter.username = profile.username;
user.twitter.displayName = profile.displayName;
user.save(function(err) {
if(err) {
res.json({
'message': err
});
}
return done(null, user);
});
}
return done(null, user);
}
let newUser = new User();
newUser.twitter.id = profile.id;
newUser.twitter.token = key;
newUser.twitter.username = profile.username;
newUser.twitter.displayName = profile.displayName;
newUser.twitter.registerDate = Date.now();
newUser.save(function(err) {
if(err) {
res.json({
'message': err
});
}
return done(null, newUser);
});
});
}));
The best way to do this from my experience is to pass the information you want to store in the JWT to the url and store the token in localStorage on the callback page. Once the user identifier is stored in the browser, redirect to the desired page.
In my project I have two roles
they are
admin and
user
and my requirment is, i have to restrict the users tasks like admin can only add users but eventhough logged as user i can able to post. I has to overcome this problem can any one help me. I am using express.js, mongodb server side, client side angular.js
Here is my code
adduser function:
exports.adduser = function(req, res) {
delete req.body.roles;
var user = new User(req.body);
var message = null;
// Add missing user fields
user.provider = 'local';
user.displayName = user.firstName + ' ' + user.lastName;
user.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
//send mail to user
agenda.now('New_User_Create_Notify', {data:user.username});
res.jsonp(user);
}
});
};
route:::
app.route('/auth/adduser').post(users.adduser);
if you have got users roles set
you can do this in adduser function
if(req.body.roles.indexOf("admin") == -1){
return res.status(403).send({
message:"user doesnt have required permision"
});
}else{
user.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
//send mail to user
agenda.now('New_User_Create_Notify', {data:user.username});
res.jsonp(user);
}
}
I am using passport.js + passport-facebook-token to secure my API build with Strongloop's Loopback Framework.
Why is passport serializing the deserialized user again after it has successfully been deserialized? Also the passport.authenticate method is called on every request! What am I doing wrong?
Here is node's log:
deserializeUser, id: XXXXXXXXXXXXXXXX
User found.
serializeUser, id: XXXXXXXXXXXXXXXX
GET /api/events?access_token=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 304 182ms
Here is the js code:
passport.use(new FacebookTokenStrategy({
clientID: XXXXXXXXXXXXXXXX,
clientSecret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX'
},
function(accessToken, refreshToken, profile, done) {
//check user table for anyone with a facebook ID of profile.id
User.findOne({
'facebookId': profile.id
}, function(err, user) {
if (err) {
return done(err);
}
if (user) {
console.log("User found.");
return done(err, user);
} else {
console.log("User not found.");
User.create({
email: profile.emails[0].value,
facebookId: profile.id,
password: 'secret'
}, function(err, user) {
console.log(user.id);
console.log(user.email);
console.log(user.facebookId);
console.log("User created");
return done(err, user);
});
}
});
}));
passport.serializeUser(function(user, done) {
console.log('serializeUser, id: ' + user.facebookId);
done(null, user.facebookId);
});
passport.deserializeUser(function(id, done) {
console.log('deserializeUser, id: ' + id);
User.findOne({
'facebookId': id
}, function(err, user) {
if (!err) {
done(null, user);
} else {
done(err, user);
}
});
});
Regarding your question about why passport.authenticate is called on every request, it is because you defined it as a middleware, probably before any routing logic happens.
If you have private and public sections on your app, you could do something like that :
// Define a specific that will handle authentication logic
app.get("/auth", passport.authenticate('facebook-token',function(){...});
// Public sections which do not require authentication
app.get("/public1",...);
app.post("/public2",...);
// Private sections which do require authentication
app.get("/private1", function(req,res,next){
if (req.isAuthenticated()){ // Check if user is authenticated
// do things...
}else{ // Wow, this guy is not authenticated. Kick him out of here !
res.redirect("/auth");
}
});
Now, if you have multiple private sections, you'll probably find it a bit tidious to do the same thing for each private section.
You could define a custom function that will check if the user is authenticated, and allow the request to proceed if he is.
Something like
function isThisGuyAuthenticated(req,res,next){
if (req.isAuthenticated()){
return next(); // Ok this guy is clean, please go on !
}else{
res.redirect("/auth"); // This guy is shady, please authenticate !
}
}
And use it like :
app.get("/private1",isThisGuyAuthenticated, doCrazySecretStuff); // doCrazySecretStuff will not be called if the user is not authenticated
app.get("/private2", isThisGuyAuthenticated, getCocaColaRecipe);
app.get("/private3", isThisGuyAuthenticated, flyToMars);
app.get("/public", showInfo); // showInfo will be called whether the user is authenticated or not
Now, if your app only has private sections, you could avoid repeating calls to isThisGuyAuthenticated by defining it as middleware (but not by defining passport.authenticate itself as a middleware !);
// Endpoint that will be hit is the user is redirected to /auth
// BEWARE it needs to be above the middleware, otherwise you'll end up with an infinite redirection loop
app.get("/auth", passport.authenticate('facebook-token',function(){...});
// Middleware that will be called on every request
app.use(isThisGuyAuthenticated);
// You app's endpoints
app.get("/private1", doCrazySecretStuff); // doCrazySecretStuff will not be called if the user is not authenticated
app.get("/private2", getCocaColaRecipe);
app.get("/private3", flyToMars);
Is that clear ?
EDIT : I mistakenly put the middleware before the "/auth" endpoint. Make sure it's placed after
I'm attempting to authenticate a user using Box.com OAuth2.0. I make the initial call and login which redirects to my callback url with the authorization code. At this point my server handles the callback using passport but for some reason it returns a 302 and redirects to the beginning of the oauth authentication process.
//box authentication routes
app.get('/api/box', passport.authorize('box'));
// the callback after box has authorized the user
app.get('/api/box/callback', passport.authorize('box', {
successRedirect: '/',
failureRedirect: '/login'
})
);
I verified that my route is being called by using my own handler and the request data seems to be correct. Box returns a 200 and the url contains the authorization code.
app.get('/api/box/callback', function(req, res) {
console.log('auth called')
});
This is my passport strategy:
passport.use(new BoxStrategy({
clientID: config.box.clientID,
clientSecret: config.box.clientSecret,
callbackURL: config.box.callbackURL,
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
process.nextTick(function() {
if(!req.user) {
// try to find the user based on their google id
User.findOne({ 'box.id' : profile.id }, function(err, user) {
if (err)
return done(err);
if (user) {
// if a user is found, log them in
return done(null, user);
} else {
// if the user isnt in our database, create a new user
var newUser = new User();
// set all of the relevant information
newUser.box.id = profile.id;
newUser.box.accessToken = accessToken;
newUser.box.refreshToken = refreshToken;
newUser.box.name = profile.name;
newUser.box.email = profile.login;
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
} else {
// user already exists and is logged in, we have to link accounts
var user = req.user;
// update the current users box credentials
user.box.id = profile.id;
user.box.accessToken = accessToken;
user.box.refreshToken = refreshToken;
user.box.name = profile.name;
user.box.email = profile.login;
// save the user
user.save(function(err) {
if (err)
throw err;
return done(null, user);
});
}
});
}
));
Would appreciate any insight as to what might be causing this redirect behavior.
It ended up being an instance of PeerServer that was somehow causing the redirect.
I'm trying to get some basic Google authentication going with PassportJS.
The amount of information on the web seems rather limited and I keep hitting issues so thought I would try here.
I am using the code from https://github.com/mattgaidica/twitter-mongo with a few modifications to use it for Google OAuth (it doesn't use the Twitter keys, it uses passport-google, passport.authenticate('google', ...)).
I keep ending up with this error: 'Error: failed to serialize user into session'
passport.serializeUser(function(user, done) {
console.log(user); //does not have the fields we created earlier, user.uid does not exist.
done(null, user.uid);
});
My Passport Strategy:
passport.use(new GoogleStrategy({
returnURL: "http://localhost:3000/auth/google/callback"
},
function(identifier, profile, done) {
User.findOne({uid: profile.id}, function(err, user) {
if(user) {
done(null, user);
} else {
var user = new User();
user.provider = "google";
user.uid = identifier;
user.name = profile.displayName;
user.save(function(err) {
if(err) { throw err; }
done(null, user);
});
}
})
}
));
The fields of this other user are not the same as the one originally created, what happened to the other user?
I'm a bit lost as to what is going on here, if anyone could let me know what I'm doing wrong I would be very grateful.
In your query, you use profile.id:
User.findOne({uid: profile.id}, function(err, user) { ... });
But that should be identifier.
Also, you're using the OpenID-version of the Passport Google plug-in, which is rather old (and doesn't even work properly on Node 0.10). I'd suggest you use passport-google-oauth instead.