I'm building a social media app using MERN stack.
I'm using POSTMAN to test the Backend API.
Below is the dependencies list i.e., package.json file.
{
"name": "SocialMediaApp",
"version": "1.0.0",
"description": "A simple MERN-stack based social media app.",
"main": "index.js",
"scripts": {
"development": "nodemon"
},
"author": "Prithvi",
"license": "MIT",
"keywords": [
"react",
"node",
"express",
"mongodb",
"mern"
],
"dependencies": {
"@hot-loader/react-dom": "^17.0.1",
"compression": "^1.7.4",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"express": "^4.17.1",
"express-jwt": "^6.0.0",
"helmet": "^4.4.1",
"jshint": "^2.12.0",
"jsonwebtoken": "^8.5.1",
"loadash": "^1.0.0",
"mongodb": "^3.6.4",
"mongoose": "^5.12.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-hot-loader": "^4.13.0"
},
"devDependencies": {
"@babel/core": "^7.13.10",
"@babel/preset-env": "^7.13.10",
"@babel/preset-react": "^7.12.13",
"babel-loader": "^8.2.2",
"file-loader": "^6.2.0",
"nodemon": "^2.0.7",
"webpack": "^5.24.4",
"webpack-cli": "^4.5.0",
"webpack-dev-middleware": "^4.1.0",
"webpack-hot-middleware": "^2.25.0",
"webpack-node-externals": "^2.5.2"
}
}
Actually, white trying to test the Backend API for POST request for signin at the endpoint http://localhost:3000/auth/signin
, I get the error as shown below with error code 401 Unauthorized
.
{
"error": "Could not sign in ! :("
}
Below is the user model file i.e., user.model.js.
import mongoose from "mongoose";
import crypto from "crypto";
const UserSchema = new mongoose.Schema({
name: {
type: String,
trim: true,
required: 'Name is required',
},
email: {
type: String,
trim: true,
unique: 'Email already exists',
match: [/.+\@.+\..+/, 'Please fill a valid email address'],
required: 'Email is required !',
},
hashed_password: {
type: String,
required: 'Password is required !',
},
salt: String,
updated: Date,
created: {
type: Date,
default: Date.now,
},
});
/**
* The password string that's provided by the user is not stored directly in the user
* document. Instead, it is handled as a virtual field.
*/
UserSchema
.virtual('password')
.set(function (password) {
this._password = password;
this.salt = this.makeSalt();
this.hashed_password = this.encryptPassword(password);
})
.get(function () {
return this._password;
});
/**
* To add validation constraints to the actual password string that's selected by the end
* user, we need to add custom validation logic and associate it with the hashed_password
* field in the schema.
*/
UserSchema.path('hashed_password').validate(function (v) {
if (this._password && this._password.length < 6) {
this.invalidate('password', 'Password must be atleast 6 characters.');
}
if (this.isNew && !this._password) {
this.invalidate('password', 'Password is required.');
}
}, null);
/**
* The encryption logic and salt generation logic, which are used to generate the
* hashed_password and salt values representing the password value, are defined as
* UserSchema methods.
*/
UserSchema.methods = {
authenticate: function (plainText) {
return this.authenticate(plainText) === this.hashed_password;
},
encryptPassword: function (password) {
if (!password) return '';
try {
return crypto
.createHmac('sha1', this.salt)
.update(password)
.digest('hex');
} catch (err) {
return '';
}
},
makeSalt: function () {
return Math.round(new Date().valueOf() * Math.random()) + '';
},
};
export default mongoose.model('User', UserSchema);
Below is the controller file i.e., auth.controller.js.
import User from "../models/user.model";
import jwt from 'jsonwebtoken';
import expressJwt from "express-jwt";
import config from "./../../config/config";
const signin = async (req, res) => {
try {
let user = await User.findOne({
"email": req.body.email
});
if (!user) {
return res.status('401').json({
error: "User not found"
});
}
if (!user.authenticate(req.body.password)) {
return res.status('401').send({
error: "Email and passwords don't match."
});
}
const token = jwt.sign({
_id: user._id,
}, config.jwtSecret);
res.cookie('t', token, {
expire: new Date() + 9999
});
return res.json({
token,
user: {
_id: user._id,
name: user.name,
email: user.email
}
});
} catch (err) {
return res.status('401').json({
error: "Could not sign in ! :("
});
}
};
const signout = (req, res) => {
res.clearCookie("t");
return res.status('200').json({
message: "signed out"
});
};
const requireSignin = expressJwt({
secret: config.jwtSecret,
userProperty: 'auth',
algorithms: ['HS256']
});
const hasAuthorization = (req, res, next) => {
const authorized = req.profile && req.auth && req.profile._id == req.auth._id;
if (!(authorized)) {
return res.status('403').json({
error: "User isn't authorized !"
});
}
next();
};
export default {
signin,
signout,
requireSignin,
hasAuthorization
}
And here is the routes fle i.e., auth.routes.js.
import express from "express";
import authCtrl from "../controllers/auth.controller";
const router = express.Router();
router.route('/auth/signin')
.post(authCtrl.signin);
router.route('/auth/signout')
.get(authCtrl.signout);
export default router;
Lastly, express.js file.
import express from "express";
import path from "path";
import cookieParser from "cookie-parser";
import compress from "compression";
import cors from "helmet";
import helmet from "cors";
import Template from "./../template";
import userRoutes from "./routes/user.routes";
import authRoutes from "./routes/auth.routes";
const CURRENT_WORKING_DIR = process.cwd();
const app = express();
app.use(express.json());
app.use(express.urlencoded({
extended: true
}));
app.use(cookieParser());
app.use(compress());
app.use(helmet());
app.use(cors());
app.use('/dist', express.static(path.join(CURRENT_WORKING_DIR, 'dist')));
app.use('/', userRoutes);
app.use('/', authRoutes);
app
.get("/", (req, res) => {
res.status(200).send(Template());
});
app.use((err, req, res, next) => {
if (err.name === 'UnauthorizedError') {
res.status(401).json({
"error": err.name + ": " + err.message
});
} else if (err) {
res.status(400).json({
"error": err.name + ": " + err.message
});
console.log(err);
}
});
export default app;
P.S
I'm getting the following error in the terminal for user.model.js.
RangeError: Maximum call stack size exceeded
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
I think that you are trying to do something like this:
in the method authenticate just call encryptPassword()
instead of authenticate()
again. You were calling authenticate()
in a recursive loop without control; that was the cause of the error.
UserSchema.methods = {
authenticate: function (plainText) {
return this.encryptPassword(plainText) === this.hashed_password;
},
encryptPassword: function (password) {
if (!password) return '';
try {
return crypto
.createHmac('sha1', this.salt)
.update(password)
.digest('hex');
} catch (err) {
return '';
}
},
makeSalt: function () {
return Math.round(new Date().valueOf() * Math.random()) + '';
},
};