Keeping track of users history in express to make a 'back' button? - node.js

I'm making an express app and I want to include a 'back' button in the app UI that does basically exactly what the browser back button does.
I tried holding an array variable in the server that simply collects all of the URL params visited. For example, for the '/:lang' route ...
const browsingHistory = [];
app.get("/:lang", (req, res) => {
const lang = req.params.lang;
if (lang === "en" || lang === "fr") {
const templateVars = {
menuItems: db[lang].menuItems,
lang,
};
res.render("root", templateVars);
}
if (lang !== "favicon.ico") {
browsingHistory.push(lang);
console.log(`Browsing history: ${browsingHistory}`);
}
});
BUT I'm realizing this only works when locally hosted — once deployed, if there are multiple users simultaneously, how to keep track of each users' individual history? Or is there a better way of doing this?

Storing the browsing history will require user sessions. On each request, you will have to store the route that the user hits in their session variable.
In Express, this can be accomplished with the express-session library. You will want to initiate each session with some history property that begins as an empty array. Once express-session is set up, you can do something similar to the following
app.get("/:lang", (req, res) => {
const lang = req.params.lang;
req.session.history.push(lang);
...
});
app.get("/getMyPageHistory", (req, res) => {
res.send(req.session.history);
});
req.session will be unique for each user. So, you can store each user's unique history in this variable.
With that said, if you go down this route, you eventually will want some external session storage. By default, sessions are saved in your server's memory. This introduces a few issues that are explained in the express-session documentation. Here is their warning
Warning The default server-side session storage, MemoryStore, is purposely not designed for a production environment. It will leak memory under most conditions, does not scale past a single process, and is meant for debugging and developing.
They provide a list of compatible session stores

Related

Node/Express - User/session variables

