=> {
+ const { email, password } = req.body;
+ const user = await findUserByEmail(email);
+
+ if (!user || !(await bcrypt.compare(password, user.password))) {
+ res.status(400).json({ message: "Invalid email or password" });
+ return;
+ }
+
+ const tokens = generateTokens(user);
+
+ res.json(tokens);
+};
+
+export const logout = async (req: Request, res: Response) => {
+ const authHeader = req.headers["authorization"];
+ const token = authHeader && authHeader.split(" ")[1];
+
+ if (token == null)
+ return res.status(401).json({ message: "No token provided" });
+
+ await addToBlacklist(token); // Add token to blacklist in Prisma
+ res.status(200).json({ message: "Logged out successfully" });
+};
+
+export const forgotPassword = async (req: Request, res: Response) => {
+ const { email } = req.body;
+
+ const user = await findUserByEmail(email);
+
+ if (!user) return res.status(404).json({ message: "User not found" });
+
+ // Generate a password reset token
+ const token = randomBytes(32).toString("hex");
+
+ await resetPasswordLink(token, user);
+ // Send the reset email
+ await sendResetPasswordEmail(email, token);
+
+ res.status(200).json({ message: "Password reset link sent" });
+};
+
+export const resetPassword = async (req: Request, res: Response) => {
+ const { token, newPassword } = req.body;
+
+ const result = await resetPasswordByToken(token, newPassword);
+
+ res.status(200).json({ ...result });
+};
diff --git a/src/ controllers/homeController.ts b/src/ controllers/homeController.ts
new file mode 100644
index 0000000..c6f5f7f
--- /dev/null
+++ b/src/ controllers/homeController.ts
@@ -0,0 +1,9 @@
+import { Request, Response } from "express";
+
+export const home = (req: Request, res: Response) => {
+ res.status(200).json({
+ message: "Welcome to the API",
+ status: "success",
+ data: null,
+ });
+};
diff --git a/src/ controllers/profileController.ts b/src/ controllers/profileController.ts
new file mode 100644
index 0000000..b6fcd27
--- /dev/null
+++ b/src/ controllers/profileController.ts
@@ -0,0 +1,53 @@
+// src/controllers/profileController.ts
+import { Request, Response } from "express";
+import { PrismaClient } from "@prisma/client";
+
+const prisma = new PrismaClient();
+
+// Get User Profile
+export const getUserProfile = async (req: Request, res: Response) => {
+ const userId = req.user?.id;
+
+ try {
+ const user = await prisma.user.findUnique({
+ where: { id: userId },
+ select: {
+ id: true,
+ email: true,
+ username: true,
+ role: true,
+ createdAt: true,
+ updatedAt: true,
+ },
+ });
+
+ if (!user) {
+ return res.status(404).json({ message: "User not found" });
+ }
+
+ res.status(200).json(user);
+ } catch (error) {
+ res.status(500).json({ message: "Error fetching profile data", error });
+ }
+};
+
+// Update User Profile
+export const updateUserProfile = async (req: Request, res: Response) => {
+ const userId = req.user?.id;
+ const { username, email } = req.body;
+
+ try {
+ const user = await prisma.user.update({
+ where: { id: userId },
+ data: {
+ username,
+ email,
+ updatedAt: new Date(),
+ },
+ });
+
+ res.status(200).json({ message: "Profile updated successfully", user });
+ } catch (error) {
+ res.status(500).json({ message: "Error updating profile", error });
+ }
+};
diff --git a/src/access.log b/src/access.log
new file mode 100644
index 0000000..5c5b85c
--- /dev/null
+++ b/src/access.log
@@ -0,0 +1,22 @@
+::1 - - [15/Aug/2024:20:53:07 +0000] "POST /login HTTP/1.1" 404 145 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:20:54:04 +0000] "POST /login HTTP/1.1" 404 145 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:20:54:18 +0000] "POST /login HTTP/1.1" 404 145 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:20:54:32 +0000] "POST /auth/login HTTP/1.1" 400 39 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:20:55:03 +0000] "POST /auth/register HTTP/1.1" 200 197 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:20:58:40 +0000] "POST /auth/register HTTP/1.1" 200 406 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:20:58:45 +0000] "POST /auth/register HTTP/1.1" 400 34 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:20:58:50 +0000] "POST /auth/login HTTP/1.1" 200 406 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:20:58:51 +0000] "POST /auth/login HTTP/1.1" 200 406 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:20:58:51 +0000] "POST /auth/login HTTP/1.1" 200 406 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:20:58:52 +0000] "POST /auth/login HTTP/1.1" 200 406 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:20:58:53 +0000] "POST /auth/login HTTP/1.1" 200 406 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:20:58:53 +0000] "POST /auth/login HTTP/1.1" 200 406 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:20:59:00 +0000] "POST /auth/logout HTTP/1.1" 200 37 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:21:22:59 +0000] "POST /auth/login HTTP/1.1" 400 39 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:21:24:59 +0000] "POST /auth/register HTTP/1.1" 200 406 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:21:25:28 +0000] "GET /a HTTP/1.1" 401 31 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:21:25:56 +0000] "GET /a HTTP/1.1" 401 26 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:21:25:56 +0000] "GET /a HTTP/1.1" 401 26 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:21:26:09 +0000] "GET /a HTTP/1.1" 200 13 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:21:26:23 +0000] "GET /a HTTP/1.1" 403 26 "-" "PostmanRuntime/7.37.3"
+::1 - - [15/Aug/2024:21:26:28 +0000] "GET /a HTTP/1.1" 200 13 "-" "PostmanRuntime/7.37.3"
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..cb04a9e
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,48 @@
+import express from "express";
+import bodyParser from "body-parser";
+import auth from "./routes/auth";
+import admin from "./routes/admin";
+import web from "./routes/web";
+import fs from "fs";
+import path from "path";
+import morgan from "morgan";
+import dotenv from "dotenv";
+import helmet from "helmet";
+import cors from "cors"; // Import CORS
+
+dotenv.config();
+
+const app = express();
+
+const port = process.env.PORT || 4000;
+
+// Implemented Security Policy
+app.use(helmet());
+app.use(cors());
+
+// Middleware for logging requests
+// Create a write stream (in append mode) for logging to a file
+const logStream = fs.createWriteStream(path.join(__dirname, "access.log"), {
+ flags: "a",
+});
+
+// Setup morgan to log requests to the console and the file
+app.use(morgan("combined", { stream: logStream }));
+app.use(morgan("dev")); // Logs to the console in 'dev' format
+
+// Middleware
+app.use(bodyParser.json());
+
+// default routes
+app.use("/", web);
+
+// authentication routes
+app.use("/auth", auth);
+
+// admin middleware routes
+app.use("/admin", admin);
+
+// Start the server
+app.listen(port, () => {
+ console.log(`Server is running on http://localhost:${port}`);
+});
diff --git a/src/middleware/isAdmin.ts b/src/middleware/isAdmin.ts
new file mode 100644
index 0000000..41f0e91
--- /dev/null
+++ b/src/middleware/isAdmin.ts
@@ -0,0 +1,12 @@
+import { Request, Response, NextFunction } from 'express';
+import { Role } from '@prisma/client';
+
+export const isAdmin = (req: Request, res: Response, next: NextFunction) => {
+ const user = req.user;
+
+ if (user?.role === Role.Admin) {
+ next();
+ } else {
+ res.status(403).json({ message: 'Access denied. Admins only.' });
+ }
+};
diff --git a/src/middleware/isAuthenticated.ts b/src/middleware/isAuthenticated.ts
new file mode 100644
index 0000000..efc82f2
--- /dev/null
+++ b/src/middleware/isAuthenticated.ts
@@ -0,0 +1,31 @@
+import { Request, Response, NextFunction } from "express";
+import jwt from "jsonwebtoken";
+import { isTokenBlacklisted } from "../utils/tokenBlacklist";
+
+const jwtSecret = process.env.JWT_SECRET || "your-secret-key";
+
+interface JwtPayload {
+ id: number;
+ email: string;
+}
+
+export const isAuthenticated = async (
+ req: Request,
+ res: Response,
+ next: NextFunction
+) => {
+ const authHeader = req.headers["authorization"];
+ const token = authHeader && authHeader.split(" ")[1];
+
+ if (token == null) return res.status(401).json({ message: "Unauthorized" });
+ if (await isTokenBlacklisted(token))
+ return res.status(403).json({ message: "Token has been revoked" });
+
+ try {
+ const decoded = jwt.verify(token, jwtSecret) as JwtPayload;
+ req.user = decoded; // Attach user information to the request object
+ next();
+ } catch {
+ res.status(403).json({ message: "Unauthorized" });
+ }
+};
diff --git a/src/middleware/isManager.ts b/src/middleware/isManager.ts
new file mode 100644
index 0000000..7ce3402
--- /dev/null
+++ b/src/middleware/isManager.ts
@@ -0,0 +1,12 @@
+import { Request, Response, NextFunction } from "express";
+import { Role } from "@prisma/client";
+
+export const isManager = (req: Request, res: Response, next: NextFunction) => {
+ const user = req.user;
+
+ if (user?.role === Role.Manager || user?.role === Role.Admin) {
+ next();
+ } else {
+ res.status(403).json({ message: "Access denied. Managers only." });
+ }
+};
diff --git a/src/models/User.ts b/src/models/User.ts
new file mode 100644
index 0000000..6c720b6
--- /dev/null
+++ b/src/models/User.ts
@@ -0,0 +1,73 @@
+// src/models/userModel.ts
+import { PrismaClient, User } from "@prisma/client";
+import bcrypt from "bcryptjs";
+
+const prisma = new PrismaClient();
+
+export const addUser = async (
+ username: string,
+ email: string,
+ password: string
+) => {
+ const hashedPassword = await bcrypt.hash(password, 10);
+ return prisma.user.create({
+ data: {
+ username,
+ email,
+ password: hashedPassword,
+ },
+ });
+};
+
+export const findUserByEmail = async (email: string) => {
+ return prisma.user.findUnique({
+ where: { email },
+ });
+};
+
+export const findUserById = async (id: number) => {
+ return prisma.user.findUnique({
+ where: { id },
+ });
+};
+
+export const resetPasswordLink = async (token: string, user: User) => {
+ const expiresAt = new Date(Date.now() + 3600000); // 1 hour from now
+
+ // Store the token in the database
+ await prisma.passwordResetToken.create({
+ data: {
+ token,
+ userId: user.id,
+ expiresAt,
+ },
+ });
+};
+
+export const resetPasswordByToken = async (
+ token: string,
+ newPassword: string
+) => {
+ // Verify the token
+ const resetToken = await prisma.passwordResetToken.findUnique({
+ where: { token },
+ });
+
+ if (!resetToken || resetToken.expiresAt < new Date()) {
+ return { message: "Invalid or expired token", success: false };
+ }
+
+ // Update the user's password
+ const hashedPassword = await bcrypt.hash(newPassword, 10);
+
+ await prisma.user.update({
+ where: { id: resetToken.userId },
+ data: { password: hashedPassword },
+ });
+
+ // Delete the used token
+ await prisma.passwordResetToken.delete({
+ where: { token },
+ });
+ return { message: "successfully reset", success: true };
+};
diff --git a/src/routes/admin.ts b/src/routes/admin.ts
new file mode 100644
index 0000000..7b40263
--- /dev/null
+++ b/src/routes/admin.ts
@@ -0,0 +1,24 @@
+import { Router } from "express";
+import {
+ setUserRole,
+ getAllUsersWithRoles,
+ getUserRole,
+ deleteUserRole,
+} from "../ controllers/adminController";
+import { isAdmin } from "../middleware/isAdmin";
+
+const router = Router();
+
+// Set or Update User Role
+router.post("/role", isAdmin, setUserRole);
+
+// Get All Users with Roles
+router.get("/roles", isAdmin, getAllUsersWithRoles);
+
+// Get Single User Role
+router.get("/role/:userId", isAdmin, getUserRole);
+
+// Delete User Role (reset to default role)
+router.delete("/role/:userId", isAdmin, deleteUserRole);
+
+export default router;
diff --git a/src/routes/auth.ts b/src/routes/auth.ts
new file mode 100644
index 0000000..22cc7a4
--- /dev/null
+++ b/src/routes/auth.ts
@@ -0,0 +1,18 @@
+import { Router } from "express";
+import {
+ register,
+ login,
+ forgotPassword,
+ logout,
+ resetPassword,
+} from "../ controllers/authController";
+
+const router = Router();
+
+router.post("/register", register);
+router.post("/login", login);
+router.post("/logout", logout); // Route to logout and invalidate a refresh token
+router.post("/forgot-password", forgotPassword);
+router.post("/reset-password", resetPassword);
+
+export default router;
diff --git a/src/routes/web.ts b/src/routes/web.ts
new file mode 100644
index 0000000..53271b7
--- /dev/null
+++ b/src/routes/web.ts
@@ -0,0 +1,17 @@
+// src/routes/profileRoutes.ts
+import { Router } from "express";
+import {
+ getUserProfile,
+ updateUserProfile,
+} from "../ controllers/profileController";
+import { isAuthenticated } from "../middleware/isAuthenticated";
+import { home } from "../ controllers/homeController";
+
+const router = Router();
+
+router.get("/profile", isAuthenticated, getUserProfile);
+
+router.put("/profile", isAuthenticated, updateUserProfile);
+
+router.get("/", home);
+export default router;
diff --git a/src/types/express.d.ts b/src/types/express.d.ts
new file mode 100644
index 0000000..f958134
--- /dev/null
+++ b/src/types/express.d.ts
@@ -0,0 +1,10 @@
+// src/types/express.d.ts
+import { JwtPayload } from "jsonwebtoken";
+
+declare global {
+ namespace Express {
+ interface Request {
+ user?: JwtPayload; // Extend Request with the user property
+ }
+ }
+}
diff --git a/src/utils/email.ts b/src/utils/email.ts
new file mode 100644
index 0000000..a2143b8
--- /dev/null
+++ b/src/utils/email.ts
@@ -0,0 +1,24 @@
+import nodemailer from "nodemailer";
+
+const transporter = nodemailer.createTransport({
+ service: "gmail", // You can use any email service
+ auth: {
+ user: process.env.EMAIL_USER, // Your email
+ pass: process.env.EMAIL_PASS, // Your email password or app password
+ },
+});
+
+export const sendResetPasswordEmail = async (
+ email: string,
+ resetToken: string
+) => {
+ const resetUrl = `${process.env.FRONTEND_URL}/reset-password?token=${resetToken}`;
+
+ await transporter.sendMail({
+ from: process.env.EMAIL_USER,
+ to: email,
+ subject: "Password Reset Request",
+ text: `To reset your password, please click the following link: ${resetUrl}`,
+ html: `To reset your password, please click the following link: Reset Password
`,
+ });
+};
diff --git a/src/utils/tokenBlacklist.ts b/src/utils/tokenBlacklist.ts
new file mode 100644
index 0000000..9bd6b19
--- /dev/null
+++ b/src/utils/tokenBlacklist.ts
@@ -0,0 +1,37 @@
+// src/utils/tokenBlacklist.ts
+import { PrismaClient } from "@prisma/client";
+import jwt from "jsonwebtoken";
+
+const prisma = new PrismaClient();
+
+export const addToBlacklist = async (token: string) => {
+ const decoded = jwt.decode(token) as jwt.JwtPayload;
+ const expiresAt = decoded.exp ? new Date(decoded.exp * 1000) : new Date();
+
+ await prisma.blacklistedToken.create({
+ data: {
+ token,
+ expiresAt,
+ },
+ });
+};
+
+export const isTokenBlacklisted = async (token: string) => {
+ const blacklistedToken = await prisma.blacklistedToken.findUnique({
+ where: { token },
+ });
+
+ if (blacklistedToken) {
+ // Optionally, check if token is expired
+ const now = new Date();
+ if (blacklistedToken.expiresAt < now) {
+ // Optionally: Remove expired token from blacklist
+ await prisma.blacklistedToken.delete({
+ where: { token },
+ });
+ return false;
+ }
+ return true;
+ }
+ return false;
+};
diff --git a/tsconfig.json b/tsconfig.json
index ac3f414..380fd3f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,26 +1,16 @@
-// Good
{
- "compileOnSave": true,
"compilerOptions": {
- "sourceMap": true,
- "outDir": "./built",
- "allowJs": true,
+ "target": "ES6",
+ "module": "commonjs", // This is the correct module system for Node.js
"strict": true,
- "skipLibCheck": true,
+ "allowJs": false,
"esModuleInterop": true,
- "target": "ES2020",
- "lib": ["esnext"],
- "allowUnreachableCode": false,
- "noImplicitReturns": true,
- "noImplicitAny": true,
- "typeRoots": ["./typings"],
- "outFile": "./built/combined.js"
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "outDir": "./dist", // Specifies the output directory for compiled files
+ "rootDir": "./src" , // Specifies the root directory of input files
+ "typeRoots": ["./src/types", "./node_modules/@types"]
},
- "include": ["./**/*"],
- "exclude": [
- "./plugins/**/*",
- "./typings/**/*",
- "node_modules",
- "./built/**/*" // This is what fixed it!
- ]
-}
+ "include": ["src/**/*.ts"],
+ "exclude": ["node_modules"]
+}
\ No newline at end of file