I am using passportjs to allow user login via linkedin.
The required result is that I will get their email, name and last name
This is my code
exports.linkedInLogin = function( req, res, next ){
var widgetId = req.params.widgetId;
passport.use(new LinkedInStrategy({
consumerKey: conf.linkedIn.apiKey,
consumerSecret: conf.linkedIn.secretKey,
profileFields: ['id', 'first-name', 'last-name', 'email-address', 'headline'],
callbackURL: req.absoluteUrl('/backend/widgets/' + widgetId + '/login/linkedin/callback')
},
function(token, tokenSecret, profile, done) {
logger.info('linkedin logged in success',arguments);
}
));
passport.authenticate('linkedin')( req, res, next );
};
exports.linkedInLoginCallback = function( req, res ){
logger.info('linkedin login callback',req.query);
res.send(req.query);
};
I have 2 questions
First Question
This function is never invoked - why?
function(token, tokenSecret, profile, done) {
logger.info('linkedin logged in success',arguments);
}
Second Question
How should I get the user's email? I can't seem to find it anywhere.
EDIT :
After reading another Q/A I change the 'authenticate' line to
passport.authenticate('linkedin', { scope: ['r_basicprofile', 'r_emailaddress'] })( req, res, next );
and change callack to
exports.linkedInLoginCallback = function( req, res ){
var request = require('request');
logger.info(req.query.oauth_token);
var options =
{
url: 'https://api.linkedin.com/v1/people/~/email-address',
// url: 'https://api.linkedin.com/v1/people/~',
headers: { 'x-li-format': 'json' },
qs: { oauth2_access_token: req.query.oauth_token } // or &format=json url parameter
};
request(options, function ( error, r, body ) {
logger.info('hello',error, body);
res.send( { 'query' : req.query, 'error' : error, 'body' : body} );
if ( r.statusCode != 200 ) {
return;
}
try {
logger.info(body);
}
catch (e) {
return;
}
});
// logger.info('linkedin login callback',req);
};
But I keep getting Invalid access token.
After a day of reading and tutorialing, it seems that the answer is much simpler than I thought.
There was one thing I had to change - and it seems I did not mention it in the question.
The route mapping to the callback.
Before, it was
app.get('/backend/widgets/:widgetId/login/linkedin/callback',controllers.widgetLogin.linkedInLoginCallback);
but it seems that if I simply add a middleware here
passport.authorize('linkedin')
so now it looks like this
app.get('/backend/widgets/:widgetId/login/linkedin/callback', passport.authorize('linkedin'), controllers.widgetLogin.linkedInLoginCallback);
I get all the information I need on req.account. So my callback is now
exports.linkedInLoginCallback = function( req, res ){
logger.info('linkedin login callback',req.account);
res.send(200,req.account);
};
So all the magic relies on the callback's middleware!
Alternatively, if the routing is not under your control, you could change your callback to the following:
exports.linkedInLoginCallback = function( req, res ){
passport.authorize('linkedin')(req, res, function(){
logger.info('linkedin login callback',req.account);
res.send(200,req.account);
});
};
Which is my drug of choice.. seems like a simple function and callback without middleware cr%$.
Related
So, I'm not 100% why this isn't working as intended. I have an Edit Profile React component (I'm learning how to build a SSR-based application currently, using the MERN stack) - but when I submit the edit, I get an error that "user.save is not a function - Code:
From the routes:
router.route('/api/users/:userId')
.get(authCtrl.requireSignin, userCtrl.read)
.put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update)
.delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove)
The API Helper:
const update = async (params, credentials, user) => {
try {
let response = await fetch('/api/users/' + params.userId, {
method: 'PUT',
headers: {
"Accept": 'application/json',
Authorization: 'Bearer ' + credentials.t
},
body: user
})
return await response.json()
} catch (err) {
console.log(err)
}
}
And lastly, the actual controller, that handles all the logic behind the update: (This function sanitizes the password information before passing it back to the client, hence the undefineds)
const update = (req, res) => {
let form = new formidable.IncomingForm()
form.keepExtensions = true
form.parse(req, async (err, fields, files) => {
if (err) {
return res.status(400).json({
error: "Photo could not be uploaded"
})
}
let user = req.profile
user = extend(user, fields)
user.updated = Date.now()
if(files.photo){
user.photo.data = fs.readFileSync(files.photo.path)
user.photo.contentType = files.photo.type
}
try {
await user.save()
user.hashed_password = undefined
user.salt = undefined
res.json(user)
} catch (err) {
console.log(err)
return res.status(400).json({
error: errorHandler.getErrorMessage(err)
})
}
})
}
This isn't a production level application, just for me learning how to do this from scratch (without CRA, and all contained in one project using SSR)
EDIT: After some digging, console.logs and console.dirs, I discovered that the updates passed from the component aren't even being passed to the controller. The stale data (from the database) are logging, but req.profile is completely empty. I may re-visit this code completely and make some major changes to it.. All part of learning, right?
Here are the auth methods that were requested (I'm using Session Storage for now, but that may change to localStorage):
import User from '../models/user.model'
import jwt from 'jsonwebtoken'
import expressJwt from 'express-jwt'
import config from './../../config/config'
const signin = async (req, res) => {
try {
let user = await User.findOne({email: req.body.email})
if (!user) {
return res.status(401).json({error: "User not found"})
}
if (!user.authenticate(req.body.password)) {
return res.status(401).send({error: "Email and Password do not match"})
}
const token = jwt.sign({_id: user._id}, config.jwtSecret)
res.cookie('t', token, {expire: new Date() + 9999})
return res.json({
token,
user: {
_id: user._id,
name: user.name,
email: user.email
}
})
} catch (err) {
return res.status(401).json({error: "Could not sign in"})
}
}
const signout = (req, res) => {
res.clearCookie('t')
return res.status(200).json({message: "Signed out"})
}
const requireSignin = expressJwt({
secret: config.jwtSecret,
algorithms: ['sha1', 'RS256', 'HS256'],
userProperty: 'auth'
})
const hasAuthorization = (req, res, next) => {
const authorized = req.profile && req.auth
&& req.profile._id == req.auth._id
if (!(authorized)) {
return res.status(403).json ({error: "User is not authorized"})
}
next()
}
export default {
signin,
signout,
requireSignin,
hasAuthorization
}
Possible places where you could have a mistake: (code is not shown)
If your req.profile isn't a mongoose object, this won't work
let user = req.profile
From your other posts, I think you're probably getting req.profile from your jwt. That means this is not a mongoose object. What you'll need to do is either:
As you mentioned, use findByIdAndUpdate passing the id and the object to be updated. Note that if you have a mongoose middleware for save it won't run here
Do a user = await User.findById(id), update the user as you see fit, then use user.save. This gives you a bit more control over it, but runs 2 operations.
This has been solved.. My issue was apparently with the form not passing the request body properly to the API, which was caused by a faulty install of a dependency. Once I got that solved, the rest fell into place, and I can now do what I need to do with ease..
Thank you all who attempted to troubleshoot this with me.
I have the following express routes:
router.get(
"/auth/google/callback",
passport.authenticate("google", { failureRedirect: "/error", session: false }),
function(req, res) {
var token = req.user.token;
res.redirect("/getData?token=" + token);
}
);
router.get('/getData', function(req, res) {
var token = req.query.token;
console.log(token)
request('https://www.googleapis.com/analytics/v3/management/accounts?access_token=' + token,
function (error, response, body) {
let views = []
JSON.parse(body).items.forEach(view => {
views.push({
name: view.webPropertyId + ' - ' + view.name + ' (' + view.websiteUrl + ')'
})
})
res.redirect("/test?data="+ views);
});
})
router.get('/test', function(req, res) {
var testdata = req.user.data;
console.log(testdata)
res.send("ok")
})
I have two console log console.log(token) and console.log(testdata). The first console log is working, the second one doesn't return me anything. I've tried many ways to pass the data from the request to the test page and I'm not able to to it. why?
It looks like you are trying to pass the data through a query parameter, and then trying to read the data through req.user. This looks to me like just a syntax mistake. Change req.user.data with req.query.data.
I am trying to add a cookie to the response object (res) inside a different get request. However, if I try the following it will give me an error where I cannot set headers after they are sent. I assume by calling "request" I send the header already, how can I achieve adding a cookie to the response object using data from the separate get request? The synchronous nature won't let me save the data outside of the get request either. I am using request module from npm btw, thank you.
/* GET home page. */
router.get('/', function(req, res, next) {
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
var status = "Not currently logged in.";
if (req.cookies.token !== undefined) {
status = "Currently Logged in.";
}
if (req.cookies.email !== undefined) {
request('https://localhost:44338/api/customer/' + req.cookies.email
{json: true}, (err, response, body) => {
res.cookie('user', body[0].customerID, {maxAge: 9000000});
//console.log(body);
});
}
res.render('index', { title: 'Mighty Morphin Store', data: "", status: status});
});
You have to send your response in the callback function after setting cookies.
/* GET home page. */
router.get('/', function(req, res, next) {
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
var status = "Not currently logged in.";
if (req.cookies.token !== undefined) {
status = "Currently Logged in.";
}
if (req.cookies.email !== undefined) {
request('https://localhost:44338/api/customer/' + req.cookies.email
{json: true}, (err, response, body) => {
res.cookie('user', body[0].customerID, {maxAge: 9000000});
//console.log(body);
res.render('index', { title: 'Mighty Morphin Store', data: "", status: status});
});
}
});
I am new to Hapi.js.I am using "hapi-auth-jwt2" module for authentication token and role verification. I set the scope and sent that scope from the callback of validateFunc . It will worked very well for checking te role based authentication. But i want the result i am returning from the validateFunc but don't know where i can get that.
validateFunc: function (token, request, callback) {
Async.auto({
session: function (done) {
Session.findByCredentials(token.sessionId, token.sessionKey, done);
},
user: ['session', function (results, done) {
if (!results.session) {
return done();
}
User.findById(results.session.user, done);
}],
}, (err, results) => {
if (err) {
return callback(err);
}
if (!results.session) {
return callback(null, false);
}
results.scope = token.scope;
callback(null, Boolean(results.user), results);
});
}
});
};
`
It verify the scope or Role in the domain i.e:-
routeOptions: {
scope:{
createScope:"admin"
},
create: {
pre : function(payload, Log){
console.log("preee runnnig........");
console.log(payload);
}
}
I am getting the payload Json what i am sending from the client side but i want the results i am sending from the callback of validateFunc, because i want to use that data here in pre prior to send the request.I am working on implicitly created API via Rest Hapi Module.
So how can i get that datain pre hooks from the validateFunc . Any help is much appreciated.
Thanks
This is actually a feature that is being worked on and hopefully will be done soon.
For now, you can omit the generated create endpoint and replace it with your own in order to access the request object.
The resulting code would look something like this:
'use strict';
const RestHapi = require('rest-hapi');
module.exports = function (server, mongoose, logger) {
server.route({
method: 'POST',
path: '/pathName',
config: {
handler: function(request, reply) {
/* modify request.payload here and access auth info through request.auth.credentials */
const Model = mongoose.model('modelName');
return RestHapi.create(Model, request.payload, logger)
.then(function (result) {
return reply(result);
})
.catch(function (error) {
return reply(error);
});
},
tags: ['api'],
plugins: {
'hapi-swagger': {}
}
}
});
};
I have an authentication middleware I will like to test, the middleware makes an external call to an authentication service and based on the returned statusCode either calls the next middleware/controller or it returns a 401 status. Something like what I have below.
var auth = function (req, res, next) {
needle.get('http://route-auth-service.com', options, function (err, reply) {
if (reply.statusCode === 200) {
next();
} else {
res.statusCode(401)
}
})
}
I use SinonJS, nock, and node-mocks-http for testing and my simple test is as below.
// require all the packages and auth middleware
it('should login user, function (done) {
res = httpMocks.createResponse();
req = httpMocks.createRequest({
url: '/api',
cookies: {
'session': true
}
});
nock('http://route-auth-service.com')
.get('/')
.reply(200);
var next = sinon.spy()
auth(res, req, next);
next.called.should.equal(true); // Fails returns false instead
done();
});
The test always fails and returns false, I feel that the reason is because the needle call is asynchronous, and before the call returns the assertion part is reached. I have been working on this all day, I need help please.
you need to split the test setup away from the assertion
// this may be "beforeEach"
// depends on what testing framework you're using
before(function(done){
res = httpMocks.createResponse();
req = httpMocks.createRequest({
url: '/api',
cookies: {
'session': true
}
});
nock('http://route-auth-service.com').get('/').reply(200);
var next = sinon.spy();
auth(res, req, function() {
next();
done();
});
});
it('should login user', function () {
next.called.should.equal(true); // Fails returns false instead
});