Jump to content

Vue - CORS crashing my backend

trylo

Hi!

 

I'm a hobbyist programmer. I'm trying to write a Vue Web App just for fun, but I want to make it secure.
I'm hosting it on my Unraid machine. I have separate containers for the frontend and for the backend. I have a container with Nginx Proxy Manager that is giving me reverse proxy for my other things (like Home Assistant or Nextcloud) and I want to use it to for my little Web App.

 

Right now I'm struggling with something that probably most of you will consider silly: user registration. I have the frontend and the backend setup and it works... until I want to implement CORS.

When I do that and I try to register a user - my backend container crashes and the webbrowser consol gives me an error saying:
 

Access to XMLHttpRequest at 'https://db.domain.com/register' from origin 'https://app.domain.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

I have no idea what is wrong. It looks like maybe Nginx Proxy Manager is stripping the header? But I don't know how to fix this. Can anyone help me troubleshooti this?

This is my backend code:

import dotenv from 'dotenv';
import nodemailer from 'nodemailer';
import crypto from 'crypto';
import express from 'express';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import mysql from 'mysql';
import cors from 'cors';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { body, validationResult } from 'express-validator';
import helmet from 'helmet';
import cron from 'node-cron';
import rateLimit from 'express-rate-limit';

process.on('uncaughtException', (error) => {
    console.error('Uncaught Exception:', error);
});

process.on('unhandledRejection', (error) => {
    console.error('Unhandled Promise Rejection:', error);
});

dotenv.config({
    path: process.env.NODE_ENV === 'production' ? '.env.production' : '.env.development'
});

const app = express();
const corsOptions = {
    origin: ['https://app.domain.com', 'https://db.domain.com'],
    credentials: true,
    methods: 'GET,HEAD,PUT,PATCH,POST,',
    allowedHeaders: 'Content-Type,Authorization'
};
app.use(cors(corsOptions));
app.use(helmet());
app.use(bodyParser.json());
app.use(cookieParser());
app.set('trust proxy', 1);

// Configure MariaDB connection pool
const pool = mysql.createPool({
    connectionLimit: 10, // Optional: adjust the number of connections in the pool
    host: process.env.DB_HOST,
    port: process.env.DB_PORT,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME
});

// Test connection to MariaDB
pool.getConnection((err, connection) => {
    if (err) {
        console.error('Error getting connection from pool:', err);
        return;
    }
    console.log('Connected to the database.');
    connection.release(); // Release the connection back to the pool
});

// Rate limiting
const loginLimiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes window
    max: 5, // limit each IP to 5 login requests
    message: 'Zbyt wiele pr�b logowania, spr�buj ponownie za 15 minut.'
});

// Configure nodemailer transporter
let transporter = nodemailer.createTransport({
    host: "smtpserver.com",
    port: 587, // Common ports are 587 (for TLS) or 465 (for SSL)
    secure: false, // true for 465, false for other ports
    auth: {
        user: process.env.EMAIL_USERNAME, // Your SMTP username
        pass: process.env.EMAIL_PASSWORD  // Your SMTP password
    },
    tls: {
        // Do not fail on invalid certs (if you are using self-signed certificates)
        rejectUnauthorized: false
    }
});

