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 cors from 'cors';
|
||||||
import {ENV} from './config/env';
|
import {ENV} from './config/env';
|
||||||
import {connectDB, readyState} from './config/db';
|
import {connectDB, readyState} from './config/db';
|
||||||
|
import authRoutes from './routes/authRoutes';
|
||||||
|
|
||||||
const app: Application = express()
|
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
|
//start Server
|
||||||
|
|
||||||
const start = async (): Promise<void> => {
|
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