Нужно улучшить защиту пользовательских данных , заменив localstorage на cookie , access , refresh токены 


const ApiError = require('../error/ApiError');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const { Basket, User } = require('../models/models');
const speakeasy = require('speakeasy');
const qrcode = require('qrcode');

const generateJwt = (id, number, role) => {
    return jwt.sign(
        { id, number, role },
        process.env.SECRET_KEY,
        { expiresIn: '24h' }
    );
}

class UserController {
    async registration(req, res, next) {
        const { number, password, role = 'USER' } = req.body;

        try {
            // Устанавливаем роль по умолчанию, если она не указана явно
            const userRole = role || 'USER';

            // Проверка наличия обязательных полей
            if (!number || !password) {
                return next(ApiError.badRequest('Некорректный логин или пароль'));
            }

            // Проверка наличия пользователя с таким же логином (number)
            const candidate = await User.findOne({ where: { number } });
            if (candidate) {
                return next(ApiError.badRequest('Пользователь с таким логином уже существует'));
            }

            // Хеширование пароля
            const hashPassword = await bcrypt.hash(password, 10);

            // Создание нового пользователя
            const user = await User.create({ number, role: userRole, password: hashPassword });

            // Создание корзины для пользователя
            await Basket.create({ userId: user.id });

            // Генерация JWT токена
            const token = generateJwt(user.id, user.number, user.role);

            // Отправка токена в ответе
            return res.json({ token });
        } catch (e) {
            // Перехват ошибок и передача их в обработчик ошибок
            return next(ApiError.internal(e.message));
        }
    }

    async login(req, res, next) {
        const { number, password } = req.body;

        try {
            // Поиск пользователя по логину (number)
            const user = await User.findOne({ where: { number } });
            if (!user) {
                return next(ApiError.internal('Пользователь не найден'));
            }

            // Проверка пароля
            const comparePassword = bcrypt.compareSync(password, user.password);
            if (!comparePassword) {
                return next(ApiError.internal('Указан неверный пароль'));
            }

            // Проверка, включена ли 2FA
            if (user.twoFactorSecret) {
                return res.json({ requires2FA: true });
            }

            // Генерация JWT токена
            const token = generateJwt(user.id, user.number, user.role);

            // Отправка токена в ответе
            return res.json({ token });
        } catch (e) {
            // Перехват ошибок и передача их в обработчик ошибок
            return next(ApiError.internal(e.message));
        }
    }

    async verify2FA(req, res, next) {
        const { number, token } = req.body;

        try {
            // Поиск пользователя по логину (number)
            const user = await User.findOne({ where: { number } });
            if (!user) {
                return next(ApiError.internal('Пользователь не найден'));
            }

            // Проверка 2FA токена
            const verified = speakeasy.totp.verify({
                secret: user.twoFactorSecret,
                encoding: 'base32',
                token,
            });

            if (!verified) {
                return next(ApiError.internal('Неверный 2FA токен'));
            }

            // Генерация JWT токена
            const jwtToken = generateJwt(user.id, user.number, user.role);

            // Отправка токена в ответе
            return res.json({ token: jwtToken });
        } catch (e) {
            // Перехват ошибок и передача их в обработчик ошибок
            return next(ApiError.internal(e.message));
        }
    }

    async enable2FA(req, res, next) {
        try {
            const secret = speakeasy.generateSecret({ length: 20 });
            const user = await User.findByPk(req.user.id);
            user.twoFactorSecret = secret.base32;
            await user.save();

            qrcode.toDataURL(secret.otpauth_url, (err, data_url) => {
                if (err) {
                    return next(ApiError.internal('Ошибка генерации QR кода'));
                }
                res.json({ secret: secret.base32, qrCodeUrl: data_url });
            });
        } catch (e) {
            // Перехват ошибок и передача их в обработчик ошибок
            return next(ApiError.internal(e.message));
        }
    }

    async check(req, res, next) {
        // Генерация JWT токена для текущего пользователя из запроса
        const token = generateJwt(req.user.id, req.user.number, req.user.role);
        return res.json({ token });
    }
}

module.exports = new UserController();