Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User Authentication using JWT token #17

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
689 changes: 680 additions & 9 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,24 @@
"description": "Back-End Repo for Team 1 of Baboon/Bald Eagle Practicum",
"main": "app.js",
"scripts": {
"dev": "nodemon src/server.js"
"dev": "nodemon src/server.js",
"seed": "node prisma/seed.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"@prisma/client": "^6.1.0",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.18.2",
"express-favicon": "^2.0.4",
"joi": "^17.13.3",
"jsonwebtoken": "^9.0.2",
"mongoose": "^7.0.1",
"morgan": "^1.10.0"
"morgan": "^1.10.0",
"pg": "^8.13.1",
"prisma": "^6.1.0"
},
"devDependencies": {
"nodemon": "^2.0.21"
Expand Down
82 changes: 82 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}


model users {
user_id Int @id @default(autoincrement())
username String @unique @db.VarChar(50)
email String @unique @db.VarChar(50)
password_hash String @db.VarChar(255)
role_id Int
user_role user_role[] @relation("users")
comments comments[] @relation("user-comments")
likes likes[] @relation("user-likes")
projects projects[] @relation("users-projects")
}

model roles{
role_id Int @id @default(autoincrement())
role_name String @unique @db.VarChar(50)
roles user_role[] @relation("roles")

}

model user_role{
id Int @id @default(autoincrement())
user_role users @relation("users", fields: [user_id], references: [user_id])
user_id Int
roles roles @relation("roles", fields: [role_id], references: [role_id])
role_id Int
}

model projects {
project_id Int @id @default(autoincrement())
name String @db.VarChar(255)
description String
tags String
github_link String? @db.VarChar(50)
youtube_link String? @db.VarChar(50)
is_public Boolean
owner users @relation("users-projects", fields: [created_by], references: [user_id])
created_by Int
create_at DateTime @default(now())
comment comments[] @relation("projects-comments")
likes likes[] @relation("projects-likes")
}

model comments {
comment_id Int @id @default(autoincrement())
content String
created_at DateTime @default(now())
update_at DateTime @updatedAt
user users @relation("user-comments", fields: [user_id], references: [user_id])
user_id Int
project projects @relation("projects-comments", fields: [project_id], references: [project_id])
project_id Int

}


model likes {
like_id Int @id @default(autoincrement())
created_at DateTime @default(now())
user users @relation("user-likes", fields: [user_id], references: [user_id])
user_id Int @unique
project projects @relation("projects-likes", fields: [project_id], references: [project_id])
project_id Int @unique

}


42 changes: 42 additions & 0 deletions prisma/seed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const { PrismaClient } = require('@prisma/client');
const bcrypt = require('bcryptjs')

const prisma = new PrismaClient();

async function hashPassword(password) {
const salt = await bcrypt.genSalt(10)
password = await bcrypt.hash(password, salt)
return password;
}


async function main() {

// Insert dummy roles
await prisma.roles.createMany({
data: [
{ role_name: "admin", },
{ role_name: "user", },
],
});
// hashed password which will encrypt will be 'password' for both user.
password = await hashPassword('Password')
// Insert dummy users
await prisma.users.createMany({
data: [
{ username: "Alice", email: "[email protected]", password_hash: password, role_id: 1 },
{ username: "Bob", email: "[email protected]", password_hash: password, role_id: 2},
],
});

console.log("Dummy data seeded successfully!");
}

main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
13 changes: 10 additions & 3 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@ const cors = require('cors')
const favicon = require('express-favicon');
const logger = require('morgan');

const mainRouter = require('./routes/mainRouter.js');
const mainRouter = require('./routes/mainRouter');
const authRouter = require('./routes/authRouter');
const userRouter = require('./routes/userRouter');

// middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(logger('dev'));
app.use(express.static('public'))
app.use(express.static('public'));
app.use(favicon(__dirname + '/public/favicon.ico'));

const authenticateToken = require('./middlewares/authMiddleware');

// routes
app.use('/api/v1', mainRouter);
app.use('/api/v1', authRouter);
app.use('/api/v1', userRouter);
app.use('/api/v1', authenticateToken, mainRouter);


module.exports = app;
15 changes: 15 additions & 0 deletions src/controllers/userController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { createUser } = require('../services/userService');

const createUserController = async (req, res) => {
try {
const user = await createUser(req.body);
res.status(201).json(user);
} catch (error) {
console.log(error)
res.status(400).json({ error: error.message });
}
};

