-
Notifications
You must be signed in to change notification settings - Fork 62
Add server.js and backend config files for authentication #204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| router.get("/logout", (req, res) => { | ||
| req.logout(err => { | ||
| if(err) return res.status(500).json({ message: "Logout failed" }); | ||
| res.json({ message: "Logged out successfully" }); | ||
| }); | ||
| }); | ||
|
|
||
| // Get logged-in user | ||
| router.get("/current", (req, res) => { | ||
| if (req.isAuthenticated()) { | ||
| res.json({ user: { username: req.user.username } }); | ||
| } else { | ||
| res.status(401).json({ user: null }); | ||
| } | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,16 @@ | ||||||||||||||||||
| const mongoose = require('mongoose'); | ||||||||||||||||||
|
|
||||||||||||||||||
| const connectDB = async () => { | ||||||||||||||||||
| try { | ||||||||||||||||||
| await mongoose.connect('mongodb://127.0.0.1:27017/githubTracker', { | ||||||||||||||||||
| useNewUrlParser: true, | ||||||||||||||||||
| useUnifiedTopology: true, | ||||||||||||||||||
| }); | ||||||||||||||||||
| console.log('MongoDB connected'); | ||||||||||||||||||
|
Comment on lines
+5
to
+9
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Use env-configured Mongo URI; drop legacy options Hard-coding the URI hinders env parity and the listed options are no longer needed on modern Mongoose. Prefer an env var and defaults. - await mongoose.connect('mongodb://127.0.0.1:27017/githubTracker', {
- useNewUrlParser: true,
- useUnifiedTopology: true,
- });
- console.log('MongoDB connected');
+ const uri = process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017/githubTracker';
+ await mongoose.connect(uri);
+ console.log('MongoDB connected');📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| } catch (err) { | ||||||||||||||||||
| console.error(err.message); | ||||||||||||||||||
| process.exit(1); | ||||||||||||||||||
| } | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| module.exports = connectDB; | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,37 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import passport from 'passport'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Strategy as LocalStrategy } from 'passport-local'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import bcrypt from 'bcryptjs'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+3
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainConsolidate duplicate Passport configs. Both 🏁 Script executed: #!/bin/bash
# Locate all passport strategy registrations to remove duplicates.
rg -nP -C2 "\bpassport\.use\(\s*new\s+LocalStrategy"Length of output: 0 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Locating passportConfig.js"
rg --files | grep passportConfig.js || echo "No passportConfig.js found"
echo
echo "Locating passport.js"
rg --files | grep 'config/passport.js' || echo "No passport.js found"
echo
echo "Searching for passport.use occurrences"
rg -n "passport\.use" || echo "No passport.use calls found"
echo
echo "Searching for new LocalStrategy"
rg -n "new LocalStrategy" || echo "No new LocalStrategy instantiations found"
echo
echo "Searching for passport-local imports"
rg -n "passport-local" || echo "No passport-local imports found"Length of output: 1143 Consolidate Passport strategy registrations. We have three separate LocalStrategy initializations in config/passport.js, backend/config/passport.js, and backend/config/passportConfig.js. Centralize into a single module and remove the duplicates to prevent multiple registrations. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import User from '../models/User.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| passport.use( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new LocalStrategy(async (username, password, done) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Find user by username | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const user = await User.findOne({ username }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!user) return done(null, false, { message: 'Incorrect username' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Compare passwords | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isMatch = await bcrypt.compare(password, user.password); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isMatch) return done(null, false, { message: 'Incorrect password' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return done(null, user); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+6
to
+17
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Login will fail if clients send email; set Elsewhere (routes/tests) the login uses -passport.use(
- new LocalStrategy(async (username, password, done) => {
+passport.use(
+ new LocalStrategy(
+ { usernameField: 'email', passwordField: 'password', session: true },
+ async (email, password, done) => {
try {
- // Find user by username
- const user = await User.findOne({ username });
- if (!user) return done(null, false, { message: 'Incorrect username' });
+ // Find user by email
+ const user = await User.findOne({ email });
+ if (!user) return done(null, false, { message: 'Email not found' });
- // Compare passwords
- const isMatch = await bcrypt.compare(password, user.password);
- if (!isMatch) return done(null, false, { message: 'Incorrect password' });
+ // Prefer model method if defined
+ const isMatch = typeof user.comparePassword === 'function'
+ ? await user.comparePassword(password)
+ : await bcrypt.compare(password, user.password);
+ if (!isMatch) return done(null, false, { message: 'Incorrect password' });
- return done(null, user);
- } catch (err) {
- return done(err);
- }
- })
+ return done(null, user);
+ } catch (err) {
+ return done(err);
+ }
+ }
+ )
);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return done(err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Serialize user into the session | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| passport.serializeUser((user, done) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done(null, user.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Deserialize user from the session | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| passport.deserializeUser(async (id, done) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const user = await User.findById(id).select('-password'); // Exclude password | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done(null, user); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done(err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,23 +2,25 @@ const passport = require("passport"); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const LocalStrategy = require('passport-local').Strategy; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const User = require("../models/User"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Local strategy for login using email | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| passport.use( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new LocalStrategy( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { usernameField: "email" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { usernameField: "email" }, // use email instead of username | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async (email, password, done) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const user = await User.findOne( {email} ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const user = await User.findOne({ email }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!user) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return done(null, false, { message: 'Email is invalid '}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return done(null, false, { message: 'Email is invalid' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isMatch = await user.comparePassword(password); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isMatch) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return done(null, false, { message: 'Invalid password' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Return user object | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return done(null, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id : user._id.toString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: user._id.toString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| username: user.username, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| email: user.email | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+5
to
26
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid credential enumeration in error messages Different messages for unknown email vs bad password allow user enumeration. Return a single generic message. - if (!user) {
- return done(null, false, { message: 'Email is invalid' });
- }
+ if (!user) {
+ return done(null, false, { message: 'Invalid email or password' });
+ }
@@
- if (!isMatch) {
- return done(null, false, { message: 'Invalid password' });
- }
+ if (!isMatch) {
+ return done(null, false, { message: 'Invalid email or password' });
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -29,7 +31,7 @@ passport.use( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Serialize user (store user info in session) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Serialize user (store user id in session) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| passport.serializeUser((user, done) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done(null, user.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,42 +1,44 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const express = require("express"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const passport = require("passport"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const User = require("../models/User"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const express = require('express'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const router = express.Router(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const passport = require('passport'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const User = require('../models/User'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Signup | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.post('/signup', async (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { username, email, password } = req.body; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const user = new User({ username, email, password }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await user.save(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(201).json({ message: 'User created successfully' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(400).json({ error: err.message }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+7
to
+16
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainAlign responses with tests and standardize JSON shape Tests in spec/auth.routes.spec.cjs assert:
Ensure all responses use Run to sanity-check strings/statuses without executing tests: Also applies to: 19-29, 32-36, 39-41 🏁 Script executed: #!/bin/bash
rg -nC1 -e "User created successfully|User already exists|Login successful|Logged out successfully" backend/routes/auth.js
rg -nC2 -e "status\\(401\\)|status\\(400\\)" backend/routes/auth.js
rg -nC3 -e "expect\\(res.body.message\\)" spec/auth.routes.spec.cjsLength of output: 2224 Align auth routes with spec: status codes & JSON shape
🤖 Prompt for AI Agents💡 Verification agent 🧩 Analysis chainSignup: handle duplicate emails, validate inputs, and standardize error shape
Apply: router.post('/signup', async (req, res) => {
- const { username, email, password } = req.body;
+ const { username, email, password } = req.body;
+ if (!username || !email || !password) {
+ return res.status(400).json({ message: 'username, email and password are required' });
+ }
try {
const user = new User({ username, email, password });
await user.save();
- res.status(201).json({ message: 'User created successfully' });
+ return res.status(201).json({ message: 'User created successfully' });
} catch (err) {
- res.status(400).json({ error: err.message });
+ if (err && err.code === 11000) {
+ // duplicate key (e.g., unique email)
+ return res.status(400).json({ message: 'User already exists' });
+ }
+ return res.status(400).json({ message: 'Invalid signup data' });
}
});Confirm the User model enforces a unique index on email and hashes passwords (e.g., pre-save with bcrypt). If not, I can provide a patch. 🏁 Script executed: #!/bin/bash
# Locate the User model file in backend/models
user_model_file=$(rg -l "mongoose\\.model\\(['\"]User['\"]" -g "backend/models/*.js")
echo "User model file: $user_model_file"
# Check if email field has a unique constraint
echo "--- Checking unique constraint on email ---"
rg -n "email\\s*:\\s*\\{[^}]*unique" "$user_model_file" || echo "No unique constraint found on email"
# Check for bcrypt import or usage
echo "--- Checking for bcrypt import ---"
rg -n "require\\(['\"]bcrypt['\"]\\)" "$user_model_file" || echo "No bcrypt import found"
echo "--- Checking for bcrypt usage ---"
rg -n "bcrypt\\." "$user_model_file" || echo "No bcrypt usage found"
# Check for pre-save hook for password hashing
echo "--- Checking for pre\\('save' hook ---"
rg -n "pre\\('save'" "$user_model_file" || echo "No pre-save hook found"Length of output: 1129 🏁 Script executed: #!/bin/bash
file=backend/models/User.js
echo "--- Searching for schema.index usage ---"
rg -n "UserSchema\\.index" "$file" || echo "No schema.index usage"
echo "--- Searching for inline unique on email ---"
rg -n "'email'\\s*:\\s*\\{[^}]*unique" "$file" || echo "No inline unique on email"
echo "--- Searching for ES6 import of bcrypt ---"
rg -n "import .*bcrypt" "$file" || echo "No ES6 import bcrypt"
echo "--- Searching for require('bcrypt') ---"
rg -n "require\\(['\"]bcrypt['\"]\\)" "$file" || echo "No require bcrypt"Length of output: 851 Enforce unique email index in User model
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Signup route | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.post("/signup", async (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { username, email, password } = req.body; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const existingUser = await User.findOne( {email} ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (existingUser) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(400).json( {message: 'User already exists'} ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Login | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.post('/login', (req, res, next) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| passport.authenticate('local', (err, user, info) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (err) return next(err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!user) return res.status(400).json({ message: info.message }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newUser = new User( {username, email, password} ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await newUser.save(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(201).json( {message: 'User created successfully'} ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(500).json({ message: 'Error creating user', error: err.message }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| req.login(user, (err) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (err) return next(err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.json({ message: 'Logged in successfully', user }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })(req, res, next); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+19
to
29
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainLogin: return 401 on bad creds, align success message with tests, sanitize user, and prevent session fixation
Apply: router.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) return next(err);
- if (!user) return res.status(400).json({ message: info.message });
+ if (!user) {
+ return res.status(401).json({ message: (info && info.message) || 'Invalid email or password' });
+ }
- req.login(user, (err) => {
- if (err) return next(err);
- res.json({ message: 'Logged in successfully', user });
- });
+ const finish = () => {
+ req.login(user, (err) => {
+ if (err) return next(err);
+ const { _id, username, email } = user;
+ return res.json({ message: 'Login successful', user: { id: _id, username, email } });
+ });
+ };
+ if (req.session && typeof req.session.regenerate === 'function') {
+ return req.session.regenerate((regenErr) => (regenErr ? next(regenErr) : finish()));
+ }
+ return finish();
})(req, res, next);
});Ensure passport/session middleware is configured before these routes (express-session, passport.initialize(), passport.session()). 🏁 Script executed: #!/bin/bash
# Check tests for expected login messages and status codes
echo "=== Login message references ==="
rg -n "Login successful" || echo 'No occurrences of "Login successful" found'
rg -n "Logged in successfully" || echo 'No occurrences of "Logged in successfully" found'
echo
echo "=== Status code expectations in tests ==="
rg -n "\.expect(400)" || echo 'No .expect(400) in tests'
rg -n "\.expect(401)" || echo 'No .expect(401) in tests'
echo
echo "=== User schema definitions ==="
rg -l "new Schema" -g "*user*.js" || echo 'No user schema files found'
rg -n "new Schema" -g "*user*.js" -C3
echo "Checking user schema fields:"
rg -nE "username|email|password" -g "*user*.js" -C1 || echo 'Unable to locate username/email/password fields in user schema'
echo
echo "=== Passport & session middleware ==="
rg -n "passport.initialize" -C2 || echo 'passport.initialize not configured'
rg -n "express-session" -C2 || echo 'express-session not configured'
rg -n "passport.session" -C2 || echo 'passport.session not configured'
echo
echo "=== Auth routes mounting ==="
rg -n "routes/auth" -C2
rg -n "use.*auth" -g "*app*.js" -C2 || echo 'Auth routes not found in app setup'Length of output: 5862 🏁 Script executed: # Display the beginning of the test suite to verify status code assertions for login failures
sed -n '1,200p' spec/auth.routes.spec.cjsLength of output: 3393 backend/routes/auth.js – Login: use 401 on invalid credentials, match test message, sanitize user, regenerate session
Apply: router.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) return next(err);
- if (!user) return res.status(400).json({ message: info.message });
+ if (!user) {
+ return res.status(401).json({ message: (info && info.message) || 'Invalid email or password' });
+ }
- req.login(user, (err) => {
- if (err) return next(err);
- res.json({ message: 'Logged in successfully', user });
- });
+ const finish = () => {
+ req.login(user, (err) => {
+ if (err) return next(err);
+ const { _id, username, email } = user;
+ return res.json({ message: 'Login successful', user: { id: _id, username, email } });
+ });
+ };
+ if (req.session && typeof req.session.regenerate === 'function') {
+ return req.session.regenerate((regenErr) => (regenErr ? next(regenErr) : finish()));
+ }
+ return finish();
})(req, res, next);
});Session and Passport middleware are already configured before these routes. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Login route | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.post("/login", passport.authenticate('local'), (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(200).json( { message: 'Login successful', user: req.user } ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Logout | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.get('/logout', (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| req.logout(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.json({ message: 'Logged out successfully' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+32
to
36
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Logout: destroy the session and clear cookie; keep GET for now but harden flow
Apply: -router.get('/logout', (req, res) => {
- req.logout(() => {
- res.json({ message: 'Logged out successfully' });
- });
-});
+router.get('/logout', (req, res, next) => {
+ req.logout((err) => {
+ if (err) return next(err);
+ if (req.session) {
+ return req.session.destroy(() => {
+ res.clearCookie('connect.sid');
+ return res.json({ message: 'Logged out successfully' });
+ });
+ }
+ return res.json({ message: 'Logged out successfully' });
+ });
+});Optionally add (outside this hunk) a POST variant mirroring the above to discourage CSRFable state changes via GET. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Logout route | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.get("/logout", (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| req.logout((err) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(500).json({ message: 'Logout failed', error: err.message }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(200).json({ message: 'Logged out successfully' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Protected route (example) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.get('/profile', (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!req.isAuthenticated()) return res.status(401).json({ message: 'Not authenticated' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.json({ user: req.user }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+39
to
+41
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Profile: don’t return the full user object Return a minimal, safe shape to avoid leaking sensitive fields. Apply: router.get('/profile', (req, res) => {
if (!req.isAuthenticated()) return res.status(401).json({ message: 'Not authenticated' });
- res.json({ user: req.user });
+ const { _id, username, email } = req.user;
+ return res.json({ user: { id: _id, username, email } });
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| module.exports = router; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # Logs | ||
| logs | ||
| *.log | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
| pnpm-debug.log* | ||
| lerna-debug.log* | ||
|
|
||
| node_modules | ||
| dist | ||
| dist-ssr | ||
| *.local | ||
|
|
||
| # Editor directories and files | ||
| .vscode/* | ||
| !.vscode/extensions.json | ||
| .idea | ||
| .DS_Store | ||
| *.suo | ||
| *.ntvs* | ||
| *.njsproj | ||
| *.sln | ||
| *.sw? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # React + Vite | ||
|
|
||
| This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. | ||
|
|
||
| Currently, two official plugins are available: | ||
|
|
||
| - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh | ||
| - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh | ||
|
|
||
| ## Expanding the ESLint configuration | ||
|
|
||
| If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import js from '@eslint/js' | ||
| import globals from 'globals' | ||
| import reactHooks from 'eslint-plugin-react-hooks' | ||
| import reactRefresh from 'eslint-plugin-react-refresh' | ||
| import { defineConfig, globalIgnores } from 'eslint/config' | ||
|
|
||
| export default defineConfig([ | ||
| globalIgnores(['dist']), | ||
| { | ||
| files: ['**/*.{js,jsx}'], | ||
| extends: [ | ||
| js.configs.recommended, | ||
| reactHooks.configs['recommended-latest'], | ||
| reactRefresh.configs.vite, | ||
| ], | ||
| languageOptions: { | ||
| ecmaVersion: 2020, | ||
| globals: globals.browser, | ||
| parserOptions: { | ||
| ecmaVersion: 'latest', | ||
| ecmaFeatures: { jsx: true }, | ||
| sourceType: 'module', | ||
| }, | ||
| }, | ||
| rules: { | ||
| 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], | ||
| }, | ||
| }, | ||
| ]) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Vite + React</title> | ||
| </head> | ||
| <body> | ||
| <div id="root"></div> | ||
| <script type="module" src="/src/main.jsx"></script> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| { | ||
| "name": "client", | ||
| "private": true, | ||
| "version": "0.0.0", | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite", | ||
| "build": "vite build", | ||
| "lint": "eslint .", | ||
| "preview": "vite preview" | ||
| }, | ||
| "dependencies": { | ||
| "axios": "^1.11.0", | ||
| "react": "^19.1.1", | ||
| "react-dom": "^19.1.1", | ||
| "react-router-dom": "^7.8.2" | ||
| }, | ||
| "devDependencies": { | ||
| "@eslint/js": "^9.33.0", | ||
| "@types/react": "^19.1.10", | ||
| "@types/react-dom": "^19.1.7", | ||
| "@vitejs/plugin-react": "^5.0.0", | ||
| "eslint": "^9.33.0", | ||
| "eslint-plugin-react-hooks": "^5.2.0", | ||
| "eslint-plugin-react-refresh": "^0.4.20", | ||
| "globals": "^16.3.0", | ||
| "vite": "^7.1.2" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| #root { | ||
| max-width: 1280px; | ||
| margin: 0 auto; | ||
| padding: 2rem; | ||
| text-align: center; | ||
| } | ||
|
|
||
| .logo { | ||
| height: 6em; | ||
| padding: 1.5em; | ||
| will-change: filter; | ||
| transition: filter 300ms; | ||
| } | ||
| .logo:hover { | ||
| filter: drop-shadow(0 0 2em #646cffaa); | ||
| } | ||
| .logo.react:hover { | ||
| filter: drop-shadow(0 0 2em #61dafbaa); | ||
| } | ||
|
|
||
| @keyframes logo-spin { | ||
| from { | ||
| transform: rotate(0deg); | ||
| } | ||
| to { | ||
| transform: rotate(360deg); | ||
| } | ||
| } | ||
|
|
||
| @media (prefers-reduced-motion: no-preference) { | ||
| a:nth-of-type(2) .logo { | ||
| animation: logo-spin infinite 20s linear; | ||
| } | ||
| } | ||
|
|
||
| .card { | ||
| padding: 2em; | ||
| } | ||
|
|
||
| .read-the-docs { | ||
| color: #888; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,35 @@ | ||||||||||||||||||||||||
| import { useState } from 'react' | ||||||||||||||||||||||||
| import reactLogo from './assets/react.svg' | ||||||||||||||||||||||||
| import viteLogo from '/vite.svg' | ||||||||||||||||||||||||
| import './App.css' | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| function App() { | ||||||||||||||||||||||||
| const [count, setCount] = useState(0) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||
| <a href="https://vite.dev" target="_blank"> | ||||||||||||||||||||||||
| <img src={viteLogo} className="logo" alt="Vite logo" /> | ||||||||||||||||||||||||
| </a> | ||||||||||||||||||||||||
| <a href="https://react.dev" target="_blank"> | ||||||||||||||||||||||||
| <img src={reactLogo} className="logo react" alt="React logo" /> | ||||||||||||||||||||||||
|
Comment on lines
+12
to
+16
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add rel="noopener noreferrer" to external links Prevents window.opener attacks. Apply: - <a href="https://vite.dev" target="_blank">
+ <a href="https://vite.dev" target="_blank" rel="noopener noreferrer">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
- <a href="https://react.dev" target="_blank">
+ <a href="https://react.dev" target="_blank" rel="noopener noreferrer">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome (2.1.2)[error] 12-12: Avoid using target="_blank" without rel="noopener" or rel="noreferrer". Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details. (lint/security/noBlankTarget) [error] 15-15: Avoid using target="_blank" without rel="noopener" or rel="noreferrer". Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details. (lint/security/noBlankTarget) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| </a> | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| <h1>Vite + React</h1> | ||||||||||||||||||||||||
| <div className="card"> | ||||||||||||||||||||||||
| <button onClick={() => setCount((count) => count + 1)}> | ||||||||||||||||||||||||
| count is {count} | ||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||
| <p> | ||||||||||||||||||||||||
| Edit <code>src/App.jsx</code> and save to test HMR | ||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| <p className="read-the-docs"> | ||||||||||||||||||||||||
| Click on the Vite and React logos to learn more | ||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| export default App | ||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Router not declared/exported; file won’t work as-is.
Add Router setup and export, and return status codes explicitly.
📝 Committable suggestion
🤖 Prompt for AI Agents