forked from CSI-KJSCE/Travel-policy-
Add Dark Mode, Password Hashing for better security , Settings Page, Policy PDF in Policy section,UI Changes
This commit is contained in:
35
backend/package-lock.json
generated
35
backend/package-lock.json
generated
@@ -15,7 +15,9 @@
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"jwa": "^2.0.1",
|
||||
"jws": "^4.0.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"nodemailer": "^6.9.16",
|
||||
"prisma": "^5.20.0"
|
||||
@@ -196,7 +198,8 @@
|
||||
"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=="
|
||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
@@ -414,6 +417,7 @@
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
@@ -789,12 +793,12 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsonwebtoken": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
|
||||
"integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jws": "^3.2.2",
|
||||
"jws": "^4.0.1",
|
||||
"lodash.includes": "^4.3.0",
|
||||
"lodash.isboolean": "^3.0.3",
|
||||
"lodash.isinteger": "^4.0.4",
|
||||
@@ -816,21 +820,23 @@
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
||||
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
|
||||
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "1.0.1",
|
||||
"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==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
|
||||
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jwa": "^1.4.1",
|
||||
"jwa": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
@@ -1124,6 +1130,7 @@
|
||||
"integrity": "sha512-6obb3ucKgAnsGS9x9gLOe8qa51XxvJ3vLQtmyf52CTey1Qcez3A6W6ROH5HIz5Q5bW+0VpmZb8WBohieMFGpig==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@prisma/engines": "5.20.0"
|
||||
},
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"jwa": "^2.0.1",
|
||||
"jws": "^4.0.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"nodemailer": "^6.9.16",
|
||||
"prisma": "^5.20.0"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// Generator to create Prisma Client
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["native", "darwin-arm64", "linux-musl-arm64-openssl-3.0.x"]
|
||||
@@ -40,7 +39,7 @@ enum Designation {
|
||||
model User {
|
||||
profileId String @id @default(uuid())
|
||||
userName String
|
||||
email String @unique @db.Text
|
||||
email String @unique
|
||||
password String
|
||||
|
||||
institute Institute?
|
||||
@@ -54,9 +53,9 @@ model User {
|
||||
}
|
||||
|
||||
model Application {
|
||||
applicationId String @id @default(uuid())
|
||||
applicationId String @id @default(uuid())
|
||||
applicantId String
|
||||
applicant User @relation("AppliedApplications", fields: [applicantId], references: [profileId])
|
||||
applicant User @relation("AppliedApplications", fields: [applicantId], references: [profileId])
|
||||
institute Institute
|
||||
department String
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import prisma from "../src/config/prismaConfig.js";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
// Common password for all users
|
||||
// Use a common password for all development users to make testing easier
|
||||
const commonPassword = "securePassword123";
|
||||
|
||||
// Applicant and Validator data
|
||||
// Secure the password with hashing even for seed data!
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
const hashedPassword = await bcrypt.hash(commonPassword, salt);
|
||||
|
||||
const institutes = [
|
||||
"KJSIDS",
|
||||
"SKSC",
|
||||
@@ -28,96 +34,116 @@ async function main() {
|
||||
"Library",
|
||||
];
|
||||
|
||||
// Create VC (single, no department or institute)
|
||||
console.log("Seeding VC...");
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
userName: "Validator_VC",
|
||||
email: "vc@example.com",
|
||||
password: commonPassword,
|
||||
console.log("Seeding started...");
|
||||
|
||||
// VC is usually global
|
||||
// We use 'upsert' here. It means "Update if exists, Insert if not".
|
||||
// This prevents errors if we run the seed script multiple times.
|
||||
await prisma.user.upsert({
|
||||
where: { email: "vc@somaiya.edu" },
|
||||
update: {},
|
||||
create: {
|
||||
userName: "Vice Chancellor",
|
||||
email: "vc@somaiya.edu",
|
||||
password: hashedPassword,
|
||||
institute: "KJSCE",
|
||||
department: "Management",
|
||||
designation: "VC",
|
||||
},
|
||||
});
|
||||
|
||||
for (const institute of institutes) {
|
||||
// Create HOI for each institute
|
||||
// 1. Create HOI for this institute
|
||||
console.log(`Seeding HOI for ${institute}...`);
|
||||
const hoiEmail = `hoi.${institute.toLowerCase()}@example.com`;
|
||||
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
const hoiEmail = `hoi@${institute.toLowerCase()}.edu`;
|
||||
await prisma.user.upsert({
|
||||
where: { email: hoiEmail },
|
||||
update: {},
|
||||
create: {
|
||||
userName: `HOI_${institute}`,
|
||||
email: hoiEmail,
|
||||
password: commonPassword,
|
||||
password: hashedPassword,
|
||||
institute,
|
||||
department: "Administration",
|
||||
designation: "HOI",
|
||||
},
|
||||
});
|
||||
|
||||
// Create Accounts for each institute
|
||||
// 2. Create Accounts for this institute
|
||||
console.log(`Seeding Accounts for ${institute}...`);
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
const accountsEmail = `accounts.${institute.toLowerCase()}@example.com`;
|
||||
await prisma.user.upsert({
|
||||
where: { email: accountsEmail },
|
||||
update: {},
|
||||
create: {
|
||||
userName: `Validator_Accounts_${institute}`,
|
||||
email: `accounts.${institute.toLowerCase()}@example.com`,
|
||||
password: commonPassword,
|
||||
email: accountsEmail,
|
||||
password: hashedPassword,
|
||||
institute,
|
||||
designation: "ACCOUNTS",
|
||||
},
|
||||
});
|
||||
|
||||
for (const department of departments) {
|
||||
// Create HOD for each department of each institute
|
||||
// 3. Create HOD for each department of each institute
|
||||
console.log(`Seeding HOD for ${department} in ${institute}...`);
|
||||
const hodEmail = `hod.${department.toLowerCase()}.${institute.toLowerCase()}@example.com`;
|
||||
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
await prisma.user.upsert({
|
||||
where: { email: hodEmail },
|
||||
update: {},
|
||||
create: {
|
||||
userName: `HOD_${department}_${institute}`,
|
||||
email: hodEmail,
|
||||
password: commonPassword,
|
||||
password: hashedPassword,
|
||||
institute,
|
||||
department,
|
||||
designation: "HOD",
|
||||
},
|
||||
});
|
||||
|
||||
// Create Faculty for each department of each institute
|
||||
// 4. Create Faculty for each department of each institute
|
||||
console.log(`Seeding Faculty for ${department} in ${institute}...`);
|
||||
const facultyEmail = `faculty.${department.toLowerCase()}.${institute.toLowerCase()}@example.com`;
|
||||
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
await prisma.user.upsert({
|
||||
where: { email: facultyEmail },
|
||||
update: {},
|
||||
create: {
|
||||
userName: `Faculty_${department}_${institute}`,
|
||||
email: facultyEmail,
|
||||
password: commonPassword,
|
||||
password: hashedPassword,
|
||||
institute,
|
||||
department,
|
||||
designation: "FACULTY",
|
||||
},
|
||||
});
|
||||
// Create Student for each department of each institute
|
||||
|
||||
// 5. Create Student for each department of each institute
|
||||
console.log(`Seeding Student for ${department} in ${institute}...`);
|
||||
const studentEmail = `student.${department.toLowerCase()}.${institute.toLowerCase()}@example.com`;
|
||||
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
await prisma.user.upsert({
|
||||
where: { email: studentEmail },
|
||||
update: {},
|
||||
create: {
|
||||
userName: `Student_${department}_${institute}`,
|
||||
email: studentEmail,
|
||||
password: commonPassword,
|
||||
password: hashedPassword,
|
||||
institute,
|
||||
department,
|
||||
designation: "STUDENT",
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("Seeding completed!");
|
||||
}
|
||||
|
||||
console.log("Seeding completed successfully.");
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}).finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
main()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -55,11 +55,16 @@ const createApplication = async (req, res) => {
|
||||
});
|
||||
|
||||
if (!intimationApplication) {
|
||||
return res.status(404).send({ message: "Intimation Application not found" });
|
||||
return res
|
||||
.status(404)
|
||||
.send({ message: "Intimation Application not found" });
|
||||
}
|
||||
|
||||
if ( intimationApplication["formName"] !== "Travel Intimation Form") {
|
||||
return res.status(400).send({ message: "Intimation Application ID is not of a Travel Intimation Form" });
|
||||
if (intimationApplication["formName"] !== "Travel Intimation Form") {
|
||||
return res.status(400).send({
|
||||
message:
|
||||
"Intimation Application ID is not of a Travel Intimation Form",
|
||||
});
|
||||
}
|
||||
|
||||
const validationFields = [
|
||||
@@ -111,7 +116,9 @@ const createApplication = async (req, res) => {
|
||||
},
|
||||
});
|
||||
if (!primarySupervisor) {
|
||||
return res.status(404).send({ message: "Faculty not found (Incorrect Primary Supervisor Email)" });
|
||||
return res.status(404).send({
|
||||
message: "Faculty not found (Incorrect Primary Supervisor Email)",
|
||||
});
|
||||
}
|
||||
anotherSupervisor = await prisma.user.findUnique({
|
||||
where: {
|
||||
@@ -161,7 +168,10 @@ const createApplication = async (req, res) => {
|
||||
if (applicant.designation === "STUDENT") {
|
||||
// Validate that primary supervisor email is provided for student applications
|
||||
if (!formData.primarySupervisorEmail) {
|
||||
return res.status(400).send({ message: "Primary supervisor email is required for student applications" });
|
||||
return res.status(400).send({
|
||||
message:
|
||||
"Primary supervisor email is required for student applications",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +225,7 @@ const createApplication = async (req, res) => {
|
||||
department,
|
||||
institute,
|
||||
totalExpense,
|
||||
formData,
|
||||
formData: JSON.stringify(formData),
|
||||
proofOfTravel: proofOfTravelBuffer,
|
||||
proofOfAccommodation: proofOfAccommodationBuffer,
|
||||
proofOfAttendance: proofOfAttendanceBuffer,
|
||||
@@ -312,7 +322,7 @@ const updateApplication = async (req, res) => {
|
||||
return res.status(404).send({ message: "Application not found" });
|
||||
}
|
||||
|
||||
const originalFormData = originalApplication.formData;
|
||||
const originalFormData = JSON.parse(originalApplication.formData);
|
||||
|
||||
// Verify that disabled fields haven't been changed
|
||||
// Only expenses can be edited, everything else should remain the same regardless of resubmission status
|
||||
@@ -400,7 +410,7 @@ const updateApplication = async (req, res) => {
|
||||
|
||||
const updatedData = {
|
||||
totalExpense,
|
||||
formData,
|
||||
formData: JSON.stringify(formData),
|
||||
proofOfTravel: proofOfTravelBuffer,
|
||||
proofOfAccommodation: proofOfAccommodationBuffer,
|
||||
proofOfAttendance: proofOfAttendanceBuffer,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import prisma from "../config/prismaConfig.js";
|
||||
import generateToken from "../services/generateToken.js";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
const applicantLogin = async (req, res) => {
|
||||
try {
|
||||
@@ -8,7 +9,7 @@ const applicantLogin = async (req, res) => {
|
||||
// Check if the applicant profile exists
|
||||
const validProfile = await prisma.user.findUnique({
|
||||
where: {
|
||||
email
|
||||
email,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -19,14 +20,51 @@ const applicantLogin = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Check if the password is correct
|
||||
if (validProfile.password !== password) {
|
||||
let isPasswordCorrect = false;
|
||||
let needsMigration = false;
|
||||
|
||||
// Step 1: Try checking if it's a hashed password
|
||||
try {
|
||||
isPasswordCorrect = await bcrypt.compare(password, validProfile.password);
|
||||
} catch (err) {
|
||||
isPasswordCorrect = false;
|
||||
}
|
||||
|
||||
// Step 2: If the bcrypt check failed, maybe they have an old plain-text password?
|
||||
// This is a "Fallback" mechanism to prevent old users from getting locked out after we added hashing.
|
||||
if (!isPasswordCorrect) {
|
||||
if (validProfile.password === password) {
|
||||
isPasswordCorrect = true;
|
||||
needsMigration = true; // Flag them for migration to secure hash
|
||||
}
|
||||
}
|
||||
|
||||
if (!isPasswordCorrect) {
|
||||
return res.status(404).json({
|
||||
message: "Wrong Password",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
// Step 3: If they were using a plain-text password, let's secure it now!
|
||||
// We automatically update their password to a hash so next time they log in safely.
|
||||
if (needsMigration) {
|
||||
try {
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
const hashedPassword = await bcrypt.hash(password, salt);
|
||||
await prisma.user.update({
|
||||
where: { profileId: validProfile.profileId },
|
||||
data: { password: hashedPassword },
|
||||
});
|
||||
console.log(
|
||||
`[Security] Automatically migrated password for user ${email}`
|
||||
);
|
||||
} catch (migrationError) {
|
||||
console.log("Could not update password", migrationError);
|
||||
// It's checked, so we just log the error and continue
|
||||
}
|
||||
}
|
||||
|
||||
// Create token object
|
||||
const tokenObject = {
|
||||
id: validProfile.profileId,
|
||||
@@ -41,7 +79,11 @@ const applicantLogin = async (req, res) => {
|
||||
|
||||
// Set the token as a cookie
|
||||
return res
|
||||
.cookie("access_token", token, { sameSite: 'None', secure: true, httpOnly: true })
|
||||
.cookie("access_token", token, {
|
||||
sameSite: "Lax",
|
||||
secure: false,
|
||||
httpOnly: true,
|
||||
})
|
||||
.status(200)
|
||||
.json({
|
||||
message: "Login Successful",
|
||||
@@ -62,7 +104,7 @@ const validatorLogin = async (req, res) => {
|
||||
// Check if the validator profile exists
|
||||
let validProfile = await prisma.user.findUnique({
|
||||
where: {
|
||||
email
|
||||
email,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -73,14 +115,48 @@ const validatorLogin = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Check if the password is correct
|
||||
if (validProfile.password !== password) {
|
||||
let isPasswordCorrect = false;
|
||||
let needsMigration = false;
|
||||
|
||||
// Step 1: Try checking if it's a hashed password
|
||||
try {
|
||||
isPasswordCorrect = await bcrypt.compare(password, validProfile.password);
|
||||
} catch (err) {
|
||||
isPasswordCorrect = false;
|
||||
}
|
||||
|
||||
// Step 2: If the bcrypt check failed, maybe they have an old plain-text password?
|
||||
// We check this so old users don't get locked out.
|
||||
if (!isPasswordCorrect) {
|
||||
if (validProfile.password === password) {
|
||||
isPasswordCorrect = true;
|
||||
needsMigration = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isPasswordCorrect) {
|
||||
return res.status(404).json({
|
||||
message: "Wrong Password",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
// Step 3: If they had a plain-text password, let's secure it now!
|
||||
// We update it to a hash so next time it is safe.
|
||||
if (needsMigration) {
|
||||
try {
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
const hashedPassword = await bcrypt.hash(password, salt);
|
||||
await prisma.user.update({
|
||||
where: { profileId: validProfile.profileId },
|
||||
data: { password: hashedPassword },
|
||||
});
|
||||
console.log(`Updated password for validator ${email}`);
|
||||
} catch (migrationError) {
|
||||
console.log("Could not update password", migrationError);
|
||||
}
|
||||
}
|
||||
|
||||
// Create token object
|
||||
const tokenObject = {
|
||||
id: validProfile.profileId,
|
||||
@@ -95,7 +171,11 @@ const validatorLogin = async (req, res) => {
|
||||
|
||||
// Set the token as a cookie
|
||||
return res
|
||||
.cookie("access_token", token, { sameSite: 'None', secure: true, httpOnly: true })
|
||||
.cookie("access_token", token, {
|
||||
sameSite: "Lax",
|
||||
secure: false,
|
||||
httpOnly: true,
|
||||
})
|
||||
.status(200)
|
||||
.json({
|
||||
message: "Login Successful",
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
applicantDesignations,
|
||||
validatorDesignations,
|
||||
} from "../config/designations.js";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
const dataRoot = async (req, res) => {
|
||||
try {
|
||||
@@ -98,7 +99,12 @@ const getApplicationsByStatus = async (req, res) => {
|
||||
}),
|
||||
...(status === "ACCEPTED" && {
|
||||
AND: [
|
||||
{ OR: [{ facultyValidation: "ACCEPTED" }, { facultyValidation: null }] },
|
||||
{
|
||||
OR: [
|
||||
{ facultyValidation: "ACCEPTED" },
|
||||
{ facultyValidation: null },
|
||||
],
|
||||
},
|
||||
{ OR: [{ hodValidation: "ACCEPTED" }, { hodValidation: null }] },
|
||||
{ OR: [{ hoiValidation: "ACCEPTED" }, { hoiValidation: null }] },
|
||||
{ OR: [{ vcValidation: "ACCEPTED" }, { vcValidation: null }] },
|
||||
@@ -185,15 +191,18 @@ const getApplicationsByStatus = async (req, res) => {
|
||||
}
|
||||
|
||||
// Format response with selected fields
|
||||
const responseApplications = applications.map((application) => ({
|
||||
applicationId: application.applicationId,
|
||||
applicantName: application.applicantName,
|
||||
formData: {
|
||||
eventName: application.formData.eventName,
|
||||
applicantDepartment: application.formData.applicantDepartment,
|
||||
},
|
||||
createdAt: application.createdAt,
|
||||
}));
|
||||
const responseApplications = applications.map((application) => {
|
||||
const parsedFormData = JSON.parse(application.formData);
|
||||
return {
|
||||
applicationId: application.applicationId,
|
||||
applicantName: application.applicantName,
|
||||
formData: {
|
||||
eventName: parsedFormData.eventName,
|
||||
applicantDepartment: parsedFormData.applicantDepartment,
|
||||
},
|
||||
createdAt: application.createdAt,
|
||||
};
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
message: `${status} Applications Fetched Successfully`,
|
||||
@@ -308,9 +317,14 @@ const getApplicationData = async (req, res) => {
|
||||
}
|
||||
|
||||
// Respond with the full application data and current status
|
||||
const parsedApplicationFull = {
|
||||
...applicationFull,
|
||||
formData: JSON.parse(applicationFull.formData),
|
||||
};
|
||||
|
||||
return res.status(200).json({
|
||||
message: "Application data retrieved successfully",
|
||||
data: { ...applicationFull, currentStatus },
|
||||
data: { ...parsedApplicationFull, currentStatus },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error retrieving application data:", error);
|
||||
@@ -436,4 +450,62 @@ const getFile = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
export { getApplicationData, getFile, dataRoot, getApplicationsByStatus };
|
||||
export {
|
||||
getApplicationData,
|
||||
getFile,
|
||||
dataRoot,
|
||||
getApplicationsByStatus,
|
||||
changePassword,
|
||||
};
|
||||
|
||||
const changePassword = async (req, res) => {
|
||||
try {
|
||||
const user = req.user;
|
||||
const { oldPassword, newPassword } = req.body;
|
||||
|
||||
if (!user || !user.id) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
|
||||
// Get the current user from DB to check password
|
||||
const dbUser = await prisma.user.findUnique({
|
||||
where: { profileId: user.id },
|
||||
});
|
||||
|
||||
if (!dbUser) {
|
||||
return res.status(404).json({ message: "User not found" });
|
||||
}
|
||||
|
||||
let isPasswordCorrect = false;
|
||||
|
||||
// 1. Try bcrypt
|
||||
try {
|
||||
isPasswordCorrect = await bcrypt.compare(oldPassword, dbUser.password);
|
||||
} catch (err) {
|
||||
isPasswordCorrect = false;
|
||||
}
|
||||
|
||||
// 2. Try plaintext (fallback)
|
||||
if (!isPasswordCorrect && dbUser.password === oldPassword) {
|
||||
isPasswordCorrect = true;
|
||||
}
|
||||
|
||||
if (!isPasswordCorrect) {
|
||||
return res.status(400).json({ message: "Incorrect old password" });
|
||||
}
|
||||
|
||||
// Hash the new password
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
const hashedPassword = await bcrypt.hash(newPassword, salt);
|
||||
|
||||
await prisma.user.update({
|
||||
where: { profileId: user.id },
|
||||
data: { password: hashedPassword },
|
||||
});
|
||||
|
||||
return res.status(200).json({ message: "Password updated successfully" });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return res.status(500).json({ message: "Internal Server Error" });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import express from 'express';
|
||||
import { applicantLogin, logout, validatorLogin } from '../controllers/authControllers.js';
|
||||
import express from "express";
|
||||
import {
|
||||
applicantLogin,
|
||||
logout,
|
||||
validatorLogin,
|
||||
} from "../controllers/authControllers.js";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -8,4 +12,8 @@ router.post('/validator-login', validatorLogin);
|
||||
|
||||
router.get('/logout', logout)
|
||||
|
||||
export default router;
|
||||
router.get("/", (req, res) => {
|
||||
res.send("Travel Policy Backend is Running!");
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import express from 'express';
|
||||
import { dataRoot, getApplicationData, getApplicationsByStatus, getFile } from '../controllers/generalControllers.js';
|
||||
import express from "express";
|
||||
import {
|
||||
dataRoot,
|
||||
getApplicationData,
|
||||
getApplicationsByStatus,
|
||||
getFile,
|
||||
changePassword,
|
||||
} from "../controllers/generalControllers.js";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/dataRoot", dataRoot );
|
||||
|
||||
router.get('/getApplications/:status', getApplicationsByStatus);
|
||||
router.post("/changePassword", changePassword);
|
||||
|
||||
router.get("/getApplications/:status", getApplicationsByStatus);
|
||||
|
||||
router.get("/getApplicationData/:applicationId", getApplicationData);
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import nodeMailer from 'nodemailer';
|
||||
|
||||
export default async function sendMail({ emailId, link, type, status, designation }) {
|
||||
if (!process.env.TravelPolicyEmail || !process.env.TravelPolicyEmailPass) {
|
||||
return;
|
||||
}
|
||||
export default async function sendMail({
|
||||
emailId,
|
||||
link,
|
||||
type,
|
||||
status,
|
||||
designation,
|
||||
}) {
|
||||
// Check if we have email password in env
|
||||
if (!process.env.TravelPolicyEmail || !process.env.TravelPolicyEmailPass) {
|
||||
console.log("No email password found in .env, so I can't send emails.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("parametrs", emailId, link, type, status, designation);
|
||||
|
||||
@@ -48,20 +56,20 @@ export default async function sendMail({ emailId, link, type, status, designatio
|
||||
<a href=${link}>View Application</a>
|
||||
<p>Thank you.</p>
|
||||
`,
|
||||
};
|
||||
break;
|
||||
case 'accounts':
|
||||
mailOptions = {
|
||||
from: process.env.TravelPolicyEmail,
|
||||
to: emailId,
|
||||
subject: 'Transfer money to the applicant',
|
||||
html: `
|
||||
<p>Please transfer the travel policy amount to the applicant's account. Click on the link below to view the application:</p>`
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid email type');
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "accounts":
|
||||
mailOptions = {
|
||||
from: process.env.TravelPolicyEmail,
|
||||
to: emailId,
|
||||
subject: "Transfer money to the applicant",
|
||||
html: `
|
||||
<p>Please transfer the travel policy amount to the applicant's account. Click on the link below to view the application:</p>`,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
throw new Error("Invalid email type");
|
||||
}
|
||||
|
||||
try {
|
||||
await transporter.sendMail(mailOptions);
|
||||
|
||||
Reference in New Issue
Block a user