| | 1 | = Phase 9 — Security (SQL Injection & Least Privilege) = |
| | 2 | |
| | 3 | == Overview == |
| | 4 | This section explains how security is enforced in the Wedding Planner database system, with a focus on SQL injection prevention and the principle of least privilege. |
| | 5 | Security is implemented through a combination of database-level controls, safe query practices, and role-based access management in PostgreSQL. |
| | 6 | |
| | 7 | == What we cover in this phase == |
| | 8 | * SQL injection prevention through parameterized queries |
| | 9 | * Database-level protection using constraints |
| | 10 | * Role-based access control (RBAC) |
| | 11 | * Least privilege principle for database users |
| | 12 | |
| | 13 | == SQL Injection Prevention == |
| | 14 | |
| | 15 | === Description === |
| | 16 | SQL injection occurs when user input is directly concatenated into SQL queries, allowing attackers to manipulate query logic and access or modify unauthorized data. |
| | 17 | |
| | 18 | Examples include: |
| | 19 | * bypassing authentication |
| | 20 | * reading private wedding or user data |
| | 21 | * deleting bookings or entire tables |
| | 22 | |
| | 23 | === Example: Unsafe vs Safe Query === |
| | 24 | |
| | 25 | '''Unsafe (vulnerable to SQL injection)''' |
| | 26 | {{{ |
| | 27 | SELECT * FROM "user" |
| | 28 | WHERE email = 'user_input'; |
| | 29 | }}} |
| | 30 | |
| | 31 | If user_input = 'x' OR '1'='1', all users are returned. |
| | 32 | |
| | 33 | '''Safe (parameterized query)''' |
| | 34 | {{{ |
| | 35 | SELECT * FROM "user" |
| | 36 | WHERE email = $1; |
| | 37 | }}} |
| | 38 | |
| | 39 | === Reason for use === |
| | 40 | Parameterized queries ensure that malicious input is always treated as data, not executable SQL code. |
| | 41 | |
| | 42 | == Input Validation and Domain Constraints == |
| | 43 | While application-level validation is applied, the database also enforces security-related constraints: |
| | 44 | * NOT NULL on mandatory fields (email, names) |
| | 45 | * UNIQUE constraints to prevent duplicate accounts |
| | 46 | * CHECK constraints to prevent invalid values |
| | 47 | |
| | 48 | These rules ensure that even if malicious input reaches the database, it cannot violate domain rules. |
| | 49 | |
| | 50 | == Principle of Least Privilege == |
| | 51 | |
| | 52 | === Concept === |
| | 53 | The principle of least privilege states that each user or service should have only the minimum permissions required to perform its tasks. |
| | 54 | |
| | 55 | In the Wedding Planner system, different database roles are created for different responsibilities. |
| | 56 | |
| | 57 | === Role-Based Access Control (RBAC) === |
| | 58 | Roles used in our application: |
| | 59 | * wp_app_user – used by the application backend |
| | 60 | * wp_admin – used only for schema migrations and maintenance |
| | 61 | * wp_readonly – used for reporting or analytics |
| | 62 | |
| | 63 | === Example === |
| | 64 | {{{ |
| | 65 | CREATE ROLE wp_app_user LOGIN PASSWORD 'strong_password'; |
| | 66 | |
| | 67 | GRANT CONNECT ON DATABASE wedding_planner TO wp_app_user; |
| | 68 | GRANT USAGE ON SCHEMA public TO wp_app_user; |
| | 69 | |
| | 70 | GRANT SELECT, INSERT, UPDATE, DELETE |
| | 71 | ON "user", wedding, venue, venue_booking |
| | 72 | TO wp_app_user; |
| | 73 | }}} |
| | 74 | |
| | 75 | === Explanation === |
| | 76 | This role represents the database user used by the application backend. |
| | 77 | It is allowed to connect to the database and use the public schema, but its privileges are strictly limited to data manipulation on specific application tables. |
| | 78 | |
| | 79 | === Reason for use === |
| | 80 | This approach enforces the principle of least privilege and reduces the impact of: |
| | 81 | * application bugs |
| | 82 | * SQL injection vulnerabilities |
| | 83 | * compromised application credentials |
| | 84 | |
| | 85 | Even if the application role is abused, attackers cannot DROP tables, ALTER constraints/triggers, or access data outside the permitted scope. |
| | 86 | |
| | 87 | == Protecting Sensitive Data == |
| | 88 | |
| | 89 | === Example 1: Unauthorized Data Access Prevention === |
| | 90 | In the Wedding Planner system, sensitive data protection is enforced by ensuring that queries are always scoped to the authenticated user and never rely on client-supplied identifiers alone. |
| | 91 | |
| | 92 | Example endpoint: |
| | 93 | {{{ |
| | 94 | GET /weddings/{id}/guests |
| | 95 | }}} |
| | 96 | |
| | 97 | Potential attack scenario: |
| | 98 | An attacker could attempt to access another user’s wedding by guessing or manipulating the wedding ID. |
| | 99 | |
| | 100 | To prevent this, the database query always filters by both wedding ID and the authenticated user ID. |
| | 101 | |
| | 102 | '''SQL code''' |
| | 103 | {{{ |
| | 104 | SELECT g.* |
| | 105 | FROM guest g |
| | 106 | JOIN wedding w ON w.wedding_id = g.wedding_id |
| | 107 | WHERE g.wedding_id = $1 |
| | 108 | AND w.user_id = $2; |
| | 109 | }}} |
| | 110 | |
| | 111 | '''Explanation''' |
| | 112 | $1 represents the wedding ID from the request, while $2 represents the authenticated user’s ID. |
| | 113 | |
| | 114 | '''Reason for use''' |
| | 115 | If the wedding does not belong to the user, the query returns no results, keeping data secure. |
| | 116 | |
| | 117 | === Example 2: Availability Query Without Sensitive Data Exposure === |
| | 118 | Example endpoint: |
| | 119 | {{{ |
| | 120 | GET /availability/venue?date=2026-06-10 |
| | 121 | }}} |
| | 122 | |
| | 123 | Potential attack scenario: |
| | 124 | An attacker could try to infer booking details for venues by repeatedly |