# Implementation Hints & Code Snippets Ez a fájl segítséget nyújt az implementációhoz konkrét kód példákkal. --- ## 🔐 JWT Token Kezelés ### Token Generálás ```javascript import jwt from 'jsonwebtoken'; function generateAccessToken(user) { return jwt.sign( { userId: user.id, email: user.email, role: user.role }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN || '7d' } ); } function generateRefreshToken(user) { return jwt.sign( { userId: user.id }, process.env.JWT_REFRESH_SECRET, { expiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '30d' } ); } ``` ### Token Validálás ```javascript import jwt from 'jsonwebtoken'; function verifyAccessToken(token) { try { return jwt.verify(token, process.env.JWT_SECRET); } catch (error) { if (error.name === 'TokenExpiredError') { throw new Error('Token expired'); } throw new Error('Invalid token'); } } function verifyRefreshToken(token) { try { return jwt.verify(token, process.env.JWT_REFRESH_SECRET); } catch (error) { throw new Error('Invalid refresh token'); } } ``` ### Token Kiolvasása Request-ből ```javascript function extractToken(req) { // 1. Először cookie-ból próbálkozunk if (req.cookies && req.cookies.accessToken) { return req.cookies.accessToken; } // 2. Ha nincs cookie, akkor Authorization header const authHeader = req.headers.authorization; if (authHeader && authHeader.startsWith('Bearer ')) { return authHeader.substring(7); // "Bearer " eltávolítása } return null; } ``` --- ## 🔒 Jelszó Hash-elés ### Regisztrációnál ```javascript import bcrypt from 'bcrypt'; async function hashPassword(plainPassword) { const saltRounds = 10; return await bcrypt.hash(plainPassword, saltRounds); } // Használat: const hashedPassword = await hashPassword(req.body.password); const user = await userRepository.create({ email: req.body.email, username: req.body.username, password: hashedPassword, // Hash-elt jelszó! role: 'USER' }); ``` ### Bejelentkezésnél ```javascript import bcrypt from 'bcrypt'; async function verifyPassword(plainPassword, hashedPassword) { return await bcrypt.compare(plainPassword, hashedPassword); } // Használat: const user = await userRepository.findByEmail(req.body.email); if (!user) { return res.status(401).json({ error: 'Invalid credentials' }); } const isPasswordValid = await verifyPassword(req.body.password, user.password); if (!isPasswordValid) { return res.status(401).json({ error: 'Invalid credentials' }); } ``` --- ## 🍪 Cookie Kezelés ### Cookie Beállítás ```javascript function setAuthCookies(res, accessToken, refreshToken) { const cookieOptions = { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict' }; // Access token - 7 nap res.cookie('accessToken', accessToken, { ...cookieOptions, maxAge: 7 * 24 * 60 * 60 * 1000 }); // Refresh token - 30 nap res.cookie('refreshToken', refreshToken, { ...cookieOptions, maxAge: 30 * 24 * 60 * 60 * 1000 }); } // Használat a login()-ban: const accessToken = generateAccessToken(user); const refreshToken = generateRefreshToken(user); setAuthCookies(res, accessToken, refreshToken); ``` ### Cookie Törlés ```javascript function clearAuthCookies(res) { res.clearCookie('accessToken'); res.clearCookie('refreshToken'); } // Használat a logout()-ban: clearAuthCookies(res); res.json({ success: true, message: 'Logged out successfully' }); ``` --- ## 📝 Input Validáció ### Email Validáció ```javascript function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } ``` ### Password Validáció ```javascript function isValidPassword(password) { // Minimum 8 karakter, legalább egy szám és egy betű return password && password.length >= 8; } ``` ### Register Input Validáció ```javascript function validateRegisterInput(data) { const errors = []; if (!data.email || !isValidEmail(data.email)) { errors.push('Valid email is required'); } if (!data.username || data.username.length < 3) { errors.push('Username must be at least 3 characters'); } if (!data.password || !isValidPassword(data.password)) { errors.push('Password must be at least 8 characters'); } return { isValid: errors.length === 0, errors }; } // Használat: const validation = validateRegisterInput(req.body); if (!validation.isValid) { return res.status(400).json({ errors: validation.errors }); } ``` --- ## 🔴 Redis Session Management ### Session Tárolás ```javascript import { redis } from '../../infrastructure/database/redis.js'; async function createSession(userId, sessionData) { const sessionKey = `session:${userId}`; const ttl = 7 * 24 * 60 * 60; // 7 nap másodpercben await redis.set( sessionKey, JSON.stringify({ userId, loginTime: new Date().toISOString(), ...sessionData }), 'EX', ttl ); } // Használat a login()-ban: await createSession(user.id, { email: user.email, role: user.role }); ``` ### Session Lekérés ```javascript async function getSession(userId) { const sessionKey = `session:${userId}`; const session = await redis.get(sessionKey); if (!session) { return null; } return JSON.parse(session); } ``` ### Session Törlés ```javascript async function deleteSession(userId) { const sessionKey = `session:${userId}`; await redis.del(sessionKey); } // Használat a logout()-ban: await deleteSession(req.user.id); ``` --- ## 🛡️ AuthController Implementáció ### Register ```javascript async register(req, res) { try { // 1. Input validáció const validation = validateRegisterInput(req.body); if (!validation.isValid) { return res.status(400).json({ errors: validation.errors }); } const { email, username, password } = req.body; // 2. Uniqueness ellenőrzés const existingUser = await this.userRepository.findByEmail(email); if (existingUser) { return res.status(409).json({ error: 'Email already exists' }); } const existingUsername = await this.userRepository.findByUsername(username); if (existingUsername) { return res.status(409).json({ error: 'Username already exists' }); } // 3. Jelszó hash-elés const hashedPassword = await bcrypt.hash(password, 10); // 4. User létrehozás const user = await this.userRepository.create({ email, username, password: hashedPassword, role: 'USER' }); // 5. Token generálás const accessToken = generateAccessToken(user); const refreshToken = generateRefreshToken(user); // 6. Cookie beállítás setAuthCookies(res, accessToken, refreshToken); // 7. Válasz res.status(201).json({ success: true, data: { user: user.toPublicObject(), accessToken, refreshToken } }); } catch (error) { res.status(500).json({ error: error.message }); } } ``` ### Login ```javascript async login(req, res) { try { const { email, username, password } = req.body; // Input validáció if (!password || (!email && !username)) { return res.status(400).json({ error: 'Email/username and password required' }); } // User keresés let user; if (email) { user = await this.userRepository.findByEmail(email); } else { user = await this.userRepository.findByUsername(username); } if (!user) { return res.status(401).json({ error: 'Invalid credentials' }); } // Jelszó ellenőrzés const isPasswordValid = await bcrypt.compare(password, user.password); if (!isPasswordValid) { return res.status(401).json({ error: 'Invalid credentials' }); } // Token generálás const accessToken = generateAccessToken(user); const refreshToken = generateRefreshToken(user); // Cookie beállítás setAuthCookies(res, accessToken, refreshToken); // Redis session (opcionális) await createSession(user.id, { email: user.email, role: user.role }); res.json({ success: true, data: { user: user.toPublicObject(), accessToken, refreshToken } }); } catch (error) { res.status(500).json({ error: error.message }); } } ``` ### Logout ```javascript async logout(req, res) { try { // Redis session törlés if (req.user && req.user.id) { await deleteSession(req.user.id); } // Cookie törlés clearAuthCookies(res); res.json({ success: true, message: 'Logged out successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } } ``` ### Refresh Token ```javascript async refreshToken(req, res) { try { // Refresh token kiolvasása const refreshToken = req.cookies.refreshToken; if (!refreshToken) { return res.status(401).json({ error: 'Refresh token required' }); } // Token validálás const decoded = verifyRefreshToken(refreshToken); // User lekérés const user = await this.userRepository.findById(decoded.userId); if (!user) { return res.status(401).json({ error: 'User not found' }); } // Új access token generálás const newAccessToken = generateAccessToken(user); // Cookie frissítés res.cookie('accessToken', newAccessToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 7 * 24 * 60 * 60 * 1000 }); res.json({ success: true, data: { accessToken: newAccessToken } }); } catch (error) { res.status(401).json({ error: 'Invalid refresh token' }); } } ``` ### Get Current User ```javascript async getCurrentUser(req, res) { try { // req.user az authenticateToken middleware állítja be if (!req.user) { return res.status(401).json({ error: 'Not authenticated' }); } res.json({ success: true, data: { user: req.user.toPublicObject() } }); } catch (error) { res.status(500).json({ error: error.message }); } } ``` --- ## 🛡️ AuthMiddleware Implementáció ### authenticateToken ```javascript export async function authenticateToken(req, res, next) { try { // 1. Token kiolvasása const token = extractToken(req); if (!token) { return res.status(401).json({ error: 'Access token required' }); } // 2. Token validálás const decoded = verifyAccessToken(token); // 3. User betöltése (FONTOS: ezt implementálni kell!) // Ehhez kell egy userRepository instance // Lehetőség 1: req.app.locals.userRepository // Lehetőség 2: Singleton pattern // Lehetőség 3: Middleware factory function const userRepository = req.app.locals.userRepository; const user = await userRepository.findById(decoded.userId); if (!user) { return res.status(401).json({ error: 'User not found' }); } // 4. req.user beállítása req.user = user; // 5. Következő middleware next(); } catch (error) { return res.status(401).json({ error: 'Invalid or expired token' }); } } ``` **FONTOS:** A userRepository-t be kell injektálni a middleware-be! Megoldás az index.js-ben: ```javascript // index.js-ben app.locals.userRepository = userRepository; ``` ### requireRole ```javascript export function requireRole(allowedRoles) { return (req, res, next) => { // Az authenticateToken után fut, tehát req.user létezik if (!req.user) { return res.status(401).json({ error: 'Authentication required' }); } if (!allowedRoles.includes(req.user.role)) { return res.status(403).json({ error: 'Insufficient permissions', required: allowedRoles, current: req.user.role }); } next(); }; } // Használat: // router.delete('/users/:id', authenticateToken, requireRole(['ADMIN']), ...) ``` ### checkOwnership ```javascript export function checkOwnership(getResourceOwnerId) { return async (req, res, next) => { try { if (!req.user) { return res.status(401).json({ error: 'Authentication required' }); } // Resource owner ID megszerzése const ownerId = await getResourceOwnerId(req); // Admin mindent módosíthat if (req.user.role === 'ADMIN') { return next(); } // Ownership ellenőrzés if (req.user.id !== ownerId) { return res.status(403).json({ error: 'You can only modify your own resources' }); } next(); } catch (error) { return res.status(403).json({ error: 'Access denied' }); } }; } // Használat blogRoutes.js-ben: router.put('/:id', authenticateToken, checkOwnership(async (req) => { const blogRepository = req.app.locals.blogRepository; const blog = await blogRepository.findById(req.params.id); if (!blog) { throw new Error('Blog not found'); } return blog.authorId; }), (req, res) => blogController.updateBlog(req, res) ); ``` --- ## 🔗 Route Protection Példák ### authRoutes.js ```javascript import express from 'express'; import { authenticateToken } from '../middlewares/authMiddleware.js'; export function createAuthRoutes(authController) { const router = express.Router(); // Publikus route-ok router.post('/register', (req, res) => authController.register(req, res)); router.post('/login', (req, res) => authController.login(req, res)); router.post('/refresh', (req, res) => authController.refreshToken(req, res)); // Védett route-ok router.post('/logout', authenticateToken, (req, res) => authController.logout(req, res) ); router.get('/me', authenticateToken, (req, res) => authController.getCurrentUser(req, res) ); return router; } ``` ### blogRoutes.js ```javascript import express from 'express'; import { authenticateToken, checkOwnership } from '../middlewares/authMiddleware.js'; export function createBlogRoutes(blogController) { const router = express.Router(); // Publikus route-ok router.get('/', (req, res) => blogController.getAllBlogs(req, res)); router.get('/:id', (req, res) => blogController.getBlog(req, res)); // Védett route-ok router.post('/', authenticateToken, (req, res) => blogController.createBlog(req, res) ); router.put('/:id', authenticateToken, checkOwnership(async (req) => { const blogRepository = req.app.locals.blogRepository; const blog = await blogRepository.findById(req.params.id); return blog?.authorId; }), (req, res) => blogController.updateBlog(req, res) ); router.delete('/:id', authenticateToken, checkOwnership(async (req) => { const blogRepository = req.app.locals.blogRepository; const blog = await blogRepository.findById(req.params.id); return blog?.authorId; }), (req, res) => blogController.deleteBlog(req, res) ); return router; } ``` --- ## 🔧 index.js Módosítások ```javascript // Repository-k injektálása app.locals-ba app.locals.userRepository = userRepository; app.locals.blogRepository = blogRepository; // Így a middleware-ek hozzáférhetnek: // const userRepository = req.app.locals.userRepository; ``` --- ## 🧪 Tesztelési Példák ### Regisztráció ```bash curl -X POST http://localhost:3000/api/auth/register \ -H "Content-Type: application/json" \ -d '{ "email": "alice@example.com", "username": "alice", "password": "Alice1234" }' ``` ### Bejelentkezés ```bash curl -X POST http://localhost:3000/api/auth/login \ -H "Content-Type: application/json" \ -c cookies.txt \ -d '{ "email": "alice@example.com", "password": "Alice1234" }' ``` ### Védett endpoint (cookie-val) ```bash curl -X GET http://localhost:3000/api/auth/me \ -b cookies.txt ``` ### Védett endpoint (Bearer token-nel) ```bash curl -X GET http://localhost:3000/api/auth/me \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ``` --- **Ez a hints fájl minden szükséges kódrészletet tartalmaz a sikeres implementációhoz! 🚀**