How can i make a one time route using Express (node.js)? - javascript

I tried to to that with a random generated string but the route was not created.

The snippet below gives you an idea on how it can be implemented with temporary token. Basically you need to use a path param as a token, verify if that token exists and then discard it after first usage. You can test this code with:
http://localhost:3000/temporary-link/d5407341-3a54-4e30-acf1-09d2174b3e23
After hitting it a second time, it won't work.
var express = require('express');
var app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
//get from database or something like it
const allowedUUIDs = ['d5407341-3a54-4e30-acf1-09d2174b3e23',
'7811c8bf-ddf7-4439-a193-93dca12a0656',
'cef82390-9c0a-43e9-93e8-1a80aa5eced5',
'86520485-9d09-48ba-b65c-9bab0ff2e3a2'
];
//test your UUID (access token)
function checkSingleAccess(requestUUID) {
const index = allowedUUIDs.indexOf(requestUUID);
const UUIDExists = index > -1;
if(UUIDExists){
//remove from array, or your database
allowedUUIDs.splice(index, 1);
}
console.log(UUIDExists);
return UUIDExists;
}
//set a path parameter with :uuid
app.use('/temporary-link/:uuid', function(req, res, next) {
//get your path parameter
const requestUUID = req.params.uuid;
//test if allowed
if(checkSingleAccess(requestUUID)){
res.send(`UUID ${requestUUID} allowed` );
}else{
//if not build an error
const errorResponse = `UUID ${requestUUID} not allowed`;
res.status(403, errorResponse);
res.send(errorResponse);
}
});
module.exports = app;

Related

How to share/store data between routes in express (nodejs)?

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.

Check if header value match

Is it possible to check the header value in Node.js? I would like to create a route that can be accessed only if the user supplies a header and its value matches what is coded. For example, suppose that route expect a header like AccessKey: 12345 so it checks if there is such a header containing such value and if it doesn't match it throws an error. I tried to use something like res.hasHeader() like this:
app.route('/rest/api/here').get((req, res) => {
if (res.hasHeader('AccessKey', '12345')){
res.send('test')
} else {
res.send('Header value doesn\'t match')
}
})
but it only checks if the header exists itself and doesn't check if the value match. Application is mostly for educational purposes so this approach is acceptable, if possible.
I would recommend using a library to parse the result, here is a complete example with comments to explain the parts. As the #Heretic Monkey pointed, out the token is on the request object, but thats not the approach i would use.
// this is standard set of imports in app generated by
// express --no-view
// from package npm i -g express-generator
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
// token 'parsing' library
var bearer = require('express-bearer-token');
// more boilerplate
var app = express();
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// look for the key in headers: { Authorization: AccessKey <your key> }
// this library also has options for query, body, etc...
// https://www.npmjs.com/package/express-bearer-token
app.use(bearer({ headerKey: 'AccessKey' }));
// if present and what you wanted, proceed, else, fail
var protect = (req, res, next) => (
(req.token && req.token === '12345')
? next()
: next(new Error('bad token'))
);
// example protected (can protect a whole router with router.use(protect))
app.get('/protected', protect, (r, s) => s.json({ data: 'api' }));
// example not protected
app.get('/example', (r, s) => s.json({ not: 'protected' }));
// make sure to status 500 to make axios client throw
app.use((error, r, s, n) => s
.status(500)
.json({ error: (error + '') }));
// run the server
var server = app.listen(3000);
// client code (axios works in browser same exact api)
var axios = require('axios');
// wait until server started
setTimeout(async function() {
// you will get status 500 without key on protected route
try {
await axios.get('http://localhost:3000/protected');
console.log('nope, wont see me print')
} catch (e) {
console.log('error for protected no token:', e.response.data.error);
}
// non protected works as expected
var example = await axios.get('http://localhost:3000/example');
console.log('got example data fine: ', example.data);
// for protected, need to supply header
var protected = await axios({
method: 'get',
url: 'http://localhost:3000/protected',
headers: { Authorization: 'AccessKey 12345' }
});
console.log('got protected data fine w/tok: ', protected.data);
// wait for server to shut down then exit the program
server.close(() => console.log('bye'))
}, 500);

