I have an express server that gets a list of podcasts, from an endpoint.
This apart works fine, but there is a token that I use in requests to authorize entry to the endpoints.
the response when gaining an access token looks like:
{ access_token: '8c9d31761cbd32da25f1f1b988b527cde01c9d8a',
expires_in: 604800,
token_type: 'Bearer',
scope: 'podcast_read episode_read podcast_update episode_publish' }
I have a refresh token that I use when refreshing the token and works well.
The way I'm doing it at the moment is, I have a text file that holds the token, the app reads from this when making a request, I have set up a function, that is called every time the podcasts route is called router.get('/podcasts', checkIfTokenValid, (req, res, next) => { to check if the token is valid or expired if so, refresh the token and write the new token to the file.
The only thing about this is; the write to file function is executed after the podcasts route connects to the endpoint, so the old access token is used.
Logging to the console, the functions are executed before the podcasts route gets all the podcasts, except for the writeAccessTokenToFile() function.
Just wondering, is there a better way to do this?
var express = require('express');
var router = express.Router();
var app = express();
var path = require('path');
var fs = require('fs');
const request = require('request');
var refreshToken = '425153ed4ddb4aee5sjsjsfaeffc46ab9944aece0400f';
var clientId = 'myId';
var client_secret = 'secret';
var isAccessTokenValid;
var access_token_file = path.join(__dirname, 'access_token.txt');
function refreshAccessToken() {
console.log('refreshAccessToken')
var body = { 'grant_type': 'refresh_token', 'refresh_token': refreshToken }
var options = {
url: `https://api.podbean.com/v1/oauth/token`,
headers: { 'Authorization': 'Basic ' + new Buffer(clientId + ":" + client_secret).toString('base64') },
json: body
}
request.post(options, (err, response, body) => {
// console.log(body.expires_in*1000)
if (err) {
return response.status(500).json({
title: 'An error has occured',
error: err
})
}
console.log(body)
writeAccessTokenToFile(body.access_token);
})
}
function getAccessToken() {
return fs.readFileSync(access_token_file, 'utf8');
}
function writeAccessTokenToFile(token) {
console.log('writeAccessTokenToFile = '+ token)
var data = getAccessToken();
var result = data.replace(data, token);
fs.writeFileSync(access_token_file, result, 'utf8');
}
function checkIfTokenValid (req, res, next) {
console.log('checkIfTokenValid')
var options = {
url: `https://api.podbean.com/v1/oauth/debugToken?access_token=${getAccessToken()}`,
headers: { 'Authorization': 'Basic ' + new Buffer(clientId + ":" + client_secret).toString('base64') }
}
request(options, (err, response, body) => {
if (err) {
return res.status(500).json({
title: 'An error has occured',
error: err
})
}
// console.log(JSON.parse(body))
isAccessTokenValid = JSON.parse(body).is_valid;
if (isAccessTokenValid) {
refreshAccessToken();
}
next();
})
};
router.get('/podcasts', checkIfTokenValid, (req, res, next) => {
var options = {
url: `https://api.podbean.com/v1/podcasts?access_token=${getAccessToken()}`
}
request(options, (err, response, body) => {
if (err) {
return res.status(500).json({
title: 'An error has occured',
error: err
})
}
res.json(JSON.parse(body));
next();
})
});
module.exports = router;
Related
I am currently running node with the express middleware and handling request with the GET method. Errors like 404s or 500s are handled at the end of my script, after the GET method, but so far I have not been able to use Node's standard error handling method with err,req,res,next. The below code works fine for me. But ...
server.js
...
// Routing
app.use('/', function (req, res, next) {
req.const = {
nonce: nonce,
publicRoot: publicRoot,
client: client,
cachelifespan: cachelifespan
};
next();
}, router);
// Error handling
app.use('/', function (req, res, next) {
req.const = {
nonce: nonce,
publicRoot: publicRoot,
};
next();
}, errorHandler);
...
errorhandler.js:
...
errorHandler.use(function (req, res) {
let id = req.ip;
let url = req.url;
// Import
let nonce = req.const.nonce;
let publicRoot = req.const.publicRoot;
res.statusCode = 500;
let status = res.statusCode;
try {
let localPath = path.join(publicRoot, req.path);
fs.accessSync(localPath);
} catch (err) {
status = 404;
};
if (req.accepts('html')) {
try {
res.render('system/error', { title: status.toString(), description: 'This is an error!', nonce: 'nonceValue' }, function(err, html) {
html = html.replace("nonceValue", nonce);
res.send(html);
});
} catch (err) {
logError(id, status, req.url, err)
};
} else if (req.accepts('json')) {
res.send({ error: status.toString() });
} else {
res.type('txt').send("error: " + status.toString());
}
logError(id, status, req.url);
});
...
... if I try to write the app.use function to ...
app.use(function (err, req, res, next) {
... it bugs and Node's default error handler takes over.
I am not entirely sure how to fix that.
Or if I am doing the error handling right?
Any suggestions?
I found a solution to my problem.
Inside server.js (the main app js), I load the errorHandler in the beginning of the file:
const errorHandler = require('./errorhandler');
And in the last step, after the basic routing, I added the errorhandler:
// Routing
app.use('/', function (req, res, next) {
req.const = {
nonce: nonce,
publicRoot: publicRoot,
client: client,
cachelifespan: cachelifespan
};
next();
}, router);
// Error handling
app.use('/', function (req, res, next) {
req.const = {
nonce: nonce,
publicRoot: publicRoot,
};
next();
}, errorHandler);
Inside the separate errorhandler file:
//--------------------
// NODE
//--------------------
const express = require('express');
const errorHandler = express.Router();
//--------------------
// LOGGING
//--------------------
const { getDate, logOutput, logError } = require('./logger');
//--------------------
// ROUTER
//--------------------
errorHandler.use(function (req, res) {
let id = req.ip;
let url = req.url;
// Import
let nonce = req.const.nonce;
let publicRoot = req.const.publicRoot;
// Cookies
let insultor = "Samuel L Jackson says";
let insults = ['Motherfucker!!!', 'Son_of_a_bitch!!!', 'Asshole!!!', 'Punkass_motherfucker!!!', 'Whiny_little_bitch!!!', 'You_deserve_to_die_and_I_hope_you_burn_in_hell!!!', 'Web_motherfucker_do_you_speak_it?'];
let number = Math.floor(Math.random() * insults.length);
let insulted = false;
for (let [key, value] of Object.entries(req.cookies)) {
if (key.toString() == insultor) {
value = value.toString() + "_-_" + insults[number];
res.cookie(insultor, value);
insulted = true;
}
}
if (!insulted) {
res.cookie(insultor, insults[number]);
}
res.statusCode = 500;
let status = res.statusCode;
try {
let localPath = path.join(publicRoot, req.path);
fs.accessSync(localPath);
} catch (err) {
status = 404;
};
if (req.accepts('html')) {
try {
res.render('system/error', { title: status.toString(), description: 'This is an error, Motherfucker!', nonce: 'nonceValue' }, function(err, html) {
html = html.replace("nonceValue", nonce);
res.send(html);
});
} catch (err) {
logError(id, status, req.url, err)
};
} else if (req.accepts('json')) {
res.send({ error: status.toString() });
} else {
res.type('txt').send("error: " + status.toString());
}
logError(id, status, req.url);
});
module.exports = errorHandler;
I am trying to complete jwt sign in I am trying to create the jwt token in login.Then I am trying to use it within my user-questions route.
I am using a react front end.
Is this the correct way to do so?
I am currently getting error
const token = req.cookies.auth;
[0] ^
[0]
[0] ReferenceError: req is not defined
Below is my routes login code which assigns the token once the my sql server return that the values for email and password exist. User-questions tries to use this jwt. and I have also included how the token is verfied in a function
Verfiy users
app.get("/user-questions", verifyToken, function(req, res) {
app.use(function(req, res, next) {
// decode token
if (token) {
jwt.verify(token, "secret", function(err, token_data) {
if (err) {
console.info("token did not work");
return res.status(403).send("Error");
} else {
req.user_data = token_data;
sql.connect(config, function(err) {
if (err) console.log(err);
// create Request object
var request = new sql.Request();
// query to the database and get the records
request.execute("dbo.ViewQuestions", function(err, recordset) {
if (err) console.log(err);
// send records as a response
res.json(recordset);
next();
});
});
}
});
} else {
console.info("no token");
console.log("no token");
return res.status(403).send("No token");
}
});
});
Login route
app.post("/login", async (req, response) => {
try {
await sql.connect(config);
var request = new sql.Request();
var Email = req.body.email;
var Password = req.body.password;
console.log({ Email, Password });
request.input("Email", sql.VarChar, Email);
request.input("Password", sql.VarChar, Password);
var queryString =
"SELECT * FROM TestLogin WHERE email = #Email AND password = #Password";
//"SELECT * FROM RegisteredUsers WHERE email = #Email AND Password = HASHBYTES('SHA2_512', #Password + 'skrrt')";
const result = await request.query(queryString);
if (result.recordsets[0].length > 0) {
console.info("/login: login successful..");
console.log(req.body);
token = jwt.sign(
{ Email },
"secretkey",
{ expiresIn: "30s" },
(err, token) => {
res.json({
token
});
res.cookie("auth", token);
res.send("ok");
}
);
} else {
console.info("/login: bad creds");
response.status(400).send("Incorrect email and/or Password!");
}
} catch (err) {
console.log("Err: ", err);
response.status(500).send("Check api console.log for the error");
}
});
Verify users
// Verify Token
function verifyToken(req, res, next) {
// Get auth header value
const bearerHeader = req.headers["authorization"];
// Check if bearer is undefined
if (typeof bearerHeader !== "undefined") {
// Split at the space
const bearer = bearerHeader.split(" ");
// Get token from array
const bearerToken = bearer[1];
// Set the token
req.token = bearerToken;
// Next middleware
next();
} else {
// Forbidden
res.sendStatus(403);
}
}
Please advise if this in theory should work. And if not please advise how to resolve.
EDIT :
The error has been resolved however now simply my jwt tokens to do not work. As when logged in and I manually route to user-questions it does not load the component and within the console it says 403 not available (this is set in the code when the jwt token is not working).
UPDATE:
How would I include
['authorization'] = 'Bearer ' + token;
into
handleSubmit(e) {
e.preventDefault();
if (this.state.email.length < 8 || this.state.password.length < 8) {
alert(`please enter the form correctly `);
} else {
const data = { email: this.state.email, password: this.state.password };
fetch("/login", {
method: "POST", // or 'PUT'
headers: {
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json",
},
body: JSON.stringify(data)
})
// .then(response => response.json())
.then(data => {
console.log("Success:", data);
})
.catch(error => {
console.error("Error:", error);
});
}
}
There are a couple of errors with your code:
In your /login route:
You are trying to set the "auth" cookie after the response is being sent
You are trying to send a response twice, once via res.json and once via res.send
You are assigning to a token variable that no longer exists (token = jwt.sign(...))
In your verifyToken method:
This method is only verifying that the request has a token set, it's not validating or decoding it. I would consider moving your jwt.verify() call to this method.
In your /user-questions route:
You're calling app.use inside of app.get, when both of these are intended to be called at the root level. Remove your app.use call.
You need to grab token from the request, ex. const { token } = req;
You are sending a response via res.json(), but you are still calling next() afterwards. From the Express docs:
If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function.
This is how I would make these changes:
/login route:
app.post("/login", async (req, response) => {
try {
await sql.connect(config);
var request = new sql.Request();
var Email = req.body.email;
var Password = req.body.password;
console.log({ Email, Password });
request.input("Email", sql.VarChar, Email);
request.input("Password", sql.VarChar, Password);
var queryString =
"SELECT * FROM TestLogin WHERE email = #Email AND password = #Password";
//"SELECT * FROM RegisteredUsers WHERE email = #Email AND Password = HASHBYTES('SHA2_512', #Password + 'skrrt')";
const result = await request.query(queryString);
if (result.recordsets[0].length > 0) {
console.info("/login: login successful..");
console.log(req.body);
jwt.sign(
{ Email },
"secretkey",
{ expiresIn: "30s" },
(err, token) => res.cookie('auth', token).json({ token })
);
} else {
console.info("/login: bad creds");
response.status(400).send("Incorrect email and/or Password!");
}
} catch (err) {
console.log("Err: ", err);
response.status(500).send("Check api console.log for the error");
}
});
verifyToken method:
// Verify Token
function verifyToken(req, res, next) {
// Get auth header value
const bearerHeader = req.headers["authorization"];
// Check if bearer is undefined
if (typeof bearerHeader !== "undefined") {
// Split at the space
const bearer = bearerHeader.split(" ");
// Get token from array
const bearerToken = bearer[1];
// verify the token and store it
jwt.verify(bearerToken, "secret", function(err, decodedToken) {
if (err) {
console.info("token did not work");
return res.status(403).send("Error");
}
// Set the token
req.token = bearerToken;
req.decodedToken = decodedToken;
next();
});
} else {
// Forbidden
res.sendStatus(403);
}
}
/user-questions route:
app.get("/user-questions", verifyToken, function(req, res) {
// if a request has made it to this point, then we know they have a valid token
// and that token is available through either req.token (encoded)
// or req.decodedToken
sql.connect(config, function(err) {
if (err) console.log(err);
// create Request object
var request = new sql.Request();
// query to the database and get the records
request.execute("dbo.ViewQuestions", function(err, recordset) {
if (err) console.log(err);
// send records as a response
res.json(recordset);
});
});
});
i send a file from "react dropzone component" to "node server" and try to upload this with "multer" but no show any error, the file no upload and req.file/s is undefined
var express = require('express');
var router = express.Router();
var msg = require('../helpers/MessageHandler');
var CM = require('../helpers/ContentMessages.json');
var storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, 'public/uploads/');
},
filename: function(req, file, cb) {
cb(null, Date.now() + file.originalname);
}
});
var upload = multer({storage: storage}).any();
var path = '/enterprise';
router.post(path, function(req, res, next) {
var enterprise = req.body.obj;
console.log(req.body);
console.log(req.files);
console.log(req.file);
upload(req, res, function(err) {
if(err) {
return res.status(500).json(msg.prototype.errorMsg(err));
} else {
return res.status(200).json(msg.prototype.success(CM.message.success.doc_create, null));
}
});
});
the react component is somthing like this,
in the fetch function i send a object with all the fields
insertObj (values) {
console.info(values);
const obj = JSON.stringify({obj: values});
let url = '/api/v1/enterprise';
const headers = { 'Content-Type': 'application/json', 'Access-Control-Request-Method': '*'};
const req = new Request(url, {method: 'POST', headers: headers, body: obj});
fetch(req)
.then((response) => {
return response.json();
})
.then((enterprise) => {
console.log(enterprise);
}).catch((error) => {
console.log(error);
});
}
inside multer req.files will be visible. So change your code to this:
upload(req, res, function(err) {
var enterprise = req.body.obj;
console.log(req.body);
console.log(req.files);
console.log(req.file);
if(err) {
return res.status(500).json(msg.prototype.errorMsg(err));
} else {
return res.status(200).json(msg.prototype.success(CM.message.success.doc_create, null));
}
});
Also instead of 'Content-Type': 'application/json' there should be enctype='multipart/form-data'.
I have made simple signup, signin and article using MEAN.JS with jsonwebtoken.
In signup page after user entering all values i am passing values to server through signup api. The server side I am creating jsonwebtoken and am passing to client side
exports.create = function (req, res, next) {
var newUser = new User(req.body);
newUser.provider = 'local';
newUser.role = 'user';
newUser.save(function(err, user) {
if (err) return validationError(res, err);
var token = jwt.sign({
_id: user._id
}, config.secrets.session, {
expiresInMinutes: 60 * 5
});
res.json({
token: token
});
});
};
After getting that token client calling some 'me' api (I did not understand what is that me is passing)
client side signup controller:
$scope.register = function(form) {
Auth.createUser({
username: $scope.user.name,
useremail: $scope.user.email,
password: $scope.user.password
})
};
auth.service:
createUser: function(user, callback) {
var cb = callback || angular.noop;
return User.save(user,
function(data) {
$cookieStore.put('token', data.token);
currentUser = User.get();
return cb(user);
},
function(err) {
this.logout();
return cb(err);
}.bind(this)).$promise;
}
user.service :
.factory('User', function ($resource) {
return $resource('/api/users/:id/:controller', {
id: '#_id'
},
{
changePassword: {
method: 'PUT',
params: {
controller:'password'
}
},
get: {
method: 'GET',
params: {
id:'me'
}
}
});
});
After signup:
get: {
method: 'GET',
params: {
id:'me'
}
}
I did not understand this. In server side 'me' api looking like this
route:
router.get('/me', auth.isAuthenticated(), controller.me);
controller :
exports.me = function(req, res, next) {
var userId = req.user._id;
User.findOne({
_id: userId
}, '-salt -hashedPassword', function(err, user) {
if (err) return next(err);
if (!user) return res.status(401).send('Unauthorized');
res.json(user);
});
};
auth.service:
var validateJwt = expressJwt({ secret: config.secrets.session });
/**
* Attaches the user object to the request if authenticated
* Otherwise returns 403
*/
function isAuthenticated() {
return compose()
// Validate jwt
.use(function(req, res, next) {
// allow access_token to be passed through query parameter as well
if(req.query && req.query.hasOwnProperty('access_token')) {
req.headers.authorization = 'Bearer ' + req.query.access_token;
}
validateJwt(req, res, next);
})
// Attach user to request
.use(function(req, res, next) {
User.findById(req.user._id, function (err, user) {
if (err) return next(err);
if (!user) return res.status(401).send('Unauthorized');
req.user = user;
next();
});
}).use(function (err, req, res, next) {
if (err.name === 'UnauthorizedError') {
var e = [];
e.push(err);
return res.status(401).send(e);
}
});
}
I want to know what they are passing in the 'me' api and how I'm getting 'req.user._id' in exports.me function. If I want to make the 'me' api (my own), how can I pass this my token?
The server side console I'm getting this: GET /api/users/me 200 876ms - 339b.
I haven't been able to find an answer to this error as of yet and am wondering if it has to do with version 4 of Express
controller.js
var request = require('request');
var apiOptions = {
server : "http://localhost:3000"
};
if(process.env.NODE_ENV === 'production') {
apiOptions.server = "https://heroku_path";
}
module.exports.homelist = function(req, res) {
var requestOptions, path;
path = '/api/locations';
requestOptions = {
url: apiOptions.server + path,
method: "GET",
json: {},
qs : {
lng : -117.929835,
lat : 33.614675,
max : 30000
}
};
request(requestOptions, function(err, res, body) {
renderHomepage(req, res, body);
});
var renderHomepage = function(req, res, body) {
res.render('jade-template', {
title: 'I'm a main title',
pageHeader: {
title: 'I'm a title',
strapline: 'I'm a strapline'
},
locations: body,
sidebar: 'yadda yadda'
});
};
The homelist function gets called from my routes.js which is then where my API gets called from my request with the query found in requestOptions.
however when the request callback is fired and renderHomepage in invoked with the API variable body I get the error:
res.render('jade-template', {
^
TypeError: res.render is not a function
All my routes are set up and tested fine. Does anyone have a solution for this?
The res argument in the renderHomepage function (the request's callback) is not the same as the res of the express route!
Change your call the request to something like:
request(requestOptions, function(err, serverRes, body) {
renderHomepage(req, res, body);
});
Then the response to your request to /api/locations is serverRes, and the response you send to your client is res.