Index: backend/index.js
===================================================================
--- backend/index.js	(revision 1e86d93cd481299e570d5d59b1b325e072414036)
+++ backend/index.js	(revision b5b78113cc4c1eae18ed3bf646fae141c862af84)
@@ -1,5 +1,3 @@
-// backend/index.js
-require("dotenv").config();
-
+// index.js finalna verzija
 const express = require("express");
 const cors = require("cors");
@@ -8,4 +6,5 @@
 const bcrypt = require("bcrypt");
 const jwt = require("jsonwebtoken");
+require("dotenv").config();
 
 const pool = require("./db");
@@ -13,85 +12,72 @@
 const app = express();
 
-// ---- Security middleware ----
+// Security middlewares
 app.use(helmet());
-
-// Limit JSON size, basic DoS protection
-app.use(express.json({ limit: "100kb" }));
-
-// Safer CORS (adjust FRONTEND_ORIGIN in .env)
-const allowed = process.env.CORS_ORIGIN
-  ? process.env.CORS_ORIGIN.split(",")
-  : ["http://localhost:3000"];
-app.use(
-  cors({
-    origin: function (origin, cb) {
-      if (!origin) return cb(null, true); // Postman/curl
-      if (allowed.includes(origin)) return cb(null, true);
-      return cb(new Error("Not allowed by CORS"));
-    },
-    credentials: true,
-  })
-);
-
-// Rate-limits (tighter on auth endpoints)
-const authLimiter = rateLimit({
-  windowMs: Number(process.env.RATE_LIMIT_WINDOW_MS || 15 * 60 * 1000),
-  max: Number(process.env.RATE_LIMIT_MAX || 50),
-  standardHeaders: true,
-  legacyHeaders: false,
-});
-app.use("/api/login", authLimiter);
-app.use("/api/register-student", authLimiter);
-app.use("/api/register-instructor", authLimiter);
-
-// Helpers
-const JWT_SECRET = process.env.JWT_SECRET || "dev_secret_change_me";
-
-function validateEmail(email) {
-  return typeof email === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
-}
-function sanitize(s) {
-  return typeof s === "string" ? s.trim() : s;
-}
-async function hashPassword(plain) {
-  if (!plain || plain.length < 6) throw new Error("Weak password");
-  return await bcrypt.hash(plain, 10);
-}
-function signToken(payload) {
-  return jwt.sign(payload, JWT_SECRET, { expiresIn: "2h" });
+app.use(cors({ origin: "http://localhost:3000", credentials: true }));
+app.use(express.json({ limit: "1mb" }));
+app.set("trust proxy", 1);
+const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 300 });
+app.use(limiter);
+
+// Small helpers
+const JWT_SECRET = process.env.JWT_SECRET || "dev_fallback";
+function signJwt(payload) {
+  return jwt.sign(payload, JWT_SECRET, {
+    expiresIn: process.env.JWT_EXPIRES || "2h",
+  });
 }
 function authOptional(req, _res, next) {
-  const hdr = req.headers.authorization || "";
-  const token = hdr.startsWith("Bearer ") ? hdr.slice(7) : null;
-  if (token) {
+  const h = req.headers.authorization || "";
+  if (h.startsWith("Bearer ")) {
     try {
-      req.user = jwt.verify(token, JWT_SECRET);
-    } catch (_e) {
-      // ignore invalid token (endpoint remains usable without auth)
-    }
+      req.user = jwt.verify(h.slice(7), JWT_SECRET);
+    } catch {}
   }
   next();
 }