Express over Node.js - TypeError: Cannot read property 'forEach' of undefined

I'm building my first node/express app and am following this tut.
I am at a point where I am trying to get all JSON data and put it in an array to be sent to the template and rendered. When I try to run the app via CLI, I get the following error:
Directory Structure
The data output at the var blogsurlall location
hellotest.js
var routes = require('./routes/index');
var express = require('express');
var app = express();
var request = require("request");
var blogsurlall = "https://[JSON export URL location configured in a Drupal 8 view]";
app.set('view engine','ejs');
var server = app.listen (2000, function(){ console.log('Waiting for you on port 2000'); });
/* Get all global blogs data */
request({
url: blogsurlall,
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
blogsdata_all = body;
}
// Create blogs array for footer.
var blogs = [];
// Fill up the array with blogs.
blogsdata_all.blogs.forEach(function(item){
blogs = blogs.concat(item);
});
app.locals.blogsdata = blogs;
});
app.use('/', routes);
index.js
var express = require('express');
var routes = express.Router();
routes.get('/', function(req, res){ res.render('default',{title: 'Home', body: 'blogsdata'}); });
routes.get('/about-us', function(req, res){ res.send('<h1>Lucius Websystems</h1>Amsterdam, The Netherlands'); });
routes.get('/about/:name?', function(req, res){ var name = req.params.name; res.send('<h1>' +name +'</h1>About text'); });
/* GET Blog detail page. */
routes.get('/blog/:blogid', function(req, res, next) {
// Place json data in a var.
var blogsdata = req.app.locals.blogsdata;
// Create array.
var blogItem = [];
// Check and build current URL
var currentURL = '/blog/' + req.params.blogid;
// Lop through json data and pick correct blog-item based on current URL.
blogsdata.forEach(function (item) {
if (item.title == currentURL) {
blogItem = item;
}
});
if (blogItem.length == 0) {
// Render the 404 page.
res.render('404', {
title: '404',
body: '404'
});
} else {
// Render the blog page.
res.render('blog-detail', {
blog: blogItem
});
}
});
module.exports = routes;
From the CLI error, it appears no blog data is even returned to be read into the array.
I have carefully gone through the tutorial several times and I think there are steps that may be implied that I am missing.
Can someone please help me understand how to get the blog data so that it can be read into the array and output to my template?
Also open to troubleshooting suggestions in comments.
Thanks for reading!
The error is raising in this line:
blogsdata_all.blogs.forEach(function(item){
As the error says, blogs is undefined.
If there is an error in the request or status code isn't 200, the body is not assigned to the variable, but you are not finishing the execution, so the variable in that case would be undefined.
Other possible problem is the json received doesn't have blogs as key of the body.
Check this both things and let us know if you found the problem

Access request body in check function of express-validator v4

I just started using express.js with express-validator to validate some input data and I have problems accessing the request body in the new check API that was introduced in version 4.0.0.
In older versions, you simply added express-validator as middleware in your app.js somewhere after body-parser:
// ./app.js
const bodyParser = require("body-parser");
const expressValidator = require("express-validator");
const index = require("./routes/index");
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(expressValidator());
Then in my index route, I could check the fields in the final callback function of the post method.
// ./routes/index.js
const express = require("express");
const router = express.Router();
router.post("/submit", (req, res, next) => {
// check email
req.check('email','Invalid email address').isEmail()
// check if password is equal to password confirmation
req.check('password', 'Invalid password')
/* Access request body to compare password
field with password confirmation field */
.equals(req.body.confirmPassword)
// get errors
const errors = req.validationErrors();
// do stuff
});
Like in this example, I could easily check whether the values of my password field and the password confirmation field of my form are equal. However, since version 4, they have a new API which requires you to load the express-validator directly in your router file and pass the check functions as array of functions before the final callback in the post method, like this:
// ./routes/index.js
const express = require("express");
const router = express.Router();
const { check, validationResult } = require("express-validator/check");
router.post(
"/submit",
[
// Check validity
check("email", "Invalid email").isEmail(),
// Does not work since req is not defined
check("password", "invalid password").isLength({ min: 4 })
.equals(req.body.confirmPassword) // throws an error
],
(req, res, next) => {
// return validation results
const errors = validationResult(req);
// do stuff
});
This doesn't work since req is not defined. So my quetsion is: how can I access the request object in a check() chain to compare two different fields with the new express-validator API? Thanks very much in advance!
After fiddling around for a while, I found a way to achieve this by using custom validators. The validator function passed to the custom method accepts an object containing the request body:
router.post(
"/submit",
[
// Check validity
check("email", "Invalid email").isEmail(),
check("password", "invalid password")
.isLength({ min: 4 })
.custom((value,{req, loc, path}) => {
if (value !== req.body.confirmPassword) {
// trow error if passwords do not match
throw new Error("Passwords don't match");
} else {
return value;
}
})
],
(req, res, next) => {
// return validation results
const errors = validationResult(req);
// do stuff
});

How to set the API key in Stampery API.JS file

I am working on setting up Stampery. I am unable to figure out where to set the string API key in this API.JS file. The documentation says to set the STAMPERY_TOKEN as the API key not sure how to do this. Any help would be appreciated.
The link for Stampery is https://github.com/stampery/office.
'use strict';
const express = require('express');
const router = express.Router();
const bodyParser = require('body-parser')
const Stampery = require('stampery');
const development = process.env.NODE_ENV !== 'production';
const stamperyToken = process.env.STAMPERY_TOKEN;
var proofsDict = {}
if (!stamperyToken) {
console.error('Environment variable STAMPERY_TOKEN must be set before running!');
process.exit(-1);
}
//var stampery = new Stampery(process.env.STAMPERY_TOKEN, development ? 'beta' : false);
// For now, always use production Stampery API due to not making it work against beta.
var stampery = new Stampery(process.env.STAMPERY_TOKEN);
router.use(bodyParser.json());
router.post('/stamp', function (req, res) {
var hash = req.body.hash;
// Throw error 400 if no hash
if (!hash)
return res.status(400).send({error: 'No Hash Specified'});
// Transform hash to upper case (Stampery backend preferes them this way)
hash = hash.toUpperCase()
// Throw error 422 if hash is malformed
var re = /^[A-F0-9]{64}$/;
if (!(re.test(hash)))
return res.status(422).send({error: 'Malformed Hash'});
stampery.stamp(hash, function(err, receipt) {
if (err)
res.status(503).send({error: err});
else
res.send({result: receipt.id, error: null});
});
});
router.get('/proofs/:hash', function (req, res) {
var hash = req.params.hash;
stampery.getByHash(hash, function(err, receipts) {
if (err)
res.status(503).send({error: err});
else
if (receipts.length > 0)
res.send({result: receipts[0], error: null});
else
res.status(200).send({error: 'Oops! This email has not yet been attested by any blockchain.'});
});
});
module.exports = router;
I have added the following in Azure website. Should this suffice :
You need to set up STAMPERY_TOKEN environment veriable before starting your server.
You can do this like this for example (in Windows) set STAMPERY_TOKEN=your-token&& node app.js
There are 2 ways to add this to environment (For Ubuntu).
Add to bashrc File. Like:
export STAMPERY_TOKEN="YOUR-TOKEN"
Pass these params before running server. Like:
STAMPERY_TOKEN=YOUR-TOKEN node server.js
To access this variable you can get by:
console.log(process.env["STAMPERY_TOKEN"]);

Categories

Resources