I'm trying my hand at MEAN for the first time and I'm coming up against some resistance with error handling on jwt token expiration. I've got the error handler here on the express server.
const handleUnauthorisedError = (err: any, req: any, res: express.Response, next: any) => {
if (err.name === "UnauthorizedError") {
if (err.message === "jwt expired") {
res.header("Token-Expired", "true");
}
console.error(err);
res.status(401);
return res.json({ message: `${err.name}: ${err.message}` }).end();
}
};
Which is then being added here after the routes.
app.use("/api", setupRoutes());
app.use(handleUnauthorisedError);
Within my angular interceptor I've got this error handler
private handle401Error = (err: HttpErrorResponse, req: HttpRequest<any>, next: HttpHandler) => {
console.log(err.headers);
if (err.headers.has("Token-Expired")) {
this.tokenSubject.next(null);
return this.authenticationService.refresh().pipe(switchMap(() => {
return next.handle(this.addToken(req));
}), catchError(() => {
return this.authenticationService.logoutExpired().pipe(finalize(() => {
this.router.navigate(["/login"]);
}));
}));
}
return this.authenticationService.logoutExpired().pipe(finalize(() => {
this.router.navigate(["/login"]);
}));
}
However finalize never seems to occur, unless I terminate the express server. It's as if the connection is persisting maybe?
Z
I think finalize does not occur because this operator works when the source completes or throws an error. More on this here.
Think of the finalize operator as the finally statement from try/catch.
In your code, you are only emitting values.
Another question that arises is why would you use finalize?
I'd use this when I'm dealing with http calls.
But, if for some reason you still want to use this operator, you could make sure that you throw an error from this.authenticationService.logoutExpired():
logoutExpired () {
// Some logic here..
return throwError('err');
}
Here is a little StackBlitz demo.
Related
Here's my frigo controller :
const fs = require('fs');
const mqtt = require('mqtt');
const transporter = require('../params/mail')
const winston = require('../params/log');
const User = require("../models/User");
const { cli } = require('winston/lib/winston/config');
exports.OpenTheCase = async (req, res) => {};
exports.AddCard = async (req, res) => {};
exports.ShowCurrentTemperature = async (req, res) => {};
exports.ShowCurrentHumidity = async (req, res) => {};
exports.SetAlarm = async (req, res) => {};
exports.AlarmIsOn = async (req, res) => {};
const options = {
clientId: 'backendserver1032',
key: fs.readFileSync('./certs/mqtt_cert/client.key'),
cert: fs.readFileSync('./certs/mqtt_cert/client.crt'),
ca: [ fs.readFileSync('./certs/mqtt_cert/ca.crt') ]
}
const client = mqtt.connect('mqtts://localhost:8883', options);
exports.OpenTheCase = async (req, res) => {
try {
client.publish('RFID', 'RFID_OPEN');
res.status(200).json({ 'case':"opened" });
}
catch(e){
res.status(200).json({ 'state':"something went wrong" });
}
}
exports.AddCard = async (req, res) => {
try {
client.publish('RFID', 'RFID_ADD');
res.status(200).json({ 'card':"will be added" });
}
catch(e){
res.status(200).json({ 'state':"something went wrong" });
}
}
exports.ShowCurrentTemperature = async (req, res) => {
try {
client.subscribe('temperature');
client.on('message', (topic, message, packet) => {
res.status(200).json({ 'temperature': message.toString('ascii') })
client.unsubscribe('temperature')
})
}
catch(e){
res.status(200).json({ 'state':"something went wrong" });
}
return
}
exports.ShowCurrentHumidity = async (req, res) => {
try {
client.subscribe('humidity');
client.on('message', (topic, message) => {
res.status(200).json({"temperature": message.toString('ascii')});
client.unsubscribe('humidity')
});
}
catch(e){
res.status(200).json({ 'state':"something went wrong" });
}
return
}
The problem is : when I try to get "ShowCurrentTemperature", it works once and after it. It says that the http header was already send.
Here's my route :
router.get("/frigo/Temperature",auth.verifyToken, frigoController.ShowCurrentTemperature)
I really thank you.
I had try several things, like adding return or trying to end the connection but none of them works.
I'm getting out of idea. If someone can help me through this.
This design is never going to work.
You are basically leaking client.on('message',...) handlers. Every call to the HTTP endpoint adds a new handler which holds a reference to it's local res object.
Calling unsubscribe() does not remove the message handler, so the instant you call subscribe() again in either of the HTTP routes the very next message to arrive will be delivered to ALL the old handlers which will ALL try to call res.send() on a already complete HTTP transaction.
You are trying to map an inherently asynchronous protocol (MQTT) into a synchronous HTTP request, which is a really bad idea.
You may be able to get it to work by swapping all the client.on('message', ...) calls to client.once('message', ....) so the handlers only fire once, but this is a truly UGLY hack. (EDIT: On reflection, this still has a race condition where you may end up handling the wrong message if both HTTP endpoints are called too close together, so I stand by my first statement that this design can never work properly)
The right thing to do is to run the whole MQTT client totally independently of the HTTP requests and just have the single background client.on('message',...) handler update some global variables with the latest temperature and humidity that the HTTP routes can just return the latest value, rather than try and grab the next published message.
In a nutshell, this error happens when you try to send more than one response using res. This could happen in your code when there is an exception during client.unsubscribe('temperature'), because then the execution will flow into the catch where you are sending another response. You can avoid that by moving the unsubscribe after the try-catch:
exports.ShowCurrentTemperature = async(req, res) => {
try {
client.subscribe('temperature');
client.on('message', (topic, message, packet) => {
res.status(200).json({
'temperature': message.toString('ascii')
})
})
} catch (e) {
res.status(200).json({
'state': "something went wrong"
});
}
client.unsubscribe('temperature')
}
Update:
Actually, the more likely explanation is that you receive more than one message before you unsubscribe, and hence the first res.. is executed multiple times. So probably best to unsubscribe before sending a response. Since more handlers could queue up in the meantime, though, you probably need to add a guard as well to make sure you never send more than one response:
exports.ShowCurrentTemperature = async(req, res) => {
let done = false;
try {
client.subscribe('temperature');
client.on('message', (topic, message, packet) => {
client.unsubscribe('temperature')
!done && res.status(200).json({
'temperature': message.toString('ascii')
})
done = true;
})
} catch (e) {
!done && res.status(200).json({
'state': "something went wrong"
});
}
}
BTW, you could also just use mqtt directly on the client, which would be more elegant, if that's fine from an authorization perspective.
I am reading a code that has two files like below:
first file that uses the currentuser middleware:
const router = express.Router();
router.get("/api/users/currentuser", currentUser, (req, res) => {
res.send({ currentUser: req.currentUser || null });
});
export { router as currentUserRouter };
Second file that defines the middleware:
interface UserPayload {
id: string;
email: string;
}
declare global {
namespace Express {
interface Request {
currentUser?: UserPayload;
}
}
}
export const currentUser = (
req: Request,
res: Response,
next: NextFunction
) => {
if (!req.session?.jwt) {
return next();
}
try {
const payload = jwt.verify(
req.session.jwt,
process.env.JWT_KEY!
) as UserPayload;
req.currentUser = payload;
} catch (err) {}
next();
};
I understand that if there is a verified jwt token, the middleware will take the the payload out of it and add it to the req object. But what if it fails and it can't add the payload/current user to the req? What would happen for the following request and what will the res object look like?
router.get("/api/users/currentuser", currentUser, (req, res) => {
res.send({ currentUser: req.currentUser || null });
});
Could you edit this get request to show how can I catch the probable error if I am not the writer of the middleware?
If you had a catchall exception handler, and your middleware threw an exception, you would determine the response.
If your middleware threw an exception and you did not catch it, the system might just exit the process.
If your middleware did not throw an exception, and did not call next(), and did not respond, the request would hang.
If your middleware returned a response, and did not call next(), your send function would never get invoked.
The bottom line is that you need to dump the response on your server and see exactly how your middleware handles this.
In most of my auth middleware, I choose to not call next(), and return a 403 error. But there are some benefits by throwing an exception, then returning a 403 from a catchall handler.
You need to respond with an error HTTP status code, and an error message in the body. The exact status and message depends on the type of the exception and its parameters, so you need to catch it and check it.
The current express middleware does not handle errors, it just does not set the req.currentUser = payload;, so you won't know about the user. I don't think this is a proper solution for an authentication error.
In the documentation you can see how error are handled:
https://expressjs.com/en/guide/using-middleware.html
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).send('Something broke!')
})
So I would rewrite the code and if the JWT verification fails, then I return for example 401 unauthorized. https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
I guess you are using this JWT library: https://github.com/auth0/node-jsonwebtoken According to the docs and the code there are 3 types of errors: TokenExpiredError, JsonWebTokenError, NotBeforeError for verify. Here you can check when they are thrown: https://github.com/auth0/node-jsonwebtoken/blob/master/verify.js , here are their definitions: https://github.com/auth0/node-jsonwebtoken/tree/master/lib
So in the catch block you just check the type of the error with instanceof e.g. if (err instanceof jwt.JsonWebTokenError) ... and send the message accordingly with the res.status(401) and put the next() to the end of the try block, because it should be called only if the verification does not fail.
I am trying to run a test that says
test("POST /register, malformed and edge cases", async () => {
await request(server).post(`/auth/register`).expect(400);
});
I am sending an empty request body. My goal is to trigger the error handler to respond with some sort of 4xx status code.
I am working using the boilerplate in this blog: https://jasonwatmore.com/post/2020/05/13/node-mongo-api-with-email-sign-up-verification-authentication-forgot-password
Here's what I'm doing:
I hit this route with a POST request: this.router.post("/register", registerUserSchema, this.register);
I expect that the registerUserSchema is engaged and indeed it is. I can tell because a console.log statement happens. Then
function registerUserSchema(req: Request, res: Response, next: NextFunction) {
const schema: ObjectSchema<any> = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
confirmPassword: Joi.string().valid(Joi.ref("password")).required(),
acceptTerms: Joi.boolean().valid(true).required(),
});
validateRequest(req, next, schema);
}
the function downstream of registerUserSchema is this: validateRequest(req, next, schema); which does occur as I expedct, leading to
this
function validateRequest(req: Request, next: NextFunction, schema: ObjectSchema<any>) {
const options = {
abortEarly: false, // include all errors
allowUnknown: true, // ignore unknown props
stripUnknown: true, // remove unknown props
};
const { error, value } = schema.validate(req.body, options);
console.log(error, "12rm");
if (error) {
next(`Validation error: ${error.details.map(x => x.message.replaceAll('"', "")).join(", ")}`);
// next(error);
} else {
req.body = value;
next();
}
}
export default validateRequest;
I know that this "Validation error:" text makes it out of the server because it shows up in Postman.
However, what I really want is to be able to modify the status code from 500 to 4xx. I presumed this errorHandler would do it because I say this.app.use(errorHandler); in my app.ts file. But it doesn't happen, my console.log doesn't do anything during a malformed request to /auth/register
function errorHandler(err: any, request: Request, response: Response) {
console.log("In the error handler, 5rm", err, typeof err);
}
If someone can enlighten me: How do I anticipate where next() will activate next in my application?
In this case the solution was
const err = new Error(`Validation error: ${error.details.map(x => x.message.replaceAll('"', "")).join(", ")}`);
err.name = "ValidationError";
next(err);
then the error handler figured out it was next as I expected it to
I have configured axios-retry in my nodejs application as per https://www.npmjs.com/package/axios-retry
Following is my code
import axios from 'axios';
import axiosRetry from 'axios-retry';
export class RetryRoute {
public async testRetry(
req: express.Request,
res: express.Response,
next: express.NextFunction,
): Promise<any> {
const client = axios.create({ baseURL: `http://www.test.com/` });
axiosRetry(axios, { retries: 3 });
client.get('/error')
.then(result => {
this.logger.info('success', result);
result.data;
}).catch(error => {
console.log('error', error.message);
error !== undefined
});
}
}
console.log('error', error.message);. prints as expected. which means call is failed with code 404 as expected. But next call from retry not happening.
You need to specify retry condition, below code retry for every failed request
axiosRetry(axios, {
retries: 3,
retryCondition: () => true
});
From https://github.com/softonic/axios-retry
By default, it retries if it is a network error or a 5xx error on an
idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).
A http 404 status code means the request was handled successfully but was invalid. It doesn't retry in this case as it would expect the retry to also fail presumably with almost 100% certainty. 404 errors shouldn't be transient in the same way 500 errors or network errors may be.
I have some promise
getSomeInfo(data) {
return new Promise((resolve, reject) => {
/* ...some code... */
someObject.getData((err, info) => {
if (info) {
resolve(info)
}
else {
reject("Error")
}
})
})
}
I use this promise and want to send response to client from Controller (AdonisJS):
async create ({ request, response }) {
this.getSomeInfo(data).then(info => {
console.log(info) // It's work, i get the data from promise
response.status(201).json({ // but this is not work
code: 201,
message: "Data received!",
data: info
})
})
}
Why response is not work?
Simply do this.
async create ({ request, response }) {
const info = await this.getSomeInfo(data)
console.log(info)
response.status(201).json({
code: 201,
message: "Data received!",
data: info
})
}
When marking a function as async the function must return a Promise, this can be done explicitly.
async create({ request, response }) {
return this.getSomeInfo(data).then(info => {
console.log(info) // It's work, i get the data from promise
response.status(201).json({ // but this is not work
code: 201,
message: "Data received!",
data: info
})
})
}
Or implicitly using the await keyword.
async create({ request, response }) {
const info = await this.getSomeInfo(data)
console.log(info) // It's work, i get the data from promise
response.status(201).json({ // but this is not work
code: 201,
message: "Data received!",
data: info
})
}
If your console.log(info) inside of create() works and shows the data you want, but the response.status(201).json(...) does not send a response, then I can see the following possibilities:
You've already sent a response to this request (and thus cannot send another one)
The .json() method is having trouble converting info to JSON (perhaps because of circular references) and throwing an exception.
You aren't passing the arguments request and response properly and thus response isn't what it is supposed to be.
You can test for the second case like this:
create ({ request, response }) {
this.getSomeInfo(data).then(info => {
console.log(info) // It's work, i get the data from promise
response.status(201).json({ // but this is not work
code: 201,
message: "Data received!",
data: info
});
}).catch(e => {
console.log("Error in create()", e);
response.sendStatus(500);
});
}
Also, there is no reason for this method to be declared async as you don't show that you're using await or any of the features of an async function.
In the comments, you say that this function is called directly by a router (I assume an Express router). If that's the case, then the function arguments are not declared properly as they come as two separate arguments, not as properties of an object. Change the function declaration to this:
create (request, response) { ... }