Phase 2 done
This commit is contained in:
57
server/src/controllers/authController.ts
Normal file
57
server/src/controllers/authController.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Request, Response } from "express";
|
||||
import { User } from "../models/User";
|
||||
import { generateToken } from "../utils/generateToken";
|
||||
|
||||
|
||||
export const register = async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const {name, email, password} = req.body
|
||||
if (!name || !email || !password) {
|
||||
res.status(400).json({message: 'All fields are required'});
|
||||
return;
|
||||
}
|
||||
|
||||
const existingUser = await User.findOne({email});
|
||||
if (existingUser) {
|
||||
res.status(409).json({message:'Email already registered'});
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await User.create({ name, email, password });
|
||||
|
||||
const token = generateToken(user._id, user.email);
|
||||
res.status(201).json({
|
||||
token,
|
||||
user,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Register error:', error)
|
||||
res.status(500).json({ message: 'Server error during registration' })
|
||||
}
|
||||
};
|
||||
|
||||
export const login = async (req: Request, res: Response ): Promise<void> => {
|
||||
try {
|
||||
const {email, password} = req.body;
|
||||
if (!email || !password) {
|
||||
res.status(400).json({
|
||||
message: 'Email and password are required'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await User.findOne({ email });
|
||||
if (!user) {
|
||||
res.status(401).json({message:'Invalid Email or password'})
|
||||
return;
|
||||
}
|
||||
|
||||
const token = generateToken(user._id, user.email)
|
||||
|
||||
res.json({token, user})
|
||||
} catch(error) {
|
||||
console.error('Login error:', error)
|
||||
res.status(500).json({ message: 'Server error during login' });
|
||||
|
||||
}
|
||||
}
|
||||
36
server/src/middleware/authMiddleware.ts
Normal file
36
server/src/middleware/authMiddleware.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { User } from '../models/User';
|
||||
import { ENV } from '../config/env';
|
||||
|
||||
interface JwtPayload {
|
||||
id: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export const protect = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
res.status(401).json({ message: 'No token Provided' });
|
||||
return;
|
||||
}
|
||||
const token = authHeader.split(' ')[1];
|
||||
const decode =jwt.verify(token, ENV.JWT_SECRET) as JwtPayload;
|
||||
const user = await User.findById(decode.id)
|
||||
if (!user) {
|
||||
res.status(401).json({ message: 'User no longer exists' })
|
||||
return;
|
||||
}
|
||||
|
||||
req.user = user;
|
||||
next()
|
||||
|
||||
} catch(error) {
|
||||
res.status(401).json({ message: 'Invalid or expired Token' })
|
||||
}
|
||||
}
|
||||
61
server/src/models/User.ts
Normal file
61
server/src/models/User.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Document, model, Schema, Types } from "mongoose";
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
|
||||
export interface IUser {
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
createdAt?: Date;
|
||||
}
|
||||
|
||||
export interface IUserDocument extends IUser, Document {
|
||||
_id: Types.ObjectId
|
||||
comparePassword(candidatePassword: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
const userSchema = new Schema<IUserDocument>({
|
||||
name: {
|
||||
type: String,
|
||||
required: [true, 'Name is required'],
|
||||
trim: true,
|
||||
minLength: [2, 'Name must be at least 2 characters'],
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: [true, 'Email is required'],
|
||||
unique: true,
|
||||
lowercase: true, // always store as lowercase
|
||||
trim: true,
|
||||
match: [/^\S+@\S+\.\S+$/, 'Please enter a valid email'],
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: [true, 'Password is required'],
|
||||
minlength: [6, 'Password must be at least 6 characters'],
|
||||
},
|
||||
},
|
||||
{ timestamps: true } // auto-adds createdAt and updatedAt
|
||||
);
|
||||
|
||||
userSchema.pre('save', async function () {
|
||||
if (!this.isModified('password')) return ;
|
||||
|
||||
const salt = await bcrypt.genSalt(12);
|
||||
this.password = await bcrypt.hash(this.password, salt);
|
||||
});
|
||||
|
||||
userSchema.methods.comparePassword = async function (
|
||||
candidatePassword: string
|
||||
) : Promise<boolean> {
|
||||
return bcrypt.compare(candidatePassword, this.password)
|
||||
}
|
||||
|
||||
userSchema.set('toJSON', {
|
||||
transform: (_doc, ret) => {
|
||||
delete ret.password;
|
||||
return ret
|
||||
}
|
||||
})
|
||||
|
||||
export const User = model<IUserDocument>('User', userSchema);
|
||||
10
server/src/routes/authRoutes.ts
Normal file
10
server/src/routes/authRoutes.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Router } from 'express';
|
||||
import { register, login } from '../controllers/authController';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post('/register', register);
|
||||
router.post ('/login', login)
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -2,6 +2,7 @@ import express, {Application, Request, Response} from 'express';
|
||||
import cors from 'cors';
|
||||
import {ENV} from './config/env';
|
||||
import {connectDB, readyState} from './config/db';
|
||||
import authRoutes from './routes/authRoutes';
|
||||
|
||||
const app: Application = express()
|
||||
|
||||
@@ -27,6 +28,10 @@ app.get('/health/db', (req: Request, res: Response)=> {
|
||||
});
|
||||
|
||||
|
||||
//api routes
|
||||
app.use('/api/auth', authRoutes);
|
||||
|
||||
|
||||
//start Server
|
||||
|
||||
const start = async (): Promise<void> => {
|
||||
|
||||
9
server/src/types/express.d.ts
vendored
Normal file
9
server/src/types/express.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import { IUserDocument } from '../model/User';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: IUserDocument
|
||||
}
|
||||
}
|
||||
}
|
||||
21
server/src/utils/generateToken.ts
Normal file
21
server/src/utils/generateToken.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { Types } from 'mongoose';
|
||||
import { ENV } from '../config/env';
|
||||
|
||||
interface TokenPayload {
|
||||
id: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export const generateToken = (
|
||||
userId: Types.ObjectId,
|
||||
email: string,
|
||||
): string => {
|
||||
const payload: TokenPayload = {
|
||||
id: userId.toString(),
|
||||
email,
|
||||
};
|
||||
return jwt.sign(payload, ENV.JWT_SECRET, {
|
||||
expiresIn: '7d'
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user