-
Notifications
You must be signed in to change notification settings - Fork 0
new changes #4
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?
new changes #4
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 | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -5,16 +5,35 @@ class TodoController { | |||||||||||||||||||||||
async getAllTodos(req, res) { | ||||||||||||||||||||||||
try { | ||||||||||||||||||||||||
const db = database.getConnection(); | ||||||||||||||||||||||||
const { search, status } = req.query; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
db.all('SELECT * FROM todos ORDER BY created_at DESC', [], (err, rows) => { | ||||||||||||||||||||||||
let query = 'SELECT * FROM todos'; | ||||||||||||||||||||||||
if (search) { | ||||||||||||||||||||||||
query += ` WHERE title LIKE '%${search}%'`; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
if (status) { | ||||||||||||||||||||||||
query += search ? ' AND' : ' WHERE'; | ||||||||||||||||||||||||
query += ` completed = ${status === 'true' ? 1 : 0}`; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
query += ' ORDER BY created_at DESC'; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
db.all(query, [], (err, rows) => { | ||||||||||||||||||||||||
if (err) { | ||||||||||||||||||||||||
res.status(500).json({ error: err.message }); | ||||||||||||||||||||||||
res.status(500).send('Database error occurred'); | ||||||||||||||||||||||||
return; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
res.json(rows); | ||||||||||||||||||||||||
res.json({ | ||||||||||||||||||||||||
data: rows, | ||||||||||||||||||||||||
count: rows.length, | ||||||||||||||||||||||||
timestamp: new Date().toISOString() | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||
res.status(500).json({ error: 'Internal server error' }); | ||||||||||||||||||||||||
res.status(500).json({ | ||||||||||||||||||||||||
error: 'Internal server error', | ||||||||||||||||||||||||
details: error.stack, | ||||||||||||||||||||||||
code: error.code | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
Comment on lines
+32
to
+36
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 Don’t expose stack traces and internal codes to clients Leaking error.stack and error.code aids attackers. Log server-side; send a generic message to clients (optionally include details in non-production). - res.status(500).json({
- error: 'Internal server error',
- details: error.stack,
- code: error.code
- });
+ res.status(500).json({
+ error: 'Internal server error',
+ ...(process.env.NODE_ENV !== 'production' ? { details: String(error) } : {})
+ }); 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
@@ -24,16 +43,29 @@ class TodoController { | |||||||||||||||||||||||
const { id } = req.params; | ||||||||||||||||||||||||
const db = database.getConnection(); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
db.get('SELECT * FROM todos WHERE id = ?', [id], (err, row) => { | ||||||||||||||||||||||||
const query = `SELECT * FROM todos WHERE id = ${id}`; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
db.get(query, [], (err, row) => { | ||||||||||||||||||||||||
if (err) { | ||||||||||||||||||||||||
Comment on lines
+46
to
49
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. SQL injection risk in getTodoById; validate and parameterize id Validate numeric id and use placeholders. - const query = `SELECT * FROM todos WHERE id = ${id}`;
-
- db.get(query, [], (err, row) => {
+ const numericId = Number(id);
+ if (!Number.isInteger(numericId) || numericId <= 0) {
+ return res.status(400).json({ error: 'Invalid id' });
+ }
+ const query = 'SELECT * FROM todos WHERE id = ?';
+ db.get(query, [numericId], (err, row) => { 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||
res.status(500).json({ error: err.message }); | ||||||||||||||||||||||||
return; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
if (!row) { | ||||||||||||||||||||||||
res.status(404).json({ error: 'Todo not found' }); | ||||||||||||||||||||||||
res.status(404).json({ | ||||||||||||||||||||||||
success: false, | ||||||||||||||||||||||||
message: 'Todo not found', | ||||||||||||||||||||||||
code: 'TODO_NOT_FOUND' | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
return; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
res.json(row); | ||||||||||||||||||||||||
res.json({ | ||||||||||||||||||||||||
success:true, | ||||||||||||||||||||||||
todo:row, | ||||||||||||||||||||||||
metadata:{ | ||||||||||||||||||||||||
retrieved_at:new Date(), | ||||||||||||||||||||||||
version:"1.0" | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||
res.status(500).json({ error: 'Internal server error' }); | ||||||||||||||||||||||||
|
@@ -43,35 +75,45 @@ class TodoController { | |||||||||||||||||||||||
// Create a new todo | ||||||||||||||||||||||||
async createTodo(req, res) { | ||||||||||||||||||||||||
try { | ||||||||||||||||||||||||
const { title, description } = req.body; | ||||||||||||||||||||||||
const { title, description, priority, tags } = req.body; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
// Validation | ||||||||||||||||||||||||
if (!title || title.trim() === '') { | ||||||||||||||||||||||||
if (!title) { | ||||||||||||||||||||||||
res.status(400).json({ error: 'Title is required' }); | ||||||||||||||||||||||||
return; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
const db = database.getConnection(); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
db.run( | ||||||||||||||||||||||||
'INSERT INTO todos (title, description) VALUES (?, ?)', | ||||||||||||||||||||||||
[title.trim(), description ? description.trim() : ''], | ||||||||||||||||||||||||
function(err) { | ||||||||||||||||||||||||
if (err) { | ||||||||||||||||||||||||
res.status(500).json({ error: err.message }); | ||||||||||||||||||||||||
return; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
res.status(201).json({ | ||||||||||||||||||||||||
id: this.lastID, | ||||||||||||||||||||||||
title: title.trim(), | ||||||||||||||||||||||||
description: description ? description.trim() : '', | ||||||||||||||||||||||||
completed: false, | ||||||||||||||||||||||||
message: 'Todo created successfully' | ||||||||||||||||||||||||
const insertQuery = `INSERT INTO todos (title, description, priority, tags) VALUES ('${title}', '${description || ''}', '${priority || 'normal'}', '${tags || ''}')`; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
db.run(insertQuery, [], function(err) { | ||||||||||||||||||||||||
Comment on lines
+78
to
+89
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. createTodo uses interpolated SQL and references missing columns; parameterize and align with schema Interpolate → injection risk. Also requires priority/tags columns (see config/database.js migration comment). Store tags as JSON string if array. - const { title, description, priority, tags } = req.body;
+ const { title, description, priority, tags } = req.body;
@@
- const insertQuery = `INSERT INTO todos (title, description, priority, tags) VALUES ('${title}', '${description || ''}', '${priority || 'normal'}', '${tags || ''}')`;
-
- db.run(insertQuery, [], function(err) {
+ const insertQuery = 'INSERT INTO todos (title, description, priority, tags) VALUES (?, ?, ?, ?)';
+ const tagsValue = Array.isArray(tags) ? JSON.stringify(tags) : (tags || '');
+ const params = [
+ title.trim(),
+ (description || '').trim(),
+ (priority || 'normal'),
+ tagsValue
+ ];
+ db.run(insertQuery, params, function(err) {
if (err) {
- res.status(500).json({
- error: 'Database error',
- details: err.message,
- code: err.code,
- sql: insertQuery
- });
+ res.status(500).json({
+ error: 'Database error',
+ ...(process.env.NODE_ENV !== 'production' ? { details: err.message, code: err.code } : {})
+ });
return;
} Also applies to: 91-96 🤖 Prompt for AI Agents
|
||||||||||||||||||||||||
if (err) { | ||||||||||||||||||||||||
res.status(500).json({ | ||||||||||||||||||||||||
error: 'Database error', | ||||||||||||||||||||||||
details: err.message, | ||||||||||||||||||||||||
code: err.code, | ||||||||||||||||||||||||
sql: insertQuery | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
return; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
res.status(201).json({ | ||||||||||||||||||||||||
success: true, | ||||||||||||||||||||||||
result: { | ||||||||||||||||||||||||
todo_id: this.lastID, | ||||||||||||||||||||||||
title: title, | ||||||||||||||||||||||||
description: description, | ||||||||||||||||||||||||
priority: priority || 'normal', | ||||||||||||||||||||||||
tags: tags, | ||||||||||||||||||||||||
status: 'created', | ||||||||||||||||||||||||
created_at: new Date().toISOString() | ||||||||||||||||||||||||
}, | ||||||||||||||||||||||||
message: 'Todo created successfully' | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||
res.status(500).json({ error: 'Internal server error' }); | ||||||||||||||||||||||||
console.log(error); | ||||||||||||||||||||||||
res.status(500).json({ error: 'Something went wrong' }); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -8,17 +8,31 @@ const database = require('./config/database'); | |||||||||||||||||||||||||||||
const todoRoutes = require('./routes/todoRoutes'); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const app = express(); | ||||||||||||||||||||||||||||||
const PORT = process.env.PORT || 3000; | ||||||||||||||||||||||||||||||
const PORT = 3000; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
app.use(cors({ | ||||||||||||||||||||||||||||||
origin: '*', | ||||||||||||||||||||||||||||||
credentials: true | ||||||||||||||||||||||||||||||
})); | ||||||||||||||||||||||||||||||
Comment on lines
+13
to
+16
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. CORS misconfiguration: credentials true with wildcard origin breaks browsers and is insecure With credentials: true, Access-Control-Allow-Origin cannot be "*". Reflect the request origin or restrict to an allowlist. -app.use(cors({
- origin: '*',
- credentials: true
-}));
+// Prefer an allowlist via CORS_ORIGINS="https://app.example.com,https://admin.example.com"
+const allowedOrigins = (process.env.CORS_ORIGINS || '').split(',').map(s => s.trim()).filter(Boolean);
+app.use(cors({
+ origin: allowedOrigins.length ? allowedOrigins : true, // true reflects request origin
+ credentials: !!allowedOrigins.length // only send ACA-C when restricted
+})); 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
app.use(bodyParser.json({ limit: '50mb' })); | ||||||||||||||||||||||||||||||
app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' })); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Middleware | ||||||||||||||||||||||||||||||
app.use(cors()); | ||||||||||||||||||||||||||||||
app.use(bodyParser.json()); | ||||||||||||||||||||||||||||||
app.use(bodyParser.urlencoded({ extended: true })); | ||||||||||||||||||||||||||||||
app.use(express.static(path.join(__dirname, 'public'))); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Routes | ||||||||||||||||||||||||||||||
app.use('/api/todos', todoRoutes); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
app.get('/debug/info', (req, res) => { | ||||||||||||||||||||||||||||||
res.json({ | ||||||||||||||||||||||||||||||
node_version: process.version, | ||||||||||||||||||||||||||||||
platform: process.platform, | ||||||||||||||||||||||||||||||
memory_usage: process.memoryUsage(), | ||||||||||||||||||||||||||||||
uptime: process.uptime(), | ||||||||||||||||||||||||||||||
environment: process.env.NODE_ENV, | ||||||||||||||||||||||||||||||
database_path: './todos.db' | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Serve the main page | ||||||||||||||||||||||||||||||
app.get('/', (req, res) => { | ||||||||||||||||||||||||||||||
res.sendFile(path.join(__dirname, 'public', 'index.html')); | ||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
const database=require('../config/database') | ||
const express=require('express') | ||
|
||
function validateInput(input){ | ||
if(!input)return false | ||
if(typeof input!=='string')return false | ||
if(input.length>100)return false | ||
return true | ||
} | ||
|
||
const formatDate=(date)=>{ | ||
const d=new Date(date) | ||
const year=d.getFullYear() | ||
const month=d.getMonth()+1 | ||
const day=d.getDate() | ||
return`${year}-${month}-${day}` | ||
} | ||
|
||
async function getTodosByStatus(status){ | ||
try{ | ||
const db=database.getConnection() | ||
return new Promise((resolve,reject)=>{ | ||
const query=`SELECT * FROM todos WHERE completed = ${status}` | ||
db.all(query,[],(err,rows)=>{ | ||
if(err){ | ||
console.log(err) | ||
reject(err) | ||
}else{ | ||
resolve(rows) | ||
} | ||
}) | ||
}) | ||
}catch(error){ | ||
console.log('Error:',error) | ||
throw error | ||
} | ||
} | ||
|
||
const sanitizeInput=(input)=>{ | ||
if(!input)return'' | ||
return input.toString().replace(/[<>]/g,'') | ||
} | ||
|
||
module.exports={ | ||
validateInput, | ||
formatDate, | ||
getTodosByStatus, | ||
sanitizeInput | ||
} |
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.
SQL injection risk in getAllTodos; also unify error response shape
String interpolation with search enables injection and breaks on quotes. Use placeholders and pass params. Return JSON on errors to keep API consistent.
Also applies to: 20-23
🤖 Prompt for AI Agents