+function authRequired(req, res, next) {
+  const h = req.headers.authorization || "";
+  if (!h.startsWith("Bearer "))
+    return res.status(401).json({ success: false, error: "Missing token" });
+  try {
+    req.user = jwt.verify(h.slice(7), JWT_SECRET);
+    next();
+  } catch {
+    return res.status(401).json({ success: false, error: "Invalid token" });
+  }
+}
+
+// Health & hello (no DB)
+app.get("/health", (_req, res) =>
+  res.json({ ok: true, service: "backend", ts: new Date().toISOString() })
+);
+app.get("/api/hello", (_req, res) => res.json({ ok: true }));
+
+// DB ping (quick connectivity check)
+app.get("/api/db-ping", async (_req, res) => {
+  try {
+    const r = await pool.query("SELECT 1 as ok");
+    res.json({ ok: r.rows[0].ok === 1 });
+  } catch (e) {
+    console.error(e);
+    res.status(500).json({ ok: false, error: "db" });
+  }
+});
 
 // EXISTING ROUTES
 
-// 1) Register Student (hashes password)
+// 1) Register Student
 app.post("/api/register-student", async (req, res) => {
-  try {
-    const username = sanitize(req.body.username);
-    const email = sanitize(req.body.email);
-    const password = sanitize(req.body.password);
-    const firstName = sanitize(req.body.firstName);
-    const lastName = sanitize(req.body.lastName);
-
-    if (!validateEmail(email))
-      return res.status(400).json({ success: false, error: "Invalid email" });
-    const password_hash = await hashPassword(password);
-
+  const { username, email, password, firstName, lastName } = req.body;
+  try {
+    const hash =
+      password && !password.startsWith("$2b$")
+        ? await bcrypt.hash(password, 10)
+        : password || "x";
     const result = await pool.query(
       `INSERT INTO "User" (username, email, password_hash, first_name, last_name)
        VALUES ($1, $2, $3, $4, $5)
        RETURNING user_id`,
-      [username, email, password_hash, firstName, lastName]
+      [username, email, hash, firstName, lastName]
     );
     res.json({ success: true, userId: result.rows[0].user_id });
@@ -103,34 +89,27 @@
     }
     console.error(err);
-    res
-      .status(400)
-      .json({ success: false, error: err.message || "Server error" });
-  }
-});
-
-// 2) Register Instructor (hashes password)
+    res.status(500).json({ success: false, error: "Server error" });
+  }
+});
+
+// 2) Register Instructor
 app.post("/api/register-instructor", async (req, res) => {
-  try {
-    const instructorEmail = sanitize(req.body.instructorEmail);
-    const instructorPassword = sanitize(req.body.instructorPassword);
-    const firstName = sanitize(req.body.firstName);
-    const lastName = sanitize(req.body.lastName);
-    const biography = sanitize(req.body.biography);
-
-    if (!validateEmail(instructorEmail))
-      return res.status(400).json({ success: false, error: "Invalid email" });
-    const instructor_password_hash = await hashPassword(instructorPassword);
-
+  const {
+    instructorEmail,
+    instructorPassword,
+    firstName,
+    lastName,
+    biography,
+  } = req.body;
+  try {
+    const hash =
+      instructorPassword && !instructorPassword.startsWith("$2b$")
+        ? await bcrypt.hash(instructorPassword, 10)
+        : instructorPassword || "x";
     const result = await pool.query(
       `INSERT INTO "Instructor" (instructor_email, instructor_password_hash, first_name, last_name, biography)
        VALUES ($1, $2, $3, $4, $5)
        RETURNING instructor_id`,
-      [
-        instructorEmail,
-        instructor_password_hash,
-        firstName,
-        lastName,
-        biography,
-      ]
+      [instructorEmail, hash, firstName, lastName, biography]
     );
     res.json({ success: true, instructorId: result.rows[0].instructor_id });
@@ -142,7 +121,5 @@
     }
     console.error(err);
-    res
-      .status(400)
-      .json({ success: false, error: err.message || "Server error" });
+    res.status(500).json({ success: false, error: "Server error" });
   }
 });
@@ -165,14 +142,7 @@
 
 // 4) POST register-event