I am attempting to prototype a user journey in Node. The prototype will be used by several testers in parallel.
How can I define and store variables via the routing file that are only accessible by the user on that session?
e.g.
router.post('/thepage', (req, res) => {
passedInfo = req.body
dataObj.incomeWork = passedInfo['work']
dataObj.incomeRent = passedInfo['rent']
dataObj.incomeOther = passedInfo['other-income']
res.render(`/thepage`, dataObj )
}
I've looked a the express-session module, but that requires the variable to be passed from route to route to sustain its values.

Serve multiple protected static folders using Express

I'm trying to write a very simple Express app, which is supposed to serve multiple static folders.
I have a root folder "stories" which contains multiple folders (story-1, story2, etc...). Each story folder contains static assets (scripts, CSS stylesheets, subpages...).
My users can unlock each of those stories, so each story folder must be protected. (If anyone tries to access http://backend/stories/story-1, it should give a 401 Forbidden).
My initial thought was to generate a one-time JWT upfront (like a signed url; not a bearer), add it to query params like http://backend/stories/story-1?jwt=the-jwt-token, then do some backend logic to test if the user has access to this content before serving it.
I tried fiddling with a basic express configuration + a custom authorization middleware :
Project structure :
...
-- /src
-- /stories ⬅️ custom public folder
-- /story-1 ⬅️ public but protected
- index.html
- /subpages
-page2.html
-page3.html
- /styles
- /scripts
-- /story-2 ⬅️ public but protected
- index.html
- /subpages
-page2.html
-page3.html
- /styles
- /scripts
-- /story-3 ⬅️ public but protected
- index.html
- /subpages
-page2.html
-page3.html
- /styles
- /scripts
etc...
index.js :
const express = require("express");
const { authorized } = require("./middlewares/authorized");
const app = express();
const port = 3000;
app.use("/stories/:story", authorized);
app.use("/stories", express.static(__dirname + "/stories"));
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
authorized.js :
exports.authorized = (req, res, next) => {
const jwt = req.query.jwt;
if (!jwt) return res.sendStatus(401);
// todo : custom logic to test if the user has access to this content, if yes we do next(), if no we return a 401.
return next();
};
This simple example works partially, when I try to go to http://localhost:3000/stories/first-story (without JWT), I get a 401 (that's ok).
But when I add the jwt :
http://localhost:3000/stories/first-story/?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
The middleware runs for every assets that are linked in the index.html, but those assets urls don't have the JWT query params, which leads to a 401.
I guess it's totally normal because that's how middlewares are intended to work. My guess is that i'm configuring express router wrong :
app.use("/stories/:story", authorized);
app.use("/stories", express.static(__dirname + "/stories"));
I would like to run the middleware only once, when any of the /:story subfolders inside /stories are asked to be served.
You write:
I would like to run the middleware only once, when any of the /:story subfolders inside /stories are asked to be served.
But if every .html (sub-)page is served by a separate HTTP request, each of these requests must be protected, assuming that the HTML contains material that is worthy of protection. (The styles and scripts may not need this extra protection.)
Therefore it is OK that the authorized middleware runs for each such request. And if the JWT was in a cookie (as suggested by Kaneki21), it would be present automatically in each request.
I would separate out the access control logic from the identity logic. You can use your jwt to verify that the user is who they say the are, and then use your existing knowledge of who that user is to grant them access.
I put a simple example using cookie-backed sessions below, note that you can add sequential middleware a, b, and c all in one function via app.use('/foobar',a,b,c).
// other setup
...
const session = require('express-session'),
fs = require('fs'),
{ Router } = require('express');
const secret = (() => {
let secretFile = '/path/to/my/secret.txt';
try {
// try reading a saved secret
return fs.readFileSync(secretFile,'utf8');
}
catch(err) {
// otherwise generate secret and save it
let random = require('crypto').randomBytes(128).toString('base64');
fs.writeFileSync(secretFile,random);
return random;
}
})();
// Add the session middleware to the app
app.use(session(
{ secret,
name: 'stories-and-whatnot',
cookie: { sameSite: true } }
));
// Create a router for stories and add it to the app
let storyRouter = Router();
app.use('/stories', storyRouter);
// add identity middleware to storyRouter
storyRouter.use( authorized);
let storyMax = 10;
for(let i=0; i<storyMax; i++) {
// set up the individual story routers
storyRouter.use(
`/story-${i}`,
function(req,res,next) {
if(!req.session.storyAccess || !req.session.storyAccess[i]) {
// If the user's session doesn't show it has access, reject with 401
res.status(401).end(`You do not have access to story ${i}`);
}
else {
// Otherwise let them proceed to the static router
next();
}
},
express.static(require('path').join(__dirname,`stories/story-${i}`)
);
}
...
// And somewhere else you have something like this
app.get('/access-granted', authorized, function(req,res,next) {
let { id } = req.query;
if(!req.session.storyAccess)
req.session.storyAccess = {};
req.session.storyAccess[id] = true;
res.end(`Access granted to story ${id}`);
});
You might consider, not using a middleware at all for the serving of content. But rather, to setup the user's set of "approved" paths.
That way a user, failing authentication, would have no valid paths, other then perhaps a preset collection of "base" paths.
This way, later after your authentication middleware the "routing" can be constrained to just that users set of "granted" paths.
Essentially model the access using sessions, which are established on first request, and then updated and maintained as things progress.
One solution is that you check if the user has the right to view the page on the client side. You'll need some JavaScript on the client side to do this.
You can store the token in LocalStorage after login. Then, at the beginning of the protected HTML file, you include your JS code to retrieve the token, and send a request to the server to check if the user is authenticated or not. Then, based on the response of the server you show the content or hide it.
To be honest, I rarely see the JWT in the URL. People talk about it here, here, here... You should revise your current approach carefully.

confused about node-localstorage

so I'm making a site with node js, and I need to use localstorage, so I'm using the node-localstorage library. So basically, in one file I add data to it, and in another file I want to retrieve it. I'm not 100% sure about how to retrieve it. I know I need to use localStorage.getItem to retrieve it, but do I need to include localStorage = new LocalStorage('./scratch');? So I was wondering what the localStorage = new LocalStorage('./scratch'); did. So here is my code for adding data:
const ls = require('node-localstorage');
const express = require("express");
const router = express.Router();
router.route("/").post((req, res, next) => {
var localStorage = new ls.LocalStorage('./scratch');
if(req.body.name != undefined){
localStorage.setItem("user", req.body.name);
res.redirect('/')
}
else{
console.log("undefind")
}
});
module.exports = router;
If my question is confusing, I just want to know what var localStorage = new ls.LocalStorage('./scratch'); does.
A drop-in substitute for the browser native localStorage API that runs on node.js.
It creates an instance of the "localStorage" class, which this library provides. The constructor expects the location of the file, the scripts stores the key, value elements in.
Opinion: This looks pointless to me - I guess it fits your use case.

per-request session in meteor server?

I am adding an auth layer and I think I have it figured out except for one tricky detail.
My Meteor app doesn't have any routes but I've added a hook into the connect middleware so that the "/" route errors if there isn't a correct API token. If the token is okay then I call next() to forward the route to Meteor.
The problem is that, depending on the token, I need to set server-side parameters for the connection, and I don't know how to do this. For example, say I have a static list of API keys mapped to permission levels. If a user sends a request with "ADMIN_API_KEY" then I would like to set Session.permission_level = "admin" for use by the Meteor server's functions. Session is just for the client in Meteor, though.
# this code's in coffeescript
WebApp.connectHandlers.use '/', (req, res, next) ->
validator = new RequestValidator(req, next)
validations = [
"valid_namespace",
"only_https"
]
error = validator.validate(validations)
next(error)
# <<<<<<<<<<<<<<<<<<<<<<<<
# Here I want to set some config option which can be
# read by the server in the same way it can read things like
# Meteor.user()
In Rails I would just say session[:permission_level] = "admin". But it seems to not work this way in Meteor.
By the way, I am not using a Routing package yet in Meteor, though if that would make this easier than I would.
I'm not sure about Session I've been doing something like
import { DDP } from 'meteor/ddp';
import { DDPCommon } from 'meteor/ddp-common';
export const authMiddleware = (req, res, next) => {
const userId = identifyUser(req); // parse the request to get the token you expect
if (!userId) {
return next();
}
DDP._CurrentInvocation.withValue(new DDPCommon.MethodInvocation({
isSimulation: false,
userId,
}), () => {
next();
// in that context, Meteor.userId corresponds to userId
});
};
for my REST api and that works well regarding the user Id and being able to call Meteor function that should be invoke in a DDP context, like Users.find(...).

Not cookie based session management in node.js

I am looking for a non-cookie based session management in node.js, something like pass a parameter in the URL like &session_id=. It will know that a session has expired when a request comes with an session_id. I've looked at connect library, but it looks that it is cookie based only.
Warning
Passing the session id as a GET parameter is considered bad practice. Why? It is dangerous because people don't usually care about session id and they will probably publish/share links with their session ids inside.
It's also a problem because when a user clicks an external link on your web, and goes to another site, that new site will be able to see the session_id in the referrer link.
So I don't think it is a good idea. Cookies are more secure.
Have a look at: Session Hijacking
For every request you receive, you will get all of the client cookies accordingly.
You can also set client cookies in the response HTTP headers using "Set-Cookie."
Using a GET parameter is unsafe. Any user could accidently share their session ID, but if you want 100% security, I'd share session IDs via cookies, and I would use HTTPS to prevent snoopers from stealing cookies.
You can use localstorage or sessionStorage..
almost same as cookie
not a cookie
better than a cookie!
More info: https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage
It's very -very- easy to use... in Js for example:
<script>
// check if Storage is avaible
if(typeof(Storage)!=="undefined") {
// Save data to local storage (no exiparion date)
localStorage.setItem("name_always", "bxx");
// Save data to the current session (removes when the tab is closed)
sessionStorage.setItem("name_now", "bxx");
} else {
// No Storage support...
}
// Access to stored data
alert( "For only now, my name is: " + sessionStorage.getItem("name_now"));
alert( "Every day, my name is: " + localStorage.getItem("name_always"));
</script>
Tags: javascript html5 local-storage session-storage
You can use sessions with a store in node.js. For example, you have express application and want to use session like system in your webapp. You can use connect-mongo module for this. This will let you store your session in db. In your app.js
var express = require('express'),
, mongoStore = require('connect-mongo')(express);
var app = express();
app.configure('all', function () {
app.use(express.session({
secret: "terces",
cookie: { maxAge: 24 * 60 * 60 * 1000 },
store: new mongoStore({
url: your_db_url
})
}));
app.use(function(req, res, next) {
res.locals.session = req.session;
next();
});
});
With basic code above, you have session in express that you can use it in your controllers and views directly. In your controller;
app.post('/blog/create/?', function(req, res, next) {
if (!req.session.user) {
next("You need to login in order to create blog!");
}
});
In your view, you can use session.user in order to generate profile menu for example.

Resources