Skip to content

Commit

Permalink
feat(user) : adds auth + profile (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
Artlfmj authored Oct 1, 2023
2 parents bab9838 + b418ff4 commit fc65e5a
Show file tree
Hide file tree
Showing 11 changed files with 1,614 additions and 41 deletions.
849 changes: 848 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

31 changes: 29 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,35 @@
{
"dependencies": {
"bcrypt": "^5.1.1",
"connect-flash": "^0.1.1",
"cookie-parser": "^1.4.6",
"csurf": "^1.11.0",
"ejs": "^3.1.9",
"express": "^4.18.2",
"express-rate-limit": "^7.0.2",
"express-session": "^1.17.3",
"mongodb": "^6.1.0",
"mongoose": "^7.5.3"
}
"mongoose": "^7.5.3",
"morgan": "^1.10.0",
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"passport-local-mongoose": "^8.0.0"
},
"name": "course-manager",
"description": "<img src=\"https://m3-markdown-badges.vercel.app/stars/3/1/Artlfmj/course-manager\"> <img src=\"https://m3-markdown-badges.vercel.app/issues/3/1/Artlfmj/course-manager\">",
"version": "0.0.1",
"main": "src/app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Artlfmj/course-manager.git"
},
"author": "Artlfmj",
"license": "ISC",
"bugs": {
"url": "https://github.com/Artlfmj/course-manager/issues"
},
"homepage": "https://github.com/Artlfmj/course-manager#readme"
}
243 changes: 205 additions & 38 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -1,57 +1,224 @@
const express = require('express');
const mongoose = require('mongoose');
const fs = require('fs');
const express = require("express");
const mongoose = require("mongoose");
const fs = require("fs");
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const session = require("express-session");
const flash = require("connect-flash");
const morgan = require("morgan");
const bcrypt = require("bcrypt"); // Import bcrypt for password hashing
const rateLimit = require("express-rate-limit");
const csrf = require("csurf");
const cookieParser = require("cookie-parser");

const User = require("./db/User");
const isAuthenticated = require("./middlewares/isAuthenticated");

const app = express();

// Read configuration from config.json
let config;
try {
const configData = fs.readFileSync('config.json');
config = JSON.parse(configData);
} catch (err) {
console.error('Error reading config file:', err);
process.exit(1);
}
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 requests per windowMs
message: "Too many requests from this IP, please try again later.",
});

app.set("view engine", "ejs");
app.set("views", "src/views");
app.use(express.urlencoded({ extended: true }));
app.use(morgan("dev"));

const config = require("../config.json");

// Connect to MongoDB using the configuration
mongoose.connect(config.mongodb_uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
mongoose
.connect(config.mongodb_uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
console.log('Connected to MongoDB');
console.log("Connected to MongoDB");
// Start your application logic here
})
.catch((err) => {
console.error('Error connecting to MongoDB:', err);
console.error("Error connecting to MongoDB:", err);
process.exit(1);
});

// Simulated user data (replace with actual user data from your database)
const users = [
{ username: 'user1', password: 'password1' },
{ username: 'user2', password: 'password2' },
];

// Define your login function here
function login(username, password) {
const user = users.find((u) => u.username === username && u.password === password);
return !!user; // Return true if the user exists, false otherwise
}

