-
Notifications
You must be signed in to change notification settings - Fork 98
feat: MongoDB auth, session history, and full-width UI #231
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
base: main
Are you sure you want to change the base?
Changes from all commits
ef9548d
945708d
691bd0c
db142dc
4336efc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -39,4 +39,48 @@ router.get("/logout", (req, res) => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Get user tracker history | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.get("/tracker-history", async (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!req.user) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(401).json({ message: 'Not authenticated' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const user = await User.findById(req.user.id).select('trackerHistory'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(200).json({ trackerHistory: user?.trackerHistory || [] }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(500).json({ message: 'Error fetching tracker history', error: err.message }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Save tracker search to history | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.post("/tracker-history", async (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!req.user) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(401).json({ message: 'Not authenticated' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { username, searchedAt } = req.body; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const user = await User.findById(req.user.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!user) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(404).json({ message: 'User not found' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Remove duplicate if exists (keep only unique searches) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user.trackerHistory = user.trackerHistory.filter( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| item => item.username.toLowerCase() !== username.toLowerCase() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Add new search at the beginning | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user.trackerHistory.unshift({ username, searchedAt: new Date(searchedAt) }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+60
to
+74
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. Validate The route can throw when Suggested patch const { username, searchedAt } = req.body;
+ if (typeof username !== "string" || !username.trim()) {
+ return res.status(400).json({ message: "username is required" });
+ }
+ const parsedSearchedAt = searchedAt ? new Date(searchedAt) : new Date();
+ if (Number.isNaN(parsedSearchedAt.getTime())) {
+ return res.status(400).json({ message: "searchedAt must be a valid date" });
+ }
try {
const user = await User.findById(req.user.id);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
// Remove duplicate if exists (keep only unique searches)
user.trackerHistory = user.trackerHistory.filter(
- item => item.username.toLowerCase() !== username.toLowerCase()
+ item => item.username?.toLowerCase() !== username.toLowerCase()
);
// Add new search at the beginning
- user.trackerHistory.unshift({ username, searchedAt: new Date(searchedAt) });
+ user.trackerHistory.unshift({ username, searchedAt: parsedSearchedAt });📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Keep only last 10 searches | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user.trackerHistory = user.trackerHistory.slice(0, 10); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user.lastTrackedAt = new Date(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await user.save(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(200).json({ message: 'Tracker history saved', trackerHistory: user.trackerHistory }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(500).json({ message: 'Error saving tracker history', error: err.message }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| module.exports = router; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,14 +12,22 @@ require('./config/passportConfig'); | |
| const app = express(); | ||
|
|
||
| // CORS configuration | ||
| app.use(cors('*')); | ||
| app.use(cors({ | ||
| origin: process.env.FRONTEND_URL || 'http://localhost:5173', | ||
| credentials: true | ||
| })); | ||
|
|
||
| // Middleware | ||
| app.use(bodyParser.json()); | ||
| app.use(session({ | ||
| secret: process.env.SESSION_SECRET, | ||
| resave: false, | ||
| saveUninitialized: false, | ||
| cookie: { | ||
| httpOnly: true, | ||
| secure: false, // set to true in production with HTTPS | ||
| sameSite: 'lax' | ||
| } | ||
|
Comment on lines
+26
to
+30
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. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify current session/proxy configuration in backend
rg -n "app\\.set\\(['\"]trust proxy['\"]|session\\(|secure:|sameSite:" backend/server.js
rg -n "NODE_ENV|FRONTEND_URL|SESSION_SECRET" backend/.env .env 2>/dev/null || trueRepository: GitMetricsLab/github_tracker Length of output: 218 Add environment-aware session cookie security and trust proxy configuration. The session cookie has Set Suggested patch+app.set('trust proxy', 1);
+
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
- secure: false, // set to true in production with HTTPS
- sameSite: 'lax'
+ secure: process.env.NODE_ENV === 'production',
+ sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax'
}
}));🤖 Prompt for AI Agents |
||
| })); | ||
| app.use(passport.initialize()); | ||
| app.use(passport.session()); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
| "dev": "vite --host", | ||
| "build": "vite build", | ||
| "lint": "eslint .", | ||
| "test": "jasmine", | ||
|
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. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find all Jasmine spec files
fd -e cjs -e js "spec\."
# Check jasmine configuration
fd -t f "jasmine.json"Repository: GitMetricsLab/github_tracker Length of output: 123 🏁 Script executed: #!/bin/bash
# Check repository structure
echo "=== Repository structure ==="
ls -la | head -30
# Check if there are separate frontend and backend directories
echo -e "\n=== Checking for frontend/backend directories ==="
[ -d "frontend" ] && echo "frontend/ exists" || echo "frontend/ does NOT exist"
[ -d "backend" ] && echo "backend/ exists" || echo "backend/ does NOT exist"
# Check location of spec files - are they at root or in backend?
echo -e "\n=== Full paths of spec files ==="
find . -name "*.spec.cjs" -o -name "*.spec.js" 2>/dev/null | head -20
# Read frontend/package.json if it exists
echo -e "\n=== frontend/package.json (lines 1-20) ==="
[ -f "frontend/package.json" ] && head -20 frontend/package.json || echo "frontend/package.json not found"
# Read backend/package.json if it exists
echo -e "\n=== backend/package.json (lines 1-20) ==="
[ -f "backend/package.json" ] && head -20 backend/package.json || echo "backend/package.json not found"
# Check if spec directory exists at root
echo -e "\n=== Check spec/ directory ==="
[ -d "spec" ] && echo "spec/ exists at root" && ls -la spec/ || echo "spec/ does NOT exist at root"Repository: GitMetricsLab/github_tracker Length of output: 2655 🏁 Script executed: #!/bin/bash
# Check for root-level package.json
echo "=== Root-level package.json ==="
[ -f "package.json" ] && cat package.json || echo "No package.json at root"
# Check for jasmine configuration files
echo -e "\n=== Jasmine configuration files ==="
fd -t f -i "jasmine" 2>/dev/null | head -20
# Check if frontend has jasmine config
echo -e "\n=== Frontend directory contents ==="
ls -la frontend/ | grep -E "(jasmine|spec|test)"
# Check if there's a jasmine.json or similar
echo -e "\n=== Looking for test config files ==="
find . -maxdepth 2 -type f \( -name "*jasmine*" -o -name "*.spec.js" -o -name "*.spec.cjs" -o -name "jest.config.*" \) 2>/dev/nullRepository: GitMetricsLab/github_tracker Length of output: 321 🏁 Script executed: #!/bin/bash
# Read jasmine configuration
echo "=== spec/support/jasmine.mjs ==="
cat spec/support/jasmine.mjs
# Check what dependencies are in frontend vs backend for jasmine
echo -e "\n=== Jasmine in frontend/package.json dependencies ==="
grep -i "jasmine" frontend/package.json || echo "No jasmine in frontend dependencies"
echo -e "\n=== Jasmine in backend/package.json dependencies ==="
grep -i "jasmine" backend/package.json || echo "No jasmine in backend dependencies"
# Check full devDependencies in both
echo -e "\n=== Frontend devDependencies ==="
grep -A 20 '"devDependencies"' frontend/package.json | head -25
echo -e "\n=== Backend devDependencies ==="
grep -A 20 '"devDependencies"' backend/package.json | head -25Repository: GitMetricsLab/github_tracker Length of output: 1379 Move the test script to backend/package.json. The 🤖 Prompt for AI Agents |
||
| "preview": "vite preview", | ||
| "docker:dev": "docker compose --profile dev up --build", | ||
| "docker:prod": "docker compose --profile prod up -d --build" | ||
|
|
@@ -45,12 +46,15 @@ | |
| "eslint-plugin-react": "^7.37.2", | ||
| "eslint-plugin-react-hooks": "^5.0.0", | ||
| "eslint-plugin-react-refresh": "^0.4.14", | ||
| "express": "^5.2.1", | ||
|
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. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check if backend has its own package.json
fd -t f "package.json" backend/
# If it exists, check its dependencies
if [ -f backend/package.json ]; then
echo "=== backend/package.json dependencies ==="
cat backend/package.json | jq '.dependencies, .devDependencies'
fiRepository: GitMetricsLab/github_tracker Length of output: 393 🏁 Script executed: cat -n frontend/package.jsonRepository: GitMetricsLab/github_tracker Length of output: 2227 Remove backend dependencies from frontend package.json devDependencies. The following backend runtime dependencies are misplaced in
Remove lines 49, 50, 53-55 (and 🤖 Prompt for AI Agents |
||
| "express-session": "^1.18.2", | ||
| "globals": "^15.11.0", | ||
| "jasmine": "^5.9.0", | ||
| "mongoose": "^9.6.2", | ||
| "passport": "^0.7.0", | ||
| "passport-local": "^1.0.0", | ||
| "supertest": "^7.1.4", | ||
| "typescript-eslint": "^8.59.2", | ||
| "vite": "^5.4.10" | ||
| } | ||
| } | ||
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.
Remove hardcoded MongoDB credentials from documentation immediately.
Line 93 exposes a full connection string with embedded credentials. This must be replaced with placeholders, and the exposed credentials should be rotated/revoked.
🔒 Proposed fix
🧰 Tools
🪛 Betterleaks (1.2.0)
[high] 93-93: Detected a MongoDB connection string with embedded credentials, potentially exposing direct database access and sensitive application data.
(mongodb-connection-string)
🪛 markdownlint-cli2 (0.22.1)
[warning] 92-92: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents