Files
2026-02-25 20:16:03 +01:00

16 KiB

Implementation Hints & Code Snippets

Ez a fájl segítséget nyújt az implementációhoz konkrét kód példákkal.


🔐 JWT Token Kezelés

Token Generálás

import jwt from 'jsonwebtoken';

function generateAccessToken(user) {
  return jwt.sign(
    {
      userId: user.id,
      email: user.email,
      role: user.role
    },
    process.env.JWT_SECRET,
    { expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
  );
}

function generateRefreshToken(user) {
  return jwt.sign(
    {
      userId: user.id
    },
    process.env.JWT_REFRESH_SECRET,
    { expiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '30d' }
  );
}

Token Validálás

import jwt from 'jsonwebtoken';

function verifyAccessToken(token) {
  try {
    return jwt.verify(token, process.env.JWT_SECRET);
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      throw new Error('Token expired');
    }
    throw new Error('Invalid token');
  }
}

function verifyRefreshToken(token) {
  try {
    return jwt.verify(token, process.env.JWT_REFRESH_SECRET);
  } catch (error) {
    throw new Error('Invalid refresh token');
  }
}

Token Kiolvasása Request-ből

function extractToken(req) {
  // 1. Először cookie-ból próbálkozunk
  if (req.cookies && req.cookies.accessToken) {
    return req.cookies.accessToken;
  }
  
  // 2. Ha nincs cookie, akkor Authorization header
  const authHeader = req.headers.authorization;
  if (authHeader && authHeader.startsWith('Bearer ')) {
    return authHeader.substring(7); // "Bearer " eltávolítása
  }
  
  return null;
}

🔒 Jelszó Hash-elés

Regisztrációnál

import bcrypt from 'bcrypt';

async function hashPassword(plainPassword) {
  const saltRounds = 10;
  return await bcrypt.hash(plainPassword, saltRounds);
}

// Használat:
const hashedPassword = await hashPassword(req.body.password);
const user = await userRepository.create({
  email: req.body.email,
  username: req.body.username,
  password: hashedPassword, // Hash-elt jelszó!
  role: 'USER'
});

Bejelentkezésnél

import bcrypt from 'bcrypt';

async function verifyPassword(plainPassword, hashedPassword) {
  return await bcrypt.compare(plainPassword, hashedPassword);
}

// Használat:
const user = await userRepository.findByEmail(req.body.email);
if (!user) {
  return res.status(401).json({ error: 'Invalid credentials' });
}

const isPasswordValid = await verifyPassword(req.body.password, user.password);
if (!isPasswordValid) {
  return res.status(401).json({ error: 'Invalid credentials' });
}

function setAuthCookies(res, accessToken, refreshToken) {
  const cookieOptions = {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict'
  };

  // Access token - 7 nap
  res.cookie('accessToken', accessToken, {
    ...cookieOptions,
    maxAge: 7 * 24 * 60 * 60 * 1000
  });

  // Refresh token - 30 nap
  res.cookie('refreshToken', refreshToken, {
    ...cookieOptions,
    maxAge: 30 * 24 * 60 * 60 * 1000
  });
}

// Használat a login()-ban:
const accessToken = generateAccessToken(user);
const refreshToken = generateRefreshToken(user);
setAuthCookies(res, accessToken, refreshToken);
function clearAuthCookies(res) {
  res.clearCookie('accessToken');
  res.clearCookie('refreshToken');
}

// Használat a logout()-ban:
clearAuthCookies(res);
res.json({ success: true, message: 'Logged out successfully' });

📝 Input Validáció

Email Validáció

function isValidEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

Password Validáció

function isValidPassword(password) {
  // Minimum 8 karakter, legalább egy szám és egy betű
  return password && password.length >= 8;
}

Register Input Validáció

function validateRegisterInput(data) {
  const errors = [];

  if (!data.email || !isValidEmail(data.email)) {
    errors.push('Valid email is required');
  }

  if (!data.username || data.username.length < 3) {
    errors.push('Username must be at least 3 characters');
  }

  if (!data.password || !isValidPassword(data.password)) {
    errors.push('Password must be at least 8 characters');
  }

  return {
    isValid: errors.length === 0,
    errors
  };
}

// Használat:
const validation = validateRegisterInput(req.body);
if (!validation.isValid) {
  return res.status(400).json({ errors: validation.errors });
}

🔴 Redis Session Management

Session Tárolás

import { redis } from '../../infrastructure/database/redis.js';

async function createSession(userId, sessionData) {
  const sessionKey = `session:${userId}`;
  const ttl = 7 * 24 * 60 * 60; // 7 nap másodpercben
  
  await redis.set(
    sessionKey,
    JSON.stringify({
      userId,
      loginTime: new Date().toISOString(),
      ...sessionData
    }),
    'EX',
    ttl
  );
}

