Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ coverage/
dist/
build/
test-results/
logs/
/data
server.pid
server.log
Expand All @@ -19,3 +20,4 @@ node_modules
*.crt
*.p12
certs/

133 changes: 131 additions & 2 deletions controller/loginController.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const getUserCredentials = require("../model/getUserCredentials.js");
const { addMfaToken, verifyMfaToken } = require("../model/addMfaToken.js");

const logLoginEvent = require("../Monitor_&_Logging/loginLogger");
const getUserCredentials = require("../model/getUserCredentials.js");
const {
Expand All @@ -7,9 +11,21 @@ const {
verifyMfaToken,
} = require("../model/addMfaToken.js");
const crypto = require("crypto");
const supabase = require("../dbConnection");
const { validationResult } = require("express-validator");
const { logSecurityEvent } = require("../services/securityEventService");
const logger = require("../utils/logger");

// Access token helper
function createAccessToken(user) {
return jwt.sign(
{
userId: user.user_id,
role: user.user_roles?.role_name || "unknown",
},
process.env.JWT_TOKEN,
{ expiresIn: "1h" }
);

const { createLog, log } = require("../services/securityLogger");
const logger = require("../utils/logger");
const nodemailer = require("nodemailer");
Expand Down Expand Up @@ -47,6 +63,8 @@ function getDeviceInfo(req) {
}

const login = async (req, res) => {
console.log("LOGIN CONTROLLER HIT");

const errors = validationResult(req);
if (!errors.isEmpty()) {
return authValidationError(res, errors.array());
Expand All @@ -60,6 +78,7 @@ const login = async (req, res) => {
clientIp = clientIp === "::1" ? "127.0.0.1" : clientIp;

if (!email || !password) {
return res.status(400).json({ error: "Email and password are required" });
log(
createLog({
event_type: "AUTH_LOGIN_FAILED",
Expand Down Expand Up @@ -104,6 +123,8 @@ const login = async (req, res) => {

const user = await getUserCredentials(email);

// User not found
if (!user) {
if (!user) {
await supabase.from("brute_force_logs").insert([
{
Expand All @@ -123,6 +144,9 @@ const login = async (req, res) => {
resource: "/api/auth/login",
metadata: {
email,
reason: "user_not_found",
},
});
reason: "account_not_found",
},
});
Expand Down Expand Up @@ -153,6 +177,8 @@ const login = async (req, res) => {

const isPasswordValid = await bcrypt.compare(password, user.password);

// Wrong password
if (!isPasswordValid) {
if (!isPasswordValid) {
await supabase.from("brute_force_logs").insert([
{
Expand Down Expand Up @@ -208,6 +234,15 @@ const login = async (req, res) => {
});
}

// MFA enabled
if (user.mfa_enabled) {
const token = crypto.randomInt(100000, 999999);

await addMfaToken(user.user_id, token);

await logSecurityEvent({
event_type: "MFA_CHALLENGE_ISSUED",
severity: "low",
await supabase.from("brute_force_logs").insert([
{
email,
Expand All @@ -227,8 +262,30 @@ const login = async (req, res) => {
event_type: "AUTH_LOGIN_SUCCESS",
severity_level: "LOW",
user_id: user.user_id,
source_service: "login-controller",
ip_address: clientIp,
user_agent: req.headers["user-agent"],
resource: "/api/auth/login",
metadata: {
email,
},
});

return res.status(202).json({
message: "An MFA Token has been generated for this login attempt",
});
}

// Successful login
await logSecurityEvent({
event_type: "LOGIN_SUCCESS",
severity: "low",
user_id: user.user_id,
ip_address: clientIp,
user_agent: req.headers["user-agent"],
resource: "/api/auth/login",
metadata: {
email,
},
endpoint: req.originalUrl,
method: req.method,
status: "SUCCESS",
Expand Down Expand Up @@ -289,6 +346,18 @@ const login = async (req, res) => {
session,
});
} catch (err) {
console.error("Login error:", err);

if (logger && logger.error) {
logger.error("Login error", err);
}

return res.status(500).json({ error: "Internal server error" });
}
};

// ================= MFA LOGIN =================
=======
log(
createLog({
event_type: "SYSTEM_ERROR",
Expand Down Expand Up @@ -322,7 +391,13 @@ const loginMfa = async (req, res) => {
const password = req.body.password;
const mfa_token = req.body.mfa_token;

let clientIp =
req.headers["x-forwarded-for"] || req.socket.remoteAddress || req.ip;
clientIp = clientIp === "::1" ? "127.0.0.1" : clientIp;

if (!email || !password || !mfa_token) {
return res.status(400).json({
error: "Email, password, and token are required",
return authFail(res, {
message: msg("auth.login.mfa_required"),
code: AUTH_ERROR_CODES.MFA_REQUIRED,
Expand All @@ -332,7 +407,22 @@ const loginMfa = async (req, res) => {

try {
const user = await getUserCredentials(email);

if (!user) {
await logSecurityEvent({
event_type: "MFA_FAILED",
severity: "medium",
user_id: null,
ip_address: clientIp,
user_agent: req.headers["user-agent"],
resource: "/api/auth/login-mfa",
metadata: {
email,
reason: "user_not_found",
},
});

return res.status(401).json({ error: "Invalid credentials" });
return authFail(res, {
message: msg("auth.login.failed_credentials"),
code: AUTH_ERROR_CODES.INVALID_CREDENTIALS,
Expand All @@ -344,6 +434,45 @@ const loginMfa = async (req, res) => {
const validToken = await verifyMfaToken(user.user_id, mfa_token);

if (!validPassword || !validToken) {
await logSecurityEvent({
event_type: "MFA_FAILED",
severity: "medium",
user_id: user.user_id,
ip_address: clientIp,
user_agent: req.headers["user-agent"],
resource: "/api/auth/login-mfa",
metadata: {
email,
reason: "invalid_password_or_mfa_token",
},
});

return res.status(401).json({ error: "Invalid credentials" });
}

await logSecurityEvent({
event_type: "MFA_SUCCESS",
severity: "low",
user_id: user.user_id,
ip_address: clientIp,
user_agent: req.headers["user-agent"],
resource: "/api/auth/login-mfa",
metadata: {
email,
},
});

const token = createAccessToken(user);

return res.status(200).json({ user, token });
} catch (err) {
console.error("MFA error:", err);

if (logger && logger.error) {
logger.error("MFA error", err);
}

return res.status(500).json({ error: "Internal server error" });
return authFail(res, {
message: msg("auth.login.mfa_invalid"),
code: AUTH_ERROR_CODES.MFA_INVALID,
Expand Down
Loading
Loading