-app.post("/api/register-event", authOptional, async (req, res) => {
-  try {
-    const eventId = req.body.eventId;
-    const userId = req.user?.sub || req.body.userId;
-
-    if (!userId)
-      return res
-        .status(401)
-        .json({ success: false, error: "Missing user identity" });
-
+app.post("/api/register-event", async (req, res) => {
+  const { userId, eventId } = req.body;
+  try {
     const check = await pool.query(
       `SELECT 1 FROM "User_Event" WHERE user_id = $1 AND event_id = $2`,
@@ -185,5 +155,4 @@
       });
     }
-
     await pool.query(
       `INSERT INTO "User_Event" (user_id, event_id) VALUES ($1, $2)`,
@@ -197,25 +166,24 @@
 });
 
-// AUTH (JWT login)
-
-// POST /api/login   { type: "user"|"instructor", email, password }
+// 5) LOGIN
 app.post("/api/login", async (req, res) => {
   try {
-    const type = req.body.type === "instructor" ? "instructor" : "user";
-    const email = sanitize(req.body.email);
-    const password = sanitize(req.body.password);
-    if (!validateEmail(email) || !password)
-      return res.status(400).json({ success: false, error: "Invalid input" });
+    const { type, email, password } = req.body;
+    if (!type || !email || !password)
+      return res.status(400).json({ success: false, error: "Missing fields" });
 
     if (type === "user") {
-      const q = await pool.query(
-        `SELECT user_id, password_hash, username FROM "User" WHERE email=$1`,
+      const { rows } = await pool.query(
+        `SELECT user_id, username, email, password_hash FROM "User" WHERE email=$1`,
         [email]
       );
-      if (q.rowCount === 0)
+      if (!rows[0])
         return res
           .status(401)
           .json({ success: false, error: "Invalid credentials" });
-      const ok = await bcrypt.compare(password, q.rows[0].password_hash || "");
+
+      const ok = rows[0].password_hash?.startsWith("$2b$")
+        ? await bcrypt.compare(password, rows[0].password_hash)
+        : password === rows[0].password_hash;
       if (!ok)
         return res
@@ -223,24 +191,30 @@
           .json({ success: false, error: "Invalid credentials" });
 
-      const token = signToken({
-        sub: q.rows[0].user_id,
+      const token = signJwt({
+        sub: rows[0].user_id,
         role: "user",
-        username: q.rows[0].username,
-      });
-      return res.json({ success: true, token, role: "user" });
-    } else {
-      const q = await pool.query(
-        `SELECT instructor_id, instructor_password_hash, first_name, last_name
-         FROM "Instructor" WHERE instructor_email=$1`,
+        email: rows[0].email,
+      });
+      return res.json({
+        success: true,
+        token,
+        role: "user",
+        userId: rows[0].user_id,
+      });
+    }
+
+    if (type === "instructor") {
+      const { rows } = await pool.query(
+        `SELECT instructor_id, instructor_email, instructor_password_hash FROM "Instructor" WHERE instructor_email=$1`,
         [email]
       );
-      if (q.rowCount === 0)
+      if (!rows[0])
         return res
           .status(401)
           .json({ success: false, error: "Invalid credentials" });
-      const ok = await bcrypt.compare(
-        password,
-        q.rows[0].instructor_password_hash || ""
-      );
+
+      const ok = rows[0].instructor_password_hash?.startsWith("$2b$")
+        ? await bcrypt.compare(password, rows[0].instructor_password_hash)
+        : password === rows[0].instructor_password_hash;
       if (!ok)
         return res
@@ -248,28 +222,38 @@
           .json({ success: false, error: "Invalid credentials" });
 
-      const token = signToken({
-        sub: q.rows[0].instructor_id,
+      const token = signJwt({
+        sub: rows[0].instructor_id,
         role: "instructor",
-      });
-      return res.json({ success: true, token, role: "instructor" });
-    }
-  } catch (err) {
-    console.error(err);
-    res.status(500).json({ success: false, error: "Server error" });
-  }
-});
-
-// Procedure, reports, explain
-
-// 5) POST /api/book-class (transaction + function)
+        email: rows[0].instructor_email,
+      });
+      return res.json({
+        success: true,
+        token,
+        role: "instructor",
+        instructorId: rows[0].instructor_id,
+      });
+    }
+
+    return res.status(400).json({ success: false, error: "Unknown type" });
+  } catch (e) {
+    console.error(e);
+    res.status(500).json({ success: false, error: "Server error" });
+  }
+});
+
+// 6) Book a class
 app.post("/api/book-class", authOptional, async (req, res) => {
   const client = await pool.connect();
   try {
-    const classId = req.body.classId;
-    const userId = req.user?.sub || req.body.userId;
+    const classId = Number(req.body.classId);
+    const jwtUserId = req.user?.role === "user" ? Number(req.user.sub) : null;
+    const bodyUserId = req.body.userId ? Number(req.body.userId) : null;
+    const userId = jwtUserId || bodyUserId;
     if (!userId)
       return res
         .status(401)
         .json({ success: false, error: "Missing user identity" });
+    if (!classId)
+      return res.status(400).json({ success: false, error: "Missing classId" });
 
     await client.query("BEGIN");
@@ -286,5 +270,7 @@
     res.json({ success: true, code: "OK" });
   } catch (err) {
-    await client.query("ROLLBACK");
+    try {
+      await pool.query("ROLLBACK");
+    } catch {}
     console.error(err);
     res.status(500).json({ success: false, error: "Server error" });
@@ -294,19 +280,18 @@
 });
 
-// Reports from VIEWS
+// 7) Reports
 app.get("/api/reports/top-spenders", async (_req, res) => {
   try {
-    const q = `
+    const sql = `
       SELECT user_id, username, email, spend_packages, spend_merch, total_spend,
              RANK() OVER (ORDER BY total_spend DESC) AS spend_rank
       FROM vw_user_spend
-      ORDER BY total_spend DESC, user_id
-      LIMIT 50;
-    `;
-    const result = await pool.query(q);
-    res.json({ success: true, rows: result.rows });
-  } catch (err) {
-    console.error(err);
-    res.status(500).json({ success: false, error: "Server error" });
+      ORDER BY total_spend DESC, user_id;
+    `;
+    const { rows } = await pool.query(sql);
+    res.json(rows);
+  } catch (e) {
+    console.error(e);
+    res.status(500).json({ error: "server" });
   }
 });
@@ -314,14 +299,15 @@
 app.get("/api/reports/class-utilization", async (_req, res) => {
   try {
-    const q = `
-      SELECT *, DENSE_RANK() OVER (PARTITION BY date ORDER BY utilization_pct DESC NULLS LAST) AS daily_rank
+    const sql = `
+      SELECT *,
+             DENSE_RANK() OVER (PARTITION BY date ORDER BY utilization_pct DESC NULLS LAST) AS daily_rank
       FROM vw_class_utilization
       ORDER BY date, start_time, class_id;
     `;
-    const result = await pool.query(q);
-    res.json({ success: true, rows: result.rows });
-  } catch (err) {
-    console.error(err);
-    res.status(500).json({ success: false, error: "Server error" });
+    const { rows } = await pool.query(sql);
+    res.json(rows);
+  } catch (e) {
+    console.error(e);
+    res.status(500).json({ error: "server" });
   }
 });
@@ -329,5 +315,5 @@
 app.get("/api/reports/training-pop-monthly", async (_req, res) => {
   try {
-    const q = `
+    const sql = `
       SELECT training_id, training_name, month, num_bookings,
              RANK() OVER (PARTITION BY month ORDER BY num_bookings DESC NULLS LAST) AS rank_in_month
@@ -335,27 +321,31 @@
       ORDER BY month DESC, rank_in_month, training_name;
     `;
-    const result = await pool.query(q);
-    res.json({ success: true, rows: result.rows });
-  } catch (err) {
-    console.error(err);
-    res.status(500).json({ success: false, error: "Server error" });
-  }
-});
-
-// EXPLAIN/ANALYZE proofs
+    const { rows } = await pool.query(sql);
+    res.json(rows);
+  } catch (e) {
+    console.error(e);
+    res.status(500).json({ error: "server" });
+  }
+});
+
+// 8) DEBUG/EXPLAIN
 app.get("/api/debug/explain-events", async (_req, res) => {
   try {
-    const plan = await pool.query(`
-      EXPLAIN ANALYZE
-      SELECT *
-      FROM "Event"
+    const sql = `
+      EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)
+      SELECT * FROM "Event"
       WHERE date >= CURRENT_DATE
       ORDER BY date, time
       LIMIT 50;
-    `);
-    res.json({ success: true, plan: plan.rows.map((r) => r["QUERY PLAN"]) });
-  } catch (err) {
-    console.error(err);
-    res.status(500).json({ success: false, error: "Server error" });
+    `;
+    const { rows } = await pool.query(sql);
+    if (rows[0] && rows[0]["QUERY PLAN"]) {
+      res.json(rows[0]["QUERY PLAN"]);
+    } else {
+      res.status(500).json({ error: "Empty plan" });
+    }
+  } catch (e) {
+    console.error(e);
+    res.status(500).json({ error: "server" });
   }
 });
