-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #32 from AhmedFatthy1040/issue-19-integration
Authentication System Implementation and Frontend Integration
- Loading branch information
Showing
14 changed files
with
591 additions
and
83 deletions.
There are no files selected for viewing
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 |
---|---|---|
@@ -1,11 +1,25 @@ | ||
import express from 'express'; | ||
import cors from 'cors'; | ||
import imageRoutes from "./routes/imageRoutes"; | ||
import authRoutes from "./routes/authRoutes"; | ||
import { authenticateToken } from './middlewares/authMiddleware'; | ||
|
||
const app = express(); | ||
|
||
// Enable CORS for frontend requests | ||
app.use(cors({ | ||
origin: 'http://localhost:3000', // Your frontend URL | ||
credentials: true | ||
})); | ||
|
||
app.use(express.json()); | ||
app.use(express.urlencoded({ extended: true })); | ||
|
||
app.use("/api/images", imageRoutes); | ||
// Public routes | ||
app.use("/api/auth", authRoutes); | ||
|
||
// Protected routes - ensure authenticateToken is applied to all image routes | ||
app.use("/api/images", authenticateToken); // Add authentication middleware first | ||
app.use("/api/images", imageRoutes); // Then add the routes | ||
|
||
export default app; |
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,60 @@ | ||
import { Request, Response } from 'express'; | ||
import { AuthService } from '../services/authService'; | ||
|
||
export class AuthController { | ||
static async register(req: Request, res: Response): Promise<Response> { | ||
try { | ||
const { username, email, password } = req.body; | ||
|
||
// Validate input | ||
if (!username || !email || !password) { | ||
return res.status(400).json({ message: 'All fields are required' }); | ||
} | ||
|
||
// Validate email format | ||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | ||
if (!emailRegex.test(email)) { | ||
return res.status(400).json({ message: 'Invalid email format' }); | ||
} | ||
|
||
// Validate password strength | ||
if (password.length < 6) { | ||
return res.status(400).json({ message: 'Password must be at least 6 characters long' }); | ||
} | ||
|
||
const user = await AuthService.registerUser(username, email, password); | ||
return res.status(201).json({ | ||
message: 'User registered successfully', | ||
user | ||
}); | ||
} catch (error: unknown) { | ||
if (error instanceof Error) { | ||
return res.status(400).json({ message: error.message }); | ||
} | ||
return res.status(500).json({ message: 'An unknown error occurred' }); | ||
} | ||
} | ||
|
||
static async login(req: Request, res: Response): Promise<Response> { | ||
try { | ||
const { email, password } = req.body; | ||
|
||
if (!email || !password) { | ||
return res.status(400).json({ message: 'Email and password are required' }); | ||
} | ||
|
||
const { user, token } = await AuthService.loginUser(email, password); | ||
|
||
return res.status(200).json({ | ||
message: 'Login successful', | ||
user, | ||
token | ||
}); | ||
} catch (error: unknown) { | ||
if (error instanceof Error) { | ||
return res.status(401).json({ message: error.message }); | ||
} | ||
return res.status(500).json({ message: 'An unknown error occurred' }); | ||
} | ||
} | ||
} |
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,31 @@ | ||
import { Request, Response, NextFunction } from 'express'; | ||
import { AuthService } from '../services/authService'; | ||
|
||
declare global { | ||
namespace Express { | ||
interface Request { | ||
user?: { | ||
userId: number; | ||
email: string; | ||
}; | ||
} | ||
} | ||
} | ||
|
||
export const authenticateToken = (req: Request, res: Response, next: NextFunction): void => { | ||
const authHeader = req.headers['authorization']; | ||
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN | ||
|
||
if (!token) { | ||
res.status(401).json({ message: 'Authentication token is required' }); | ||
return; | ||
} | ||
|
||
try { | ||
const user = AuthService.verifyToken(token); | ||
req.user = user; | ||
next(); | ||
} catch (error) { | ||
res.status(403).json({ message: 'Invalid or expired 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,14 @@ | ||
export interface User { | ||
id: number; | ||
username: string; | ||
email: string; | ||
password: string; | ||
createdAt: Date; | ||
} | ||
|
||
export interface UserResponse { | ||
id: number; | ||
username: string; | ||
email: string; | ||
createdAt: Date; | ||
} |
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,15 @@ | ||
import { Router, Request, Response } from 'express'; | ||
import { AuthController } from '../controllers/authController'; | ||
|
||
const router = Router(); | ||
|
||
// Explicitly type the route handlers as RequestHandler | ||
router.post('/register', async (req: Request, res: Response) => { | ||
await AuthController.register(req, res); | ||
}); | ||
|
||
router.post('/login', async (req: Request, res: Response) => { | ||
await AuthController.login(req, res); | ||
}); | ||
|
||
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 |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import bcrypt from 'bcrypt'; | ||
import jwt from 'jsonwebtoken'; | ||
import pool from '../config/db'; | ||
import { User, UserResponse } from '../models/user'; | ||
|
||
export class AuthService { | ||
private static readonly JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; | ||
private static readonly SALT_ROUNDS = 10; | ||
|
||
static async registerUser(username: string, email: string, password: string): Promise<UserResponse> { | ||
// Check if user already exists | ||
const existingUser = await pool.query( | ||
'SELECT * FROM users WHERE email = $1 OR username = $2', | ||
[email, username] | ||
); | ||
|
||
if (existingUser.rows.length > 0) { | ||
throw new Error('User with this email or username already exists'); | ||
} | ||
|
||
// Hash password | ||
const hashedPassword = await bcrypt.hash(password, this.SALT_ROUNDS); | ||
|
||
// Insert new user | ||
const query = ` | ||
INSERT INTO users (username, email, password, created_at) | ||
VALUES ($1, $2, $3, NOW()) | ||
RETURNING id, username, email, created_at as "createdAt" | ||
`; | ||
|
||
const result = await pool.query(query, [username, email, hashedPassword]); | ||
return result.rows[0]; | ||
} | ||
|
||
static async loginUser(email: string, password: string): Promise<{ user: UserResponse; token: string }> { | ||
// Find user | ||
const result = await pool.query( | ||
'SELECT * FROM users WHERE email = $1', | ||
[email] | ||
); | ||
|
||
const user = result.rows[0]; | ||
if (!user) { | ||
throw new Error('User not found'); | ||
} | ||
|
||
// Verify password | ||
const isValidPassword = await bcrypt.compare(password, user.password); | ||
if (!isValidPassword) { | ||
throw new Error('Invalid password'); | ||
} | ||
|
||
// Generate JWT token | ||
const token = jwt.sign( | ||
{ userId: user.id, email: user.email }, | ||
this.JWT_SECRET, | ||
{ expiresIn: '24h' } | ||
); | ||
|
||
// Return user data (excluding password) and token | ||
const userResponse: UserResponse = { | ||
id: user.id, | ||
username: user.username, | ||
email: user.email, | ||
createdAt: user.created_at | ||
}; | ||
|
||
return { user: userResponse, token }; | ||
} | ||
|
||
static verifyToken(token: string): { userId: number; email: string } { | ||
try { | ||
return jwt.verify(token, this.JWT_SECRET) as { userId: number; email: string }; | ||
} catch (error) { | ||
throw new Error('Invalid token'); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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
Oops, something went wrong.