// Használat a login()-ban:
await createSession(user.id, {
  email: user.email,
  role: user.role
});

Session Lekérés

async function getSession(userId) {
  const sessionKey = `session:${userId}`;
  const session = await redis.get(sessionKey);
  
  if (!session) {
    return null;
  }
  
  return JSON.parse(session);
}

Session Törlés

async function deleteSession(userId) {
  const sessionKey = `session:${userId}`;
  await redis.del(sessionKey);
}

// Használat a logout()-ban:
await deleteSession(req.user.id);

🛡️ AuthController Implementáció

Register

async register(req, res) {
  try {
    // 1. Input validáció
    const validation = validateRegisterInput(req.body);
    if (!validation.isValid) {
      return res.status(400).json({ errors: validation.errors });
    }

    const { email, username, password } = req.body;

    // 2. Uniqueness ellenőrzés
    const existingUser = await this.userRepository.findByEmail(email);
    if (existingUser) {
      return res.status(409).json({ error: 'Email already exists' });
    }

    const existingUsername = await this.userRepository.findByUsername(username);
    if (existingUsername) {
      return res.status(409).json({ error: 'Username already exists' });
    }

    // 3. Jelszó hash-elés
    const hashedPassword = await bcrypt.hash(password, 10);

    // 4. User létrehozás
    const user = await this.userRepository.create({
      email,
      username,
      password: hashedPassword,
      role: 'USER'
    });

    // 5. Token generálás
    const accessToken = generateAccessToken(user);
    const refreshToken = generateRefreshToken(user);

    // 6. Cookie beállítás
    setAuthCookies(res, accessToken, refreshToken);

    // 7. Válasz
    res.status(201).json({
      success: true,
      data: {
        user: user.toPublicObject(),
        accessToken,
        refreshToken
      }
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
}

Login

async login(req, res) {
  try {
    const { email, username, password } = req.body;

    // Input validáció
    if (!password || (!email && !username)) {
      return res.status(400).json({ 
        error: 'Email/username and password required' 
      });
    }

    // User keresés
    let user;
    if (email) {
      user = await this.userRepository.findByEmail(email);
    } else {
      user = await this.userRepository.findByUsername(username);
    }

    if (!user) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    // Jelszó ellenőrzés
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    // Token generálás
    const accessToken = generateAccessToken(user);
    const refreshToken = generateRefreshToken(user);

    // Cookie beállítás
    setAuthCookies(res, accessToken, refreshToken);

    // Redis session (opcionális)
    await createSession(user.id, {
      email: user.email,
      role: user.role
    });

    res.json({
      success: true,
      data: {
        user: user.toPublicObject(),
        accessToken,
        refreshToken
      }
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
}

Logout

async logout(req, res) {
  try {
    // Redis session törlés
    if (req.user && req.user.id) {
      await deleteSession(req.user.id);
    }

    // Cookie törlés
    clearAuthCookies(res);

    res.json({
      success: true,
      message: 'Logged out successfully'
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
}

Refresh Token

async refreshToken(req, res) {
  try {
    // Refresh token kiolvasása
    const refreshToken = req.cookies.refreshToken;
    
    if (!refreshToken) {
      return res.status(401).json({ error: 'Refresh token required' });
    }

    // Token validálás
    const decoded = verifyRefreshToken(refreshToken);

    // User lekérés
    const user = await this.userRepository.findById(decoded.userId);
    if (!user) {
      return res.status(401).json({ error: 'User not found' });
    }

    // Új access token generálás
    const newAccessToken = generateAccessToken(user);

    // Cookie frissítés
    res.cookie('accessToken', newAccessToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'strict',
      maxAge: 7 * 24 * 60 * 60 * 1000
    });

    res.json({
      success: true,
      data: {
        accessToken: newAccessToken
      }
    });
  } catch (error) {
    res.status(401).json({ error: 'Invalid refresh token' });
  }
}

Get Current User

async getCurrentUser(req, res) {
  try {
    // req.user az authenticateToken middleware állítja be
    if (!req.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }

    res.json({
      success: true,
      data: {
        user: req.user.toPublicObject()
      }
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
}

🛡️ AuthMiddleware Implementáció

authenticateToken

export async function authenticateToken(req, res, next) {
  try {
    // 1. Token kiolvasása
    const token = extractToken(req);
    
    if (!token) {
      return res.status(401).json({ 
        error: 'Access token required' 
      });
    }

    // 2. Token validálás
    const decoded = verifyAccessToken(token);

    // 3. User betöltése (FONTOS: ezt implementálni kell!)
    // Ehhez kell egy userRepository instance
    // Lehetőség 1: req.app.locals.userRepository
    // Lehetőség 2: Singleton pattern
    // Lehetőség 3: Middleware factory function
    
    const userRepository = req.app.locals.userRepository;
    const user = await userRepository.findById(decoded.userId);
    
    if (!user) {
      return res.status(401).json({ error: 'User not found' });
    }

    // 4. req.user beállítása
    req.user = user;

    // 5. Következő middleware
    next();
  } catch (error) {
    return res.status(401).json({ 
      error: 'Invalid or expired token' 
    });
  }
}

FONTOS: A userRepository-t be kell injektálni a middleware-be!

Megoldás az index.js-ben:

// index.js-ben
app.locals.userRepository = userRepository;

requireRole

export function requireRole(allowedRoles) {
  return (req, res, next) => {
    // Az authenticateToken után fut, tehát req.user létezik
    if (!req.user) {
      return res.status(401).json({ error: 'Authentication required' });
    }

    if (!allowedRoles.includes(req.user.role)) {
      return res.status(403).json({ 
        error: 'Insufficient permissions',
        required: allowedRoles,
        current: req.user.role
      });
    }

    next();
  };
}

// Használat:
// router.delete('/users/:id', authenticateToken, requireRole(['ADMIN']), ...)

checkOwnership

export function checkOwnership(getResourceOwnerId) {
  return async (req, res, next) => {
    try {
      if (!req.user) {
        return res.status(401).json({ error: 'Authentication required' });
      }

      // Resource owner ID megszerzése
      const ownerId = await getResourceOwnerId(req);

      // Admin mindent módosíthat
      if (req.user.role === 'ADMIN') {
        return next();
      }

      // Ownership ellenőrzés
      if (req.user.id !== ownerId) {
        return res.status(403).json({ 
          error: 'You can only modify your own resources' 
        });
      }

      next();
    } catch (error) {
      return res.status(403).json({ error: 'Access denied' });
    }
  };
}

// Használat blogRoutes.js-ben:
router.put('/:id', 
  authenticateToken,
  checkOwnership(async (req) => {
    const blogRepository = req.app.locals.blogRepository;
    const blog = await blogRepository.findById(req.params.id);
    if (!blog) {
      throw new Error('Blog not found');
    }
    return blog.authorId;
  }),
  (req, res) => blogController.updateBlog(req, res)
);

🔗 Route Protection Példák

authRoutes.js

import express from 'express';
import { authenticateToken } from '../middlewares/authMiddleware.js';

export function createAuthRoutes(authController) {
  const router = express.Router();

  // Publikus route-ok
  router.post('/register', (req, res) => authController.register(req, res));
  router.post('/login', (req, res) => authController.login(req, res));
  router.post('/refresh', (req, res) => authController.refreshToken(req, res));

  // Védett route-ok
  router.post('/logout', authenticateToken, (req, res) => 
    authController.logout(req, res)
  );
  router.get('/me', authenticateToken, (req, res) => 
    authController.getCurrentUser(req, res)
  );

  return router;
}

blogRoutes.js

import express from 'express';
import { authenticateToken, checkOwnership } from '../middlewares/authMiddleware.js';

export function createBlogRoutes(blogController) {
  const router = express.Router();

  // Publikus route-ok
  router.get('/', (req, res) => blogController.getAllBlogs(req, res));
  router.get('/:id', (req, res) => blogController.getBlog(req, res));

  // Védett route-ok
  router.post('/', 
    authenticateToken, 
    (req, res) => blogController.createBlog(req, res)
  );

  router.put('/:id',
    authenticateToken,
    checkOwnership(async (req) => {
      const blogRepository = req.app.locals.blogRepository;
      const blog = await blogRepository.findById(req.params.id);
      return blog?.authorId;
    }),
    (req, res) => blogController.updateBlog(req, res)
  );

  router.delete('/:id',
    authenticateToken,
    checkOwnership(async (req) => {
      const blogRepository = req.app.locals.blogRepository;
      const blog = await blogRepository.findById(req.params.id);
      return blog?.authorId;
    }),
    (req, res) => blogController.deleteBlog(req, res)
  );

  return router;
}

🔧 index.js Módosítások

// Repository-k injektálása app.locals-ba
app.locals.userRepository = userRepository;
app.locals.blogRepository = blogRepository;

// Így a middleware-ek hozzáférhetnek:
// const userRepository = req.app.locals.userRepository;

🧪 Tesztelési Példák

Regisztráció

curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alice@example.com",
    "username": "alice",
    "password": "Alice1234"
  }'

Bejelentkezés

curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -c cookies.txt \
  -d '{
    "email": "alice@example.com",
    "password": "Alice1234"
  }'
curl -X GET http://localhost:3000/api/auth/me \
  -b cookies.txt

Védett endpoint (Bearer token-nel)

curl -X GET http://localhost:3000/api/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Ez a hints fájl minden szükséges kódrészletet tartalmaz a sikeres implementációhoz! 🚀