@@ -363,6 +353,6 @@
 app.get("/api/debug/explain-class-join", async (_req, res) => {
   try {
-    const plan = await pool.query(`
-      EXPLAIN ANALYZE
+    const sql = `
+      EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)
       SELECT c.class_id, c.date, c.start_time, COUNT(ubc.user_id)
       FROM "Class" c
@@ -372,14 +362,19 @@
       ORDER BY c.date, c.start_time
       LIMIT 50;
-    `);
-    res.json({ success: true, plan: plan.rows.map((r) => r["QUERY PLAN"]) });
-  } catch (err) {
-    console.error(err);
-    res.status(500).json({ success: false, error: "Server error" });
-  }
-});
-
-// Start server
-const PORT = process.env.PORT || 5000;
+    `;
+    const { rows } = await pool.query(sql);
+    if (rows[0] && rows[0]["QUERY PLAN"]) {
+      res.json(rows[0]["QUERY PLAN"]);
+    } else {
+      res.status(500).json({ error: "Empty plan" });
+    }
+  } catch (e) {
+    console.error(e);
+    res.status(500).json({ error: "server" });
+  }
+});
+
+// START SERVER
+const PORT = 5000;
 app.listen(PORT, () => {
   console.log(`Server running on port ${PORT}`);
Index: backend/package-lock.json
===================================================================
--- backend/package-lock.json	(revision 1e86d93cd481299e570d5d59b1b325e072414036)
+++ backend/package-lock.json	(revision b5b78113cc4c1eae18ed3bf646fae141c862af84)
@@ -10,7 +10,11 @@
       "license": "ISC",
       "dependencies": {
+        "bcrypt": "^6.0.0",
         "cors": "^2.8.5",
-        "dotenv": "^16.4.7",
+        "dotenv": "^16.6.1",
         "express": "^4.21.2",
+        "express-rate-limit": "^8.1.0",
+        "helmet": "^8.1.0",
+        "jsonwebtoken": "^9.0.2",
         "pg": "^8.13.2"
       }
@@ -32,4 +36,17 @@
       "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
       "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+    },
+    "node_modules/bcrypt": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
+      "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==",
+      "hasInstallScript": true,
+      "dependencies": {
+        "node-addon-api": "^8.3.0",
+        "node-gyp-build": "^4.8.4"
+      },
+      "engines": {
+        "node": ">= 18"
+      }
     },
     "node_modules/body-parser": {
@@ -56,4 +73,9 @@
       }
     },
