How to encapsulate Koa-Passport? - passport.js

Perhaps this has a very easy answer, yet something is off with my code. Here is what I want to do.
I created a koa2 app using koa-passport, and I want to encapsulate the usage of Passport in a class AuthAdapter (shortened below).
class AuthAdapter {
setup(koaApp) {
koaApp.use(passport.initialize());
passport.use('http-bearer', new PassportHttpBearerStrategy(function(token, done) {
koaApp.log.info('passport: payload request', token);
return done(null, { clientId: 1 });
}));
}
async authroute(ctx, next) {
return passport.authenticate('http-bearer', (error, user, info) => {
if (error) {
ctx.throw(500, 'Authentication Error');
} if (!user) {
ctx.throw(403, 'Authentication Forbidden');
} else {
ctx.log.debug('Passport-Route-Mw: auth ok', { user: user, info: info });
}
})(ctx, next);
}
}
And I have an API class and declared the routes like:
static _setupRoutes(koaApp, koaRouter) {
koaRouter
.get('getter', '/getter', koaApp.authAdapter.authroute, MyApi.myGetterMethod);
koaApp
.use(koaRouter.routes())
.use(koaRouter.allowedMethods());
}
... MyApi
static async myGetterMethod(ctx) {
...
}
Now the problem: setup and setupRoutes are getting called correctly. Passport verify is executing, adn the authroute method is also executing.
My problem is that myGetterMethod is not.
My suspicion is that by encapsulating the passport.authenticate, the "return" is not running as it should.
How should that be implemented? await?
UPDATE: thanks for the answer below, indeed that was the solution, so my method ended up like this:
async function authenticate(ctx, next) {
// https://github.com/rkusa/koa-passport/issues/125#issuecomment-462614317
return passport.authenticate('http-bearer', { session: false }, async(err, user, info) => {
if (err || !user) {
ctx.throw(401, 'passport-auth: user unauthenticated');
}
await next();
})(ctx);
};

i think you need call next in callback, because koa-passport will stop call next when you provide custom callback
koa-passport
line 94: call custom callback will always call resolve(false)
line 149: if resolve(cont !== false) call next
as the result, use custom callback will stop chain. you need call next in your callback.

Related

same base url with different functionalities nodejs

app.get('/api/v3/app/events', async function (req, res){
try {
let unique_id=req.query.id
console.log(unique_id)
database.collection('event').findOne(ObjectId(unique_id),function(err,data){
if(err){
res.json({error:"no data found with specified id"})
}
console.log(data)
res.json(data)}
)
} catch (error) {
console.log("internal error")
res.json({error:error})
}
})
app.get('/api/v3/app/events', function(req,res) {
try {
let limit=parseInt(req.query.limit)
let page =parseInt(req.query.page)
console.log(database.collection('event').find().sort({$natural: -1}).limit(limit).skip(page-1).toArray((err, result) => {
console.log(result);
})
)
} catch (error) {
console.log(error)
return res.json({error:"internal error "})
}
})
I have to perform these functionalities with same base url i.e '/api/v3/app/events'.
Please help . I am successful as I change the names of endpoints, but keeping them same , I gets null and undefined on the console
I'm not sure why do you need both to use same URL, but you have two choices either use a single endpoint with both of the logics. The other option would be to use the next middleware based on the id query param like this:
app.get('/api/v3/app/events', async function (req, res, next){
if (!req.query.id) {
next();
return;
}
// Rest of your endpoint logic
}
Each endpoint in Express is considered as middleware. This means that response won't be sent back, but calling the next() middleware instead and allow other middlewares to be executed. You can use same if or modify it based on your login.

How to return error message objects to the client in Express?

I have this block of code:
router.post('/users/login', async (req, res) => {
try {
const { email, password } = req.body
const user = await User.findByCredentials(email, password)
console.log(user) //false
if (!user) {
throw new Error('Login failed! Check authentication credentials')
}
const token = await user.generateAuthToken()
res.status(200).json({ user, token })
} catch (err) {
console.log(err) //Error: Login failed! Check authentication credentials at C:\Users...
res.status(400).json(err)
}
})
It works all fine until there is no Error. When error occur (user is false) in Postman I have only empty object returned {}. Also with res.status(400).json({error: err}) it gives me { "err": {} }.
I want to receive object like this { "error": "Login failed! Check authentication credentials" }
Error objects don't serialize well in JSON because some of their properties are non-enumerable and thus JSON.stringify() does not include them when res.json(err) uses it.
That's why res.json(err) doesn't show what you want.
Possible reasons why non-enumerable properties are not included is that they may contain stack traces and other information that is not meant to be sent back to the client. That's just a by-product of how the Error object works and how JSON.stringify() is implemented and res.json() just inherits those issues and the interaction between the two. I've never understood why the main .message property is non-enumerable as that has never made sense to me, but it is.
There are a number of possible work-arounds:
You could add your own .json2() method that includes all properties of an Error object (even non-enumerable ones).
You could use the Express error handling scheme where you call next(err) and you supply a centralized error handler that gets called and you can do your own serialization of the error response there.
You could make a subclass of Error that populates enumerable properties that will show up in JSON and use that.
Option #3 could look like this:
class RouteError extends Error {
constructor(msg, statusCode = 500) {
super(msg);
// define my own enumerable properties so they
// will show up in JSON automatically
this.error = msg;
this.statusCode = statusCode;
}
}
router.post('/users/login', async (req, res) => {
try {
const { email, password } = req.body
const user = await User.findByCredentials(email, password)
console.log(user) //false
if (!user) {
throw new RouteError('Login failed! Check authentication credentials', 401)
}
const token = await user.generateAuthToken()
res.status(200).json({ user, token })
} catch (err) {
console.log(err) //Error: Login failed! Check authentication credentials at C:\Users...
res.status(err.statusCode).json(err);
}
});
This example would generate this response:
{"error":"Login failed! Check authentication credentials","statusCode":401}
JSON.stringify can't capture non-enumerable object properties as jfriend00 mentions but there are some pretty direct solutions that seem worth mentioning.
One way is to create the correct object yourself using the err.message property, which contains the string provided to the Error constructor:
app.get("/foo", (req, res) => {
try {
throw Error("hello world");
}
catch (err) {
res.status(400).json({error: err.message});
}
});
Another approach if you really can't touch your res.json call is to throw an object set up how you like it. I don't like this because you lose stack trace information (see: 1, 2, 3), but it exists:
app.get("/foo", (req, res) => {
try {
throw {error: "hello world"};
}
catch (err) {
res.status(400).json(err);
}
});
In both cases, the client sees
{
error: "hello world"
}
You can throw an error in case the user is null or empty. Another thread answers a similar question.
Node.js with Express - throw Error vs next(error)
You should use the message property of the Error class for making it enumerable. You don't need to use other property for error message like the below answer using error propery.
I searched for implementation of Error class in 'core-js'
https://github.com/zloirock/core-js/blob/master/packages/core-js/internals/wrap-error-constructor-with-cause.js#:~:text=if%20(message%20!%3D%3D%20undefined)%20createNonEnumerableProperty(result%2C%20%27message%27%2C%20message)%3B
So, if you want message should be displayed in console or delivered by JSON you should use the following code
class CustomError extends Error {
constructor (message) {
// super(message)
super() // avoid message's non-enumerable property
this.name = this.constructor.name
this.message = message;
// needed for CustomError instanceof Error => true
Object.setPrototypeOf(this, new.target.prototype);
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor)
}
}
}
Then, you can send with this code
const err = new CustomError('Custom Error...');
...
res.send(err);