// Registration endpoint
app.post('/register', loginLimiter,
    [
        // Validation rules
        body('username', 'Nieprawidlowy adres email').isEmail(),
        body('password', 'Haslo musi miec minimum 9 znak�w').isLength({ min: 9 }),
    ],
    async (req, res) => {
        // Check for validation errors
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(400).json({ errors: errors.array() });
        }

        const { username, password } = req.body;

        res.setHeader('Access-Control-Allow-Origin', 'https://app.domain.com');
        res.setHeader('Access-Control-Allow-Methods', 'POST');
        res.setHeader('Access-Control-Allow-Credentials', 'true');

        // Check if the user already exists
        pool.query('SELECT * FROM users WHERE username = ?', [username], async (err, results) => {
            if (err) {
                console.error("Database query error:", err);
                res.status(500).send({ message: "Error checking user existence", error: err.message });
                return;
            }
            if (results.length > 0) {
                // User already exists
                res.status(409).send({ message: "Adres email jest juz zarejestrowany" });
                return;
            }

            // If user does not exist, continue with registration
            try {
                const hashedPassword = await bcrypt.hash(password, 10);
                const verificationToken = crypto.randomBytes(16).toString('hex');
                const tokenExpiration = new Date();
                tokenExpiration.setHours(tokenExpiration.getHours() + 9);

                // Save user with token in the database
                pool.query('INSERT INTO users (username, password, verificationToken, tokenExpiration) VALUES (?, ?, ?, ?)',
                    [username, hashedPassword, verificationToken, tokenExpiration],
                    (error, result) => {
                        if (error) {
                            console.error("Error in user registration:", error);
                            res.status(500).send({ message: "Blad rejestracji nowego uzytkownika", error: error.message });
                            return;
                        }

                        // Send verification email
                        const mailOptions = {
                            from: process.env.EMAIL_USERNAME,
                            to: username,
                            subject: 'app - weryfikacja adres email',
                            html: `<p>Potwierdz adres email klikajac ponizszy link: <a href="http://${req.headers.host}/verify-email?token=${verificationToken}">Potwierdzam adres email</a></p>`
                        };

                        transporter.sendMail(mailOptions, function (emailError, info) {
                            if (emailError) {
                                console.log(emailError);
                                res.status(500).send({ message: "Error sending verification email", error: emailError.message });
                                return;
                            } else {
                                console.log('Email sent: ' + info.response);
                                res.status(201).send({ message: "User created. Please check your email to verify your account." });
                            }
                        });
                    }
                );
            } catch (error) {
                res.status(500).send({ message: "Error hashing password", error: error.message });
            }
        });
    });

// Function to query the database using the pool
function queryDatabase(query, params, callback) {
    pool.getConnection((err, connection) => {
        if (err) {
            console.error('Error getting connection from pool:', err);
            callback(err, null);
            return;
        }

        connection.query(query, params, (error, results) => {
            connection.release(); // always release the connection back to the pool

            if (error) {
                console.error('Error executing query:', error);
                callback(error, null);
                return;
            }

            callback(null, results);
        });
    });
}

// Start server 
const port = process.env.PORT_BACKEND || 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}.`);
});
console.log('VERSION: '+process.env.VERSION);
console.log('Frontend URL: '+process.env.FRONTEND_URL);
console.log('DB Host: '+process.env.DB_HOST);

And here is my frontend call:

      async RegistrationNew() {
        const apiUrl = import.meta.env.VITE_APP_API_URL
        try {
          await axios.post(
            `${apiUrl}/register`,
            {
              username: this.username,
              password: this.password
            },
            {
              withCredentials: true
            }
          )
          alert('Registration successful! Please check your email to verify your account.')
        } catch (error) {
          if (error.response) {
            this.errorMessage =
              error.response.data.message || 'An error occurred during registration.'
          } else {
            this.errorMessage = 'Registration failed. Please try again.'
          }
          console.error('Registration error:', error)
          console.log("VERSION: "+import.meta.env.VERSION)
          console.log("API URL: "+import.meta.env.VITE_APP_API_URL)
        }
      }

 

Link to comment
Share on other sites

Link to post
Share on other sites

Have you tried using an API testing tool like Insomnia or Postman? 

Not sure if this is related, but the request handler isn't waiting for the pool.query call to finish (since the query function uses a callback). I recommend using mysql2 package instead, which allows you to use Promises. (Or use an ORM like Prisma or Sequelize, might be overkill)

🙂

Link to comment
Share on other sites

Link to post
Share on other sites

1 hour ago, duncannah said:

Have you tried using an API testing tool like Insomnia or Postman? 

Not sure if this is related, but the request handler isn't waiting for the pool.query call to finish (since the query function uses a callback). I recommend using mysql2 package instead, which allows you to use Promises. (Or use an ORM like Prisma or Sequelize, might be overkill)

I switched to mysql2 and logged as much as possible and found out that it was actually bcrypt that was crashing the backend. I switched to bcryptjs and it works now.

Thanks.

Link to comment
Share on other sites

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×