# 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! 🚀**