Stub not getting called inside .then()

I am working on unit testing an Express application. I am trying to mock up my external dependencies (Express, database, etc) and am close to a break through.
However, I am having issues with stubs not being called from within a .then() inside my business logic.
A couple of the methods I am attempting to test are the following:
/**
* Ping
* A simple endpoint to poke for testing.
* #arg {*} request - Incoming request
* #arg {*} response - Outgoing response
* #arg {*} next - Next route in series
*/
ping: (request, response, next) => {
let elapsed = Date.now() - request.start;
response.status(200).json({
request_started: new Date(request.start).toISOString(),
request_duration: `${elapsed} milliseconds`
});
},
/**
* Registration
* Allows for registration of a user name and password.
* #arg {*} request - Incoming request
* #arg {*} response - Outgoing response
* #arg {*} next - Next route in series
*/
register: (request, response, next) => {
let username = request.body.username;
let password = request.body.password;
this.model.registerUser(username, password).then(ret => {
if (ret) {
response.status(201).send("Created");
} else {
response.status(400).send("Error processing registration request. Please try again.");
}
}).catch(err => {
response.status(400).send("Error processing registration request. Please try again.");
});
}
The model in register returns a Promise that wraps a call to a database and replies based on the outcome. I have a mock of this setup as follows:
mockModel: {
registerUser: sinon.stub().callsFake(function(user, pass) {
if (typeof user !== 'undefined') {
return Promise.resolve(pass === 'pass');
}
}),
getUser: sinon.stub().callsFake(function(user, pass) {
if (typeof user !== 'undefined' && pass === 'pass') {
return Promise.resolve({id: 9999});
} else {
return Promise.resolve(false);
}
})
}
I also have the response object mocked so I can pass it in and determine if it is called correctly:
mockRes: () => {
return {
status: sinon.stub().returnsThis(),
send: sinon.stub(),
json: sinon.stub()
};
}
The problem arises when I get to the tests:
describe('Register() method', function() {
this.beforeEach(function() {
req = mockReq(0, {}, {username: 'user', password: 'pass'});
res = mockRes();
base.BaseRoutes(router, null, base.methods, mockModel);
});
it('Returns a 201 Created message upon success', function() {
base.methods.register(req, res);
chai.expect(res.status).to.have.been.calledWith(201);
chai.expect(res.send).to.have.been.calledWith('Created');
});
});
The test here fails with the following error:
1) Base Methods
Register() method
Returns a 201 Created message upon success:
AssertionError: expected stub to have been called with arguments 201
at Context.<anonymous> (test\spec.js:50:50)
Stepping through with a debugger shows that the method is getting called, yet I'm getting this failure.
Other tests in the same suite that leverage the same mocked request/response work correctly but they don't use Promise calls (example: ping() method)
I suspect that it has something to so with scoping, but I'm not sure where things are going wrong.
After running through this a few more times, I found that it was not a scope issue, but an asynchronous issue. The test case was completing before the Promise resolved/rejected.
The piece that I was missing to close the loop was that my Express route handlers needed to return the Promise that they created to handle the database call:
register: (request, response, next) => {
let username = request.body.username;
let password = request.body.password;
return this.model.registerUser(username, password).then((ret) => {
if (ret) {
response.status(201).send("Created");
} else {
response.status(400).send("Error processing registration request. Please try again.");
}
}, (err) => {
response.status(400).send("Error processing registration request. Please try again.");
});
},
And then the test, perform my assertions in the then() on the returned Promise:
it('Returns a 201 Created message upon success', function(done) {
base.methods.register(req, res).then(x => {
chai.expect(res.status).to.have.been.calledWith(201);
chai.expect(res.send).to.have.been.calledWith('Created');
}).then(done, done);
});
it('Returns a 400 message upon failure', function(done) {
req = mockReq(0, {}, {username: 'user', password: 'fail'});
base.methods.register(req, res).then(x => {
chai.expect(res.status).to.have.been.calledWith(400);
chai.expect(res.send).to.have.been.calledWith(sinon.match.string);
}).then(done, done);
While there were examples of passing the Promise to the test and handling it there and examples on testing Express route handlers in isolation, I couldn't find examples that combined the two and tested it. Hopefully this can be of service to someone else.

Passport.js: Serialization issue with local login strategy

I'm having trouble authenticating a small passport.js example project when I switch over to mongodb instead of a local array.
MongoDB is able to connect and add users from a form on the home page. On login however, there are problems.
Not sure if this matters but on the /login page onload I see 2 red errors in the browser console:
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
login:1
Unchecked runtime.lastError: The message port closed before a response was received.
I put a log statement in the relevant route handler. It does not reach or log to the console.
app.post('/login', function sendUserRecordToAxiosOrRedirect(req, res, next) {
passport.authenticate('local', function controlErrorsOrSuccess(error, userRecord, info) {
console.log('inside pp.auth callback');
if (error) return next(error);
if (userRecord == false) return res.json ({ "error" : "Invalid Username" });
if (userRecord) {
console.log('---- User Found: INFO --------');
console.log(info);
res.redirect('/')
}
})
});
From the above I suspected something is awry with the Strategy I give to passport. The log statement in this one doesn't fire either:
passport.use(new Strategy(
function(username, password, done) {
console.log('inside Strategy');
db.users.findInDatabaseByUsername(username, function (error, userRecord) {
if (error) return done(error);
if (userRecord == false) return done(null, false); // no error, no userRecord
if (userRecord.password !== password) return done(null, false);
return done(null, userRecord);
})
}
));
Since I altered my serialization functions to search the database instead of an array I suspect the problem may lie here. Neither of the log statements here fire either:
const passUserIdOnlyToACallback = function(userRecord, callbackFunction) {
console.log("serializing...");
callbackFunction(null, userRecord.id);
};
passport.serializeUser(passUserIdOnlyToACallback);
function passUserRecordToCallbackById(userId, verifyCallback) {
db.users.findInDatabaseById(userId, function(error, userRecord) {
console.log("deserializing...");
if (error) return verifyCallback(error);
verifyCallback(null, userRecord)
})
}
passport.deserializeUser(passUserRecordToCallbackById);
Everything was working fine initially with the local array code, so my alterations broke something. Does anyone see the error?

A promise was created in a route but not returned from

i tried to find a solution for this warning i get, but none of the suggested approaches worked for me. I don't want to disable this warning, because i believe this warning is there for a reason, but i can't figure out whats the real deal here.
I have the following route defined on my node.js server running express:
// GET all items
router.get('', helpers.loginRequired, function(req, res) {
debug("Get all items");
return knex.select('*').from('routes')
.then((routes) => {
res.send(routes);
return null;
})
.catch((err) => {
debug("error getting routes");
res.send({ error: "Fehler beim Laden" });
return null;
});
});
My Middleware loginRequired does not use any promise at all
function loginRequired(req, res, next) {
if (!req.user) {
req.flash('loginMessage', 'Du musst eingeloggt sein!');
req.session.redirectTo = req.path;
return res.redirect('/');
} else {
return next();
}
}
I use knex for accessing my database, which creates promises.
And everytime i hit the route, i get the following warning:
(node:10568) Warning: a promise was created in a handler but was not returned fr
om it, see *link*
at Function.Promise.attempt.Promise.try (C:\Users\qxr1088\Desktop\Privat\GPS
\bibis-tracker\node_modules\bluebird\js\release\method.js:29:9)
As you can see i tried to add the "return null;" statements as suggested by bluebird, but this does not fix the problem.

Resources