1215 lines
36 KiB
Markdown
1215 lines
36 KiB
Markdown
# FELADAT: CORS + Manuális DI Container + Email Notification
|
|
|
|
## 🎯 Cél
|
|
|
|
Fejlett backend fejlesztési technikák elsajátítása Node.js-ben **Clean Architecture** és **CQRS** mintával:
|
|
- **CORS biztonságos konfiguráció** whitelist alapú origin ellenőrzéssel
|
|
- **Saját Dependency Injection (DI) container** különböző lifecycle-okkal (singleton, transient, scoped)
|
|
- **Email küldés** Nodemailer + Handlebars template alapú értesítésekkel
|
|
|
|
**Amit már kész kaptál:**
|
|
- ✅ **Clean Architecture** struktúra (api, application, domain, infrastructure)
|
|
- ✅ **CQRS Pattern** (Commands/Handlers, Queries/Handlers)
|
|
- ✅ **Prisma ORM** PostgreSQL adatbáziskezeléssel
|
|
- ✅ **Cookie-based JWT autentikáció** (regisztráció, login, logout, védett endpointok)
|
|
- ✅ **bcrypt** jelszó hashelés
|
|
- ✅ **Repository Pattern** (Domain interfaces + Infrastructure implementations)
|
|
- ✅ **JwtService** (cookie-based token management)
|
|
- ✅ **Jest** testing framework
|
|
|
|
---
|
|
|
|
## 📐 Architektúra
|
|
|
|
Ez a projekt **Clean Architecture** mintát követi **CQRS** (Command Query Responsibility Segregation) pattern-nel:
|
|
|
|
### 🏛️ Layer Struktúra
|
|
|
|
```
|
|
src/
|
|
├── api/ # Presentation Layer
|
|
│ ├── controllers/ # HTTP request handlers
|
|
│ ├── middlewares/ # Express middlewares (auth, cors, scope)
|
|
│ ├── routers/ # Route definitions
|
|
│ └── server.js # Express app setup + DI configuration
|
|
│
|
|
├── application/ # Application Layer
|
|
│ ├── auth/commands/ # Authentication commands & handlers
|
|
│ ├── user/ # User commands & queries
|
|
│ │ ├── commands/ # Write operations
|
|
│ │ └── queries/ # Read operations
|
|
│ ├── services/ # Application services
|
|
│ │ ├── JwtService.js ✅ KÉSZ (Cookie-based JWT)
|
|
│ │ └── EmailService.js ❌ TODO (FELADAT 3)
|
|
│ └── Container.js ❌ TODO (FELADAT 1)
|
|
│
|
|
├── domain/ # Domain Layer
|
|
│ ├── models/ # Business entities
|
|
│ │ └── User.js ✅ KÉSZ (Domain model)
|
|
│ └── irepositories/ # Repository interfaces
|
|
│ └── IUserRepository.js ✅ KÉSZ
|
|
│
|
|
└── infrastructure/ # Infrastructure Layer
|
|
├── db/ # Database connection
|
|
│ └── DatabaseConnection.js ✅ KÉSZ (Prisma wrapper)
|
|
└── repositories/ # Repository implementations
|
|
└── UserRepository.js ✅ KÉSZ (Prisma-based)
|
|
```
|
|
|
|
**Key Concepts:**
|
|
- **Domain**: Üzleti entitások és szabályok (User model, IUserRepository interface)
|
|
- **Application**: Business logic (Commands, Queries, Services, DI Container)
|
|
- **Infrastructure**: Külső rendszerekhez kapcsolódás (Prisma, Database)
|
|
- **API**: HTTP layer (controllers, routes, middlewares)
|
|
|
|
---
|
|
|
|
## 🚀 Projekt Indítása
|
|
|
|
### 1. Függőségek telepítése
|
|
```bash
|
|
npm install
|
|
```
|
|
|
|
### 2. Környezeti változók konfigurálása
|
|
Másold le a `.env.example` fájlt `.env` néven:
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
Állítsd be az `.env` fájlban:
|
|
```env
|
|
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/cors_di_app?schema=public"
|
|
JWT_SECRET="titkos-kulcs-jwt-hez-változtasd-meg"
|
|
JWT_EXPIRES_IN="7d"
|
|
PORT=3000
|
|
NODE_ENV=development
|
|
|
|
# Ethereal Email (test SMTP)
|
|
ETHEREAL_USER=your-ethereal-user@ethereal.email
|
|
ETHEREAL_PASS=your-ethereal-password
|
|
|
|
# CORS (comma-separated origins)
|
|
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
|
|
```
|
|
|
|
### 3. Docker indítása (PostgreSQL)
|
|
```bash
|
|
npm run docker:up
|
|
```
|
|
|
|
### 4. Prisma migrációk futtatása
|
|
```bash
|
|
npm run prisma:migrate
|
|
npm run prisma:generate
|
|
```
|
|
|
|
### 5. Szerver indítása
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
A szerver elindul: `http://localhost:3000`
|
|
|
|
---
|
|
|
|
## 🔐 Kész API Endpoints (Teszteléshez)
|
|
|
|
### ⚠️ FONTOS: Cookie-Based Authentication
|
|
|
|
Ez a projekt **cookie-based JWT autentikációt** használ (nem localStorage!):
|
|
- JWT token **httpOnly cookie-ban** tárolódik
|
|
- Authorization header **nem szükséges**
|
|
- Böngésző **automatikusan** küldi a cookie-t
|
|
|
|
### Publikus endpointok (JWT nem kell)
|
|
|
|
#### 1. Regisztráció (Cookie-t ad vissza)
|
|
```bash
|
|
POST http://localhost:3000/api/auth/register
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"name": "John Doe",
|
|
"email": "john@example.com",
|
|
"password": "securePassword123"
|
|
}
|
|
```
|
|
|
|
**Válasz:**
|
|
```http
|
|
HTTP/1.1 201 Created
|
|
Set-Cookie: auth_token=<JWT>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=604800
|
|
|
|
{
|
|
"message": "User registered successfully",
|
|
"data": {
|
|
"user": {
|
|
"id": 1,
|
|
"name": "John Doe",
|
|
"email": "john@example.com",
|
|
"createdAt": "2026-03-04T10:00:00.000Z",
|
|
"updatedAt": "2026-03-04T10:00:00.000Z"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Figyelem:** Token **nincs** a response body-ban, csak cookie-ban!
|
|
|
|
#### 2. Bejelentkezés (Cookie-t ad vissza)
|
|
```bash
|
|
POST http://localhost:3000/api/auth/login
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"email": "john@example.com",
|
|
"password": "securePassword123"
|
|
}
|
|
```
|
|
|
|
**Válasz:**
|
|
```http
|
|
HTTP/1.1 200 OK
|
|
Set-Cookie: auth_token=<JWT>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=604800
|
|
|
|
{
|
|
"message": "Login successful",
|
|
"data": {
|
|
"user": {
|
|
"id": 1,
|
|
"name": "John Doe",
|
|
"email": "john@example.com"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 3. Kijelentkezés (Cookie törlése)
|
|
```bash
|
|
POST http://localhost:3000/api/auth/logout
|
|
Cookie: auth_token=<JWT>
|
|
```
|
|
|
|
**Válasz:**
|
|
```http
|
|
HTTP/1.1 200 OK
|
|
Set-Cookie: auth_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0
|
|
|
|
{
|
|
"message": "Logout successful"
|
|
}
|
|
```
|
|
|
|
### Védett endpointok (JWT cookie szükséges!)
|
|
|
|
A böngésző **automatikusan** küldi a cookie-t. cURL-nél használd a `-b` és `-c` flageket.
|
|
|
|
#### 4. Aktuális felhasználó lekérdezése
|
|
```bash
|
|
# cURL
|
|
curl -b cookies.txt http://localhost:3000/api/users/me
|
|
|
|
# JavaScript (fetch)
|
|
fetch('http://localhost:3000/api/users/me', {
|
|
credentials: 'include' // Cookie automatikusan küldődik
|
|
})
|
|
```
|
|
|
|
#### 5. Aktuális felhasználó frissítése
|
|
```bash
|
|
PUT http://localhost:3000/api/users/me
|
|
Cookie: auth_token=<JWT>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"name": "John Updated"
|
|
}
|
|
```
|
|
|
|
#### 6. Összes felhasználó lekérdezése
|
|
```bash
|
|
GET http://localhost:3000/api/users
|
|
Cookie: auth_token=<JWT>
|
|
```
|
|
|
|
#### 7. Egy felhasználó lekérdezése ID alapján
|
|
```bash
|
|
GET http://localhost:3000/api/users/2
|
|
Cookie: auth_token=<JWT>
|
|
```
|
|
|
|
---
|
|
|
|
## 📋 MIT KELL IMPLEMENTÁLNI - 3 FELADAT
|
|
|
|
### ⚙️ FELADAT 1: DI Container (src/application/Container.js)
|
|
### ⚙️ FELADAT 1: DI Container (src/application/Container.js)
|
|
|
|
**Cél:** Dependency Injection konténer implementálása különböző lifecycle-okkal
|
|
|
|
**⚠️ FONTOS:** A Container.js fájl **src/application/** mappában van (nem core-ban)!
|
|
|
|
A Container három lifecycle típust támogat:
|
|
- **Singleton**: Egyszer jön létre, osztva van minden request között
|
|
- **Transient**: Minden resolve()-nál új példány jön létre
|
|
- **Scoped**: Request szinten megosztott a példány (req.scope-on keresztül)
|
|
|
|
**Implementálandó metódusok:**
|
|
|
|
```javascript
|
|
/**
|
|
* Dependency Injection Container
|
|
* src/application/Container.js
|
|
*/
|
|
class Container {
|
|
constructor() {
|
|
// TODO 1: Inicializáld a services Map-et (singleton instance-ok tárolása)
|
|
// TODO 2: Inicializáld a factories Map-et (factory függvények tárolása)
|
|
// TODO 3: Inicializáld a lifetimes Map-et (lifecycle típusok tárolása)
|
|
|
|
// Példa inicializálás:
|
|
// this.services = new Map();
|
|
// this.factories = new Map();
|
|
// this.lifetimes = new Map();
|
|
}
|
|
|
|
register(name, factory, lifetime = 'singleton') {
|
|
// TODO 4: Tárold el a factory függvényt (this.factories.set(name, factory))
|
|
// TODO 5: Tárold el a lifetime típust (this.lifetimes.set(name, lifetime))
|
|
// TODO 6: Ha a lifetime === 'singleton', azonnal példányosítsd:
|
|
// - Hívd meg a factory-t: const instance = factory();
|
|
// - Tárold el: this.services.set(name, instance);
|
|
}
|
|
|
|
resolve(name, scope = null) {
|
|
// TODO 7: Ha a service regisztrálva van mint 'scoped' ÉS van scope paraméter:
|
|
// - Ellenőrizd: if (scope && scope.has(name)) return scope.get(name);
|
|
// - Ha nincs még a scope-ban, példányosítsd és tárold:
|
|
// const instance = this.factories.get(name)();
|
|
// scope.set(name, instance);
|
|
// return instance;
|
|
|
|
// TODO 8: Ha singleton, add vissza a services-ből:
|
|
// if (this.lifetimes.get(name) === 'singleton') {
|
|
// return this.services.get(name);
|
|
// }
|
|
|
|
// TODO 9: Ha transient, minden alkalommal hívj egy új factory-t:
|
|
// if (this.lifetimes.get(name) === 'transient') {
|
|
// return this.factories.get(name)();
|
|
// }
|
|
|
|
// TODO 10: Ha nem regisztrált a service, dobj hibát:
|
|
// throw new Error(`Service '${name}' is not registered`);
|
|
}
|
|
|
|
createScope() {
|
|
// TODO 11: Hozz létre egy új Map-et az scoped instance-oknak
|
|
// TODO 12: Térj vissza egy objektummal ami tartalmaz egy resolve metódust:
|
|
// const scopeMap = new Map();
|
|
// return {
|
|
// resolve: (name) => this.resolve(name, scopeMap)
|
|
// };
|
|
}
|
|
}
|
|
|
|
module.exports = Container;
|
|
```
|
|
|
|
**Tesztelés:**
|
|
A `src/api/server.js` már használja a Container-t:
|
|
|
|
```javascript
|
|
const container = new Container();
|
|
|
|
// Singleton regisztráció
|
|
container.register('PrismaClient', () => {
|
|
return databaseConnection.getClient();
|
|
}, 'singleton');
|
|
|
|
// Használat
|
|
const prisma = container.resolve('PrismaClient');
|
|
```
|
|
|
|
Indítsd el a szervert és nézd meg, hogy működik-e a DI:
|
|
```bash
|
|
npm run dev
|
|
# ✅ DI Container configured (CQRS pattern)
|
|
```
|
|
|
|
**Részletes segítség:** Nézd meg a `SEGÍTSÉG.md` fájlt teljes megoldással!
|
|
|
|
---
|
|
|
|
### 🌐 FELADAT 2: CORS Middleware (src/api/middlewares/corsMiddleware.js)
|
|
|
|
**Cél:** Whitelist alapú CORS konfiguráció implementálása
|
|
|
|
A CORS middleware csak bizonyos origin-ekről engedélyezi a hozzáférést.
|
|
|
|
```javascript
|
|
const cors = require('cors');
|
|
|
|
// Engedélyezett origin-ek whitelist-je (környezeti változóból vagy default)
|
|
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [
|
|
'http://localhost:3000',
|
|
'http://localhost:5173'
|
|
];
|
|
|
|
const corsOptions = {
|
|
origin: function (origin, callback) {
|
|
// TODO 1: Ha nincs origin (pl. backend-to-backend, Postman, curl):
|
|
// if (!origin) {
|
|
// return callback(null, true);
|
|
// }
|
|
|
|
// TODO 2: Ha az origin benne van az allowedOrigins listában:
|
|
// if (allowedOrigins.includes(origin)) {
|
|
// return callback(null, true);
|
|
// }
|
|
|
|
// TODO 3: Egyébként tiltsd le CORS hibával:
|
|
// callback(new Error('Not allowed by CORS'));
|
|
},
|
|
credentials: true, // Cookie és Authorization header engedélyezése
|
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
allowedHeaders: ['Content-Type', 'Authorization']
|
|
};
|
|
|
|
module.exports = cors(corsOptions);
|
|
```
|
|
|
|
**.env beállítás:**
|
|
```env
|
|
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173,https://myfrontend.com
|
|
```
|
|
|
|
**Aktiválás a server.js-ben:**
|
|
|
|
Uncomment this line in `src/api/server.js`:
|
|
```javascript
|
|
// A middleware chain-ben (körülbelül 130. sor)
|
|
app.use(cookieParser());
|
|
app.use(corsMiddleware); // <-- UNCOMMENT THIS LINE!
|
|
```
|
|
|
|
**Tesztelés cURL-lel:**
|
|
```bash
|
|
# Engedélyezett origin - SIKERES
|
|
curl -H "Origin: http://localhost:3000" http://localhost:3000/api/users
|
|
# Access-Control-Allow-Origin: http://localhost:3000
|
|
|
|
# Tiltott origin - HIBA
|
|
curl -H "Origin: http://malicious-site.com" http://localhost:3000/api/users
|
|
# Error: Not allowed by CORS
|
|
```
|
|
|
|
**Frontend tesztelés (React/Vue):**
|
|
```javascript
|
|
fetch('http://localhost:3000/api/auth/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include', // FONTOS: Cookie küldés/fogadás engedélyezése
|
|
body: JSON.stringify({ email, password })
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => console.log('Login sikeres:', data))
|
|
.catch(err => console.error('CORS hiba:', err));
|
|
```
|
|
|
|
**Részletes segítség:** Nézd meg a `SEGÍTSÉG.md` fájlt teljes megoldással!
|
|
|
|
---
|
|
|
|
### 📧 FELADAT 3: Email Service (src/application/services/EmailService.js)
|
|
|
|
**Cél:** Nodemailer + Handlebars template alapú email küldés implementálása
|
|
|
|
Az EmailService welcome emailt küld regisztráció után.
|
|
|
|
**⚠️ FONTOS:** Az EmailService **src/application/services/** mappában van!
|
|
|
|
```javascript
|
|
const nodemailer = require('nodemailer');
|
|
const handlebars = require('handlebars');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
class EmailService {
|
|
constructor() {
|
|
// TODO 1: Hozz létre Nodemailer transportot Ethereal tesztelő SMTP-vel
|
|
// this.transporter = nodemailer.createTransport({
|
|
// host: 'smtp.ethereal.email',
|
|
// port: 587,
|
|
// secure: false, // TLS
|
|
// auth: {
|
|
// user: process.env.ETHEREAL_USER || 'your-test-email@ethereal.email',
|
|
// pass: process.env.ETHEREAL_PASS || 'your-test-password'
|
|
// }
|
|
// });
|
|
|
|
console.log('📧 EmailService initialized');
|
|
}
|
|
|
|
async sendWelcomeEmail(userEmail, userName) {
|
|
try {
|
|
// TODO 2: Olvasd be a welcome.hbs template fájlt
|
|
// const templatePath = path.join(__dirname, 'templates', 'welcome.hbs');
|
|
// const templateSource = fs.readFileSync(templatePath, 'utf-8');
|
|
|
|
// TODO 3: Compile-old a template-et Handlebars-szal
|
|
// const template = handlebars.compile(templateSource);
|
|
|
|
// TODO 4: Generáld a HTML-t a felhasználói adatokkal
|
|
// const html = template({
|
|
// name: userName,
|
|
// date: new Date().toLocaleDateString('hu-HU')
|
|
// });
|
|
|
|
// TODO 5: Küld el az emailt
|
|
// const info = await this.transporter.sendMail({
|
|
// from: '"Clean Architecture App" <noreply@cleanarch.com>',
|
|
// to: userEmail,
|
|
// subject: 'Üdvözlünk az alkalmazásban!',
|
|
// html: html
|
|
// });
|
|
|
|
// TODO 6: Logold ki az Ethereal preview URL-t
|
|
// const previewUrl = nodemailer.getTestMessageUrl(info);
|
|
// console.log('📧 Email elküldve:', previewUrl);
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error('❌ Email küldési hiba:', error.message);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = EmailService;
|
|
```
|
|
|
|
**Email Template létrehozása:**
|
|
|
|
Hozd létre: `src/application/services/templates/welcome.hbs`
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<style>
|
|
body { font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px; }
|
|
.container { background: white; padding: 30px; border-radius: 10px; max-width: 600px; margin: 0 auto; }
|
|
h1 { color: #333; }
|
|
.footer { margin-top: 30px; font-size: 12px; color: #999; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>🎉 Üdvözlünk, {{name}}!</h1>
|
|
<p>Sikeres regisztráció az alkalmazásunkban!</p>
|
|
<p>Regisztráció dátuma: <strong>{{date}}</strong></p>
|
|
<p>Mostantól hozzáférsz az összes funkciónkhoz.</p>
|
|
<div class="footer">
|
|
<p>Ez egy automatikus üzenet, kérjük ne válaszolj rá.</p>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
**.env konfigurálás Ethereal SMTP-vel:**
|
|
|
|
1. Menj a https://ethereal.email oldalra
|
|
2. Kattints a "Create Ethereal Account" gombra
|
|
3. Másold ki a kapott user és password értékeket
|
|
4. Állítsd be a `.env` fájlban:
|
|
|
|
```env
|
|
ETHEREAL_USER=your-generated-user@ethereal.email
|
|
ETHEREAL_PASS=your-generated-password
|
|
```
|
|
|
|
**Tesztelés:**
|
|
|
|
Regisztrálj egy új felhasználót:
|
|
```bash
|
|
curl -X POST http://localhost:3000/api/auth/register \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"name": "Test User",
|
|
"email": "test@example.com",
|
|
"password": "password123"
|
|
}'
|
|
```
|
|
|
|
**Console output:**
|
|
```
|
|
📧 Email elküldve: https://ethereal.email/message/XXXXXX
|
|
```
|
|
|
|
Nyisd meg a böngészőben a linket és látni fogod az elküldött emailt!
|
|
|
|
**Részletes segítség:** Nézd meg a `SEGÍTSÉG.md` fájlt teljes megoldással és példákkal!
|
|
|
|
---
|
|
|
|
## 📁 Projekt Struktúra (Frissített)
|
|
|
|
```
|
|
src/
|
|
├── api/ # Presentation Layer
|
|
│ ├── controllers/
|
|
│ │ ├── AuthController.js ✅ KÉSZ (Cookie-based JWT)
|
|
│ │ └── UserController.js ✅ KÉSZ (CQRS queries/commands)
|
|
│ ├── middlewares/
|
|
│ │ ├── authMiddleware.js ✅ KÉSZ (Cookie-based auth)
|
|
│ │ ├── corsMiddleware.js ❌ TODO (FELADAT 2)
|
|
│ │ └── scopeMiddleware.js 📚 Opcionális
|
|
│ ├── routers/
|
|
│ │ ├── authRoutes.js ✅ KÉSZ (register, login, logout)
|
|
│ │ └── userRoutes.js ✅ KÉSZ (protected endpoints)
|
|
│ └── server.js ✅ KÉSZ (Express + DI setup)
|
|
│
|
|
├── application/ # Application Layer
|
|
│ ├── auth/commands/ ✅ KÉSZ (CQRS)
|
|
│ │ ├── RegisterUserCommand.js
|
|
│ │ ├── RegisterUserCommandHandler.js
|
|
│ │ ├── LoginUserCommand.js
|
|
│ │ └── LoginUserCommandHandler.js
|
|
│ ├── user/ ✅ KÉSZ (CQRS)
|
|
│ │ ├── commands/ # Write operations
|
|
│ │ └── queries/ # Read operations
|
|
│ ├── services/
|
|
│ │ ├── JwtService.js ✅ KÉSZ (Cookie-based JWT)
|
|
│ │ ├── EmailService.js ❌ TODO (FELADAT 3)
|
|
│ │ └── templates/
|
|
│ │ └── welcome.hbs ❌ TODO (Email template)
|
|
│ └── Container.js ❌ TODO (FELADAT 1)
|
|
│
|
|
├── domain/ # Domain Layer
|
|
│ ├── models/
|
|
│ │ └── User.js ✅ KÉSZ (Domain entity)
|
|
│ └── irepositories/
|
|
│ └── IUserRepository.js ✅ KÉSZ (Repository interface)
|
|
│
|
|
├── infrastructure/ # Infrastructure Layer
|
|
│ ├── db/
|
|
│ │ └── DatabaseConnection.js ✅ KÉSZ (Prisma wrapper)
|
|
│ └── repositories/
|
|
│ └── UserRepository.js ✅ KÉSZ (Prisma implementation)
|
|
│
|
|
├── prisma/
|
|
│ ├── schema.prisma ✅ KÉSZ (User model)
|
|
│ └── migrations/ ✅ KÉSZ
|
|
│
|
|
└── tests/ # Testing
|
|
└── unit/
|
|
├── commands/ ✅ KÉSZ (Command handler tests)
|
|
├── queries/ ✅ KÉSZ (Query handler tests)
|
|
├── controllers/ ✅ KÉSZ (Controller tests)
|
|
├── middlewares/ ✅ KÉSZ (Middleware tests)
|
|
└── services/ ✅ KÉSZ (JwtService tests)
|
|
```
|
|
|
|
---
|
|
|
|
## 🎯 Tanulási Célok
|
|
|
|
### CORS (Cross-Origin Resource Sharing)
|
|
- ✅ Origin alapú hozzáférés-vezérlés implementálása
|
|
- ✅ Whitelist konfiguráció production környezetben
|
|
- ✅ Preflight request-ek kezelése (OPTIONS)
|
|
- ✅ Credential (cookie, auth header) kezelés
|
|
|
|
### DI Container (Dependency Injection)
|
|
- ✅ Lifecycle management (Singleton, Transient, Scoped)
|
|
- ✅ Loose coupling az alkalmazásban
|
|
- ✅ Tesztelhetőség javítása (dependency injection)
|
|
- ✅ Service registry pattern
|
|
|
|
### Email Notification
|
|
- ✅ SMTP konfiguráció (Ethereal Email)
|
|
- ✅ Template-alapú email generálás (Handlebars)
|
|
- ✅ Async email küldés (nem blokkolja a request-et)
|
|
- ✅ Test email preview Ethereal-lel
|
|
|
|
### Clean Architecture + CQRS
|
|
- ✅ Layer separation (Domain, Application, Infrastructure, API)
|
|
- ✅ CQRS Pattern (Commands for writes, Queries for reads)
|
|
- ✅ Repository Pattern (interface + implementation)
|
|
- ✅ Cookie-based JWT authentication (httpOnly, secure, sameSite)
|
|
|
|
---
|
|
|
|
## 🧪 Teljes Tesztelési Flow
|
|
|
|
### 1. Szerver indítása
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
**Expected output:**
|
|
```
|
|
✅ Prisma connected to PostgreSQL
|
|
✅ DI Container configured (CQRS pattern)
|
|
🚀 Server running on http://localhost:3000
|
|
📧 Email Service configured (implement EmailService!)
|
|
🔐 JWT Authentication enabled (cookie-based)
|
|
🍪 Cookies: httpOnly, secure (production), sameSite=strict
|
|
|
|
📍 Endpoints:
|
|
POST /api/auth/register
|
|
POST /api/auth/login
|
|
POST /api/auth/logout
|
|
GET /api/users/me (protected)
|
|
PUT /api/users/me (protected)
|
|
GET /api/users (protected)
|
|
GET /api/users/:id (protected)
|
|
```
|
|
|
|
### 2. Regisztráció (cURL cookie példa)
|
|
```bash
|
|
curl -c cookies.txt -X POST http://localhost:3000/api/auth/register \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"name": "Test User",
|
|
"email": "test@example.com",
|
|
"password": "password123"
|
|
}'
|
|
```
|
|
|
|
**Kimenet:**
|
|
- ✅ JWT token cookie-ban (Set-Cookie header)
|
|
- ✅ bcrypt-tel hashelt jelszó az adatbázisban
|
|
- ✅ Email elküldve (ha implementáltad a FELADAT 3-at)
|
|
- ✅ Cookie mentve a `cookies.txt` fájlba
|
|
|
|
### 3. Bejelentkezés
|
|
```bash
|
|
curl -c cookies.txt -X POST http://localhost:3000/api/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"email": "test@example.com",
|
|
"password": "password123"
|
|
}'
|
|
```
|
|
|
|
**Kimenet:**
|
|
- ✅ JWT token cookie-ban
|
|
- ✅ bcrypt.compare jelszó ellenőrzés
|
|
|
|
### 4. Védett endpoint hívása (Cookie-val)
|
|
```bash
|
|
curl -b cookies.txt http://localhost:3000/api/users/me
|
|
```
|
|
|
|
**Kimenet:**
|
|
- ✅ authMiddleware ellenőrzi a JWT token-t cookie-ból
|
|
- ✅ req.user.userId beállítva
|
|
- ✅ GetMeQueryHandler lekéri az aktuális user-t Prisma-val
|
|
|
|
### 5. Kijelentkezés
|
|
```bash
|
|
curl -b cookies.txt -c cookies.txt -X POST http://localhost:3000/api/auth/logout
|
|
```
|
|
|
|
**Kimenet:**
|
|
- ✅ Cookie törlése (Max-Age=0)
|
|
- ✅ Következő request-nél 401 Unauthorized
|
|
|
|
---
|
|
|
|
## 🧪 Unit Testing
|
|
|
|
**Futtatás:**
|
|
```bash
|
|
npm test # Összes teszt + coverage
|
|
npm run test:watch # Watch mode
|
|
npm run test:unit # Csak unit tesztek
|
|
```
|
|
|
|
**Expected output:**
|
|
```
|
|
PASS tests/unit/commands/RegisterUserCommandHandler.test.js
|
|
PASS tests/unit/commands/LoginUserCommandHandler.test.js
|
|
PASS tests/unit/queries/GetMeQueryHandler.test.js
|
|
PASS tests/unit/controllers/AuthController.test.js
|
|
PASS tests/unit/middlewares/authMiddleware.test.js
|
|
PASS tests/unit/services/JwtService.test.js
|
|
|
|
Test Suites: 10 passed, 10 total
|
|
Tests: 75 passed, 75 total
|
|
```
|
|
|
|
---
|
|
|
|
## ❌ Hibaelhárítás
|
|
|
|
### Docker PostgreSQL nem indul
|
|
```bash
|
|
docker-compose down
|
|
docker-compose up -d
|
|
docker-compose logs postgres
|
|
```
|
|
|
|
### Prisma migration hiba
|
|
```bash
|
|
npm run prisma:migrate:reset
|
|
npm run prisma:migrate
|
|
npm run prisma:generate
|
|
```
|
|
|
|
### JWT token invalid hiba (Cookie-based)
|
|
- ✅ Ellenőrizd, hogy a `.env` fájlban van-e `JWT_SECRET`
|
|
- ✅ cURL-nél használd a `-b cookies.txt` flag-et
|
|
- ✅ Frontend-en használd a `credentials: 'include'` opciót
|
|
|
|
### Email nem megy ki
|
|
- ✅ Ellenőrizd az Ethereal SMTP credentials-t (`.env`)
|
|
- ✅ Nézd meg a console-t az Ethereal preview URL-ért
|
|
- ✅ Hozd létre a `templates/welcome.hbs` fájlt
|
|
- ✅ Ellenőrizd, hogy implementáltad-e az EmailService-t
|
|
|
|
### CORS hiba frontend-ből
|
|
- ✅ Add meg a frontend origin-t az `.env` fájlban: `ALLOWED_ORIGINS=http://localhost:5173`
|
|
- ✅ Uncomment a `app.use(corsMiddleware);` sort a `server.js`-ben
|
|
- ✅ Implementáld a CORS middleware-t (FELADAT 2)
|
|
|
|
---
|
|
|
|
## 📊 Értékelési Szempontok
|
|
|
|
### Funkcionális követelmények:
|
|
- ✅ DI Container működik (register, resolve, createScope)
|
|
- ✅ Singleton, Transient, Scoped lifecycle-ok implementálva
|
|
- ✅ CORS whitelist működik (engedélyezett és tiltott origin-ek)
|
|
- ✅ Email küldés funkcionális (Handlebars template + Ethereal SMTP)
|
|
- ✅ Template fájl létrehozva és használva
|
|
|
|
### Kód minőség:
|
|
- ✅ Kód tisztaság és SOLID elvek követése
|
|
- ✅ Hibakezelés (try-catch, validation)
|
|
- ✅ Environment változók használata (.env)
|
|
- ✅ Kommentek és dokumentáció
|
|
- ✅ Clean Architecture layering betartása
|
|
|
|
### Testing:
|
|
- ✅ Unit tesztek írása (Container, CORS, EmailService)
|
|
- ✅ Tesztek lefutnak és zöldek
|
|
- ✅ Megfelelő coverage (>70%)
|
|
|
|
---
|
|
|
|
## 📚 Hasznos Források
|
|
|
|
- 📖 [SEGÍTSÉG.md](SEGÍTSÉG.md) - Részletes példakódok és megoldások
|
|
- 📖 [ARCHITECTURE.md](ARCHITECTURE.md) - Clean Architecture dokumentáció
|
|
- 📖 [COOKIE_AUTH_MIGRATION.md](COOKIE_AUTH_MIGRATION.md) - Cookie-based JWT migration guide
|
|
- 🌐 [Ethereal Email](https://ethereal.email) - Test SMTP service
|
|
- 🌐 [Handlebars](https://handlebarsjs.com/) - Template engine
|
|
- 🌐 [Prisma Docs](https://www.prisma.io/docs) - ORM documentation
|
|
|
|
---
|
|
|
|
**Sok sikert a feladatokhoz! 🚀**
|
|
|
|
Ha elakadtál, nézd meg a `SEGÍTSÉG.md` fájlt teljes megoldásokkal és magyarázatokkal!
|
|
|
|
**Cél:** Dependency Injection konténer implementálása különböző lifecycle-okkal
|
|
|
|
A Container három lifecycle típust támogat:
|
|
- **Singleton**: Egyszer jön létre, osztva van minden request között
|
|
- **Transient**: Minden resolve()-nál új példány jön létre
|
|
- **Scoped**: Request szinten megosztott a példány (req.scope-on keresztül)
|
|
|
|
```javascript
|
|
class Container {
|
|
constructor() {
|
|
// TODO 1: Inicializáld a services Map-et (singleton instance-ok tárolása)
|
|
// TODO 2: Inicializáld a factories Map-et (factory függvények tárolása)
|
|
// TODO 3: Inicializáld a lifetimes Map-et (lifecycle típusok tárolása)
|
|
|
|
// Példa inicializálás:
|
|
// this.services = new Map();
|
|
// this.factories = new Map();
|
|
// this.lifetimes = new Map();
|
|
}
|
|
|
|
register(name, factory, lifetime = 'singleton') {
|
|
// TODO 4: Tárold el a factory függvényt (this.factories.set(name, factory))
|
|
// TODO 5: Tárold el a lifetime típust (this.lifetimes.set(name, lifetime))
|
|
// TODO 6: Ha a lifetime === 'singleton', azonnal példányosítsd:
|
|
// - Hívd meg a factory-t: const instance = factory();
|
|
// - Tárold el: this.services.set(name, instance);
|
|
}
|
|
|
|
resolve(name, scope = null) {
|
|
// TODO 7: Ha a service regisztrálva van mint 'scoped' ÉS van scope paraméter:
|
|
// - Ellenőrizd: if (scope && scope.has(name)) return scope.get(name);
|
|
// - Ha nincs még a scope-ban, példányosítsd és tárold:
|
|
// const instance = this.factories.get(name)();
|
|
// scope.set(name, instance);
|
|
// return instance;
|
|
|
|
// TODO 8: Ha singleton, add vissza a services-ből:
|
|
// if (this.lifetimes.get(name) === 'singleton') {
|
|
// return this.services.get(name);
|
|
// }
|
|
|
|
// TODO 9: Ha transient, minden alkalommal hívj egy új factory-t:
|
|
// if (this.lifetimes.get(name) === 'transient') {
|
|
// return this.factories.get(name)();
|
|
// }
|
|
|
|
// TODO 10: Ha nem regisztrált a service, dobj hibát:
|
|
// throw new Error(`Service ''${name}'' is not registered`);
|
|
}
|
|
|
|
createScope() {
|
|
// TODO 11: Hozz létre egy új Map-et az scoped instance-oknak
|
|
// TODO 12: Térj vissza egy objektummal ami tartalmaz egy resolve metódust:
|
|
// const scopeMap = new Map();
|
|
// return {
|
|
// resolve: (name) => this.resolve(name, scopeMap)
|
|
// };
|
|
}
|
|
}
|
|
|
|
module.exports = Container;
|
|
```
|
|
|
|
**Tesztelés:**
|
|
A `src/api/server.js` már használja a Container-t. Indítsd el a szervert és nézd meg, hogy működik-e a DI.
|
|
|
|
---
|
|
|
|
### FELADAT 2: CORS Middleware (src/api/middlewares/corsMiddleware.js)
|
|
|
|
**Cél:** Whitelist alapú CORS konfiguráció implementálása
|
|
|
|
A CORS middleware csak bizonyos origin-ekről engedélyezi a hozzáférést.
|
|
|
|
```javascript
|
|
const cors = require(''cors'');
|
|
|
|
// Engedélyezett origin-ek whitelist-je (környezeti változóból vagy default)
|
|
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split('','') || [
|
|
''http://localhost:3000'',
|
|
''http://localhost:5173''
|
|
];
|
|
|
|
const corsOptions = {
|
|
origin: function (origin, callback) {
|
|
// TODO 1: Ha nincs origin (pl. backend-to-backend, Postman, curl):
|
|
// if (!origin) {
|
|
// return callback(null, true);
|
|
// }
|
|
|
|
// TODO 2: Ha az origin benne van az allowedOrigins listában:
|
|
// if (allowedOrigins.includes(origin)) {
|
|
// return callback(null, true);
|
|
// }
|
|
|
|
// TODO 3: Egyébként tiltsd le CORS hibával:
|
|
// callback(new Error(''Not allowed by CORS''));
|
|
},
|
|
credentials: true, // Cookie és Authorization header engedélyezése
|
|
methods: [''GET'', ''POST'', ''PUT'', ''DELETE'', ''OPTIONS''],
|
|
allowedHeaders: [''Content-Type'', ''Authorization'']
|
|
};
|
|
|
|
module.exports = cors(corsOptions);
|
|
```
|
|
|
|
**.env beállítás:**
|
|
```env
|
|
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173,https://myfrontend.com
|
|
```
|
|
|
|
**Tesztelés:**
|
|
```bash
|
|
curl -H "Origin: http://localhost:3000" http://localhost:3000/api/users
|
|
# Sikeres - engedélyezett origin
|
|
|
|
curl -H "Origin: http://malicious-site.com" http://localhost:3000/api/users
|
|
# Hiba - tiltott origin
|
|
```
|
|
|
|
**Aktiválás a server.js-ben:**
|
|
```javascript
|
|
// src/api/server.js
|
|
const corsMiddleware = require(''./middlewares/corsMiddleware'');
|
|
|
|
app.use(corsMiddleware); // <-- Uncomment this line!
|
|
```
|
|
|
|
---
|
|
|
|
### FELADAT 3: Email Service (src/core/EmailService.js)
|
|
|
|
**Cél:** Nodemailer + Handlebars template alapú email küldés implementálása
|
|
|
|
Az EmailService welcome emailt küld regisztráció után.
|
|
|
|
```javascript
|
|
const nodemailer = require(''nodemailer'');
|
|
const handlebars = require(''handlebars'');
|
|
const fs = require(''fs'');
|
|
const path = require(''path'');
|
|
|
|
class EmailService {
|
|
constructor() {
|
|
// TODO 1: Hozz létre Nodemailer transportot Ethereal tesztelő SMTP-vel
|
|
// this.transporter = nodemailer.createTransport({
|
|
// host: ''smtp.ethereal.email'',
|
|
// port: 587,
|
|
// secure: false, // TLS
|
|
// auth: {
|
|
// user: process.env.ETHEREAL_USER || ''your-test-email@ethereal.email'',
|
|
// pass: process.env.ETHEREAL_PASS || ''your-test-password''
|
|
// }
|
|
// });
|
|
|
|
console.log('' EmailService initialized'');
|
|
}
|
|
|
|
async sendWelcomeEmail(userEmail, userName) {
|
|
try {
|
|
// TODO 2: Olvasd be a welcome.hbs template fájlt
|
|
// const templatePath = path.join(__dirname, ''templates'', ''welcome.hbs'');
|
|
// const templateSource = fs.readFileSync(templatePath, ''utf-8'');
|
|
|
|
// TODO 3: Compile-old a template-et Handlebars-szal
|
|
// const template = handlebars.compile(templateSource);
|
|
|
|
// TODO 4: Generáld a HTML-t a felhasználói adatokkal
|
|
// const html = template({
|
|
// name: userName,
|
|
// date: new Date().toLocaleDateString(''hu-HU'')
|
|
// });
|
|
|
|
// TODO 5: Küld el az emailt
|
|
// const info = await this.transporter.sendMail({
|
|
// from: ''"Clean Architecture App" <noreply@cleanarch.com>'',
|
|
// to: userEmail,
|
|
// subject: ''Üdvözlünk az alkalmazásban!'',
|
|
// html: html
|
|
// });
|
|
|
|
// TODO 6: Logold ki az Ethereal preview URL-t
|
|
// const previewUrl = nodemailer.getTestMessageUrl(info);
|
|
// console.log('' Email elküldve:'', previewUrl);
|
|
|
|
} catch (error) {
|
|
console.error('' Email küldési hiba:'', error.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = EmailService;
|
|
```
|
|
|
|
**Email Template (src/core/templates/welcome.hbs):**
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<style>
|
|
body { font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px; }
|
|
.container { background: white; padding: 30px; border-radius: 10px; max-width: 600px; margin: 0 auto; }
|
|
h1 { color: #333; }
|
|
.footer { margin-top: 30px; font-size: 12px; color: #999; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>Üdvözlünk, {{name}}! </h1>
|
|
<p>Sikeres regisztráció az alkalmazásunkban!</p>
|
|
<p>Regisztráció dátuma: <strong>{{date}}</strong></p>
|
|
<p>Mostantól hozzáférsz az összes funkciónkhoz.</p>
|
|
<div class="footer">
|
|
<p>Ez egy automatikus üzenet, kérjük ne válaszolj rá.</p>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
**.env konfigurálás Ethereal SMTP-vel:**
|
|
1. Menj a https://ethereal.email oldalra
|
|
2. Kattints a "Create Ethereal Account" gombra
|
|
3. Másold ki a kapott user és password értékeket
|
|
4. Állítsd be a `.env` fájlban:
|
|
```env
|
|
ETHEREAL_USER=your-generated-user@ethereal.email
|
|
ETHEREAL_PASS=your-generated-password
|
|
```
|
|
|
|
**Tesztelés:**
|
|
Regisztrálj egy új felhasználót:
|
|
```bash
|
|
POST http://localhost:3000/api/auth/register
|
|
{
|
|
"name": "Test User",
|
|
"email": "test@example.com",
|
|
"password": "password123"
|
|
}
|
|
```
|
|
|
|
Nézd meg a console-ban az Ethereal preview URL-t, ami így níz ki:
|
|
```
|
|
Email elküldve: https://ethereal.email/message/XXXXXX
|
|
```
|
|
|
|
Nyísd meg a böngészőben és látni fogod az elküldött emailt!
|
|
|
|
---
|
|
|
|
## Projekt Struktúra
|
|
|
|
```
|
|
src/
|
|
api/
|
|
controllers/
|
|
AuthController.js Kész (regisztráció, login)
|
|
UserController.js Kész (védett user endpointok)
|
|
middlewares/
|
|
authMiddleware.js Kész (JWT token validálás)
|
|
corsMiddleware.js TODO - FELADAT 2
|
|
scopeMiddleware.js Opcionális
|
|
routers/
|
|
authRoutes.js Kész (publikus auth endpointok)
|
|
userRoutes.js Kész (JWT védett user endpointok)
|
|
server.js Kész (Express + DI setup)
|
|
application/
|
|
auth/
|
|
AuthService.js Kész (bcrypt + JWT)
|
|
user/
|
|
UserService.js Kész (Prisma CRUD)
|
|
core/
|
|
Container.js TODO - FELADAT 1
|
|
EmailService.js TODO - FELADAT 3
|
|
JwtUtil.js Kész (JWT utility)
|
|
templates/
|
|
welcome.hbs Welcome email template
|
|
domain/
|
|
(Prisma modellekkel helyettesítve)
|
|
infrastructure/
|
|
(Prisma ORM kezeli)
|
|
prisma/
|
|
schema.prisma Kész (User model)
|
|
```
|
|
|
|
---
|
|
|
|
## Tanulási Célok
|
|
|
|
### CORS (Cross-Origin Resource Sharing)
|
|
- Origin alapú hozzáférés-vezérlés implementálása
|
|
- Whitelist konfiguráció production környezetben
|
|
- Preflight request-ek kezelése (OPTIONS)
|
|
- Credential (cookie, auth header) kezelés
|
|
|
|
### DI Container (Dependency Injection)
|
|
- Lifecycle management (Singleton, Transient, Scoped)
|
|
- Loose coupling az alkalmazásban
|
|
- Tesztelhetőség javítása (dependency injection)
|
|
- Service registry pattern
|
|
|
|
### Email Notification
|
|
- SMTP konfiguráció (Ethereal Email)
|
|
- Template-alapú email generálás (Handlebars)
|
|
- Async email küldés (nem blokkolja a request-et)
|
|
- Test email preview Ethereal-lel
|
|
|
|
---
|
|
|
|
## Teljes Tesztelési Flow
|
|
|
|
### 1. Szerver indítása
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
### 2. Regisztráció
|
|
```bash
|
|
POST http://localhost:3000/api/auth/register
|
|
{
|
|
"name": "Test User",
|
|
"email": "test@example.com",
|
|
"password": "password123"
|
|
}
|
|
```
|
|
|
|
**Kimenet:**
|
|
- JWT token válaszban
|
|
- bcrypt-tel hashelt jelszó az adatbázisban
|
|
- Email elküldve (ha implementáltad a FELADAT 3-at)
|
|
|
|
### 3. Bejelentkezés
|
|
```bash
|
|
POST http://localhost:3000/api/auth/login
|
|
{
|
|
"email": "test@example.com",
|
|
"password": "password123"
|
|
}
|
|
```
|
|
|
|
**Kimenet:**
|
|
- JWT token válaszban
|
|
- bcrypt.compare jelszó ellenőrzés
|
|
|
|
### 4. Védett endpoint hívása
|
|
```bash
|
|
GET http://localhost:3000/api/users/me
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Kimenet:**
|
|
- authMiddleware ellenőrzi a JWT token-t
|
|
- req.user.userId beállítva
|
|
- UserService lekéri az aktuális user-t Prisma-val
|
|
|
|
---
|
|
|
|
## Hibaelhárítás
|
|
|
|
### Docker PostgreSQL nem indul
|
|
```bash
|
|
docker-compose down
|
|
docker-compose up -d
|
|
docker-compose logs postgres
|
|
```
|
|
|
|
### Prisma migration hiba
|
|
```bash
|
|
npm run prisma:migrate:reset
|
|
npm run prisma:migrate
|
|
npm run prisma:generate
|
|
```
|
|
|
|
### JWT token invalid hiba
|
|
- Ellenőrizd, hogy a `.env` fájlban van-e `JWT_SECRET`
|
|
- Ellenőrizd, hogy a token `Authorization: Bearer <token>` formátumban van-e
|
|
|
|
### Email nem megy ki
|
|
- Ellenőrizd az Ethereal SMTP credentials-t (`.env`)
|
|
- Nézd meg a console-t az Ethereal preview URL-ért
|
|
- Ellenőrizd, hogy implementáltad-e az EmailService-t
|
|
|
|
---
|
|
|
|
## Értékelési Szempontok
|
|
|
|
- DI Container működik (register, resolve, createScope)
|
|
- CORS whitelist működik (engedélyezett és tiltott origin-ek)
|
|
- Email külső funkcionális (Handlebars template + Ethereal SMTP)
|
|
- Kód tisztaság és SOLID elvek követése
|
|
- Hibakezelés (try-catch, validation)
|
|
- Environment változók használata (.env)
|
|
|
|
Sok sikert! |