harmadik gyakorlat

This commit is contained in:
magdo
2026-02-25 20:16:03 +01:00
parent a837f5ecba
commit ffca701b84
34 changed files with 3397 additions and 0 deletions
+752
View File
@@ -0,0 +1,752 @@
# 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! 🚀**