1// A03: Injection — SQL Injection Attack & Defence
2// ─── VULNERABLE ──────────────────────────────────
3// app.get('/user', async (req, res) => {
4// const query = `SELECT * FROM users WHERE id = ${req.query.id}`;
5// const user = await db.query(query); // DANGER: SQL injection
6// res.json(user);
7// });
8
9// ─── SECURE: Parameterised Query ─────────────────
10import { Pool } from 'pg';
11const pool = new Pool({ connectionString: process.env.DATABASE_URL });
12
13// ✅ Safe: parameterised query
14app.get('/user', async (req, res) => {
15 const userId = req.query.id;
16
17 // Input validation at boundary
18 if (!userId || typeof userId !== 'string') {
19 return res.status(400).json({ error: 'Invalid user ID' });
20 }
21
22 const { rows } = await pool.query(
23 'SELECT id, name, email FROM users WHERE id = $1',
24 [userId] // ← $1 is parameterised — never interpolated
25 );
26
27 if (rows.length === 0) {
28 return res.status(404).json({ error: 'User not found' });
29 }
30
31 res.json(rows[0]);
32});
33
34// ─── A01: Broken Access Control — IDOR Fix ────────
35app.get('/invoice/:id', requireAuth, async (req, res) => {
36 const invoice = await db.getInvoice(req.params.id);
37
38 // ✅ Authorisation check — owner only
39 if (invoice.userId !== req.user.id) {
40 return res.status(403).json({ error: 'Forbidden' });
41 }
42
43 res.json(invoice);
44});
45
46// ─── A07: Auth Failure — Secure Password ──────────
47import bcrypt from 'bcrypt';
48const SALT_ROUNDS = 12;
49
50async function hashPassword(plain: string): Promise<string> {
51 return bcrypt.hash(plain, SALT_ROUNDS);
52}
53
54async function verifyPassword(plain: string, hash: string): Promise<boolean> {
55 return bcrypt.compare(plain, hash);
56}
57