So I have a mini express app where users can register, login and delete their account. Due html not supporting DELETE I used the npm package methodOverride.
This is what my form and express-override middleware looks like:
server.js
// Method override
app.use(
methodOverride(function (req, res) {
if (req.body && typeof req.body === 'object' && '_method' in req.body) {
let method = req.body._method;
delete req.body._method;
return method;
}
})
index.hbs
<form action="/profile/delete/{{_id}}" method="POST" class="pt-2">
<label for="delete-input_confirmation"></label>
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="btn delete-confirm">Confirm</button>
</form>
Now in my users.js for the delete request:
router.delete('/profile/delete/:id', ensureAuth, async (req, res) => {
try {
let userId = await LocalUser.findById(req.params.id);
if (!userId) {
return res.render('/');
} else {
await LocalUser.findOneAndRemove({ _id: req.params.id });
res.redirect('/register');
console.log(userId);
}
} catch (err) {
console.error(err);
return res.render('/');
}
});
Now what happens with the the code above it goes through fine but it doesn't move past the DELETE request and render the /register page. Then the node server just stops working and I have to restart it and clear my cache. In my mongo db it removes the user so I don't know why this is happening.
I'll put my comments into an answer since this ended up solving it for you.
If you're deleting the current user, but leaving their server-side session open, then that can lead to session issues. To clear that session, you can either call req.session.destroy() or you can remove the session cookie (and the session will become detached from that browser and eventually age away).
The fact that your site becomes non-functional when you have a session of a removed user probably means that you have some error handling problems in your session logic or in accessing the database on behalf of a non-existent user. Your code should be able to field errors when this happens and at least report an error, but probably force the user back to /login.
Related
Express js middleware is not working as expected. It is showing too many redirections.
When i remove the token or logout it shows in the browser that too many redirections
Middleware
const isAuthenticate = async (req, res, next) => {
const token = req.cookies.jwt;
if (token) {
jwt.verify(token, "thisisjwtsecret", async (err, token_decode) => {
if (!err) {
const u_id = token_decode._id;
const userData = await User.findOne({ _id: u_id });
req.user = userData;
req.isAuth = true;
next();
} else {
res.redirect("/user/login");
}
});
} else {
res.redirect("/user/login");
}
};
Route.js
// Auth Controller
const AuthController = require("../../controllers/auth/AuthController");
const { isAuthenticate } = require("../../middlewares/isAutheticated");
router.get("/user/login", isAuthenticate, AuthController.login);
router.post("/user/login", AuthController.checkLogin);
router.get("/user/register", isAuthenticate, AuthController.createUser);
router.post("/user/register", isAuthenticate, AuthController.storeUser);
module.exports = router;
LOgin function
// Showing Login Page to User
const login = (req, res) => {
return res.render("auth/login");
};
When i remove the token or logout it shows in the browser that too many redirections
Now that you've shown revised code for isAuthenticate(), the redirect loop is caused by the redirects in that code. Here's what happens:
Some route you have (any route) that uses isAuthenticate as middleware for the route detects that the user is not logged in. It then redirects to /user/login. That's fine up to that point. Then, the browser issues a new request for /user/login and that takes you to this route definition:
router.get("/user/login", isAuthenticate, AuthController.login);
But, that route definition again runs the isAuthenticate() middleware which redirects to /user/login and thus you have an infinite redirect loop.
Probably you just need to remove the isAuthenticate() check from this route. If the user is already going to the /user/login page, you don't need to check their authentication or redirect them. If you have a reason to want to know if they are authenticated or not, then you need a separate version that ONLY does the auth check and does not redirect and you can use that in the /user/login route definition.
Original answer before code was shown that did res.redirect().
So, this middleware you show sets req.isAuth to true or false and then calls next() to continue routing. All three code paths through that middleware just set req.isAuth and then call next(). Nowhere in this middleware does it do any redirect. So, if the core problem is too many redirections, that issue must be caused somewhere else by some other route/middleware that actually does a redirect, probably when it sees that req.isAuth is false since you said that the problem occurs when logged out or when the token is missing.
When redirecting, you have to make absolutely sure that when you redirect to a URL, there is ZERO chance (no code path of any kind) that the route handler for that URL will also redirect to that URL. That's how you get into a redirect loop.
Looking at the other routes you show, if the too many redirects issue is when redirecting to /user/login, then it seems likely the problem is in the authController.login() handler from this route:
router.get("/user/login", isAuthenticate, AuthController.login);
If the code for that route checks req.isAuth and redirects in any circumstances, then that would be an endless redirect loop.
If you need further advice, please provide the following information:
Which exact redirect URL is causing the problem of too many redirects? Is is /user/login?
Show us the code for the route that does that redirect because that's apparently where the fault is.
I know the title of this question might sound confusing, but my problem is actually simple. I have these two handlers for /login get and post requests:
loginRender(req, res) {
let options = { title: 'Login', layout: 'auth.hbs' }
res.render('login', options)
}
login (req,res){
let user = Routes.findUser(req.body.username)
let passwordCorrect = Routes.hashCompare(
req.body.password,
user.password
)
if (passwordCorrect) {
let token = Routes.jwtsign(req.body.username)
let refreshToken = Routes.jwtRefreshToken(req.body.username)
Routes.authRedirect(res, token, refreshToken)
} else {
Routes.badRequestRedirect(res, '/login')
}
}
authRedirect(res, token, refreshToken )
{
let options = {
cssPath: 'styles/querystyle.css',
}
res.cookie('access_token', `${token}`, { httpOnly: true })
res.cookie('refresh_token', `${refreshToken}`, { httpOnly: true })
res.status(200).render('query', options)
}
// app.use(urlencoded)
// app.use(cookieParser)
// app.post('/login', login)';
// app.get('/login', loginRender)
Please, ignore all unrelated stuff.
So, everytime I complete login, I get my webpage rendered and I can actually open inspector and see this:
Page Inspector
Address line
How can I fix that? I want my user to be redirected to dashboard-like page and not to receive his sensitive data in insecure form.
UPD
there's also auth middleware that only appends req.username in case we did parse jwt successfully, and there's a little bit of interaction with it, but it does not appear on page until I go to this page manually by writing the address in address line.
If you don't send the data to the Express server, then you can't read it in you login function and you can't authenticate the user.
It is not a problem is the user can use the tools in their own browser to inspect the data that they entered.
You need it to be encrypted in transport (i.e. use HTTPS and not plain HTTP, at least in production) but you don't need to worry about the user finding out their own password.
I am creating a simple login form that (for now) will log you in as long as the username and password pass the client-side validation function. The form is shown here below (paraphrased a bit).
...
<form id="logonForm">
<input id="email">
<input id="password">
<button type="submit">Logon</button>
</form>
...
It should be noted that this form is being loaded at /login route (have a GET route set up there). On load of the page this form is assigned an onsubmit function as shown below.
...
document.getElementById("logonForm").onsubmit = loginUser;
As a note, loginUser is an imported function from another file. This function has been verified to work though as I am able to step through it when I submit the form. This function does a bunch but the main important parts are depicted below.
...
method = {
method: "POST",
body: loginDetails
};
fetch(url, method)
.then(result => result.json())
.then(result => sessionStorage.setItem("userToken", result[0].userToken))
.catch(error => alert(err.message))
This fetch queries a POST route also located at the /login route with the username and password sent over in FormData() encoding. When I run through this function, no error is thrown by the fetch (or prior/after the fetch) but result returns empty/null.
Investigating this from my server-side code I find that the "POST" route doesn't seem to be actioned even when the fetch is sent. In this following code "That" is never logged to the console but "This" is.
exports.logonUser = (app, formidable, fs, jwt) => {
console.log("This");
app.post('/login', (request, result, next) => {
console.log("That");
...
}
}
This function is called in my server.js file that is run to start the server as shown below.
const home = require('./api/routes/home.js');
const login = require('./api/routes/login.js');
app.use(express.static(path.join(__dirname, process.env.STATIC || 'public/')));
login.openRoute(app, path);
login.logonUser(app, formidable, fs, jwt);
app.use((request, result, next) => {
// This is "true" until I have tokens properly working
if (true) result.redirect('/login');
else next();
});
home.openRoute(app, path);
app.get('/', (request, result) => result.redirect('/home'));
app.listen(process.env.PORT || 3000, () => console.log("[SUCCESS][SERVER] Server is listening on port 3000"));
I have tried re-arranging the functions a bit, but this seems to be the order that gets the functionality as intended (other than the login not working). I have also tried taking out the part that automatically redirects users to /login in-case something funky was happening there, but no luck in that regard either. I am presuming there is something about my server.js code though that cause the POST route on /login to not action. Is there anything that you are able to see that might cause this behaviour? Thank you for all help.
I am playing around with this library and I am experiencing an annoying scenario which I believe comes from some sort of conflict in cookies or headers authentication.
When I login to one account everything works great. But then when trying to login to another account, it simply ignore the new data provided and move through the authentication with the old data and connecting to the old account. No matter if the email or the password even exist. (Tried also with fake data).
The library doesn't have proper logout method which make sense, you dont really need one because when you run it simply using node on your machine without any server involved and there is no cookies or any kind of data in memory, everything work great. I can login to as many account as I want.
The problem is when running it on an Express server.
CODE:
// api.js
const OKCupid = require("./okc_lib");
const Promise = require("bluebird");
const okc = Promise.promisifyAll(new OKCupid());
async function start(req, res, next) {
const {
body: {
username,
password
}
} = req;
try {
await okc.loginAsync(username, password);
okc.search(
searchOpt,
(err, resp, body) => {
if (err) return console.log({ err });
const results = body.data;
// do dsomething with results
return res.status(200).json({ message: "OK" });
});
}
);
} catch (error) {
res.status(400).json({ message: "Something went wrong", error });
}
}
module.exports = { start };
// routes.js
const express = require("express");
const router = express.Router();
const { start, login } = require("../actions/okc");
router.post("/login", login);
router.post("/start", start);
module.exports = router;
So when trying first to post to url/login it works fine. But when you try to do it again with different username and password it simply go through and ignore the new data and connect to the old one.
As part of my investigation I looked at the source code of the library and found a method clearOAuthToken which clear the token from the header. However it didn't really do anything. So I tried to remove the jar initialisation from the requester helper and it was the only thing that helped me to move on and login to another account. BUT it was only for experimenting and cant be a solution as you do need those cookies for other parts of the library. It was only a proof the problem isn't in the headers but in the cookies.
Any idea how can I "reset" state of server between each call?
"when trying to login to another account, it simply ignore the new data provided and move through the authentication with the old data and connecting to the old account."
As OP mentioned in the comment, this is not an authorization header issue, but a cookie issue.
To implement the logout interface, you can manually clear the cookies:
OKCupid.prototype.logout = function(){
request = request.defaults({jar: request.jar()}) // reset the cookie jar
headers.clearOAuthToken(); // just in case
}
I'm currently getting started with Sails.js, and I want to add user accounts to my toy app, so I installed the "sails-auth" package that creates a Passport-based user authentication system. I can create new users by sending POST /user, and I can sign in with POST /auth/local.
The documentation says:
Authenticate with the local strategy via a POST to /auth/local with params identifier (email) and password). This will also create a session. See passport.local for more.
However, when I try to GET /user/me, which routes to a controller action that should return the current user in the session, the page instead gives an empty response. Why is this happening? Is there some kind of configuration step that I'm missing?
By the way, I haven't changed or messed around with the sails-auth package. It's still completely new; the "me" action looks like this:
me: function (req, res) {
res.ok(req.user);
}
EDIT: I've found a temporary workaround by searching the issues in the sails-auth repo. Instead of getting a user object from req.user, you can get a string user ID from req.session.passport.user.
Your me action as written is only going to return whatever you are passing in as the user param. Sails builds on top of Express.js so req is the request from the browser and res is the response to the browser.
Most likely you are sending the data to your me action in the req body which is why your response is blank, simply put, req.user is empty so the response is empty. In that case you would access it with req.body.user, you could also try var user = req.params();
For debugging and just generally getting a feel for how the req and res objects are structured I suggest you always start sails (in development, never in production) with the verbose flag.
sails lift --verbose
Then you can do this:
me: function(req, res){
sails.log.verbose(req);
res.ok(req.user);
}
And have it print out the entire req object so you know what's in req.user.
Typically though you would do a database lookup as the user param would be an id. Which means your me function might look (something, obviously depending on your dbc it might be pretty different) like:
me: function(req, res){
var userId = req.body.user;
User.find({'user_id': userId}.exec(function(err, user){
if(err){
//tell peeps there was an error
}else{
res.ok(user);
}
});
}
Best debugging for routes and for the request object:
'/*' : function(req, res, next) {
sails.log.verbose("method: ", req.method, "\n body: ", req.body, "\n url:", req.url);
next();
},
Just paste that at the start of your routes module.