Files
GKNB_MSTM071/Backend/harmadik gyakorlat/README.md
T
2026-02-25 20:16:03 +01:00

659 lines
17 KiB
Markdown

# Backend Fejlesztés Gyakorló Feladat
## Authentication & Authorization Implementáció
---
## 📋 Tartalom
1. [Projekt Áttekintés](#projekt-áttekintés)
2. [Architektúra](#architektúra)
3. [Előkészületek](#előkészületek)
4. [A Feladat](#a-feladat)
5. [Implementációs Útmutató](#implementációs-útmutató)
6. [Tesztelés](#tesztelés)
7. [Hasznos Források](#hasznos-források)
---
## 🎯 Projekt Áttekintés
Ez egy gyakorló projekt backend fejlesztők számára, amely az **Authentication** és **Authorization** implementálására fókuszál. A projekt egy blog platformot szimulál, ahol a felhasználók regisztrálhatnak, bejelentkezhetnek, és blogokat írhatnak.
### Technológiai Stack
- **Node.js** + **Express.js** - Backend framework
- **Prisma ORM** - Database ORM
- **PostgreSQL** - Relációs adatbázis
- **Redis** - Cache és session management
- **JWT** - Token-based authentication
- **bcrypt** - Jelszó hash-elés
- **Cookie-parser** - Cookie kezelés
- **Docker** - Konténerizáció
### Amit MEG KELL implementálni (FELADAT):
✅ Authentication (Hitelesítés)
- Regisztráció
- Bejelentkezés
- Kijelentkezés
- Token refresh
- Current user lekérés
✅ Authorization (Jogosultság kezelés)
- Authentication middleware
- Role-based access control (RBAC)
- Resource ownership ellenőrzés
### Amit NEM kell implementálni (már kész):
✅ Prisma schema és migrációk
✅ Repository pattern implementáció
✅ CQRS command/query handlers
✅ Blog CRUD műveletek
✅ Docker konfiguráció
✅ Projekt struktúra
---
## 🏗️ Architektúra
A projekt **Clean Architecture** és **CQRS** mintákat követ:
```
src/
├── domain/ # Domain réteg (business logic)
│ ├── models/ # Domain modellek
│ │ ├── User.js
│ │ └── Blog.js
│ └── repositories/ # Repository interfészek
│ ├── IUserRepository.js
│ └── IBlogRepository.js
├── application/ # Application réteg (use cases)
│ ├── commands/ # Command objektumok
│ │ ├── CreateBlogCommand.js
│ │ ├── UpdateBlogCommand.js
│ │ └── DeleteBlogCommand.js
│ ├── queries/ # Query objektumok
│ │ └── BlogQueries.js
│ └── handlers/ # Command/Query handlerek
│ ├── CreateBlogHandler.js
│ ├── UpdateBlogHandler.js
│ ├── DeleteBlogHandler.js
│ └── BlogQueryHandler.js
├── infrastructure/ # Infrastructure réteg (külső függőségek)
│ ├── database/ # Database kapcsolatok
│ │ ├── prisma.js
│ │ └── redis.js
│ ├── repositories/ # Repository implementációk
│ │ ├── UserRepository.js
│ │ └── BlogRepository.js
│ └── config/ # Konfigurációk
│ └── index.js
├── api/ # API réteg (HTTP)
│ ├── controllers/ # Controller-ek
│ │ ├── AuthController.js # ⚠️ IMPLEMENTÁLANDÓ
│ │ └── BlogController.js
│ ├── routes/ # Route definíciók
│ │ ├── authRoutes.js
│ │ ├── blogRoutes.js
│ │ └── userRoutes.js
│ └── middlewares/ # Middleware-ek
│ ├── authMiddleware.js # ⚠️ IMPLEMENTÁLANDÓ
│ └── errorHandler.js
└── index.js # Alkalmazás belépési pont
```
### Rétegek Felelősségei
**Domain réteg:**
- Domain modellek és üzleti logika
- Repository interfészek (dependency inversion)
**Application réteg:**
- Use case-ek implementációja
- Command/Query objektumok és handlerek
- Validációs logika
**Infrastructure réteg:**
- Külső rendszerek integrációja (DB, Redis)
- Repository implementációk
- Konfigurációk
**API réteg:**
- HTTP kérések kezelése
- Routing
- Middleware-ek
- Authentication & Authorization ⚠️
---
## 🚀 Előkészületek
### 1. Függőségek telepítése
```bash
npm install
```
### 2. Környezeti változók beállítása
Másold le a `.env.example` fájlt `.env` néven:
```bash
cp .env.example .env
```
Állítsd be a környezeti változókat `.env` fájlban:
```env
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/blog_db?schema=public"
REDIS_HOST=localhost
REDIS_PORT=6379
JWT_SECRET=valami-nagyon-titkos-kulcs-ide
JWT_EXPIRES_IN=7d
JWT_REFRESH_SECRET=masik-nagyon-titkos-kulcs-ide
JWT_REFRESH_EXPIRES_IN=30d
PORT=3000
NODE_ENV=development
COOKIE_SECRET=cookie-titkos-kulcs-ide
```
⚠️ **FONTOS:** Production környezetben használj erős, random generált kulcsokat!
### 3. Docker konténerek indítása
```bash
npm run docker:up
```
Ez elindítja a PostgreSQL és Redis konténereket.
### 4. Adatbázis migráció
```bash
npm run prisma:migrate
npm run prisma:generate
```
### 5. Projekt indítása (development)
```bash
npm run dev
```
A szerver elindul a `http://localhost:3000` címen.
Ellenőrizd a health endpoint-ot:
```bash
curl http://localhost:3000/health
```
---
## 📝 A Feladat
A feladatod **három fő komponens** implementálása:
### 1️⃣ AuthController implementálása
**Fájl:** `src/api/controllers/AuthController.js`
Implementálandó metódusok:
#### `register(req, res)`
- Új felhasználó regisztrációja
- Input validáció (email, username, password)
- Uniqueness ellenőrzés (email és username)
- Jelszó hash-elés `bcrypt`-tel
- User létrehozása repository-n keresztül
- JWT token generálás
- HTTP-only cookie beállítás
- User publikus adatainak visszaadása (jelszó nélkül!)
#### `login(req, res)`
- Bejelentkezés email vagy username alapján
- User keresése repository-val
- Jelszó ellenőrzés `bcrypt.compare()`-val
- Access és refresh token generálás
- Cookie-k beállítása (httpOnly, secure, sameSite)
- Opcionális: Session tárolás Redis-ben
- Sikeres bejelentkezés esetén user adatok és tokenek visszaadása
#### `logout(req, res)`
- Cookie-k törlése
- Redis session törlése (ha van)
- Refresh token invalidálás
#### `refreshToken(req, res)`
- Refresh token kiolvasása cookie-ból
- Token validálás
- Új access token generálás
- Új cookie beállítás
#### `getCurrentUser(req, res)`
- Bejelentkezett user adatainak visszaadása
- `req.user` alapján (amit az auth middleware állít be)
---
### 2️⃣ AuthMiddleware-ek implementálása
**Fájl:** `src/api/middlewares/authMiddleware.js`
#### `authenticateToken(req, res, next)`
Ez a middleware **minden védett route előtt** fut.
**Feladatai:**
1. JWT token kiolvasása:
- Cookie-ból: `req.cookies.accessToken`
- VAGY Authorization header-ből: `Bearer <token>`
2. Token validálás `jwt.verify()`-val
3. User ID kinyerése a token payload-ból
4. User betöltése repository-val
5. `req.user` beállítása a user objektummal
6. `next()` hívása
**Hibakezelés:**
- Ha nincs token → 401 Unauthorized
- Ha érvénytelen token → 401 Unauthorized
- Ha user nem létezik → 401 Unauthorized
#### `requireRole(allowedRoles)`
Ez egy **higher-order middleware**, ami role-based access control-t implementál.
**Feladatai:**
1. Visszaad egy middleware függvényt
2. Ellenőrzi, hogy `req.user` létezik (az `authenticateToken` után fut!)
3. Ellenőrzi, hogy `req.user.role` benne van-e az `allowedRoles` tömbben
4. Ha nincs jogosultság → 403 Forbidden
5. Ha van jogosultság → `next()`
**Példa használat:**
```javascript
router.delete('/admin/users/:id',
authenticateToken,
requireRole(['ADMIN']),
deleteUserController
);
```
#### `checkOwnership(getResourceOwnerId)`
Ez a middleware **resource ownership** ellenőrzést implementál.
**Feladatai:**
1. Visszaad egy middleware függvényt
2. Meghívja a `getResourceOwnerId` függvényt, hogy megszerezze a resource tulajdonos ID-ját
3. Ellenőrzi, hogy:
- `req.user.id === ownerId` (a user a tulajdonos)
- VAGY `req.user.role === 'ADMIN'` (ADMIN mindent módosíthat)
4. Ha egyik sem teljesül → 403 Forbidden
5. Ha OK → `next()`
**Példa használat:**
```javascript
router.put('/blogs/:id',
authenticateToken,
checkOwnership(async (req) => {
const blog = await blogRepository.findById(req.params.id);
return blog.authorId;
}),
(req, res) => blogController.updateBlog(req, res)
);
```
---
### 3️⃣ Middleware-ek integrálása a route-okba
**Fájlok:**
- `src/api/routes/authRoutes.js`
- `src/api/routes/blogRoutes.js`
Add hozzá az `authenticateToken` middleware-t a védett endpoint-okhoz:
**authRoutes.js:**
```javascript
router.post('/logout', authenticateToken, (req, res) => authController.logout(req, res));
router.get('/me', authenticateToken, (req, res) => authController.getCurrentUser(req, res));
```
**blogRoutes.js:**
```javascript
router.post('/', authenticateToken, (req, res) => blogController.createBlog(req, res));
router.put('/:id', authenticateToken, checkOwnership(...), (req, res) => blogController.updateBlog(req, res));
router.delete('/:id', authenticateToken, checkOwnership(...), (req, res) => blogController.deleteBlog(req, res));
```
---
## 🛠️ Implementációs Útmutató
### JWT Token Generálás
```javascript
import jwt from 'jsonwebtoken';
// Access token generálás
const accessToken = jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN }
);
// Refresh token generálás
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: process.env.JWT_REFRESH_EXPIRES_IN }
);
```
### JWT Token Validálás
```javascript
import jwt from 'jsonwebtoken';
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// decoded.userId, decoded.email, decoded.role
} catch (error) {
// Token érvénytelen vagy lejárt
throw new Error('Invalid token');
}
```
### Jelszó Hash-elés
```javascript
import bcrypt from 'bcrypt';
// Hash-elés (regisztrációnál)
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);
// Ellenőrzés (bejelentkezésnél)
const isValid = await bcrypt.compare(password, user.password);
```
### Cookie Beállítás
```javascript
// Access token cookie
res.cookie('accessToken', accessToken, {
httpOnly: true, // JavaScript nem férhet hozzá
secure: process.env.NODE_ENV === 'production', // Csak HTTPS-en
sameSite: 'strict', // CSRF védelem
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 nap
});
// Refresh token cookie
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 30 * 24 * 60 * 60 * 1000 // 30 nap
});
```
### Cookie Törlés
```javascript
res.clearCookie('accessToken');
res.clearCookie('refreshToken');
```
### Redis Session Tárolás (Opcionális)
```javascript
import { redis } from '../infrastructure/database/redis.js';
// Session mentése
await redis.set(
`session:${userId}`,
JSON.stringify({ userId, email, loginTime: new Date() }),
'EX',
60 * 60 * 24 * 7 // 7 nap TTL
);
// Session lekérése
const session = await redis.get(`session:${userId}`);
const sessionData = JSON.parse(session);
// Session törlése
await redis.del(`session:${userId}`);
```
### Token Kiolvasás Cookie-ból vagy Header-ből
```javascript
function getTokenFromRequest(req) {
// Cookie-ból
if (req.cookies && req.cookies.accessToken) {
return req.cookies.accessToken;
}
// Authorization header-ből
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
return authHeader.substring(7);
}
return null;
}
```
---
## 🧪 Tesztelés
### 1. Regisztráció
```bash
curl -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"username": "testuser",
"password": "SecurePassword123"
}'
```
**Elvárt válasz:**
```json
{
"success": true,
"data": {
"user": {
"id": "uuid...",
"email": "test@example.com",
"username": "testuser",
"role": "USER"
},
"accessToken": "eyJhbGc...",
"refreshToken": "eyJhbGc..."
}
}
```
### 2. Bejelentkezés
```bash
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "SecurePassword123"
}'
```
### 3. Current User
```bash
curl -X GET http://localhost:3000/api/auth/me \
-H "Authorization: Bearer <access_token>"
```
VAGY cookie-val:
```bash
curl -X GET http://localhost:3000/api/auth/me \
--cookie "accessToken=<token>"
```
### 4. Blog Létrehozás (Védett endpoint)
```bash
curl -X POST http://localhost:3000/api/blogs \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"title": "My First Blog",
"content": "This is awesome!",
"published": true
}'
```
### 5. Blog Módosítás (Ownership ellenőrzéssel)
```bash
curl -X PUT http://localhost:3000/api/blogs/<blog_id> \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"title": "Updated Title"
}'
```
### 6. Kijelentkezés
```bash
curl -X POST http://localhost:3000/api/auth/logout \
-H "Authorization: Bearer <access_token>"
```
---
## ✅ Ellenőrző Lista
Implementációd akkor teljes, ha:
- [ ] ✅ Sikeres regisztráció email + username + password-dal
- [ ] ✅ Email és username uniqueness ellenőrzés működik
- [ ] ✅ Jelszó hash-elve van tárolva (bcrypt)
- [ ] ✅ Sikeres bejelentkezés
- [ ] ✅ Rossz jelszóval nem lehet bejelentkezni
- [ ] ✅ JWT token generálódik és cookie-ban tárolódik
- [ ] ✅ AuthMiddleware validálja a tokent
- [ ] ✅ Védett endpoint-ok csak token-nal elérhetők
- [ ]`/api/auth/me` visszaadja a bejelentkezett user adatait
- [ ] ✅ Kijelentkezés törli a cookie-kat
- [ ] ✅ Token refresh működik
- [ ] ✅ Role-based access control működik (ADMIN vs USER)
- [ ] ✅ Ownership check működik (csak saját blog módosítható)
- [ ] ✅ Hibakezelés 401/403 válaszokkal
---
## 📚 Hasznos Források
### Dokumentációk
- [JWT](https://jwt.io/) - JSON Web Token
- [bcrypt](https://www.npmjs.com/package/bcrypt) - Password hashing
- [Prisma](https://www.prisma.io/docs) - ORM dokumentáció
- [Express](https://expressjs.com/) - Web framework
- [cookie-parser](https://www.npmjs.com/package/cookie-parser) - Cookie middleware
### Tananyagok
- [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)
- [JWT Best Practices](https://blog.logrocket.com/jwt-authentication-best-practices/)
- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
- [CQRS Pattern](https://martinfowler.com/bliki/CQRS.html)
---
## 🐛 Gyakori Hibák és Megoldások
### "JWT must be provided"
- Ellenőrizd, hogy a token szerepel-e a cookie-ban vagy Authorization header-ben
- Ellenőrizd a cookie nevét (`accessToken`)
### "Invalid token"
- Ellenőrizd a JWT_SECRET változót
- Lehet, hogy a token lejárt - próbálj újra bejelentkezni
### "User already exists"
- Email vagy username már foglalt
- Használj egyedi értékeket
### "Incorrect password"
- Ellenőrizd a bcrypt.compare() használatát
- A paraméterek sorrendje: `bcrypt.compare(plainPassword, hashedPassword)`
### "403 Forbidden"
- Nincs jogosultságod az adott művelethez
- Ellenőrizd a role-t vagy az ownership-et
---
## 🎓 Értékelési Szempontok
A feladat sikeres megoldása:
1. **Működőképesség (40%)**
- Regisztráció működik
- Bejelentkezés/kijelentkezés működik
- Védett endpoint-ok hozzáférés-védelme
2. **Biztonság (30%)**
- Jelszavak hash-elve vannak
- HTTP-only cookie-k használata
- JWT token biztonságos kezelése
- Input validáció
3. **Kódminőség (20%)**
- Letisztult, olvasható kód
- Megfelelő error handling
- Architektúra követése
4. **Extra funkciók (10%)**
- Redis session management
- Token refresh flow
- Role-based access control
- Ownership validation
---
## 💡 Tippek
1. **Kezdd a legegyszerűbbel:** Először implementáld a regisztrációt, aztán a logint.
2. **Tesztelj folyamatosan:** Minden metódus után tesztelj curl-lel vagy Postman-nel.
3. **Nézd meg a meglévő kódot:** A BlogController egy jó példa, hogyan kell használni a repository-kat és handler-eket.
4. **Debug logging:** Használj `console.log()`-ot fejlesztés közben, hogy lásd mi történik.
5. **Hibakezelés:** Minden async művelet legyen try-catch blokkban.
6. **Token lejárat:** Fejlesztés közben használj rövid lejárati időt (pl. 15 perc), hogy tesztelhesd a refresh flow-t.
---
**Jó munkát és kellemes kódolást! 🚀**