I'll go in detail on what I really want my server to do.
So basically, I'm creating a project in which the user register and then logs in.
The routes are like - localhost:3000/login and localhost:3000/register
Now when the user logs in using their credentials (POST) , it should send them to localhost:3000/home which is unique for every person. I don't want to send the data like we do for templating engines but I want to make that data accessible across all routes so that I can use it whenever I like.
What I'm having trouble with is that when the person logs in , their data gets stored in a session (which contains their name and other user data) which as of now is not sharable between routes. The session gets stored for the /login route and I'm unable to use it for the /home route or for any other route for that matter.
Is there any way that I can use save a session every time a person logs in (using POST) and make that session data available across all my routes ?
server.js
var express = require('express');
const path = require('path');
const fs = require('fs');
const session = require('express-session');
const { v4: uuidv4 } = require('uuid');
const app = express();
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'public/auth-pages')));
app.use(express.json());
var publicPath = __dirname+"/public"
var authPagesPath = publicPath+"/auth-pages"
var jsonPath = __dirname+"/json"
var usersFile = jsonPath+"/users.json"
var testVar = 1;
app.set('view engine', 'pug')
// have to be on top
app.use(logger)
app.get('/',(req,res) => {
res.sendFile('index.html',{root:publicPath})
})
app.get('/home',(req,res) => {
var data = {}
res.render('home',data)
})
app.get('/profile',(req,res) => {
var data = {}
res.render('profile',data)
})
app.get('/login',(req,res) => {
res.sendFile('login.html',{root:authPagesPath})
})
app.get('/register',(req,res) => {
res.sendFile('register.html',{root:authPagesPath})
})
app.post('/register',(req,res) => {
var data = req.body;
if (register(data)){
res.send({
register:"success"
})
}
else {
res.send({
register:"fail"
})
}
})
app.post('/login',(req,res) => {
var data = req.body;
fs.readFile(usersFile,(error,fullData) => {
var fullData = JSON.parse(fullData);
allUsernames = Object.keys(fullData);
if (!allUsernames.includes(data.username)){
res.send({
login:"fail",
reason:"invalid-username"
})
}
else if (fullData[data.username]['pwd'] != data.pwd){
res.send({
login:"fail",
reason:"wrong-pwd"
})
}
else {
res.send({
login:"success",
user:fullData[data.username]
})
// session storage
req.session.user = {
username:data.username,
id:fullData[data.username]['id']
}
console.log(req.session)
}
})
})
app.get('/test',(req,res) => {
testVar += 1;
res.send(""+testVar);
})
function register(data){
fs.readFile(usersFile,(err,fullData) => {
var fullData = JSON.parse(fullData)
if (Object.keys(fullData).includes(data.username)){
console.log('username taken')
}
else {
fullData[data.username] = {
id:uuidv4(),
pwd:data.pwd
}
fullData = JSON.stringify(fullData,null,4)
fs.writeFile(usersFile,fullData,error => {
})
}
})
return true;
}
You can use cookies to keep user data between routes.
If you dont want to store the whole data in the browser,
you can keep the user id in cookies and store the whole data in a repository object.
for example:
you can create a class that will store the state of the logged in users and can be reachable between routes.
the class should be instantiate and you should export its object.
( this solution keeps the state in memory and will be lost when service restarts/shutdown. to make the state available after restarts you can store the state in db (redis, mongo etc...)).
repo class:
class MyRepository {
constuctor() {
this.users = {};
}
set(user) {
this.users[user.id] = user;
}
get(userId) {
return this.users[userId];
}
}
let repo = new MyRepository();
module.exports = repo;
route:
const express = require('express');
const router = express.Router();
const repo = require('myrepository.js'); // this line will get you the object with all logged in users already
router.post('/login', (req, res, next) => {
// check if user logged in and get the user id (to myUserId);
user = ...
logged = ...
if (logged) {
res.cookie('userId', user.id)
repo.set(user)
}
});
route.get('/home', (req, res, next) => {
let userId = req.cookies.userId // get user id from cookie
let user = repo.get(userId);
});
You can do this by having some sort of a class that holds user's data. Please be aware that this solution is not scalable and you are designing a single point of failure.
For instance, if you have multiple servers running your application with a load balancer that routes requests to your servers. Let's say Server A creates an object from a class for User A. In the second or third request, presume that User A's request gets routed to Server B, which has not created an object that holds User A's data. This leads to scalability and even perhaps inconsistency issues.
Related
I am new to Node.js and trying to check if an e-mail is already taken by sending the email as a url parameter from iOS app. It is not working, not sure what I am doing wrong.
I am unable to console.log the email parameter in VSCode sent from the front-end, it DOES print in XCODE ( http://localhost:3000/api/user/email/test#gmail.com ) and I know the backend is getting the GET request.
My router code is:
const express = require(`express`)
const router = new express.Router()
const User = require(`../models/user-model`) // import User model
router.get(`/api/user/email/:email`, async (req, res) => {
console.log(req.params) // does NOT print email: test#gmail.com
try {
const user = await User.findOne(req.params.email)
if (user) {
console.log(user._id)
res.send({ available: false })
} else {
res.send({available: true})
}
} catch {
res.status(404).send()
}
})
Thank you!
const express = require(`express`)
const app = new express();
app.get(`/api/user/email/:email`, async (req, res) => {
console.log(req.params) // does NOT print email: test#gmail.com
try {
// const user = await User.findOne(req.params.email)
const user = {_id:123};
if (user) {
console.log(user._id)
res.send({ available: false })
} else {
res.send({available: true})
}
} catch {
res.status(404).send()
}
})
app.listen(3000,function(){
console.log("running");
})
Editing this.. I dont have enough points to comment.. your route seems to be fine, maybe you are not telling your application to use this route, somewhere before starting your application you should have something like:
this.app = new express();
...
this.app.use('/api', MailRouter); //<=== Adding your required mail route
...
I use to split url one parte here (/api) and the other one in the router (/user/email/:email). I'm not sure how to do it by adding it fully to the router (Maybe '/' maybe '')
In my express router I check if the data inserted on a form are valid then if they are I render another page passing form data. I would like to access the data I pass client-side. On the chat.ejs view I have a chatroom.js client file, I want to access the data there without having to access them in a script tag.
I thought about using Ajax but the only answer I found here on StackOverflow was marked as wrong, so how do I go about that?
router.js
module.exports=function(app) {
const express = require('express');
const router = express.Router();
const {check, validationResult} = require('express-validator');
const {matchedData} = require('express-validator/filter');
router.get('/', (req, res) => {
res.render('index', {
data: {},
errors: {}
})
});
router.post('/enter', [
check('username')
.isLength({min: 1})
.withMessage('Username is required').trim(),
check('room')//implement personalized check
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.render('index', {
data: req.body,
errors: errors.mapped()
})
}
else {
const data = matchedData(req);
return res.render('chat',{
user: data.username,
room:data.room
})
}
});
return router;
//MOVE TO SUPPORT
function find(name) {
return 1;
}
}
there is really nothing client-side so far so It seems useless just posting my views. Alternatively, I could use Ajax on client.ejs to handle the form submission but I would like to keep this clean and handle the routing with the router file.
I ended up creating two global variables in a script tag for my index.ejs page like this
<script>
var user = <%- JSON.stringify( user ) %>
var room = <%- JSON.stringify(room)%>;
</script>
and then I could access them in the chatroom.js file linked below
I'm not a pro in any way but I've started and ApolloServer/Express backend to host a site where I will have public parts and private parts for members. I am generating at JWT token in the login mutation and get's it delivered to the client.
With context I want to check if the token is set or not and based on this handle what GraphQL queries are allowed. My Express/Apollo server looks like this at the moment.
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
// get the user token from the headers
const token = (await req.headers.authorization) || '';
if (token) {
member = await getMember(token);
}
}
});
The problem is that this locks down the GraphQL API from any queries and I want/need to reach signup/login mutations for example.
Could anyone spread some light on this to help me understand what I need to do to get this to work.
the way i am doing it is that i will construct auth middleware even before graphql server as sometimes is needed to have information about authenticated user also in other middlewares not just GraphQL schema. Will add some codes, that you need to get it done
const auth = (req, res, next) => {
if (typeof req.headers.authorization !== 'string') {
return next();
}
const header = req.headers.authorization;
const token = header.replace('Bearer ', '');
try {
const jwtData = jwt.verify(token, JWT_SECRET);
if (jwtData && jwtData.user) {
req.user = jwtData.user;
} else {
console.log('Token was not authorized');
}
} catch (err) {
console.log('Invalid token');
}
return next();
};
This way i am injecting the user into each request if the right token is set. Then in apollo server 2 you can do it as follows.
const initGraphQLserver = () => {
const graphQLConfig = {
context: ({ req, res }) => ({
user: req.user,
}),
rootValue: {},
schema,
};
const apolloServer = new ApolloServer(graphQLConfig);
return apolloServer;
};
This function will initiate ApolloServer and you will apply this middleware in the right place. We need to have auth middleware before applyin apollo server 2
app.use(auth);
initGraphQLserver().applyMiddleware({ app });
assuming the app is
const app = express();
Now you will have user from user jwtData injected into context for each resolver as "user", or in req.user in other middlewares and you can use it for example like this. This is me query for saying which user is authenticated or not
me: {
type: User,
resolve: async (source, args, ctx) => {
const id = get(ctx, 'user.id');
if (!id) return null;
const oneUser = await getOneUser({}, { id, isActive: true });
return oneUser;
},
},
I hope that everything make sense even with fractionized code. Feel free to ask any more questions. There is definitely more complex auth, but this basic example is usually enough for simple app.
Best David
I'm new to KoaJS. Playing a bit now. I'm trying to redirect all request to a particular URL using a middle-ware. This seems to product ERR_TOO_MANY_REDIRECTS in Chrome. I tried a lot to debug. Can't get what is wrong.
index.js
// App
const Koa = require('koa')
const app = new Koa()
// Parser
const bodyParser = require('koa-body')
app.use(bodyParser())
// Session
const session = require('koa-session')
app.keys = ['asdfasdf##$ASDf1#$#5rasdf']
app.use(session(app))
// THIS MIDDLEWARE
app.use(async (ctx, next) => {
ctx.session.user = '121' // This is all playground. No production stuff.
const s = ctx.session.user
if (s != '1213') {
ctx.redirect('/login')
}
await next()
})
// Router
const common = require('./routes')
app.use(common.routes())
// Server
app.listen(3000, () => { console.log('Listening on http://localhost:3000') })
routes.js
const Router = require('koa-router')
const router = new Router()
// const User = require('./user')
router.get('/', async ctx => {
ctx.body = 'Home Page'
})
router.get('/login', async ctx => {
ctx.body = 'Login Page'
})
module.exports = router
Consider your middleware:
app.use(async (ctx, next) => {
ctx.session.user = '121' // This is all playground. No production stuff.
const s = ctx.session.user
if (s != '1213') {
ctx.redirect('/login')
}
await next()
})
Because s != '1213' always evaluates to "true", ctx.redirect('/login') is executed for every request.
This will do two things:
set the HTTP response code to 302, telling the browser to perform a redirect
set the Location header to /login, telling the browser to location to redirect to
Considering that this happens for every request, you end up in a loop: a request to / is redirected to /login, which itself is redirected to /login, which is also redirected to /login, ad infinitum. At some point, the browser gives up and issues a ERR_TOO_MANY_REDIRECTS error.
FWIW, after calling ctx.redirect(), you typically end the request, for instance like this:
if (s != '1213') {
return ctx.redirect('/login')
}
In your case, you don't end the request, which means that it will be passed to the router.
To answer your comment, I assume you used this:
if (s != '1213') {
ctx.url = '/login';
}
You change the URL that the router will check to see which handler it should call. Sort of like an internal redirect, or a "rewrite": a request to / is handled internally as if it were a request for /login.
This is not something that you want though, because it may confuse the browser. The correct way is to issue a proper redirect, using ctx.redirect(), which will make the browser change the URL in the location bar and issue a new request.
I'll preface this by saying I am new to nodejs in general. Coming from the world of C#, it's a completely different way of thinking for me.
I've gone through a few courses and I'm setting up a little website as sort of a test for myself. And I'm failing!
I'm using socket.io with node, and I'm trying to broadcast a message with the emitter once in a while. I don't care about specific socket points right now (although I will in the future), so the emitter for this should go out to everyone.
I am having trouble accessing the io object from other modules.
I'll post my server.js file, as well as app/index.js, socket/index.js, helpers/index.js and api/index.js. I hope that posting these will show how it's supposed to work.
Ideally, I'd like to keep all socket-related items in the socket module, for consistency. Right now, I'm trying to get a method to run in the helpers module, but ideally the socket module would be better.
Anyway, server.js:
'use strict';
const express = require('express');
const app = express();
const cryptometers = require('./app');
const api = require('./app/api');
const fs = require('fs');
const sources = require('./app/api/sources.json');
app.set('port', process.env.PORT || 3000);
app.set('view engine', 'ejs');
app.use(express.static('public'));
app.use(cryptometers.session);
app.use('/', cryptometers.router);
cryptometers.ioServer(app).listen(app.get('port'), () =>{
console.log('app listening on port ' + app.get('port'));
api.getData(sources[0].source, sources[0].url, app);
setInterval(function(){api.getData(sources[0].source, sources[0].url, app)}, 60000);
});
Standard fare here. I just added a data retriever that calls to an api once every minute, which updates the database.
app/index.js:
'use strict';
const config = require('./config');
// create an IO server instance
let ioServer = app => {
app.locals.globalMarketCap = [];
const server = require('http').Server(app);
const io = require('socket.io')(server);
io.set('transports', ['websocket']);
io.use((socket, next) => {
require('./session')(socket.request, {}, next);
});
require('./socket')(io,app);
return server;
}
// turns router into a module
module.exports = {
router: require('./routes')(),
session: require('./session'),
ioServer,
}
Here I'm initializing socket.io, binding it to the app. It's also where I initialize a local storage array of data. (Is this a good spot to do this??)
socket/index.js:
'use strict';
const h = require('../helpers');
module.exports = (io, app) => {
io.of('/').on('connection', socket =>{
console.log('socket.io connected to client');
if(app.locals.globalMarketCap){
socket.emit('globalMarketCap', JSON.stringify(app.locals.globalMarketCap));
}
})
}
Here I'm responding to connection events, and pushing out the array of data that I defined in the last file above. Again, ideally I'd like all socket type stuff to stay in here.
helpers/index.js:
'use strict';
const router = require('express').Router();
const db = require('../db');
// iterate through the routes object and mount the routes
let _registerRoutes = (routes, method) => {
for(let key in routes){
if(typeof routes[key] === 'object' && routes[key] !== null && !(routes[key] instanceof Array)){
_registerRoutes(routes[key], key);
} else {
// Register the routes
if(method === 'get'){
router.get(key, routes[key]);
} else if(method === 'post'){
router.post(key, routes[key]);
} else {
router.use(routes[key]);
}
}
}
}
let route = routes => {
_registerRoutes(routes);
return router;
}
let updateGlobalMarketCap = (app) =>{
//app.io.socket.emit('globalMarketCap', JSON.stringify(app.locals.globalMarketCap))
}
module.exports = {
route,
updateGlobalMarketCap
}
The commented out line for updateGlobalMarketCap is where my pain is. Trying to get access to the io object there.
api/index.js
'use strict';
const axios = require("axios");
const db = require("../db");
const h = require("../helpers");
let getData = (source, url, app, cryptoMeters) => {
axios
.get(url)
.then(response => {
//console.log(response.data);
response.data["source"] = source;
var data = new db.globalMarketCapModel(response.data);
app.locals.globalMarketCap = response.data;
var query = { source: source};
db.globalMarketCapModel.findOne({
"source":source
}, 'source old_total_market_cap_usd total_market_cap_usd', function(err, market) {
if (market) {
if(market.old_total_market_cap_usd != response.data.total_market_cap_usd
&& market.total_market_cap_usd != response.data.total_market_cap_usd){
response.data["old_total_market_cap_usd"] = market.total_market_cap_usd;
h.updateGlobalMarketCap(app);
}
db.globalMarketCapModel.update(query, response.data, function (err) {
if (err) {
console.log("uhoh")
} else {
return true;
}
});
} else {
data.save(function (err) {
if (err) {
console.log("uhoh")
} else {
return true;
}
})
}
})
return true;
})
.catch(error => {
console.log(error);
return false;
});
}
module.exports = {
getData
}
The getData function here is where a call to the update emitter would take place.
I've considered using standard node event emitters as a solution to my problem, but that might be gumming up the works and there's a simpler answer.
Anyway, thanks for reading, and I'm interested in any commentary on what i've written so far. pitfalls, mistakes, etc. Learning here! :)
There are many different ways to organize your code to accomplish sharing of the io object. Here's one such scheme. You break out your socket.io initialization code into its own module. You give that module two main features:
A constructor function (that you pass the server to) that allows socket.io to initialize itself on your server.
A method to get the io instance after it's been initialized.
This will allow any other code in your project that wants to get access to the io object to do something like this:
const io = require('./io.js').getIO();
Here's how that io module could be structured:
// io.js
// singleton instance of socket.io that is stored here after the
// constructor function is called
let ioInstance;
modules.exports = function(server) {
const io = require('socket.io')(server);
io.set('transports', ['websocket']);
io.use((socket, next) => {
require('./session')(socket.request, {}, next);
});
// save in higher scope so it can be obtained later
ioInstance = io;
return io;
}
// this getIO method is designed for subsequent
// sharing of the io instance with other modules once the module has been initialized
// other modules can do: let io = require("./io.js").getIO();
module.exports.getIO = function() {
if (!ioInstance) {
throw new Error("Must call module constructor function before you can get the IO instance");
}
return ioInstance;
}
And, this module would be initialized like this:
const io = require('./io.js')(server);
Where you pass it your web server so it can hook to that. It has to be initialized like this before anyone can use .getIO() on it. The storage in the module of the ioInstance makes use of the module caching. The module initialization code is only run once. After that, the same exports are just returned each time which have access to the saved ioInstance inside the module.