+    "node_modules/buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+    },
     "node_modules/bytes": {
       "version": "3.1.2",
@@ -161,7 +183,7 @@
     },
     "node_modules/dotenv": {
-      "version": "16.4.7",
-      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
-      "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
+      "version": "16.6.1",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+      "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
       "engines": {
         "node": ">=12"
@@ -182,4 +204,12 @@
       "engines": {
         "node": ">= 0.4"
+      }
+    },
+    "node_modules/ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
       }
     },
@@ -282,4 +312,21 @@
       }
     },
+    "node_modules/express-rate-limit": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.1.0.tgz",
+      "integrity": "sha512-4nLnATuKupnmwqiJc27b4dCFmB/T60ExgmtDD7waf4LdrbJ8CPZzZRHYErDYNhoz+ql8fUdYwM/opf90PoPAQA==",
+      "dependencies": {
+        "ip-address": "10.0.1"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/express-rate-limit"
+      },
+      "peerDependencies": {
+        "express": ">= 4.11"
+      }
+    },
     "node_modules/finalhandler": {
       "version": "1.3.1",
@@ -391,4 +438,12 @@
       }
     },
+    "node_modules/helmet": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
+      "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
     "node_modules/http-errors": {
       "version": "2.0.0",
@@ -422,4 +477,12 @@
       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
     },