module.exports = {
createUserController,
};
20 changes: 20 additions & 0 deletions src/db/connect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require('dotenv').config(); // Load environment variables from .env
const {Client} = require('pg');

// PostgreSQL connection using the connection string
const client = new Client({
connectionString: process.env.DATABASE_URL, // Load connection string from .env
});


// Connect to PostgreSQL
const connectDB = () => {
client.connect().then(() => {
console.log('Connected to PostgreSQL database successfully!');
})
.catch(err => {
console.error('Error connecting to PostgreSQL database:', err.stack);
});
}

module.exports = connectDB
21 changes: 21 additions & 0 deletions src/middlewares/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const jwt = require('jsonwebtoken');
require('dotenv').config();

const JWT_SECRET = process.env.JWT_SECRET;

const authenticateToken = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access denied, token missing' });
}

try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded; // Attach user info to request object
next();
} catch (error) {
res.status(403).json({ error: 'Invalid or expired token' });
}
};

module.exports = authenticateToken;
33 changes: 33 additions & 0 deletions src/middlewares/prismaValidationMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const userSchema = require('../models/userSchema');
const bcrypt = require('bcryptjs');

const prismaValidationMiddleware = async (params, next) => {

if (params.model === 'users' && params.action === 'create') {

const userInput = params.args.data;
// Validate input data
const { error } = userSchema.validate(userInput);
if (error) {
throw new Error(`Validation Error: ${error.message}`);
}
}

// Intercept User model actions
if (params.model === 'users') {

if (params.action === 'create' || params.action === 'update') {
const userInput = params.args.data;

if (userInput && userInput.password_hash) {
// Hash the password
const salt = await bcrypt.genSalt(10);
userInput.password_hash = await bcrypt.hash(userInput.password_hash, salt);
}
}
}

return next(params);
};

module.exports = prismaValidationMiddleware;
10 changes: 10 additions & 0 deletions src/models/userSchema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const Joi = require('joi');

const userSchema = Joi.object({
username: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
password_hash: Joi.string().min(8).required(),
role_id:Joi.number()
});

module.exports = userSchema;
9 changes: 9 additions & 0 deletions src/prisma/prismaClient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const { PrismaClient } = require('@prisma/client');
const prismaValidationMiddleware = require('../middlewares/prismaValidationMiddleware');

const prisma = new PrismaClient();

// Apply the middleware
prisma.$use(prismaValidationMiddleware);

module.exports = prisma;
22 changes: 22 additions & 0 deletions src/routes/authRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const express = require('express');
const { loginUser } = require('../services/authService');

const router = express.Router();

router.post('/login', async (req, res, next) => {
const { username, password_hash } = req.body;

try {
if (!username || !password_hash) {
return res.status(400).json({ error: 'Email and password are required' });
}

const { userLogin, token } = await loginUser(username, password_hash);

res.status(200).json({ userLogin, token });
} catch (error) {
next(error); // Pass error to error handler
}
});

module.exports = router;
8 changes: 8 additions & 0 deletions src/routes/userRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const express = require('express');
const { createUserController } = require('../controllers/userController');

const router = express.Router();

router.post('/users', createUserController);

module.exports = router;
7 changes: 6 additions & 1 deletion src/server.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
const { PORT = 8000 } = process.env;
require('dotenv').config();
const PORT = process.env.PORT || 8000;
const app = require("./app");

const connectDB = require("./db/connect");

connectDB(); // This will execute the connection string to connect to the database.

const listener = () => console.log(`Listening on Port ${PORT}!`);
app.listen(PORT, listener);
45 changes: 45 additions & 0 deletions src/services/authService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const prisma = require('../prisma/prismaClient');
require('dotenv').config();

const JWT_SECRET = process.env.JWT_SECRET// you will find the key .env file
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN; // you will find the key .env file

// Generate JWT Token
const generateToken = (user) => {
const payload = {
id: user.id,
email: user.email,
username: user.username
};
return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
};

// User Login
const loginUser = async (username, password_hash) => {
// Find user by email
const user = await prisma.users.findUnique({ where: { username } });
if (!user) {
throw new Error('Invalid email or password');
}

// Compare passwords
const isPasswordValid = await bcrypt.compare(password_hash, user.password_hash);
if (!isPasswordValid) {
throw new Error('Invalid email or password');
}

// Generate token
const token = generateToken(user);
const userLogin = {
id: user.user_id,
username: user.username,
email: user.email,
role: user.role_id
};

return { userLogin, token };
};

module.exports = { loginUser };
Loading
Loading