I am making a web application that allows Fortnite players to find other players to play with. Users should be able to register, login, post and comment. I have designed the frontend portion of the user login and registration features as well as the backend of the user registration but one of my requirements is that:
Before registration, the server should check whether the username provided is a real Fortnite username using the FortniteTracker API which provides user profiles using their very simple API.
Example Call: GET https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}
How do I verify the username exists before allowing the user to create the account?
I have tried creating a separate endpoint for the API call from the server side but I didn't know how to implement it into my /register endpoint
script.js
function registerRequest(username,password) {
$.ajax({
url: "http://localhost:8080/register",
type: 'POST',
data: JSON.stringify({username,password}),
contentType: "application/json",
error : function(err) {
console.log('Error here!', err)
},
success: function(data) {
console.log('Success!')
// What do I put here?
}
});
}
function handleRegisterSubmit(event) {
event.preventDefault();
const username = $(event.currentTarget).find('.username-register').val()
const password = $(event.currentTarget).find('.password-register').val()
const passwordConfirm = $(event.currentTarget).find('.password-confirm').val()
if (password === passwordConfirm) {
registerRequest(username,password)
}
else {
console.error("Passwords did not match")
}
}
$(function onLoad() {
displayRegisterPage()
$(`.js-content-section`).on('submit', '.js-register-form', handleRegisterSubmit)
}
})
server.js
app.post('/register', jsonParser, (req, res) => {
const requiredFields = ['username', 'password']
for (let i = 0; i < requiredFields.length; i++) {
const field = requiredFields[i]
if (!(field in req.body)) {
const message = `Missing \`${field}\` in request body`
console.error(message)
return res.status(400).send(message)
}
}
let username = req.body.username;
let password = req.body.password;
User.findOne({username})
.then(user => {
if (user) {
const message = `username is already taken`
console.error(message)
return res.status(400).send(message)
}
else {
User.create({username, password})
.then(user => {
const userRes = {
id: user._id,
username: user.username
}
res.status(201).json(userRes)
}
)
}
})
.catch(err => {
console.error(err)
res.status(500).json({ error: 'something went horribly wrong'})
})
})
app.get('/login', (req, res) => {
const usernameReq = User.findById(req.body.username);
if (usernameReq) {
console.log(usernameReq)
res.status(201).json(usernameReq)
}
})
schema.js
const UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true,
trim: true
},
password: {
type: String,
required: true,
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
I expect that if I register with "ninja" as a username I should be able to register since that is a valid Fortnite username. The actual output currently allows users to register with any username that isnt already taken in the database.
You would need packages like axios, request, request-promise (Promise supported version of request) etc to make the external api call. You can try implementing within the register like.
const rp = require('request-promise');
app.post('/register', jsonParser, async (req, res) => {
...
let username = req.body.username;
let password = req.body.password;
const options = {
method : 'GET',
uri: 'https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}',
resolveWithFullResponse: true
}
const data = await rp(options)
// check if response code is 200 and check for the expected body
...
// continue rest of the code
}
Or have another middleware to call the external endpoint and do the checks like:
async function checkUser (req, res, next) {
const options = {
method : 'GET',
uri: 'https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}',
resolveWithFullResponse: true
}
const data = await rp(options)
// check if response code is 200 and check for the expected body
if (checks ok)
// if all check ok go to next middleware
next()
else
// if checks did not succeed
// you could pass error to error handler like next(new Error("Hey you do not exist"))
// or render something here
}
Then mount it like:
app.post('/register', jsonParser, checkUser, (req, res) {
...
You can do it simply by sending the username to the API https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}
It will give you a response mentioning about the user exists or not. Based on the response you can make another AJAX request to register the user only if the user does not exist.
I use a Promise request to resolve, reject when someone enters their username. It is only called onClick. in your request you will be able to determine if the call was successfull or not with the username.
Related
I've been trying to make an express middleware that sends an email using Nodemailer after the previous middleware finishes. I've come up with a few different designs, but ultimately each different version has it's drawback.
Ultimately, I would like the middleware to have a response from the previous middleware. If it is a success, then send a success email, otherwise, send an error email.
I came up with a dual design where one variation pushes to an error middleware, and a success leads to the next middleware. This contains some slight issues of sending multiple headers, specifically on an the second middleware erroring. I could say, if the mail errors out, do nothing. But that doesn't seem right. If anyone has any suggestions on a good design, that would be great.
From what you described, I would suggest not to create different middleware for that, but to just create one generic email function that would handle different type of messages. Then, just use that function in the first middleware and pass different parameters based on use case (success/error).
email-controller.js
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: process.env.EMAIL_PORT,
secure: true,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASSWORD,
},
});
exports.send_email_message = (send_to, subject, message) => {
return new Promise((resolve, reject) => {
const email_message = {
from: { name: process.env.EMAIL_FRIENDLY_NAME },
to: send_to,
subject: subject,
text: message
};
transporter.sendMail(email_message).then(() => {
resolve(true);
}).catch((error) => {
reject(false);
});
})
}
custom-router.js
const { send_email_message } = require('./email-controller');
const express = require('express');
const router = express.Router();
router.get('/custom-middleware', async (req, res, next) => {
try {
// You can calculate "success" variable based on your custom logic
if(success){
await send_email_message('example#gmail.com', 'Success', 'This is body of success message.');
return res.status(200).json({ success: true });
} else {
await send_email_message('example#gmail.com', 'Error', 'This is body of error message.');
return res.status(400).json({ success: false });
}
} catch(error) {
return res.status(400).json({ success: false });
}
});
module.exports = router;
I am developing a web app with an Express sever, and I can't find the reason for the page not to load after I call res.render().
My folder structure is as follows (omitting the files and folders that are unnecessary for this question):
- app
- views
- partials
adminDashboard.ejs
welcome.ejs
- lib
- controllers
auth.controller.js
- models
user.model.js
- routes
routes.js
- public
- js
login.js
server.js
The code in question is as follows:
login.js
loginForm.addEventListener('submit', (event) => {
event.preventDefault()
const username = document.getElementById("inputUsername").value
const password = document.getElementById("inputPassword").value
const user = {
username: username,
password: password
}
return fetch(`/login`, {
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify(user)
})
.then(response => window.location.reload)
})
auth.controller.js
async function login(request, response) {
try {
//We must validate what is sent by the frond end
if (!(request.body.username, request.body.password)) {
response.status(400).send("Inputs missing")
}
//Next we verify if the user actually exists in the database
const existingUser = await User.getByUsername(request.body.username);
//We will include the password comparison logic in this if block
if (existingUser.status == 201 && (await bcrypt.compare(request.body.password, existingUser.user.Password))) {
//Create a more lean user object
const loggedUser = existingUser.user
delete loggedUser.Password
const token = jwt.sign({
username: request.body.username
},
process.env.TOKEN_KEY
)
loggedUser.token = token
return response.status(200).render('adminDashboard.ejs')
} else {
response.status(400).send("User not found")
}
} catch (err) {
//No better way of handling this error as of now
console.log(err)
}
}
async function isLoggedIn(req, res, next) {
const authHeader = await req.headers['authorization']
const token = authHeader && authHeader.split(' ')[1]
if (token == null) return res.status(200).render('welcome.ejs')
jwt.verify(token, process.env.TOKEN_KEY, (err, user) => {
if (err) return res.status(403).redirect('/login')
//req.user = user
next()
})
}
module.exports = {
login: login,
isLoggedIn: isLoggedIn
}
routes.js
var router = require("express").Router()
const auth = require("../controllers/auth.controller.js")
router.post("/login", auth.login)
Now, the problem is whenever I click login in the login page and it runs the fetch with the user credentials, nothing happens. I've used console.log to see up to where everything runs and found that everything runs as it should but when response.render is called in auth.controller.js nothing happens and the browser stays in the login page.
Thank you very much in advance!
I am currently trying to create a login system with a Mongo Database, but it won't work when I try to fetch POST the login credentials to my express.js API via the Chrome Browser. Unlike in any browser itt works when I use the Insomnia Client. I personally think the problem is either in the header or middleware part of the code. I am grateful for every indication where my problem might be.
Code:
Login function:
async function login() {
const data = getUserDataEncrypted(); //gets username and password
await fetch(url + "/checkUser/login", {
method: "POST",
mode: 'cors',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
}).then(res => {
console.log(res.json());
});
}
Used Middleware:
require('dotenv').config();
const express = require("express");
const app = express();
const mongoose = require('mongoose');
app.use(require("cors")());
app.use(express.json());
app.use(require("morgan")("combined"));
Server Side:
router.post('/login', async (req, res) => {
try {
const user = await User.find({ username: req.body.username });
if (user[0].password === req.body.password) {
res.send({
message: "Successfull Login",
login: true
});
return;
} else {
res.send({
message: "Password incorrect",
login: false
});
return;
}
} catch (error) {
res.send({
message: error.message,
req: req.body
});
}
});
User Data:
async function getUserDataEncrypted() {
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
password = await SHA256Encyption(password);
const data = {
username: username,
password: password
}
return data;
}
Images:
In the login function, try to console.log the data from getUserDataEncrypted().
If its null or undefined try to use await.
await getUserDataEncrypted();
const data = getUserDataEncrypted(); //gets username and password
You need to do some basic debugging. If you had logged the value of data then the problem would be obvious.
getUserDataEncrypted is marked as async so it returns a promise.
console.log(JSON.stringify(Promise.resolve()));
Promises stringify to empty objects.
You need to await the promise or get (and use) its result in a then() callback.
I am building a simple Node/Express app to login a user. Before user can login the app must check if the email provided exists in the database.
The structure of my app is like this:
* db/data.js
* app.js // server
I want to login a user
const data = [
{
id: 1,
email: 'xyz#xyz.com',
fist_name: 'hekw',
last_name: 'xyz',
password: 'usr$#',
},
];
export default data;
import express from 'express';
import bodyParser from 'body-parser';
import data from './db/data';
// set up the express app
const app = express();
// Parse incoming requests data
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
/**
* Sign in a user
* #param {object} req
* #param {object} res
*/
app.post(`/login`, (req, res) => {
const findUserByEmail = (email) => {
for (let i = 0; i < data.length; i++) {
return data[i]['email'] === email ? true : false;
}
};
if (findUserByEmail(req.body.email)) {
return res.status(409).send({
success: false,
message: 'email already exists',
//findUserByEmail(req.body.email)
//data,
});
}
const port = 5000;
app.listen(port, () => {
// console.log(`The server running on port ${PORT}`);
});
export default app;
I tried but I couldn't display info of a signed user. How can I achieve it?
This is what I need:
"status":"success"
"data": {
"id": 1,
"email":"xyz#xyz.com",
"first_name": "hekw",
"last_name": "xyz",
"password": "usr$#"
}
Edit
I've implemented the code below, but I want now to check for both email and password.
const findUserByEmail = (email) => data.find(user => user.email === email);
const foundUser = findUserByEmail(req.body.email);
if (!foundUser) {
return res.status(404).send({
status: 'error',
error: 'user does not exist, register first',
});
}
if (foundUser) {
// if password OK then diplay success message. How do I access pwd field here?
return res.status(200).send({
status: 'success',
data: foundUser,
});
}
First of all, I highly recommend using the MVC pattern and create a model for each separate data model. Also, an encryption method such as Bcrypt to encrypt the passwords before storing them to the database and using a token-based approach to handle user authentication.
For the purpose of the example, I provide a solution with the JWT and Bcrypt to help understand the process better, also for people who are looking for a more detailed answer. We can pass a middleware into routes to check the user is authenticated or not then fetch the proper data for the user.
const express = require('express');
const app = express();
const router = express.Router();
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
// This user model can be replaced with your data file, in your sample
const User = require('../models/userModel');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json()); // Always return JSON for the rest api
// Awlays set headers to controll the access
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
if (req.method === 'OPTIONS') {
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE');
return res.status(200).json({});
}
next();
});
// This is the user controller, below return it inside the route
const loginUserController = (req, res) => {
User.findOne({ email: req.body.email }) // find just one record by the email received from the user
.exec() // Use this to make a promise
.then(user => {
if (user.length < 1) { // check if the user found
return res.status(401).json({ // Check if email is not valid
message: 'Authentication Failed! Wrong login information used!'
})
}
// If status code is not 401 and user is found, then compare the password with DB version and pass "err" and "success" parameters
// user.password is the db password
bcrypt.compare(req.body.password, user.password, (err, success) => {
if (err) {
return res.status(401).json({
message: 'Authentication Failed! Wrong login information used!'
})
}
if (success) {
// Then we sign JWT if password matched
// process.env.JWT_KEY is our server jwt token
const token = jwt.sign({
email: user.email,
userId: user._id
}, process.env.JWT_KEY, {
expiresIn: '2d' // we can set the expire date (see th e docs for more info)
});
// Finally we return our token to store into user's browser cookie
// or we can just return the data, but its better to use jwt token and use it everywhere you want to get user data
return res.status(200).json({
message: 'Welcome to My App!',
data: user
token
});
}
// Here we return another 401 if the were no err or success
res.status(401).json({
message: 'Authentication Failed! Wrong login information used!'
})
})
})
.catch(err => {
// Use can create an error controller and put a switch inside of it to check response status code then return proper message
errorController(req, res, res.status, 'ANY');
})
}
// Finally we use our router to post and return login controller
router.post('/login', (req, res) => {
return loginUserController(req, res);
});
app.listen(process.env.PORT || 3000);
There are more advanced configurations, but for simplicity of the example, I provided a simple way to do the correct way (in my opinion). Hope it help.
Packages used in this example
jsonwebtoken
Bcrypt
Your code is not working. Following will not find the user object in your data array.
const findUserByEmail = (email) => {
for (let i = 0; i < data.length; i++) {
return data[i]['email'] === email ? true : false;
}
};
You can find the user like this:
const findUserByEmail = (email) => data.find((datum) => datum.email === email);
Assuming you are sending a POST request with email set correctly. You can use the following code to achieve the result you want:
const findUser = (email, pass) => data.find((datum) => datum.email === email && datum.password === pass);
let foundUser = findUser(req.body.email, req.body.password);
if (foundUser) {
return res.status(200).json({
"status":"success"
"data": foundUser
});
}
res.status(404).json({
"status": "Not Found"
"data": foundUser
});
I followed a tutorial to add login and registration to my Node.js app using JWT token and I'm having a hard time logging in and redirecting to my 'logged in' admin page. User registration works great, but the login portion I can't figure out.
This is the tutorial I was following:
https://medium.freecodecamp.org/learn-how-to-handle-authentication-with-node-using-passport-js-4a56ed18e81e
My code for login looks like this:
router.post('/login', auth.optional, (req, res, next) => {
console.log(req.body);
var user = {
email: req.body.email,
password: req.body.password
}
if (!user.email) {
return res.status(422).json({
errors: {
email: 'is required',
},
});
}
if (!user.password) {
return res.status(422).json({
errors: {
password: 'is required',
},
});
}
return passport.authenticate('local', { session: false }, (err, passportUser, info) => {
if (err) {
return next(err);
}
if (passportUser) {
const user = passportUser;
user.token = passportUser.generateJWT();
console.log("TOKEN: " + user.token);
res.setHeader('Authorization', 'Token ' + user.token);
return res.json({ user: user.toAuthJSON() });
}
return res.status(400).json({
errors: {
message: info,
},
});
})(req, res, next);
});
My '/admin' "logged in" route looks like this:
router.get("/admin", auth.required, function(req, res) {
res.render('admin', {
user : req.user // get the user out of session and pass to template
});
});
I'm not sure how I can redirect to my '/admin' route while also passing the token because currently I am seeing the following error after logging in. Makes sense since I am not passing the token to the '/admin' route...but how do I do that? :)
UnauthorizedError: No authorization token was found at middleware
Thanks in advance for the help!
EDIT:
Still can't figure this out and don't really understand how this flow is supposed to work...where do the headers need to be set to the token and how do I redirect to my admin page once the login is successful.
Here is my middleware code if this helps:
const getTokenFromHeaders = (req) => {
console.log("REQ: " + JSON.stringify(req.headers));
const { headers: { authorization } } = req;
if(authorization && authorization.split(' ')[0] === 'Token') {
return authorization.split(' ')[1];
}
return null;
};
const auth = {
required: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
}),
optional: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
credentialsRequired: false,
}),
};
Your code does not have a problem. You seem to be confused with the login flow from server to client (Frontend/Web).
Let's first have a look the RESTFUL way of doing it. The article also refers to the same flow.
The RESTFUL API flow looks like this:
User requests for login:
POST: /api/v1/auth/login with username and password in request body.
If successful, user is returned with basic inforamtion and token.
If not, user is returned a 401 (Unauthorized) status code.
The login flow ends here.
The token provided earlier to the user is used to make subsequent calls to the backend, which a user can use to perform different operations on the sustem. In essence, it is the client which requests server for subsequent actions with the token provided in the login request.
So for your case, user after receiving the token should make a request for retrieving admin information from the backend.
But, I am assuming you are rendering views from your server-side and you want to render the admin view once the user is successfully logged in, and that's pretty straight forward.
Instead of your res.json() after successful login. You need to use res.render().
res.render('admin', {
user: user.toAuthJSON() // assuming your user contains the token already
})
Edit:
Since res.render() does not change the url in the browser. For that, you need to use res.redirect(). But the problem is, you can not send context in res.redirect().
To achieve that, you will need to pass in the user token as query paramter. See here.
TL;DR
// assuming you are using Node v7+
const querystring = require('querystring');
const query = querystring.stringify({
token: user.token,
});
const adminRoute = '/admin?' + query;
res.redirect(adminRoute)
And in your admin route, you need to slightly modify the code.
Verify the token belongs to a real user and get user information out of the token.
Render the admin template with user information retrieved from step 1.
router.get("/admin", function(req, res) {
// verify the token
const token = req.query.token;
const user = null;
jwt.verify(token, 'secret', function (err, decoded) {
if (err) {
res.status(401).send('Unauthorized user')
}
// decoded contains user
user = decoded.user
});
res.render('admin', {
user : user
});
});
I'm somewhat new to this as well, but I've got it working as follows.
In your server.js file:
const passport = require("passport");
const JwtStrategy = require("passport-jwt").Strategy;
const ExtractJwt = require("passport-jwt").ExtractJwt;
app.use(passport.initialize());
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = Keys.secretOrKey;
passport.use(
new JwtStrategy(opts, (jwt_payload, done) => {
// somefunction looks up the id in jwt payload and
// supplies passport the authenticated user via the "Done" function
somefunction.user(jwt_payload.id)
.then(user => {
if (user) {
return done(null, user);
}
return done(null, false);
});
})
);
In your API definitions
const jwt = require("jsonwebtoken");
router.post("/login", (req, res) => {
const { userInfo } = req.body;
// userInfo has username and password in it
// anotherFuction validates the user id and password combo
anotherFunction(userInfo.id, userInfo.password)
.then(isAuthenticated => {
if (isAuthenticated) {
const payload = {
id: user.sAMAccountName,
firstname: user.givenName,
lastname: user.sn
};
// Sign Token with the payload
jwt.sign(
payload,
Keys.secretOrKey,
{ expiresIn: 3600 },
(err, token) => {
res.json({
success: true,
token: "Bearer " + token
});
}
);
} else {
// don't mind the statuses ^_^'
return res.status(401).json({ error: "Login failed." });
}
})
.catch(err => {
return res.status(400).json(err);
});
});
After calling the API you want to set the auth token. The following lets you delete the token if nothing is passed in, effectively "Logging out".
const setAuthToken = token => {
if (token) {
// Apply to every request
axios.defaults.headers.common["Authorization"] = token;
} else {
// Delete Auth Header
delete axios.defaults.headers.common["Authorization"];
}
};
If you're trying to use it in the front end, you need to use jwt_decode to pull the values from the token and set it however you deem necessary. If using redux to store login data it should look something like this. As I feel that the discussion of using localstorage for jwtToken is outside of the scope of this, just know would need to check for the token.
if (localStorage.jwtToken) {
setAuthToken(localStorage.jwtToken);
const decoded = jwt_decode(localStorage.jwtToken);
store.dispatch({
type: USER_LOGIN,
payload: decoded
});
}
Hope this helped.
From one beginner in JWT to another. Good luck.