Compare commits
4 Commits
e116b71f12
...
1012286b80
Author | SHA1 | Date |
---|---|---|
Jyotirmoy Bandyopadhayaya | 1012286b80 | |
Jyotirmoy Bandyopadhayaya | a4f8a64d47 | |
Jyotirmoy Bandyopadhayaya | a54827fd17 | |
Jyotirmoy Bandyopadhayaya | 827cf321f5 |
|
@ -2,4 +2,9 @@ JWT_ACCESS_TIME=
|
|||
JWT_REFRESH_TIME=
|
||||
REDIS_PORT=
|
||||
REDIS_HOST=
|
||||
DB_CONN_STRING=
|
||||
DB_CONN_STRING=
|
||||
MAIL_HOST=
|
||||
MAIL_PORT=
|
||||
MAIL_USER=
|
||||
MAIL_PASS=
|
||||
MAIL_FROM=
|
|
@ -0,0 +1,8 @@
|
|||
env:
|
||||
commonjs: true
|
||||
es2021: true
|
||||
node: true
|
||||
extends: eslint:recommended
|
||||
parserOptions:
|
||||
ecmaVersion: latest
|
||||
rules: {}
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx pretty-quick --staged
|
||||
npm run lint --fix
|
|
@ -0,0 +1,4 @@
|
|||
*.key
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
*.log
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
35
README.md
35
README.md
|
@ -1,23 +1,48 @@
|
|||
# jwt-auth-nodejs
|
||||
|
||||
Template to get started with Backend auth module for REST API
|
||||
|
||||
[![imgshields](https://img.shields.io/badge/Version-3-yellowgreen?style=for-the-badge)](https://shields.io/)
|
||||
[![imgshields](<https://img.shields.io/badge/NodeJS-16(JS)-yellow?style=for-the-badge>)](https://shields.io/)
|
||||
|
||||
[![forthebadge](https://forthebadge.com/images/badges/built-with-love.svg)](https://forthebadge.com)
|
||||
[![forthebadge](https://forthebadge.com/images/badges/open-source.svg)](https://forthebadge.com)
|
||||
[![forthebadge](https://forthebadge.com/images/badges/made-with-javascript.svg)](https://forthebadge.com)
|
||||
[![forthebadge](https://forthebadge.com/images/badges/0-percent-optimized.svg)](https://forthebadge.com)
|
||||
|
||||
## Fetures :-
|
||||
|
||||
- MongoDB as Database
|
||||
- JWT as Authentication
|
||||
- Nodemailer as Email Service
|
||||
- Express as Web Server
|
||||
- Error Handling with Express
|
||||
- RSA Key Value Pair for Encryption
|
||||
- Husky as Prettier ESLint and Git Hooks
|
||||
- Ready to Deploy
|
||||
|
||||
# Setup
|
||||
|
||||
After pulling this project, create a file named .env in the root of the project and add below information. Change the values of below keys as per your requirement.
|
||||
After pulling this project, create a file named .env in the root of the project and add below configuration. Change the values of below keys as per your requirement.
|
||||
|
||||
- .env example
|
||||
- .env example
|
||||
|
||||
```
|
||||
JWT_ACCESS_TIME=30s
|
||||
JWT_ACCESS_TIME=7h
|
||||
JWT_REFRESH_TIME=30d
|
||||
REDIS_HOST=192.168.100.101
|
||||
REDIS_PORT=6379
|
||||
DB_CONN_STRING=mongodb://127.0.0.1:27017/nodejsjwtauth
|
||||
MAIL_HOST=smtp.ethereal.email
|
||||
MAIL_PORT=587
|
||||
MAIL_USER=cecile.leffler67@ethereal.email
|
||||
MAIL_PASS=7TfEXqF2GQcmDRGN82
|
||||
MAIL_FROM="Cecile Leffler <cecile.leffler67@ethereal.email>"
|
||||
```
|
||||
|
||||
- Deffi-Hellman key exchange setup
|
||||
- Deffi-Hellman key exchange setup
|
||||
|
||||
```bash
|
||||
ssh-keygen -t rsa -P "" -b 4096 -m PEM -f jwtRS256.key
|
||||
ssh-keygen -e -m PEM -f jwtRS256.key > jwtRS256.key.pub
|
||||
```
|
||||
```
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
const User = require('../models/user.model')
|
||||
const redis_client = require('../helpers/redis_connect')
|
||||
const {
|
||||
GenerateAccessToken,
|
||||
GenerateRefreshToken,
|
||||
} = require('../services/auth.service')
|
||||
const Mailer = require('../services/mail.service')
|
||||
|
||||
async function Register(req, res, next) {
|
||||
// encrypt password
|
||||
const checkIfUsernameExists = await User.findOne({
|
||||
username: req.body.username,
|
||||
}).exec()
|
||||
const checkIfEmailExists = await User.findOne({
|
||||
email: req.body.email,
|
||||
}).exec()
|
||||
|
||||
if (checkIfUsernameExists !== null || checkIfEmailExists !== null)
|
||||
throw new Error('Username or Email already exists')
|
||||
|
||||
const user = new User({
|
||||
username: req.body.username,
|
||||
first_name: req.body.first_name,
|
||||
last_name: req.body.last_name,
|
||||
email: req.body.email,
|
||||
phone: req.body.phone,
|
||||
address: {
|
||||
street: req.body.address,
|
||||
city: req.body.city,
|
||||
state: req.body.state,
|
||||
zip: req.body.zip,
|
||||
country: req.body.country,
|
||||
},
|
||||
birthday: req.body.birthday,
|
||||
})
|
||||
|
||||
user.setPassword(req.body.password)
|
||||
|
||||
try {
|
||||
const saved_user = await user.save()
|
||||
|
||||
Mailer.sendMail({
|
||||
to: saved_user.email,
|
||||
subject: 'Welcome to the app',
|
||||
text: `Welcome to the app, ${saved_user.username}`,
|
||||
})
|
||||
|
||||
res.json({
|
||||
status: true,
|
||||
message: 'Registered successfully.',
|
||||
data: saved_user,
|
||||
})
|
||||
} catch (error) {
|
||||
// do logging in DB or file.
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function Login(req, res, next) {
|
||||
const username = req.body.username
|
||||
|
||||
try {
|
||||
const user = await User.findOne({
|
||||
username: username,
|
||||
}).exec()
|
||||
|
||||
if (user === null || !user.validatePassword(req.body.password))
|
||||
throw new Error('Username or Password is not valid')
|
||||
const access_token = GenerateAccessToken(user._id)
|
||||
const refresh_token = GenerateRefreshToken(user._id)
|
||||
|
||||
return res.json({
|
||||
status: true,
|
||||
message: 'Login Successfully.',
|
||||
data: {
|
||||
access_token,
|
||||
refresh_token,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function Logout(req, res, next) {
|
||||
try {
|
||||
const user_id = req.userData.user
|
||||
const token = req.token
|
||||
|
||||
await redis_client.del(user_id.toString())
|
||||
|
||||
await redis_client.set('BL_' + user_id.toString(), token)
|
||||
|
||||
return res.json({
|
||||
status: true,
|
||||
message: 'Successfully Logged out',
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function checkUsernameAvaiblity(req, res, next) {
|
||||
const username = req.params.username
|
||||
|
||||
try {
|
||||
const user = await User.findOne({
|
||||
username: username,
|
||||
}).exec()
|
||||
|
||||
if (user === null)
|
||||
return res.status(200).json({
|
||||
status: true,
|
||||
message: 'Username is available',
|
||||
})
|
||||
return res.status(200).json({
|
||||
status: false,
|
||||
message: 'Username is not available',
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function GetOnlyNewAccessToken(req, res, next) {
|
||||
try {
|
||||
const userData = req.userData.user
|
||||
const access_token = GenerateAccessToken(userData)
|
||||
|
||||
return res.json({
|
||||
status: true,
|
||||
message: 'Successfully generated new access token.',
|
||||
data: {
|
||||
access_token,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function GetNewToken(req, res, next) {
|
||||
try {
|
||||
const userData = req.userData.user
|
||||
|
||||
const access_token = GenerateAccessToken(userData)
|
||||
const refresh_token = GenerateRefreshToken(userData)
|
||||
|
||||
return res.json({
|
||||
status: true,
|
||||
message:
|
||||
'Successfully generated new access token and refresh token.',
|
||||
data: {
|
||||
access_token,
|
||||
refresh_token,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Register,
|
||||
Login,
|
||||
Logout,
|
||||
checkUsernameAvaiblity,
|
||||
GetOnlyNewAccessToken,
|
||||
GetNewToken,
|
||||
}
|
|
@ -1,138 +1,119 @@
|
|||
const User = require("../models/user.model");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const redis_client = require("../redis_connect");
|
||||
const User = require('../models/user.model')
|
||||
|
||||
async function Register(req, res) {
|
||||
// encrypt password
|
||||
const user = new User({
|
||||
username: req.body.username,
|
||||
});
|
||||
user.setPassword(req.body.password);
|
||||
async function GetUserById(req, res, next) {
|
||||
const user_id = req.params.user_id
|
||||
|
||||
try {
|
||||
const saved_user = await user.save();
|
||||
res.json({
|
||||
status: true,
|
||||
message: "Registered successfully.",
|
||||
data: saved_user,
|
||||
});
|
||||
} catch (error) {
|
||||
// do logging in DB or file.
|
||||
res.status(400).json({
|
||||
status: false,
|
||||
message: "Something went wrong.",
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
try {
|
||||
const user = await User.findById(user_id).exec()
|
||||
|
||||
async function Login(req, res) {
|
||||
const username = req.body.username;
|
||||
if (user === null) throw new Error('User not found')
|
||||
|
||||
try {
|
||||
const user = await User.findOne({
|
||||
username: username,
|
||||
}).exec();
|
||||
|
||||
if (user === null || !user.validatePassword(req.body.password))
|
||||
return res.status(401).json({
|
||||
status: false,
|
||||
message: "Username or Password is not valid.",
|
||||
});
|
||||
// console.log("user", user);
|
||||
const access_token = jwt.sign(
|
||||
{
|
||||
sub: user._id,
|
||||
},
|
||||
process.env.JWT_ACCESS_SECRET,
|
||||
{
|
||||
expiresIn: process.env.JWT_ACCESS_TIME,
|
||||
}
|
||||
);
|
||||
// console.log("access_token", access_token);
|
||||
const refresh_token = GenerateRefreshToken(user._id);
|
||||
|
||||
return res.json({
|
||||
status: true,
|
||||
message: "Login Successfully.",
|
||||
data: {
|
||||
access_token,
|
||||
refresh_token,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(401).json({
|
||||
status: true,
|
||||
message: "Login Failiure.",
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function Logout(req, res) {
|
||||
const user_id = req.userData.sub;
|
||||
const token = req.token;
|
||||
|
||||
await redis_client.del(user_id.toString());
|
||||
|
||||
await redis_client.set("BL_" + user_id.toString(), token);
|
||||
|
||||
return res.json({
|
||||
status: true,
|
||||
message: "Successfully Logged out.",
|
||||
});
|
||||
}
|
||||
|
||||
function GetAccessToken(req, res) {
|
||||
const user_id = req.userData.sub;
|
||||
const access_token = jwt.sign(
|
||||
{
|
||||
sub: user_id,
|
||||
},
|
||||
process.env.JWT_ACCESS_SECRET,
|
||||
{
|
||||
expiresIn: process.env.JWT_ACCESS_TIME,
|
||||
return res.json({
|
||||
status: true,
|
||||
message: 'User found.',
|
||||
data: user,
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
);
|
||||
const refresh_token = GenerateRefreshToken(user_id);
|
||||
return res.json({
|
||||
status: true,
|
||||
message: "Success successfully Generated RefreshToken",
|
||||
data: {
|
||||
access_token,
|
||||
refresh_token,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function GenerateRefreshToken(user_id) {
|
||||
const refresh_token = jwt.sign(
|
||||
{
|
||||
sub: user_id,
|
||||
},
|
||||
process.env.JWT_REFRESH_SECRET,
|
||||
{
|
||||
expiresIn: process.env.JWT_REFRESH_TIME,
|
||||
async function GetUserByUsername(req, res, next) {
|
||||
const username = req.params.username
|
||||
|
||||
try {
|
||||
const user = await User.findOne({
|
||||
username: username,
|
||||
}).exec()
|
||||
|
||||
if (user === null) throw new Error('User not found')
|
||||
|
||||
return res.json({
|
||||
status: true,
|
||||
message: 'User found.',
|
||||
data: user,
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
redis_client.get(user_id.toString(), (err, data) => {
|
||||
if (err) throw err;
|
||||
async function GetAllUsers(_req, res, next) {
|
||||
try {
|
||||
const users = await User.find().exec()
|
||||
|
||||
redis_client.set(
|
||||
user_id.toString(),
|
||||
JSON.stringify({
|
||||
token: refresh_token,
|
||||
})
|
||||
);
|
||||
});
|
||||
if (users === null) throw new Error('User not found')
|
||||
|
||||
return refresh_token;
|
||||
return res.json({
|
||||
status: true,
|
||||
message: 'Users found.',
|
||||
data: users,
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function UpdateUser(req, res, next) {
|
||||
const user_id = req.userData.user
|
||||
const userData = req.body
|
||||
|
||||
try {
|
||||
const user = await User.findById(user_id).exec()
|
||||
|
||||
if (user === null) throw new Error('User not found')
|
||||
|
||||
const updatedUser = await User.findByIdAndUpdate(user_id, userData, {
|
||||
new: true,
|
||||
}).exec()
|
||||
|
||||
return res.json({
|
||||
status: true,
|
||||
message: 'User updated.',
|
||||
data: updatedUser,
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function Dashboard(req, res, next) {
|
||||
const user_id = req.userData.user
|
||||
|
||||
try {
|
||||
return res.json({
|
||||
status: true,
|
||||
message: 'Hello ' + user_id + ' from dashboard.',
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function DeleteUser(req, res, next) {
|
||||
const user_id = req.userData.user
|
||||
|
||||
try {
|
||||
const user = await User.findById(user_id).exec()
|
||||
|
||||
if (user === null) throw new Error('User not found')
|
||||
|
||||
await User.findByIdAndDelete(user_id).exec()
|
||||
|
||||
return res.json({
|
||||
status: true,
|
||||
message: 'User deleted.',
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Register,
|
||||
Login,
|
||||
Logout,
|
||||
GetAccessToken,
|
||||
};
|
||||
GetUserById,
|
||||
GetAllUsers,
|
||||
GetUserByUsername,
|
||||
UpdateUser,
|
||||
Dashboard,
|
||||
DeleteUser,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
const mongoose = require('mongoose')
|
||||
|
||||
module.exports = async () => {
|
||||
await mongoose.connect(
|
||||
process.env.DB_CONN_STRING,
|
||||
{
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true,
|
||||
useFindAndModify: false,
|
||||
},
|
||||
() => console.log('Connected to MongoDB 🍀')
|
||||
)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
const redis = require('redis')
|
||||
|
||||
const redis_client = redis.createClient(
|
||||
process.env.REDIS_PORT,
|
||||
process.env.REDIS_HOST
|
||||
)
|
||||
|
||||
redis_client.on('connect', function () {
|
||||
console.log('Redis Client connected 🍒')
|
||||
})
|
||||
|
||||
module.exports = redis_client
|
55
index.js
55
index.js
|
@ -1,22 +1,45 @@
|
|||
require("dotenv").config();
|
||||
const express = require("express");
|
||||
const app = express();
|
||||
require('dotenv').config()
|
||||
const express = require('express')
|
||||
const fs = require('fs')
|
||||
const morgan = require('morgan')
|
||||
const cors = require('cors')
|
||||
|
||||
const mongoose = require("mongoose");
|
||||
const app = express()
|
||||
const connectMongo = require('./helpers/mongo_connect')
|
||||
const checkENV = require('./utils/checkENV.utils')
|
||||
const err_routes = require('./utils/errorHandler.utils')
|
||||
|
||||
mongoose.connect(
|
||||
process.env.DB_CONN_STRING,
|
||||
{ useUnifiedTopology: true, useNewUrlParser: true },
|
||||
() => console.log("Connected to MongoDB 🍀")
|
||||
);
|
||||
app.use(express.json())
|
||||
app.use(express.urlencoded({ extended: true }))
|
||||
app.use(morgan('tiny'))
|
||||
app.use(cors())
|
||||
|
||||
app.use(express.json());
|
||||
// Connect to MongoDB and Config Checking
|
||||
;(async () => {
|
||||
try {
|
||||
setTimeout(async () => {
|
||||
console.log('Connecting to MongoDB ...')
|
||||
await connectMongo()
|
||||
console.log('Checking Config ...')
|
||||
await checkENV()
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
console.log(error.message)
|
||||
process.exit(1)
|
||||
}
|
||||
})()
|
||||
|
||||
const auth_routes = require("./routes/auth.route");
|
||||
const user_routes = require("./routes/user.route");
|
||||
console.log('Auto Loading routes 🎩 ...')
|
||||
fs.readdirSync('./routes/').forEach(function (file) {
|
||||
console.log('Loading route: /' + file.split('.')[0])
|
||||
app.use(`/${file.split('.')[0]}`, require('./routes/' + file))
|
||||
})
|
||||
console.log('Loaded all routes 🎩 ...')
|
||||
|
||||
app.use("/auth", auth_routes);
|
||||
app.use("/user", user_routes);
|
||||
app.use('*', err_routes.notFound)
|
||||
app.use(err_routes.logErrors)
|
||||
app.use(err_routes.clientErrorHandler)
|
||||
app.use(err_routes.errorHandler)
|
||||
|
||||
const port = process.env.PORT || 5000;
|
||||
app.listen(port, () => console.log(`🤖 API Server is running at ${port} ...`));
|
||||
const port = process.env.PORT || 5000
|
||||
app.listen(port, () => console.log(`🤖 API Server is running at ${port} ...`))
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ES2020",
|
||||
"jsx": "preserve",
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true
|
||||
},
|
||||
"exclude": ["node_modules", "**/node_modules/*"]
|
||||
}
|
|
@ -1,75 +1,63 @@
|
|||
const jwt = require("jsonwebtoken");
|
||||
const redis_client = require("../redis_connect");
|
||||
const jwt = require('jsonwebtoken')
|
||||
const redis_client = require('../helpers/redis_connect')
|
||||
const fs = require('fs')
|
||||
const publicKey = fs.readFileSync('jwtRS256.key.pub')
|
||||
|
||||
function verifyToken(req, res, next) {
|
||||
try {
|
||||
// Bearer tokenstring
|
||||
const token = req.headers.authorization.split(" ")[1];
|
||||
function verifyToken(req, _res, next) {
|
||||
try {
|
||||
// Bearer tokenstring
|
||||
if (req.headers.authorization === undefined)
|
||||
throw new Error('Invalid request')
|
||||
|
||||
const decoded = jwt.verify(token, process.env.JWT_ACCESS_SECRET);
|
||||
req.userData = decoded;
|
||||
const token = req.headers.authorization.split(' ')[1]
|
||||
|
||||
req.token = token;
|
||||
const decoded = jwt.verify(token, publicKey)
|
||||
|
||||
// varify blacklisted access token.
|
||||
redis_client.get("BL_" + decoded.sub.toString(), (err, data) => {
|
||||
if (err) throw err;
|
||||
if (decoded.tokenType !== 'access_token')
|
||||
throw new Error('Invalid token')
|
||||
|
||||
if (data === token)
|
||||
return res.status(401).json({
|
||||
status: false,
|
||||
message: "blacklisted token.",
|
||||
});
|
||||
next();
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(401).json({
|
||||
status: false,
|
||||
message: "Your session is not valid.",
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
req.userData = decoded
|
||||
|
||||
req.token = token
|
||||
|
||||
// verify blacklisted access token.
|
||||
redis_client.get('BL_' + decoded.user.toString(), (err, data) => {
|
||||
if (err) throw err
|
||||
|
||||
if (data === token) throw new Error('Blacklisted token')
|
||||
next()
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
function verifyRefreshToken(req, res, next) {
|
||||
const token = req.body.token;
|
||||
function verifyRefreshToken(req, _res, next) {
|
||||
const token = req.body.token
|
||||
|
||||
if (token === null)
|
||||
return res.status(401).json({
|
||||
status: false,
|
||||
message: "Invalid request.",
|
||||
});
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_REFRESH_SECRET);
|
||||
req.userData = decoded;
|
||||
if (token === null) throw new Error('Invalid request')
|
||||
try {
|
||||
const decoded = jwt.verify(token, publicKey)
|
||||
req.userData = decoded
|
||||
if (decoded.tokenType !== 'refresh_token')
|
||||
throw new Error('Invalid token.')
|
||||
// verify if token is in store or not
|
||||
redis_client.get(decoded.user.toString(), (err, data) => {
|
||||
if (err) throw err
|
||||
|
||||
// verify if token is in store or not
|
||||
redis_client.get(decoded.sub.toString(), (err, data) => {
|
||||
if (err) throw err;
|
||||
if (data === null)
|
||||
throw new Error('Invalid request :- Token is not in store')
|
||||
if (JSON.parse(data).token != token)
|
||||
throw new Error('Invalid request :- Token is not in store')
|
||||
|
||||
if (data === null)
|
||||
return res.status(401).json({
|
||||
status: false,
|
||||
message: "Invalid request. Token is not in store.",
|
||||
});
|
||||
if (JSON.parse(data).token != token)
|
||||
return res.status(401).json({
|
||||
status: false,
|
||||
message: "Invalid request. Token is not same in store.",
|
||||
});
|
||||
|
||||
next();
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(401).json({
|
||||
status: true,
|
||||
message: "Your session is not valid.",
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
next()
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
verifyToken,
|
||||
verifyRefreshToken,
|
||||
};
|
||||
verifyToken,
|
||||
verifyRefreshToken,
|
||||
}
|
||||
|
|
|
@ -1,38 +1,97 @@
|
|||
const mongoose = require("mongoose");
|
||||
const crypto = require("crypto");
|
||||
const mongoose = require('mongoose')
|
||||
const crypto = require('crypto')
|
||||
const { nanoid } = require('napi-nanoid')
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
username: {
|
||||
type: String,
|
||||
require: true,
|
||||
min: 6,
|
||||
max: 255,
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
name: { type: String },
|
||||
salt: { type: String },
|
||||
hash: { type: String },
|
||||
});
|
||||
username: {
|
||||
type: String,
|
||||
require: true,
|
||||
min: 6,
|
||||
max: 255,
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
name: {
|
||||
first_name: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
last_name: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
},
|
||||
phone: {
|
||||
type: String,
|
||||
},
|
||||
address: {
|
||||
street: {
|
||||
type: String,
|
||||
},
|
||||
city: {
|
||||
type: String,
|
||||
},
|
||||
state: {
|
||||
type: String,
|
||||
},
|
||||
zip: {
|
||||
type: String,
|
||||
},
|
||||
country: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
birthday: {
|
||||
type: Date,
|
||||
},
|
||||
salt: {
|
||||
type: String,
|
||||
},
|
||||
hash: {
|
||||
type: String,
|
||||
},
|
||||
attributes: {
|
||||
isVerified: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
emailVerificationToken: {
|
||||
type: String,
|
||||
default: nanoid(),
|
||||
},
|
||||
isAdmin: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isDeleted: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
userSchema.methods.setPassword = function (password) {
|
||||
this.salt = crypto.randomBytes(16).toString("hex");
|
||||
this.hash = crypto
|
||||
.pbkdf2Sync(password, this.salt, 10000, 512, "sha512")
|
||||
.toString("hex");
|
||||
};
|
||||
this.salt = crypto.randomBytes(16).toString('hex')
|
||||
this.hash = crypto
|
||||
.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512')
|
||||
.toString('hex')
|
||||
}
|
||||
|
||||
userSchema.methods.validatePassword = function (password) {
|
||||
const hash = crypto
|
||||
.pbkdf2Sync(password, this.salt, 10000, 512, "sha512")
|
||||
.toString("hex");
|
||||
return this.hash === hash;
|
||||
};
|
||||
const hash = crypto
|
||||
.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512')
|
||||
.toString('hex')
|
||||
return this.hash === hash
|
||||
}
|
||||
|
||||
module.exports = mongoose.model("User", userSchema);
|
||||
module.exports = mongoose.model('User', userSchema)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
56
package.json
56
package.json
|
@ -1,25 +1,35 @@
|
|||
{
|
||||
"name": "jwt-auth-nodejs",
|
||||
"version": "2.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"dev": "nodemon index.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"crypto": "^1.0.1",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mongoose": "^5.11.17",
|
||||
"redis": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.7"
|
||||
}
|
||||
"name": "jwt-auth-nodejs",
|
||||
"version": "2.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"dev": "nodemon index.js",
|
||||
"pretty": "prettier --write .",
|
||||
"prepare": "husky install",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"crypto": "^1.0.1",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mongoose": "^5.11.17",
|
||||
"morgan": "^1.10.0",
|
||||
"napi-nanoid": "^0.0.3",
|
||||
"nodemailer": "^6.7.6",
|
||||
"redis": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.19.0",
|
||||
"husky": "^8.0.0",
|
||||
"nodemon": "^2.0.7",
|
||||
"prettier": "^2.7.1",
|
||||
"pretty-quick": "^3.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
const redis = require("redis");
|
||||
|
||||
const redis_client = redis.createClient(
|
||||
process.env.REDIS_PORT,
|
||||
process.env.REDIS_HOST
|
||||
);
|
||||
|
||||
redis_client.on("connect", function () {
|
||||
console.log("Redis Client connected 🍒");
|
||||
});
|
||||
|
||||
module.exports = redis_client;
|
|
@ -1,12 +1,20 @@
|
|||
const route = require('express').Router();
|
||||
const user_controller = require('../controllers/user.controller');
|
||||
const auth_middleware = require('../middlewares/auth.middleware');
|
||||
const route = require('express').Router()
|
||||
const user_controller = require('../controllers/auth.controller')
|
||||
const auth_middleware = require('../middlewares/auth.middleware')
|
||||
|
||||
route.post('/register', user_controller.Register)
|
||||
route.post('/login', user_controller.Login)
|
||||
route.get('/status/:username', user_controller.checkUsernameAvaiblity)
|
||||
route.post(
|
||||
'/token',
|
||||
auth_middleware.verifyRefreshToken,
|
||||
user_controller.GetOnlyNewAccessToken
|
||||
)
|
||||
route.post(
|
||||
'/token/reset',
|
||||
auth_middleware.verifyRefreshToken,
|
||||
user_controller.GetNewToken
|
||||
)
|
||||
route.get('/logout', auth_middleware.verifyToken, user_controller.Logout)
|
||||
|
||||
|
||||
route.post('/register', user_controller.Register);
|
||||
route.post('/login', user_controller.Login);
|
||||
route.post('/token', auth_middleware.verifyRefreshToken, user_controller.GetAccessToken);
|
||||
route.get('/logout', auth_middleware.verifyToken, user_controller.Logout);
|
||||
|
||||
module.exports = route;
|
||||
module.exports = route
|
||||
|
|
|
@ -1,8 +1,20 @@
|
|||
const route = require('express').Router();
|
||||
const auth_middleware = require('../middlewares/auth.middleware');
|
||||
const route = require('express').Router()
|
||||
const auth_middleware = require('../middlewares/auth.middleware')
|
||||
const user_controller = require('../controllers/user.controller')
|
||||
|
||||
route.get('/dashboard', auth_middleware.verifyToken, (req, res) => {
|
||||
return res.json({status: true, message: "Hello from dashboard."});
|
||||
});
|
||||
route.get('/dashboard', auth_middleware.verifyToken, user_controller.Dashboard)
|
||||
route.get('/all', auth_middleware.verifyToken, user_controller.GetAllUsers)
|
||||
route.get(
|
||||
'/:username',
|
||||
auth_middleware.verifyToken,
|
||||
user_controller.GetUserByUsername
|
||||
)
|
||||
route.get(
|
||||
'/id/:user_id',
|
||||
auth_middleware.verifyRefreshToken,
|
||||
user_controller.GetUserById
|
||||
)
|
||||
route.patch('/', auth_middleware.verifyToken, user_controller.UpdateUser)
|
||||
route.delete('/', auth_middleware.verifyToken, user_controller.DeleteUser)
|
||||
|
||||
module.exports = route;
|
||||
module.exports = route
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
const jwt = require('jsonwebtoken')
|
||||
const redis_client = require('../helpers/redis_connect')
|
||||
const fs = require('fs')
|
||||
|
||||
const privateKey = fs.readFileSync('jwtRS256.key')
|
||||
|
||||
function GenerateAccessToken(user_id) {
|
||||
let userData = {
|
||||
tokenType: 'access_token',
|
||||
user: user_id,
|
||||
}
|
||||
return jwt.sign(userData, privateKey, {
|
||||
algorithm: 'RS256',
|
||||
expiresIn: '1d',
|
||||
})
|
||||
}
|
||||
|
||||
function GenerateRefreshToken(user_id) {
|
||||
const setReferesh = {
|
||||
tokenType: 'refresh_token',
|
||||
user: user_id,
|
||||
}
|
||||
const refresh_token = jwt.sign(setReferesh, privateKey, {
|
||||
algorithm: 'RS256',
|
||||
expiresIn: '7d',
|
||||
})
|
||||
|
||||
redis_client.set(
|
||||
setReferesh.user.toString(),
|
||||
JSON.stringify({
|
||||
token: refresh_token,
|
||||
})
|
||||
)
|
||||
return refresh_token
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
GenerateAccessToken,
|
||||
GenerateRefreshToken,
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
const nodemailer = require('nodemailer')
|
||||
|
||||
let mailClient = nodemailer.createTransport({
|
||||
host: process.env.MAIL_HOST,
|
||||
port: process.env.MAIL_PORT,
|
||||
secure: false, // true for 465, false for other ports
|
||||
auth: {
|
||||
user: process.env.MAIL_USER, // generated ethereal user
|
||||
pass: process.env.MAIL_PASS, // generated ethereal password
|
||||
},
|
||||
})
|
||||
|
||||
async function sendMail(body, next) {
|
||||
const { to, subject, text } = body
|
||||
const mailOptions = {
|
||||
from: process.env.MAIL_FROM,
|
||||
to: to,
|
||||
subject: subject,
|
||||
text: text,
|
||||
}
|
||||
try {
|
||||
await mailClient.sendMail(mailOptions)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendMail,
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
const { exists } = require('fs')
|
||||
|
||||
module.exports = async (fn) => {
|
||||
const notDiffVars = []
|
||||
exists('jwtRS256.key', (chk) => {
|
||||
if (!chk) {
|
||||
throw new Error('Please generate jwtRS256.key ("Private Key")')
|
||||
}
|
||||
})
|
||||
exists('jwtRS256.key.pub', (chk) => {
|
||||
if (!chk) {
|
||||
throw new Error('Please generate jwtRS256.key.pub ("Public Key")')
|
||||
}
|
||||
})
|
||||
console.log('Development environment detected.')
|
||||
;[
|
||||
'JWT_ACCESS_TIME',
|
||||
'JWT_REFRESH_TIME',
|
||||
'REDIS_PORT',
|
||||
'REDIS_HOST',
|
||||
'DB_CONN_STRING',
|
||||
].forEach((envVar) => {
|
||||
if (process.env[envVar] === undefined) {
|
||||
notDiffVars.push(envVar)
|
||||
}
|
||||
})
|
||||
if (notDiffVars.length > 0) {
|
||||
throw new Error(`${notDiffVars.join(', ')} is not defined`)
|
||||
}
|
||||
if (typeof fn === 'function') {
|
||||
fn()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
async function notFound(_req, _res, next) {
|
||||
try {
|
||||
throw new Error('Not Found')
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
|
||||
async function logErrors(err, _req, _res, next) {
|
||||
console.error('\n')
|
||||
console.log('------------Error Log------------')
|
||||
console.error(err.stack)
|
||||
console.log('---------------------------------')
|
||||
next(err)
|
||||
}
|
||||
|
||||
async function clientErrorHandler(err, req, res, next) {
|
||||
if (req.xhr) {
|
||||
res.send({
|
||||
error: 'Something failed!',
|
||||
})
|
||||
} else {
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async function errorHandler(err, _req, res, _next) {
|
||||
if (err.message === 'Invalid request') {
|
||||
return res.status(400).send({
|
||||
status: false,
|
||||
message: err.message,
|
||||
error: err.stack,
|
||||
})
|
||||
} else if (err.message === 'Not Found') {
|
||||
return res.status(404).send({
|
||||
status: false,
|
||||
message: err.message,
|
||||
})
|
||||
} else if (err.message === 'Invalid token') {
|
||||
return res.status(403).send({
|
||||
status: false,
|
||||
message: err.message,
|
||||
error: err.stack,
|
||||
})
|
||||
} else if (err.message === 'Blacklisted token') {
|
||||
return res.status(403).send({
|
||||
status: false,
|
||||
message: err.message,
|
||||
error: err.stack,
|
||||
})
|
||||
} else if (err.message === 'Invalid token :- Token is not in store') {
|
||||
return res.status(403).send({
|
||||
status: false,
|
||||
message: err.message,
|
||||
error: err.stack,
|
||||
})
|
||||
} else if (err.message === 'User not found') {
|
||||
return res.status(404).send({
|
||||
status: false,
|
||||
message: err.message,
|
||||
error: err.stack,
|
||||
})
|
||||
} else if (err.message === 'Username or Email already exists') {
|
||||
return res.status(400).send({
|
||||
status: false,
|
||||
message: err.message,
|
||||
error: err.stack,
|
||||
})
|
||||
} else if (err.message === 'Username or Password is not valid') {
|
||||
return res.status(401).send({
|
||||
status: false,
|
||||
message: err.message,
|
||||
error: err.stack,
|
||||
})
|
||||
} else {
|
||||
return res.status(500).send({
|
||||
status: false,
|
||||
message: err.message,
|
||||
error: err.stack,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
notFound,
|
||||
logErrors,
|
||||
clientErrorHandler,
|
||||
errorHandler,
|
||||
}
|
Loading…
Reference in New Issue