830 lines
21 KiB
Markdown
830 lines
21 KiB
Markdown
# 📚 SEGÍTSÉG - Példakódok és Magyarázatok
|
|
|
|
## Tartalomjegyzék
|
|
|
|
1. [DI Container Implementálása](#1-di-container-implementálása)
|
|
2. [CORS Middleware Implementálása](#2-cors-middleware-implementálása)
|
|
3. [Email Service Implementálása](#3-email-service-implementálása)
|
|
4. [Cookie-Based JWT Használata](#4-cookie-based-jwt-használata)
|
|
5. [Tesztelési Példák](#5-tesztelési-példák)
|
|
6. [Gyakori Hibák és Megoldások](#6-gyakori-hibák-és-megoldások)
|
|
|
|
---
|
|
|
|
## 1. DI Container Implementálása
|
|
|
|
### 📁 Fájl: `src/application/Container.js`
|
|
|
|
### Teljes megoldás magyarázattal:
|
|
|
|
```javascript
|
|
/**
|
|
* Dependency Injection Container
|
|
* Supports singleton, transient, and scoped lifetimes
|
|
*/
|
|
class Container {
|
|
constructor() {
|
|
// 1. Singleton instance-ok tárolása (egyszer létrehozott objektumok)
|
|
this.services = new Map();
|
|
|
|
// 2. Factory függvények tárolása (objektumokat létrehozó függvények)
|
|
this.factories = new Map();
|
|
|
|
// 3. Lifecycle típusok tárolása (singleton/transient/scoped)
|
|
this.lifetimes = new Map();
|
|
}
|
|
|
|
/**
|
|
* Service regisztrálása
|
|
* @param {string} name - Service neve
|
|
* @param {Function} factory - Factory függvény ami a service-t létrehozza
|
|
* @param {string} lifetime - 'singleton' | 'transient' | 'scoped'
|
|
*/
|
|
register(name, factory, lifetime = 'singleton') {
|
|
// 4. Factory függvény eltárolása
|
|
this.factories.set(name, factory);
|
|
|
|
// 5. Lifetime típus eltárolása
|
|
this.lifetimes.set(name, lifetime);
|
|
|
|
// 6. Ha singleton, azonnal példányosítjuk és eltároljuk
|
|
if (lifetime === 'singleton') {
|
|
const instance = factory();
|
|
this.services.set(name, instance);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Service lekérése
|
|
* @param {string} name - Service neve
|
|
* @param {Map} scope - Opcionális scope Map scoped lifetime-hoz
|
|
* @returns {any} Service instance
|
|
*/
|
|
resolve(name, scope = null) {
|
|
// 7. Scoped lifecycle kezelése
|
|
if (this.lifetimes.get(name) === 'scoped' && scope) {
|
|
// Ha már van a scope-ban, azt adjuk vissza
|
|
if (scope.has(name)) {
|
|
return scope.get(name);
|
|
}
|
|
|
|
// Ha nincs még, létrehozzuk és eltároljuk a scope-ban
|
|
const instance = this.factories.get(name)();
|
|
scope.set(name, instance);
|
|
return instance;
|
|
}
|
|
|
|
// 8. Singleton lifecycle - mindig ugyanazt az instance-t adjuk vissza
|
|
if (this.lifetimes.get(name) === 'singleton') {
|
|
return this.services.get(name);
|
|
}
|
|
|
|
// 9. Transient lifecycle - mindig új instance-t hozunk létre
|
|
if (this.lifetimes.get(name) === 'transient') {
|
|
return this.factories.get(name)();
|
|
}
|
|
|
|
// 10. Ha nincs regisztrálva a service, hibát dobunk
|
|
throw new Error(`Service '${name}' is not registered`);
|
|
}
|
|
|
|
/**
|
|
* Új scope létrehozása scoped lifecycle-hoz (pl. request-enkénti instance-ok)
|
|
* @returns {Object} Scope objektum resolve metódussal
|
|
*/
|
|
createScope() {
|
|
// 11. Új Map létrehozása a scoped instance-oknak
|
|
const scopeMap = new Map();
|
|
|
|
// 12. Visszaadunk egy objektumot ami tartalmaz egy resolve metódust
|
|
return {
|
|
resolve: (name) => this.resolve(name, scopeMap)
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = Container;
|
|
```
|
|
|
|
### 💡 Használati példák:
|
|
|
|
#### Singleton (egy instance az egész alkalmazásban)
|
|
|
|
```javascript
|
|
const container = new Container();
|
|
|
|
// Singleton PrismaClient (egy kapcsolat az egész app-ban)
|
|
container.register('PrismaClient', () => {
|
|
return new PrismaClient();
|
|
}, 'singleton');
|
|
|
|
// Minden resolve ugyanazt az instance-t adja vissza
|
|
const prisma1 = container.resolve('PrismaClient');
|
|
const prisma2 = container.resolve('PrismaClient');
|
|
console.log(prisma1 === prisma2); // true - ugyanaz az objektum!
|
|
```
|
|
|
|
#### Transient (minden resolve új instance)
|
|
|
|
```javascript
|
|
// Transient logger (minden híváshoz új)
|
|
container.register('Logger', () => {
|
|
return {
|
|
id: Math.random(),
|
|
log: (msg) => console.log(`[${new Date().toISOString()}] ${msg}`)
|
|
};
|
|
}, 'transient');
|
|
|
|
const logger1 = container.resolve('Logger');
|
|
const logger2 = container.resolve('Logger');
|
|
console.log(logger1 === logger2); // false - különböző objektumok!
|
|
console.log(logger1.id !== logger2.id); // true - különböző ID-k
|
|
```
|
|
|
|
#### Scoped (request szinten megosztott)
|
|
|
|
```javascript
|
|
// Scoped RequestContext
|
|
container.register('RequestContext', () => {
|
|
return {
|
|
id: Math.random(),
|
|
user: null,
|
|
timestamp: Date.now()
|
|
};
|
|
}, 'scoped');
|
|
|
|
// Első request scope
|
|
const scope1 = container.createScope();
|
|
const ctx1a = scope1.resolve('RequestContext');
|
|
const ctx1b = scope1.resolve('RequestContext');
|
|
console.log(ctx1a === ctx1b); // true - ugyanaz a scope-on belül!
|
|
|
|
// Második request scope
|
|
const scope2 = container.createScope();
|
|
const ctx2 = scope2.resolve('RequestContext');
|
|
console.log(ctx1a === ctx2); // false - különböző scope-ok!
|
|
```
|
|
|
|
### 🧪 Tesztelés:
|
|
|
|
```javascript
|
|
// tests/unit/application/Container.test.js
|
|
const Container = require('../../src/application/Container');
|
|
|
|
describe('Container', () => {
|
|
let container;
|
|
|
|
beforeEach(() => {
|
|
container = new Container();
|
|
});
|
|
|
|
test('singleton - should return same instance', () => {
|
|
container.register('TestService', () => ({ id: Math.random() }), 'singleton');
|
|
|
|
const instance1 = container.resolve('TestService');
|
|
const instance2 = container.resolve('TestService');
|
|
|
|
expect(instance1).toBe(instance2);
|
|
expect(instance1.id).toBe(instance2.id);
|
|
});
|
|
|
|
test('transient - should return different instances', () => {
|
|
container.register('TestService', () => ({ id: Math.random() }), 'transient');
|
|
|
|
const instance1 = container.resolve('TestService');
|
|
const instance2 = container.resolve('TestService');
|
|
|
|
expect(instance1).not.toBe(instance2);
|
|
expect(instance1.id).not.toBe(instance2.id);
|
|
});
|
|
|
|
test('scoped - should return same instance within scope', () => {
|
|
container.register('TestService', () => ({ id: Math.random() }), 'scoped');
|
|
|
|
const scope = container.createScope();
|
|
const instance1 = scope.resolve('TestService');
|
|
const instance2 = scope.resolve('TestService');
|
|
|
|
expect(instance1).toBe(instance2);
|
|
expect(instance1.id).toBe(instance2.id);
|
|
});
|
|
|
|
test('scoped - different scopes should have different instances', () => {
|
|
container.register('TestService', () => ({ id: Math.random() }), 'scoped');
|
|
|
|
const scope1 = container.createScope();
|
|
const scope2 = container.createScope();
|
|
|
|
const instance1 = scope1.resolve('TestService');
|
|
const instance2 = scope2.resolve('TestService');
|
|
|
|
expect(instance1).not.toBe(instance2);
|
|
});
|
|
|
|
test('should throw error for unregistered service', () => {
|
|
expect(() => container.resolve('NonExistent')).toThrow(
|
|
"Service 'NonExistent' is not registered"
|
|
);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 2. CORS Middleware Implementálása
|
|
|
|
### 📁 Fájl: `src/api/middlewares/corsMiddleware.js`
|
|
|
|
### Teljes megoldás:
|
|
|
|
```javascript
|
|
const cors = require('cors');
|
|
|
|
// Engedélyezett origin-ek whitelist-je (környezeti változóból)
|
|
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [
|
|
'http://localhost:3000',
|
|
'http://localhost:5173', // Vite default port
|
|
'http://localhost:5174',
|
|
'http://localhost:4200' // Angular default port
|
|
];
|
|
|
|
/**
|
|
* CORS Configuration
|
|
* Whitelist-based origin validation
|
|
*/
|
|
const corsOptions = {
|
|
/**
|
|
* Origin ellenőrzés
|
|
* @param {string} origin - Request origin
|
|
* @param {Function} callback - Callback(error, allowed)
|
|
*/
|
|
origin: function (origin, callback) {
|
|
// 1. Ha nincs origin (backend-to-backend, Postman, curl)
|
|
// Ezeket általában engedélyezzük development-ben
|
|
if (!origin) {
|
|
return callback(null, true);
|
|
}
|
|
|
|
// 2. Ha az origin benne van az allowedOrigins listában
|
|
if (allowedOrigins.includes(origin)) {
|
|
return callback(null, true);
|
|
}
|
|
|
|
// 3. Egyébként tiltjuk CORS hibával
|
|
callback(new Error('Not allowed by CORS'));
|
|
},
|
|
|
|
// Cookie és Authorization header engedélyezése
|
|
credentials: true,
|
|
|
|
// Engedélyezett HTTP metódusok
|
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
|
|
|
// Engedélyezett header-ek
|
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
|
|
// Response header-ek amiket a frontend láthat
|
|
exposedHeaders: ['Set-Cookie'],
|
|
|
|
// Preflight cache idő (másodpercben)
|
|
maxAge: 600 // 10 perc
|
|
};
|
|
|
|
module.exports = cors(corsOptions);
|
|
```
|
|
|
|
### 💡 Használat:
|
|
|
|
#### `.env` konfiguráció:
|
|
|
|
```env
|
|
# Development
|
|
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
|
|
|
|
# Production
|
|
ALLOWED_ORIGINS=https://myapp.com,https://www.myapp.com,https://admin.myapp.com
|
|
```
|
|
|
|
#### Aktiválás `server.js`-ben:
|
|
|
|
```javascript
|
|
// src/api/server.js
|
|
const corsMiddleware = require('./middlewares/corsMiddleware');
|
|
|
|
// Middleware chain
|
|
app.use(express.json());
|
|
app.use(express.urlencoded({ extended: true }));
|
|
app.use(cookieParser());
|
|
app.use(corsMiddleware); // <-- CORS middleware hozzáadása
|
|
```
|
|
|
|
### 🧪 Tesztelés cURL-lel:
|
|
|
|
```bash
|
|
# Engedélyezett origin
|
|
curl -H "Origin: http://localhost:3000" \
|
|
-H "Access-Control-Request-Method: POST" \
|
|
-H "Access-Control-Request-Headers: Content-Type" \
|
|
-X OPTIONS \
|
|
http://localhost:3000/api/users
|
|
|
|
# Sikeres válasz:
|
|
# Access-Control-Allow-Origin: http://localhost:3000
|
|
# Access-Control-Allow-Credentials: true
|
|
|
|
# Tiltott origin
|
|
curl -H "Origin: http://malicious-site.com" \
|
|
-X GET \
|
|
http://localhost:3000/api/users
|
|
|
|
# Hiba válasz: "Not allowed by CORS"
|
|
```
|
|
|
|
### 🧪 Frontend tesztelés:
|
|
|
|
```javascript
|
|
// React/Vue/Angular frontend
|
|
fetch('http://localhost:3000/api/auth/login', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
credentials: 'include', // 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));
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Email Service Implementálása
|
|
|
|
### 📁 Fájl: `src/application/services/EmailService.js`
|
|
|
|
### Teljes megoldás:
|
|
|
|
```javascript
|
|
const nodemailer = require('nodemailer');
|
|
const handlebars = require('handlebars');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
/**
|
|
* Email Service
|
|
* Nodemailer + Handlebars template based email sending
|
|
*/
|
|
class EmailService {
|
|
constructor() {
|
|
// 1. Nodemailer transport létrehozása Ethereal 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');
|
|
}
|
|
|
|
/**
|
|
* Welcome email küldése új regisztrált felhasználónak
|
|
* @param {string} userEmail - Felhasználó email címe
|
|
* @param {string} userName - Felhasználó neve
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async sendWelcomeEmail(userEmail, userName) {
|
|
try {
|
|
// 2. Template fájl beolvasása
|
|
const templatePath = path.join(__dirname, 'templates', 'welcome.hbs');
|
|
const templateSource = fs.readFileSync(templatePath, 'utf-8');
|
|
|
|
// 3. Handlebars template compile-olása
|
|
const template = handlebars.compile(templateSource);
|
|
|
|
// 4. HTML generálása az adatokkal
|
|
const html = template({
|
|
name: userName,
|
|
email: userEmail,
|
|
date: new Date().toLocaleDateString('hu-HU'),
|
|
year: new Date().getFullYear()
|
|
});
|
|
|
|
// 5. Email küldése
|
|
const info = await this.transporter.sendMail({
|
|
from: '"Clean Architecture App" <noreply@cleanarch.com>',
|
|
to: userEmail,
|
|
subject: '🎉 Üdvözlünk az alkalmazásban!',
|
|
html: html
|
|
});
|
|
|
|
// 6. Ethereal preview URL kiírása
|
|
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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Password reset email (bővítési lehetőség)
|
|
*/
|
|
async sendPasswordResetEmail(userEmail, resetToken) {
|
|
try {
|
|
const templatePath = path.join(__dirname, 'templates', 'password-reset.hbs');
|
|
const templateSource = fs.readFileSync(templatePath, 'utf-8');
|
|
const template = handlebars.compile(templateSource);
|
|
|
|
const resetLink = `${process.env.FRONTEND_URL}/reset-password?token=${resetToken}`;
|
|
|
|
const html = template({
|
|
resetLink,
|
|
expiresIn: '1 óra'
|
|
});
|
|
|
|
const info = await this.transporter.sendMail({
|
|
from: '"Clean Architecture App" <noreply@cleanarch.com>',
|
|
to: userEmail,
|
|
subject: '🔐 Jelszó visszaállítás',
|
|
html: html
|
|
});
|
|
|
|
console.log('📧 Password reset email:', nodemailer.getTestMessageUrl(info));
|
|
return true;
|
|
} catch (error) {
|
|
console.error('❌ Password reset email hiba:', error.message);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = EmailService;
|
|
```
|
|
|
|
### 📁 Email Template: `src/application/services/templates/welcome.hbs`
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html lang="hu">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Üdvözlünk!</title>
|
|
<style>
|
|
body {
|
|
font-family: 'Segoe UI', Arial, sans-serif;
|
|
background-color: #f4f7f9;
|
|
margin: 0;
|
|
padding: 20px;
|
|
}
|
|
.container {
|
|
background: white;
|
|
padding: 40px;
|
|
border-radius: 12px;
|
|
max-width: 600px;
|
|
margin: 0 auto;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
h1 {
|
|
color: #2c3e50;
|
|
margin-top: 0;
|
|
font-size: 28px;
|
|
}
|
|
.welcome-icon {
|
|
font-size: 48px;
|
|
text-align: center;
|
|
margin: 20px 0;
|
|
}
|
|
p {
|
|
color: #555;
|
|
line-height: 1.6;
|
|
font-size: 16px;
|
|
}
|
|
.highlight {
|
|
background-color: #e3f2fd;
|
|
padding: 15px;
|
|
border-left: 4px solid #2196f3;
|
|
margin: 20px 0;
|
|
border-radius: 4px;
|
|
}
|
|
.button {
|
|
display: inline-block;
|
|
background-color: #4caf50;
|
|
color: white;
|
|
padding: 12px 30px;
|
|
text-decoration: none;
|
|
border-radius: 6px;
|
|
margin: 20px 0;
|
|
font-weight: bold;
|
|
}
|
|
.footer {
|
|
margin-top: 40px;
|
|
padding-top: 20px;
|
|
border-top: 1px solid #eee;
|
|
font-size: 12px;
|
|
color: #999;
|
|
text-align: center;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="welcome-icon">🎉</div>
|
|
|
|
<h1>Üdvözlünk, {{name}}!</h1>
|
|
|
|
<p>Gratulálunk! Sikeres regisztráció az alkalmazásunkban.</p>
|
|
|
|
<div class="highlight">
|
|
<strong>Regisztráció részletei:</strong><br>
|
|
📧 Email: {{email}}<br>
|
|
📅 Dátum: {{date}}
|
|
</div>
|
|
|
|
<p>Mostantól hozzáférsz az összes funkciónkhoz:</p>
|
|
<ul>
|
|
<li>✅ Profil kezelés</li>
|
|
<li>✅ Biztonságos autentikáció</li>
|
|
<li>✅ API hozzáférés</li>
|
|
</ul>
|
|
|
|
<center>
|
|
<a href="http://localhost:3000/api/users/me" class="button">
|
|
Profilom megtekintése
|
|
</a>
|
|
</center>
|
|
|
|
<div class="footer">
|
|
<p>Ez egy automatikus üzenet, kérjük ne válaszolj rá.</p>
|
|
<p>© {{year}} Clean Architecture App. Minden jog fenntartva.</p>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
### 💡 Ethereal Email Beállítása:
|
|
|
|
1. **Menj a https://ethereal.email oldalra**
|
|
2. **Kattints "Create Ethereal Account" gombra**
|
|
3. **Másold ki a credentials-t:**
|
|
|
|
```
|
|
Username: your-random-name@ethereal.email
|
|
Password: your-random-password
|
|
```
|
|
|
|
4. **Állítsd be a `.env` fájlban:**
|
|
|
|
```env
|
|
ETHEREAL_USER=your-random-name@ethereal.email
|
|
ETHEREAL_PASS=your-random-password
|
|
```
|
|
|
|
### 🧪 Tesztelés:
|
|
|
|
```bash
|
|
# Regisztráció (automatikusan küld welcome emailt)
|
|
POST http://localhost:3000/api/auth/register
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"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 emailt!**
|
|
|
|
---
|
|
|
|
## 4. Cookie-Based JWT Használata
|
|
|
|
### 🍪 Frontend Integration (React példa)
|
|
|
|
```javascript
|
|
// authService.js
|
|
const API_URL = 'http://localhost:3000/api';
|
|
|
|
export const authService = {
|
|
async register(name, email, password) {
|
|
const response = await fetch(`${API_URL}/auth/register`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include', // FONTOS: Cookie küldés/fogadás
|
|
body: JSON.stringify({ name, email, password })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Registration failed');
|
|
}
|
|
|
|
return response.json();
|
|
// JWT automatikusan cookie-ban tárolódik!
|
|
},
|
|
|
|
async login(email, password) {
|
|
const response = await fetch(`${API_URL}/auth/login`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({ email, password })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Login failed');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
async logout() {
|
|
const response = await fetch(`${API_URL}/auth/logout`, {
|
|
method: 'POST',
|
|
credentials: 'include'
|
|
});
|
|
|
|
return response.json();
|
|
},
|
|
|
|
async getCurrentUser() {
|
|
const response = await fetch(`${API_URL}/users/me`, {
|
|
credentials: 'include' // Cookie automatikusan küldődik
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Unauthorized');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
};
|
|
```
|
|
|
|
### 🧪 cURL Tesztelés Cookie-val:
|
|
|
|
```bash
|
|
# 1. Login + Cookie mentése
|
|
curl -c cookies.txt -X POST http://localhost:3000/api/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"test@example.com","password":"password123"}'
|
|
|
|
# 2. Protected endpoint hívása a cookie-val
|
|
curl -b cookies.txt http://localhost:3000/api/users/me
|
|
|
|
# 3. Logout
|
|
curl -b cookies.txt -c cookies.txt -X POST http://localhost:3000/api/auth/logout
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Tesztelési Példák
|
|
|
|
### Controller Teszt Cookie-val:
|
|
|
|
```javascript
|
|
// tests/unit/controllers/AuthController.test.js
|
|
const AuthController = require('../../src/api/controllers/AuthController');
|
|
|
|
describe('AuthController - Cookie-based JWT', () => {
|
|
let authController;
|
|
let mockRegisterHandler;
|
|
let mockLoginHandler;
|
|
let mockJwtService;
|
|
|
|
beforeEach(() => {
|
|
mockRegisterHandler = { handle: jest.fn() };
|
|
mockLoginHandler = { handle: jest.fn() };
|
|
mockJwtService = {
|
|
getCookieName: jest.fn().mockReturnValue('auth_token'),
|
|
getCookieOptions: jest.fn().mockReturnValue({
|
|
httpOnly: true,
|
|
secure: false,
|
|
sameSite: 'strict'
|
|
})
|
|
};
|
|
|
|
authController = new AuthController(
|
|
mockRegisterHandler,
|
|
mockLoginHandler,
|
|
mockJwtService
|
|
);
|
|
});
|
|
|
|
test('login should set JWT in cookie', async () => {
|
|
const mockReq = {
|
|
body: { email: 'test@example.com', password: 'password123' }
|
|
};
|
|
const mockRes = {
|
|
cookie: jest.fn(),
|
|
status: jest.fn().mockReturnThis(),
|
|
json: jest.fn()
|
|
};
|
|
|
|
mockLoginHandler.handle.mockResolvedValue({
|
|
user: { id: 1, email: 'test@example.com' },
|
|
token: 'mock_jwt_token'
|
|
});
|
|
|
|
await authController.login(mockReq, mockRes);
|
|
|
|
// Cookie beállítás ellenőrzése
|
|
expect(mockRes.cookie).toHaveBeenCalledWith(
|
|
'auth_token',
|
|
'mock_jwt_token',
|
|
expect.objectContaining({
|
|
httpOnly: true,
|
|
sameSite: 'strict'
|
|
})
|
|
);
|
|
|
|
// Response csak user-t tartalmaz, token nincs a body-ban
|
|
expect(mockRes.json).toHaveBeenCalledWith({
|
|
message: 'Login successful',
|
|
data: {
|
|
user: { id: 1, email: 'test@example.com' }
|
|
}
|
|
});
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Gyakori Hibák és Megoldások
|
|
|
|
### ❌ Hiba: "Service 'PrismaClient' is not registered"
|
|
|
|
**Megoldás:**
|
|
```javascript
|
|
// Ellenőrizd a server.js-ben:
|
|
container.register('PrismaClient', () => {
|
|
return databaseConnection.getClient();
|
|
}, 'singleton');
|
|
```
|
|
|
|
### ❌ Hiba: "Not allowed by CORS"
|
|
|
|
**Megoldás:**
|
|
```env
|
|
# .env fájlban add meg a frontend origin-t:
|
|
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
|
|
```
|
|
|
|
### ❌ Hiba: "No token provided in cookies"
|
|
|
|
**Megoldás:**
|
|
```javascript
|
|
// Frontend-en használd a credentials: 'include'-ot:
|
|
fetch('http://localhost:3000/api/users/me', {
|
|
credentials: 'include'
|
|
});
|
|
```
|
|
|
|
### ❌ Hiba: Email nem megy ki
|
|
|
|
**Megoldás:**
|
|
1. Ellenőrizd az Ethereal credentials-t (`.env`)
|
|
2. Hozz létre új Ethereal account-ot: https://ethereal.email
|
|
3. Ellenőrizd a template fájl elérési útját:
|
|
```javascript
|
|
const templatePath = path.join(__dirname, 'templates', 'welcome.hbs');
|
|
```
|
|
|
|
### ❌ Hiba: Container circular dependency
|
|
|
|
**Megoldás:**
|
|
```javascript
|
|
// Használj lazy loading-ot:
|
|
container.register('ServiceA', () => {
|
|
const ServiceB = container.resolve('ServiceB');
|
|
return new ServiceA(ServiceB);
|
|
}, 'singleton');
|
|
```
|
|
|
|
---
|
|
|
|
## 📚 További Olvasnivalók
|
|
|
|
- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
|
|
- [Dependency Injection Pattern](https://martinfowler.com/articles/injection.html)
|
|
- [CORS in Express](https://expressjs.com/en/resources/middleware/cors.html)
|
|
- [Nodemailer Documentation](https://nodemailer.com/about/)
|
|
- [Handlebars Templates](https://handlebarsjs.com/)
|
|
- [Cookie Security Best Practices](https://owasp.org/www-community/controls/SecureCookieAttribute)
|
|
|
|
---
|
|
|
|
**Sok sikert a feladatokhoz! 🚀**
|