// Example usage of the login function
app.get('/login', (req, res) => {
const username = req.query.username;
const password = req.query.password;

if (login(username, password)) {
res.send('Login successful');
passport.use(
new LocalStrategy(async (username, password, done) => {
try {
const user = await User.findOne({ username: username });
if (!user) return done(null, false, { message: "Incorrect username." });
const passwordMatch = await bcrypt.compare(password, user.password);
if (!passwordMatch)
return done(null, false, { message: "Incorrect password." });

return done(null, user);
} catch (err) {
return done(err);
}
})
);

passport.serializeUser((user, done) => {
done(null, user.id);
});

passport.deserializeUser((id, done) => {
User.findById(id)
.then((user) => {
done(null, user);
})
.catch((err) => {
done(err);
});
});

app.use(cookieParser());
app.use(
session({ secret: config.secret_key, resave: false, saveUninitialized: true })
);
app.use(csrf());
app.use(flash());
app.use(passport.initialize());
app.use(passport.session());

app.get("/login", limiter, (req, res) => {
if (req.isAuthenticated()) {
return res.redirect("/");
} else {
res.status(401).send('Login failed');
res.render("login", { messages: req.flash("error") }); // Pass flash messages to the template
}
});

app.post("/login",limiter, (req, res, next) => {
if (!req.body._csrf || req.body._csrf !== req.csrfToken()) {
return res.status(403).send("CSRF token validation failed.");
}
passport.authenticate("local", (err, user, info) => {
if (err) {
return next(err);
}
if (!user) {
req.flash("error", "Incorrect username or password."); // Set flash message
return res.redirect("/login"); // Redirect with flash message
}
req.logIn(user, (err) => {
if (err) {
return next(err);
}
return res.redirect("/");
});
})(req, res, next);
});

app.get("/logout", limiter, (req, res) => {
req.logout((err) => {
if (err) {
console.error("Error during logout:", err);
}
res.redirect("/login");
});
});

app.get("/", isAuthenticated, (req, res) => {
// This route is protected and can only be accessed by authenticated users
res.render("home");
});

app.get("/register", (req, res) => {
if (req.isAuthenticated()) return res.redirect("/");
res.render("register", { messages: req.flash("error") });
});

app.post("/register", limiter, async (req, res) => {
if (!req.body._csrf || req.body._csrf !== req.csrfToken()) {
return res.status(403).send("CSRF token validation failed.");
}
const { username, email, password, confirmPassword, fullName } = req.body;

try {
// Check if the username or email already exists in the database
const existingUser = await User.findOne({
$or: [{ username: username }, { email: email }],
});

if (existingUser) {
req.flash("error", "Username or email already in use.");
return res.redirect("/register");
}

// Check if the password and confirmPassword match
if (password !== confirmPassword) {
req.flash("error", "Passwords do not match.");
return res.redirect("/register");
}

// Hash the password before saving it
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);

// Create a new user document and save it to the database
const newUser = new User({
username: username,
email: email,
password: hashedPassword,
fullName
// Additional user profile fields can be added here
});

await newUser.save();

// Redirect to the login page after successful registration
res.redirect("/login");
} catch (error) {
console.error("Error during registration:", error);
req.flash("error", "Registration failed. Please try again.");
res.redirect("/register");
}
});

app.get('/profile', isAuthenticated, async (req, res) => {
res.render('profile', { user: req.user, messages: req.flash() });
});

app.post('/profile', limiter, isAuthenticated, async (req, res) => {
if (!req.body._csrf || req.body._csrf !== req.csrfToken()) {
return res.status(403).send("CSRF token validation failed.");
}
const { fullName, avatarUrl, bio, location, website } = req.body;

try {
// Find the user by their ID (you need to have the user ID stored in the session)
const userId = req.user._id; // Assuming you have a user object in the session
const user = await User.findById(userId);

if (!user) {
// Handle the case where the user is not found
return res.status(404).send('User not found.');
}

// Update the user's profile fields
user.fullName = fullName;
user.avatarUrl = avatarUrl;
user.bio = bio;
user.location = location;
user.website = website;

// Save the updated user profile
await user.save();

// Redirect to the user's profile page or any other desired page
return res.redirect('/profile');
} catch (error) {
console.error('Error updating profile:', error);
// Handle the error, display an error message, or redirect to an error page
return res.status(500).send('Error updating profile.');
}
});

app.use("/css", express.static("src/css"));

// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
Expand Down
58 changes: 58 additions & 0 deletions src/css/login.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}

.login-container {
background-color: #ffffff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
text-align: center;
}

h1 {
font-size: 24px;
margin-bottom: 20px;
}

.form-group {
margin-bottom: 15px;
}

label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}

input[type="text"],
input[type="password"] {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 3px;
}

button {
background-color: #007bff;
color: #fff;
border: none;
padding: 10px 20px;
border-radius: 3px;
cursor: pointer;
}

button:hover {
background-color: #0056b3;
}

p {
margin-top: 10px;
}
Loading

0 comments on commit fc65e5a

Please sign in to comment.