I work on a web app hosted on heroku. As database I try to use back4app. With help of the back4app documention I have realized the log in. Since two days I try to get the session to work with it.
Therefore I have read a lot of blogs, articels and of course the parse documentation. But I'm not able to get it to work. I hope, you will find my problem. Following code is my lastest attempt:
const express = require('express');
const server = express();
const path = require('path');
const bodyParser = require('body-parser');
const port = process.env.PORT || 8080;
const back4app = require('parse/node');
back4app.initialize("xxx","yyy");
back4app.serverURL = 'https://parseapi.back4app.com/';
server.use(express.static('web'));
server.use(bodyParser.json());
server.get('/lgn', (req, resp) => {
console.log("server.get('/lgn',...");
resp.sendFile(path.join(__dirname + '/web/login.html'));
});
server.post('/lgn', (req, resp) => {
const data = req.body;
console.log("server.post('/lgn',...");
if(data.email != undefined){
console.log(data.email);
resetPassword(data);
} else{
logIn(data, function(err, user){
console.log(user.get("sessionToken"));
//How to get the user object in other routes?
console.log('session');
back4app.User.enableUnsafeCurrentUser(); //is this a secure solution?
back4app.User.currentAsync().then(function(userObj) {
console.dir(userObj.get("sessionToken"));
});
if(user){
resp.send( JSON.stringify( {url: '/'}) );
} else{
console.log(err);
}
});
}
});
function logIn(data, cb) {
// Create a new instance of the user class
var user = back4app.User
.logIn(data.username, data.password)
.then(function(user) {
console.log('User successful logged in with name: ' + user.get("username"));
cb(null, user);
})
.catch(function(error){
console.log("Error: " + error.code + " " + error.message);
cb(error);
});
}
server.listen(port, (err) => {
if (err) {
return console.log('something bad happened', err)
}
console.log(`server is listening on ${port}`)
});
The userObj is null. But why? What I have to do, that I get the currentUser and his session in other routes?
(I have also tryed to work with back4app.Session, but didn't get, what I want.)
It is unsafe to use the currentUser methods in a Node.js app.
Instead of:
logIn(data, function(err, user){
console.log(user.get("sessionToken"));
//How to get the user object in other routes?
console.log('session');
back4app.User.enableUnsafeCurrentUser(); //is this a secure solution?
back4app.User.currentAsync().then(function(userObj) {
console.dir(userObj.get("sessionToken"));
});
if(user){
resp.send( JSON.stringify( {url: '/'}) );
} else{
console.log(err);
}
});
Use:
logIn(data, function(err, user){
// This is your session token. You will have to send it back to your client. The client should store it and send it to the server in the other routes.
const sessionToken = user.get("sessionToken");
console.log(sessionToken);
//How to get the user object in other routes?
//In order to get the user in other routes you will have to use the sessionToken like this:
back4app.User.me({ sessionToken }).then(function(userObj) {
console.dir(userObj.get("sessionToken"));
});
if(user){
resp.send( JSON.stringify( {url: '/'}) );
} else{
console.log(err);
}
});
Related
I am new to node.js and trying to call an ejs file from server-side js. I am calling both the post and get methods from the client js file. Below is my code.
Client-side js:
function playerlist(){
const button = document.getElementById('playerselection');
//const data={selectedplayers:window.players};
button.addEventListener('click', function() {
console.log(players);
fetch('/FantasyTeam', {method: 'POST',
body: JSON.stringify(players),
headers: {'Content-Type':'application/json'},
})
.then(function(response) {
if(response.ok) {
CallGetMethod();
return;
}
throw new Error('Request failed.');
})
.catch(function(error) {
console.log(error);
});
});
}
function CallGetMethod(){
console.log('Inside CallGetMethod');
fetch('/FantasyTeam', {method: 'GET'})
.then(function(response) {
if(response.ok) {
console.log('Inside response');
return ;
}
throw new Error('Request failed.');
})
.catch(function(error) {
console.log(error);
});
}
**Server-side js:**
app.post('/FantasyTeam', (req, res) => {
const playerlist = req.body;
console.log(playerlist);
sql.connect(dbConfig, {
useNewUrlParser: true
}).then(() => {
// create Request object
console.log("Connected!");
var request = new sql.Request();
//Insert query to FantasyTeam Table with selected players.
request.query("INSERT Query").then(function (err, result)
{
console.log('record added to db');
}).catch(err => {
console.log('Could not insert the record to DB. Exiting now...', err);
process.exit();
});
}
res.sendStatus(201);
}).catch(err => {
console.log('Could not connect to the database. Exiting now...', err);
process.exit();
});
});
app.get('/FantasyTeam', (req, res) => {
// connect to your database
sql.connect(dbConfig, {
useNewUrlParser: true
}).then(() => {
// create Request object
var request = new sql.Request();
request.query("select query"), function (err, result)
{
console.log('records fetched from DB');
//res.send(result);
//res.render(path.resolve(__dirname + "/views/FantasyTeam"),{result});
res.render('FantasyTeam.ejs', { result });
//res.render('FantasyTeam.ejs', { result });
console.log(result.recordset.length);
});
}).catch(err => {
console.log('Could not connect to the database. Exiting now...', err);
process.exit();
});
}) ;
I tried multiple ways to render the FantasyTeam.ejs file. But couldn't succeed.There is no error in code, except that ejs file is not redered. Appreciate any help in this regard.
At first you have set view engine on your node application. Then second step create a views folder on the application root and then create a pages folder in views folder then create all template in your pages folder. views folder needs to be static. finally you can render template from pages folder in views directory. See my example. For more information visit to ejs doc: https://ejs.co/#docs
const express = require('express');
const app = express()
const router = express.Router()
// Setup View Engine and set static folder
app.use(express.static('public'))
app.set('view engine','ejs')
app.set('views','views')
//Controller
router.get('/example',(req,res,next)=>{
res.render('pages/index.ejs',{You Data})
})
Hey guys I am currently am trying to do something similar to what is posted here:
How to authenticate Supertest requests with Passport?
as I would like to test other endpoints that require authentication but in addition need to pass in a jwt. Right now, I tested it on POSTMAN and on the browser and it seems like it's working fine, but my test cases keep on breaking. I have a login POST route that is setup like so:
AccountService.js
// Login POST route
router.post('/account_service/login', (req, res, next) => {
passport.authenticate('local-login', (err, user, info) => {
try {
if (err) {
const error = new Error('An Error occurred: Cannot find user');
return next(error);
} else if (!user) {
return res.redirect('/account_service/login');
}
req.login(user, { session: false }, (error) => {
if (error) {
return next(error);
}
const email = req.body.email;
const role = req.user[0].role;
const id = req.user[0].id;
const user = {
email: email,
role: role,
id: id
};
const accessToken = jwt.sign(user, config.ACCESS_TOKEN_SECRET, {
expiresIn: 28800 // expires in 8 hours
});
const cookie = req.cookies.cookieName;
if (cookie === undefined) {
// set a new cookie
console.log('setting new cookie');
res.cookie('jwt', accessToken, { maxAge: 900000, httpOnly: true });
res.send({ token: accessToken });
} else {
// cookie was already present
console.log('cookie exists', cookie);
}
res.redirect('/account_service/profile');
});
} catch (error) {
return next(error);
}
})(req, res, next);
});
After the user is authenticated, I assign a JSON web token to the user and place it in the cookie so it gets stored within the headers for authorized requests. Here is an example:
AccountService.js
// Get all users
router.get('/account_service/all_users', passport.authenticate('jwt', { session: false }), (req, res, next) => {
const sql = 'select * from user';
const params = [];
db.all(sql, params, (err, rows) => {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({
message: 'success',
data: rows
});
});
});
I use passport.authenticate to ensure that the jwt is valid. This GET request only works after I login with admin user account.
Within my passport file I have it setup like so:
passport.js
const LocalStrategy = require('passport-local').Strategy;
const db = require('../database.js');
const bcrypt = require('bcrypt');
const config = require('../config/config.js');
const JwtStrategy = require('passport-jwt').Strategy;
const cookieExtractor = function (req) {
var token = null;
if (req && req.cookies) token = req.cookies.jwt;
return token;
};
module.exports = function (passport) {
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
passport.use('local-login', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
}, (req, email, password, done) => {
try {
const sql = `select * from user WHERE email = "${email}"`;
const params = [];
db.all(sql, params, (err, row) => {
if (err) {
return done(err);
}
if (!row.length || !bcrypt.compareSync(password, row[0].password)) {
return done(null, false, req.flash('loginMessage', 'Inavalid username/password combination. Please try again.'));
}
return done(null, row);
});
} catch (error) {
return done(error);
}
}));
const opts = {};
opts.jwtFromRequest = cookieExtractor; // check token in cookie
opts.secretOrKey = config.ACCESS_TOKEN_SECRET;
// eslint-disable-next-line camelcase
passport.use(new JwtStrategy(opts, function (jwtPayload, done) {
try {
const sql = `select * from user WHERE email = "${jwtPayload.email}"`;
const params = [];
db.all(sql, params, (err, row) => {
if (err) {
return done(err);
}
if (!row.length || !bcrypt.compareSync('admin', jwtPayload.role)) {
return done(null, false, { message: '403 Forbidden' });
}
return done(null, row);
});
} catch (error) {
return done(error);
}
}));
};
Here's where I get confused as my test cases break. I am trying to login before my test cases to allow my other test cases to run but I end up getting a 401 error. Here are my test cases:
accountservice.test.js
const app = require('../../app');
const supertest = require('supertest');
const http = require('http');
describe('Account Service', () => {
let server;
let request;
beforeAll((done) => {
server = http.createServer(app);
server.listen(done);
request = supertest.agent(server);
request.post('/account_service/login')
.send({ email: 'admin#example.com', password: 'admin' })
.end(function (err, res) {
if (err) {
return done(err);
}
console.log(res);
done();
});
});
afterAll((done) => {
server.close(done);
});
it('Test request all users endpoint | GET request', async done => {
const response = await request.get('/account_service/all_users');
expect(response.status).toBe(200);
expect(response.body.message).toBe('success');
expect(response.body.data.length).toBe(3);
done();
});
});
But my test cases fail as I get a 401 error when it expects a 200 success code.
I tried thinking of a way to extract the jwt from a cookie after the login call so that I can set up the headers for the /account_service/all_users GET request code but was unable to find a way using Supertest. I saw this post: Testing authenticated routes with JWT fails using Mocha + supertest + passport but saw that it gets the token from the body.
After messing around with my code, I ended up having issues with in-memory storage and running asynchronous db.run functions that would call every time I ran my server. So I used a file to store my data and ran my tests again and it ended up working!
Here was the faulty code:
const sqlite3 = require('sqlite3').verbose();
const md5 = require('md5');
const DBSOURCE = ':memory:';
const db = new sqlite3.Database(DBSOURCE, (err) => {
if (err) {
// Cannot open database
console.error(err.message);
throw err;
} else {
db.run(`CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name text,
email text UNIQUE,
password text,
status text,
comments text,
photos text,
CONSTRAINT email_unique UNIQUE (email)
)`,
(err) => {
if (err) {
// Table already created
console.log('Table already created');
} else {
// Table just created, creating some rows
const insert = 'INSERT INTO user (name, email, password, status, comments, photos) VALUES (?,?,?,?,?,?)';
db.run(insert, ['user_delete', 'user_delete#example.com', md5('admin123456'), 'pending_deleted', 'comment1,comment2', 'https://giphy.com/gifs/9jumpin-wow-nice-well-done-xT77XWum9yH7zNkFW0']);
db.run(insert, ['user_no_delete', 'user#example.com', md5('user123456'), 'active', 'comment1', 'https://giphy.com/gifs/cartoon-we-bare-bears-wbb-NeijdlusjcduU']);
db.run(insert, ['mikey', 'mikey#example.com', md5('mikey123'), 'pending_deleted', 'comment1', 'https://giphy.com/gifs/wwe-shocked-vince-mcmahon-gdKAVlnm3bmKI']);
}
});
}
});
module.exports = db;
I simply stored this data within a file and used this code instead:
const sqlite3 = require('sqlite3').verbose();
const DBSOURCE = 'mockdb.sqlite';
// Data inserted inside file
/*
db.run(insert, ['user_delete', 'user_delete#example.com', bcrypt.hashSync('admin123456', saltRounds), 'pending_deleted', 'comment1,comment2', 'https://giphy.com/gifs/9jumpin-wow-nice-well-done-xT77XWum9yH7zNkFW0', bcrypt.hashSync('user', saltRounds)]);
db.run(insert, ['user_no_delete', 'user#example.com', bcrypt.hashSync('user123456', saltRounds), 'active', 'comment1', 'https://giphy.com/gifs/cartoon-we-bare-bears-wbb-NeijdlusjcduU', bcrypt.hashSync('user', saltRounds)]);
db.run(insert, ['mikey', 'mikey#example.com', bcrypt.hashSync('mikey123', saltRounds), 'pending_deleted', 'comment1', 'https://giphy.com/gifs/wwe-shocked-vince-mcmahon-gdKAVlnm3bmKI', bcrypt.hashSync('user', saltRounds)]);
db.run(insert, ['admin', 'admin#example.com', bcrypt.hashSync('admin', saltRounds), 'active', 'admincomments', 'adminphoto', bcrypt.hashSync('admin', saltRounds)]);
console.log('last hit in database');
});
*/
const db = new sqlite3.Database(DBSOURCE, (err) => {
if (err) {
// Cannot open database
console.error(err.message);
throw err;
}
console.log('Connection successful!');
});
module.exports = db;
I also ended up using supertest.agent.
const app = require('../../app');
const supertest = require('supertest');
const http = require('http');
const db = require('../../database/database.js');
describe('Account Service', () => {
let server;
let request;
// Find cookie management option.
beforeAll(async (done) => {
server = http.createServer(app);
server.listen(done);
request = supertest.agent(server);
done();
});
And it ended up working and successfully solving my issue!
I have a MEAN stack app, most notably using Angular with a built in Express.JS backend. I've recently FUBAR'ed my local repo and rebuilt it from a clone of my remote repo, which doesn't include any of the changes I made to ruin my former repo and which is currently functionally deployed on Heroku. However, since I've cloned the repository, I've been having an issue where I am able to make a query directly to my backend with the "/api/monsters/" url and get the results from my database, but when the frontend attempts to do so it returns "Cannot GET /api/monsters".
Here is my Server.js file. This is the version that currently works in Heroku, but to get it working locally I've had to update distDir on line 12 to "var distDir = __dirname + "/dist/monster-playbook";". I also have to replace "process.env.MONGODB_URI" with the url of my database, which I can't post here or in my github repo because the url contains my login credentials.
var express = require("express");
var bodyParser = require("body-parser");
var mongodb = require("mongodb");
var ObjectID = mongodb.ObjectID;
var MONSTER_COLLECTION = "monsters";
var app = express();
app.use(bodyParser.json());
// Create link to Angular build directory
var distDir = __dirname + "/dist";
app.use(express.static(distDir));
// Create a database variable outside of the database connection callback to reuse the connection pool in your app.
var db;
// Connect to the database before starting the application server.
mongodb.MongoClient.connect(process.env.MONGODB_URI , function (err, client) {
if (err) {
console.log(err);
process.exit(1);
}
// Save database object from the callback for reuse.
db = client.db();
console.log("Database connection ready");
// Initialize the app.
var server = app.listen(process.env.PORT || 8080
, function () {
var port = server.address().port;
console.log("App now running on port", port);
});
});
// Generic error handler used by all endpoints.
function handleError(res, reason, message, code) {
console.log("ERROR: " + reason);
res.status(code || 500).json({"error": message});
}
app.get("/api/monsters", function(req, res) {
console.log('attempted');
db.collection(MONSTER_COLLECTION).find({}).toArray(function(err, docs) {
if (err) {
handleError(res, err.message, "Failed to get monsters.");
} else {
res.status(200).json(docs);
}
});
});
app.post("/api/monsters", function(req, res) {
var newMonster = req.body;
newMonster.createDate = new Date();
if (!req.body.name) {
handleError(res, "Invalid user input", "Must provide a name.", 400);
} else {
db.collection(MONSTER_COLLECTION).insertOne(newMonster, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to create new monster.");
} else {
res.status(201).json(doc.ops[0]);
}
});
}
});
app.get("/api/monsters/:id", function(req, res) {
db.collection(MONSTER_COLLECTION).findOne({ _id: new ObjectID(req.params.id) }, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to get monster");
} else {
res.status(200).json(doc);
}
});
});
app.put("/api/monsters/:id", function(req, res) {
var updateDoc = req.body;
delete updateDoc._id;
db.collection(MONSTER_COLLECTION).updateOne({_id: new ObjectID(req.params.id)}, updateDoc, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to update monster");
} else {
updateDoc._id = req.params.id;
res.status(200).json(updateDoc);
}
});
});
app.delete("/api/monsters/:id", function(req, res) {
db.collection(MONSTER_COLLECTION).deleteOne({_id: new ObjectID(req.params.id)}, function(err, result) {
if (err) {
handleError(res, err.message, "Failed to delete contact");
} else {
res.status(200).json(req.params.id);
}
});
});
Edit: Here's the code repo for this site: https://github.com/allredbm/Monster-Playbook.
I'm following the tutorial of Heroku: https://devcenter.heroku.com/articles/mean-apps-restful-api
I followed every step without the deployment to Heroku server and worked in my localhost.
So when I open my application with the port localhost/8080 I found the error of (Cannot Get / ).
this is my server code :
var express = require("express");
var bodyParser = require("body-parser");
var mongodb = require("mongodb");
var ObjectID = mongodb.ObjectID;
var CONTACTS_COLLECTION = "contacts";
var app = express();
app.use(bodyParser.json());
// Create link to Angular build directory
var distDir = __dirname + "/dist/";
app.use(express.static(distDir));
// Create a database variable outside of the database connection callback to reuse the connection pool in your app.
var db;
// Connect to the database before starting the application server.
mongodb.MongoClient.connect(process.env.MONGODB_URI || "mongodb://localhost:27017/test", { useNewUrlParser: true }, function (err, client) {
if (err) {
console.log(err);
process.exit(1);
}
// Save database object from the callback for reuse.
db = client.db();
console.log("Database connection ready");
// Initialize the app.
var server = app.listen(process.env.PORT || 8080, function () {
var port = server.address().port;
console.log("App now running on port", port);
});
});
// CONTACTS API ROUTES BELOW
// Generic error handler used by all endpoints.
function handleError(res, reason, message, code) {
console.log("ERROR: " + reason);
res.status(code || 500).json({"error": message});
}
/* "/api/contacts"
* GET: finds all contacts
* POST: creates a new contact
*/
app.get("/api/contacts", function(req, res) {
db.collection(CONTACTS_COLLECTION).find({}).toArray(function(err, docs) {
if (err) {
handleError(res, err.message, "Failed to get contacts.");
} else {
res.status(200).json(docs);
}
});
});
app.post("/api/contacts", function(req, res) {
var newContact = req.body;
newContact.createDate = new Date();
if (!req.body.name) {
handleError(res, "Invalid user input", "Must provide a name.", 400);
} else {
db.collection(CONTACTS_COLLECTION).insertOne(newContact, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to create new contact.");
} else {
res.status(201).json(doc.ops[0]);
}
});
}
});
/* "/api/contacts/:id"
* GET: find contact by id
* PUT: update contact by id
* DELETE: deletes contact by id
*/
app.get("/api/contacts/:id", function(req, res) {
db.collection(CONTACTS_COLLECTION).findOne({ _id: new ObjectID(req.params.id) }, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to get contact");
} else {
res.status(200).json(doc);
}
});
});
app.put("/api/contacts/:id", function(req, res) {
var updateDoc = req.body;
delete updateDoc._id;
db.collection(CONTACTS_COLLECTION).updateOne({_id: new ObjectID(req.params.id)}, updateDoc, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to update contact");
} else {
updateDoc._id = req.params.id;
res.status(200).json(updateDoc);
}
});
});
app.delete("/api/contacts/:id", function(req, res) {
db.collection(CONTACTS_COLLECTION).deleteOne({_id: new ObjectID(req.params.id)}, function(err, result) {
if (err) {
handleError(res, err.message, "Failed to delete contact");
} else {
res.status(200).json(req.params.id);
}
});
});
PS: when I run the command ng serve: the main page shows but no connection with the database it's just a static page .. please help
and I'm a beginner in this mean languages.
It seems that it misses a part in your server code.
You have configured your API part, which can be called by your Angular app, but you're not serving your Angular app.
You should listen to all GET request and send as only file your index.html which in fact should contain your Angular SPA.
Something like this:
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
You can have a look at the Express doc: http://expressjs.com/fr/api.html#res.sendFile
I'm trying to enable oauth2 login through Twitter in a node app. I have my Twitter account, I am whitelisted for email, have changed the settings on the Twitter development page (so that on login, the user is informed that my app can access their email).
But the user object still does not contain an email entry. Is email a different request, or am I missing something? I'll put the server code below, and can fire up the web server on demand, in case anybody wants to see the console output.
Relevant code in my server.js:
var express = require('express'),
request = require('request'),
path = require('path'),
fs = require('fs'),
bodyParser = require("body-parser");
var Twitter = require("node-twitter-api");
var app = express();
...
var twitter = new Twitter({
consumerKey: 'XXXXXXX',
consumerSecret: 'XXXXXX',
callback: 'http://thelionstestingjungle.com'});
var _requestSecret;
app.get("/tokenRequest", function(req, res) {
console.log('Token request');
twitter.getRequestToken(function(err, requestToken, requestSecret) {
if (err) {
console.log('Token request error');
res.status(500).send(err);
} else {
console.log('Request secret: ' + requestSecret);
_requestSecret = requestSecret;
res.redirect("https://api.twitter.com/oauth/authenticate?oauth_token=" + requestToken);
}
});});
app.get("/accessToken", function(req, res) {
console.log('Access token');
var requestToken = req.query.oauth_token,
verifier = req.query.oauth_verifier;
twitter.getAccessToken(requestToken, _requestSecret, verifier, function(err, accessToken, accessSecret) {
if (err) {
console.log('Get info error');
res.status(500).send(err);
} else {
twitter.verifyCredentials(accessToken, accessSecret, function(err, user) {
if (err) {
console.log('Verification in get info error');
res.status(500).send(err);
} else {
console.log('User: ' + JSON.stringify(user));
res.send(user);
}
});
}
});});
...
SO! The call is indeed different. The verify_credentials call to the API takes a handful of optional params. See the doc page:
https://dev.twitter.com/rest/reference/get/account/verify_credentials
To retrieve user email, add a include_email=true param to your URL. To do this with the node-twitter-api module:
var params = {
'include_email' : true
};
twitter.verifyCredentials(accessToken, accessSecret, params, function(err, user)
The module takes a dictionary, and parses the key-value pairs into URL params, and adds them for you.
EASY!