+    "node_modules/ip-address": {
+      "version": "10.0.1",
+      "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
+      "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
+      "engines": {
+        "node": ">= 12"
+      }
+    },
     "node_modules/ipaddr.js": {
       "version": "1.9.1",
@@ -429,4 +492,84 @@
         "node": ">= 0.10"
       }
+    },
+    "node_modules/jsonwebtoken": {
+      "version": "9.0.2",
+      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+      "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+      "dependencies": {
+        "jws": "^3.2.2",
+        "lodash.includes": "^4.3.0",
+        "lodash.isboolean": "^3.0.3",
+        "lodash.isinteger": "^4.0.4",
+        "lodash.isnumber": "^3.0.3",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.isstring": "^4.0.1",
+        "lodash.once": "^4.0.0",
+        "ms": "^2.1.1",
+        "semver": "^7.5.4"
+      },
+      "engines": {
+        "node": ">=12",
+        "npm": ">=6"
+      }
+    },
+    "node_modules/jsonwebtoken/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+    },
+    "node_modules/jwa": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
+      "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
+      "dependencies": {
+        "buffer-equal-constant-time": "^1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "dependencies": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/lodash.includes": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+      "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
+    },
+    "node_modules/lodash.isboolean": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+      "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
+    },
+    "node_modules/lodash.isinteger": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+      "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
+    },
+    "node_modules/lodash.isnumber": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+      "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
+    },
+    "node_modules/lodash.isplainobject": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+    },
+    "node_modules/lodash.isstring": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+      "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
+    },
+    "node_modules/lodash.once": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+      "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
     },
     "node_modules/math-intrinsics": {
@@ -503,4 +646,22 @@
       "engines": {
         "node": ">= 0.6"
+      }
+    },
+    "node_modules/node-addon-api": {
+      "version": "8.5.0",
+      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
+      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
+      "engines": {
+        "node": "^18 || ^20 || >= 21"
+      }
+    },
+    "node_modules/node-gyp-build": {
+      "version": "4.8.4",
+      "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
+      "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
+      "bin": {
+        "node-gyp-build": "bin.js",
+        "node-gyp-build-optional": "optional.js",
+        "node-gyp-build-test": "build-test.js"
       }
     },
@@ -736,4 +897,15 @@
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
     },
+    "node_modules/semver": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/send": {
       "version": "0.19.0",
Index: backend/package.json
===================================================================
--- backend/package.json	(revision 1e86d93cd481299e570d5d59b1b325e072414036)
+++ backend/package.json	(revision b5b78113cc4c1eae18ed3bf646fae141c862af84)
@@ -16,7 +16,11 @@
   "license": "ISC",
   "dependencies": {
+    "bcrypt": "^6.0.0",
     "cors": "^2.8.5",
-    "dotenv": "^16.4.7",
+    "dotenv": "^16.6.1",
     "express": "^4.21.2",
+    "express-rate-limit": "^8.1.0",
+    "helmet": "^8.1.0",
+    "jsonwebtoken": "^9.0.2",
     "pg": "^8.13.2"
   }
