-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
backend: created user model and authMiddleware
This commit adds: - User Model: Created with name and password fields. - Auth Middleware: JWT authentication middleware. - User controller: Login and Register user. - Auth Endpoints: Register and login endpoints. Fixes: #62
- Loading branch information
1 parent
19abe9b
commit 584ec25
Showing
11 changed files
with
673 additions
and
26 deletions.
There are no files selected for viewing
Binary file not shown.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { Request, Response } from 'express'; | ||
import { catchError, ControllerFunction } from '../utils'; | ||
import jwt from 'jsonwebtoken'; | ||
import { IUser, User } from '../models/userModel'; | ||
import { Types } from 'mongoose'; | ||
import bcrypt from 'bcrypt'; | ||
|
||
const getUserToken = (_id: string | Types.ObjectId): string => { | ||
const token = jwt.sign({ _id }, process.env.JWT_SECRET as string, { | ||
expiresIn: '30d', | ||
}); | ||
return token; | ||
}; | ||
|
||
const registerUser: ControllerFunction = catchError( | ||
async (req: Request, res: Response): Promise<void> => { | ||
const { username, password }: IUser = req.body; | ||
|
||
if ([username, password].some((field) => field?.trim() === '')) { | ||
throw new Error('All fields are required'); | ||
} | ||
const existingUser = await User.findOne({ username }); | ||
if (existingUser) { | ||
throw new Error('User already exists'); | ||
} | ||
|
||
const hashedPassword = await bcrypt.hash(password, 10); | ||
const user = await User.create({ | ||
username, | ||
password: hashedPassword, | ||
}); | ||
|
||
const createdUser = await User.findById(user._id).select('-password'); | ||
|
||
if (!createdUser) { | ||
throw new Error('Failed to create user'); | ||
} | ||
|
||
res.status(201).send({ | ||
message: 'User created successfully', | ||
createdUser, | ||
}); | ||
} | ||
); | ||
|
||
const loginUser: ControllerFunction = catchError( | ||
async (req: Request, res: Response): Promise<void> => { | ||
const { username, password }: IUser = req.body; | ||
|
||
if (!username) { | ||
throw new Error('Username is required'); | ||
} | ||
|
||
const user = await User.findOne({ username }); | ||
if (!user) { | ||
throw new Error('User not found'); | ||
} | ||
|
||
const isPasswordCorrect = await user.isPasswordCorrect(password); | ||
if (!isPasswordCorrect) { | ||
throw new Error('Invalid credentials'); | ||
} | ||
|
||
const token: string = getUserToken((user as IUser)._id as string); | ||
|
||
const loggedInUser = await User.findById(user._id).select('-password'); | ||
|
||
const options = { | ||
httpOnly: true, | ||
secure: true, | ||
}; | ||
|
||
res.status(200).cookie('accessToken', token, options).json({ | ||
token, | ||
user: loggedInUser, | ||
}); | ||
} | ||
); | ||
|
||
export { registerUser, loginUser }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import jwt from 'jsonwebtoken'; | ||
import User, { IUser } from '../models/userModel'; | ||
import { NextFunction, Request, Response } from 'express'; | ||
|
||
export interface AuthRequest extends Request { | ||
user: IUser; | ||
} | ||
|
||
export const verifyToken = async ( | ||
req: AuthRequest, | ||
res: Response, | ||
next: NextFunction | ||
) => { | ||
try { | ||
const accessToken: string = req.cookies.accessToken; | ||
|
||
if (!accessToken) { | ||
return res.status(401).json({ error: 'Access token is required' }); | ||
} | ||
|
||
const { _id } = jwt.verify( | ||
accessToken, | ||
process.env.JWT_SECRET as string | ||
) as { _id: string }; | ||
|
||
const user: IUser | null = await User.findOne({ _id }); | ||
|
||
if (!user) { | ||
return res.status(404).json({ error: 'User not found' }); | ||
} | ||
|
||
req.user = user; | ||
next(); | ||
} catch (error) { | ||
return res.status(401).json({ | ||
error: (error as Error)?.message || 'Invalid access token', | ||
}); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import mongoose from 'mongoose'; | ||
import bcrypt from 'bcrypt'; | ||
import { NextFunction } from 'express'; | ||
|
||
export interface IUser extends mongoose.Document { | ||
username: string; | ||
password: string; | ||
isPasswordCorrect: (password: string) => Promise<boolean>; | ||
} | ||
|
||
const userSchema = new mongoose.Schema<IUser>( | ||
{ | ||
username: { | ||
type: String, | ||
required: true, | ||
unique: true, | ||
}, | ||
password: { | ||
type: String, | ||
required: [true, 'Password is required'], | ||
minlength: [6, 'Password must be at least 6 characters long'], | ||
}, | ||
}, | ||
{ | ||
timestamps: true, | ||
} | ||
); | ||
|
||
userSchema.pre('save', async function (next: NextFunction): Promise<void> { | ||
if (!this.isModified('password')) return next(); | ||
|
||
this.password = await bcrypt.hash(this.password, 10); | ||
next(); | ||
}); | ||
|
||
userSchema.methods.isPasswordCorrect = async function ( | ||
password: string | ||
): Promise<boolean> { | ||
return await bcrypt.compare(password, this.password); | ||
}; | ||
|
||
const User = mongoose.model<IUser>('User', userSchema); | ||
|
||
export { User }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import express from 'express'; | ||
import { loginUser, registerUser } from '../controllers/userController'; | ||
|
||
const router = express.Router(); | ||
|
||
router.route('/register').post(registerUser); | ||
router.route('/login').post(loginUser); | ||
|
||
export default router; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,13 @@ | ||
import { Request, Response } from 'express'; | ||
|
||
type ControllerFunction = (req: Request, res: Response) => Promise<void>; | ||
export type ControllerFunction = (req: Request, res: Response) => Promise<void>; | ||
|
||
export function catchError(fn: ControllerFunction): ControllerFunction { | ||
return async function (req: Request, res: Response) { | ||
try { | ||
return await fn(req, res); | ||
} catch (error) { | ||
res.status(500).json({ error: error }); | ||
res.status(500).json({ error: (error as Error).message }); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import dotenv from 'dotenv'; | ||
import mongoose from 'mongoose'; | ||
|
||
dotenv.config(); | ||
|
||
describe('insert user', () => { | ||
beforeAll(async () => { | ||
await mongoose.connect(`${process.env.MONGO_URI}/users`); | ||
}); | ||
|
||
afterAll(async () => { | ||
await mongoose.connection.close(); | ||
}); | ||
|
||
it('should insert a user into collection', async () => { | ||
const users = mongoose.connection.collection('users'); | ||
const mockUser = { name: 'user1234', password: 'pass1' }; | ||
await users.insertOne(mockUser); | ||
|
||
const insertedUser = await users.findOne({ name: 'user1234' }); | ||
expect(insertedUser).toEqual(mockUser); | ||
}); | ||
}); |