negyedik gyakorlat + megoldasok
This commit is contained in:
@@ -0,0 +1,829 @@
|
||||
# 📚 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! 🚀**
|
||||
Reference in New Issue
Block a user