-
Notifications
You must be signed in to change notification settings - Fork 6
Yaroslav kazeev w3 databases #16
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
5fd8a16
b1f3bae
963f7b1
6ba1345
3b2b8ad
ee728b1
32ede35
a094140
167b33a
f618c75
5ceea47
9c129d3
fce9c83
fc458cb
d82f9b1
9673efb
093ef6c
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,40 @@ | ||
| export const accounts = [ | ||
| { account_number: 101, balance: 5000 }, | ||
| { account_number: 102, balance: 12000 }, | ||
| { account_number: 103, balance: 7500 }, | ||
| { account_number: 104, balance: 3000 }, | ||
| { account_number: 105, balance: 9500 }, | ||
| ]; | ||
|
|
||
| export const account_changes = [ | ||
| { | ||
| account_number: 101, | ||
| amount: 1000, | ||
| changed_date: "2025-09-01", | ||
| remark: "Salary deposit", | ||
| }, | ||
| { | ||
| account_number: 102, | ||
| amount: -500, | ||
| changed_date: "2025-09-02", | ||
| remark: "Grocery shopping", | ||
| }, | ||
| { | ||
| account_number: 103, | ||
| amount: 2000, | ||
| changed_date: "2025-09-03", | ||
| remark: "Freelance payment", | ||
| }, | ||
| { | ||
| account_number: 104, | ||
| amount: -1000, | ||
| changed_date: "2025-09-04", | ||
| remark: "Rent payment", | ||
| }, | ||
| { | ||
| account_number: 105, | ||
| amount: 1500, | ||
| changed_date: "2025-09-05", | ||
| remark: "Gift received", | ||
| }, | ||
| ]; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import { Client } from "pg"; | ||
| const config = { | ||
| host: "localhost", | ||
| user: "hyfuser", | ||
| password: "hyfpassword", | ||
| database: "transactions_week3", | ||
| port: 5432, | ||
| }; | ||
| const client = new Client(config); | ||
|
|
||
| async function seedDatabase(client) { | ||
| try { | ||
| await client.connect(); | ||
| console.log("Connected to PostgreSQL database!"); | ||
|
|
||
| const donator_account_number = 101; | ||
| const receiver_account_number = 102; | ||
|
Comment on lines
+16
to
+17
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. Suggestion: in real life situation it is better to check whether the account exists and the balance is enough before doing actual transactions.
Author
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. I disagree once again here. Limitations should be enforced in the DB, for example, allowing the balance only a positive integer. Then, we can rely on the RDBMS to throw an error if some of the transactional statements can not be executed and abort the whole transaction. No check inside the code is needed. |
||
| const amount = 1000; | ||
| const changed_date = new Date().toISOString().slice(0, 10); // 'YYYY-MM-DD' | ||
| const remark = `Transfer from 101 to 102`; | ||
|
|
||
| await client.query("BEGIN"); | ||
| await client.query( | ||
| "UPDATE ACCOUNT SET balance = balance - $1 WHERE account_number = $2", | ||
| [amount, donator_account_number] | ||
| ); | ||
| await client.query( | ||
| "UPDATE ACCOUNT SET balance = balance + $1 WHERE account_number = $2", | ||
| [amount, receiver_account_number] | ||
| ); | ||
| await client.query( | ||
| "INSERT INTO account_changes(account_number, amount, changed_date, remark) VALUES($1, $2, $3, $4)", | ||
| [donator_account_number, -amount, changed_date, remark] | ||
| ); | ||
| await client.query( | ||
| "INSERT INTO account_changes(account_number, amount, changed_date, remark) VALUES($1, $2, $3, $4)", | ||
| [receiver_account_number, amount, changed_date, remark] | ||
| ); | ||
| await client.query("COMMIT"); | ||
| console.log("Transaction completed!"); | ||
| } catch (error) { | ||
| console.error("Error seeding database:", error); | ||
|
Comment on lines
+41
to
+42
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. Suggestion: although
Author
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. I would like to save a couple of code lines and rely on the behavior of the Postgres database management system, which should be known to the developer's team. On top of that, I do not expect it to be changed to let's say, MySQL, which will require the rewrite of many functions, where a transaction block is just one of them. |
||
| } finally { | ||
| await client.end(); | ||
| } | ||
| } | ||
|
|
||
| seedDatabase(client); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { Client } from "pg"; | ||
|
|
||
| // Database connection configuration | ||
| const config = { | ||
| host: "localhost", | ||
| user: "hyfuser", | ||
| password: "hyfpassword", | ||
| database: "transactions_week3", | ||
| port: 5432, | ||
| }; | ||
|
|
||
| const client = new Client(config); | ||
|
|
||
| async function createTables(client) { | ||
| const CREATE_ACCOUNT_TABLE = ` | ||
| CREATE TABLE IF NOT EXISTS account ( | ||
| account_number SMALLINT PRIMARY KEY, | ||
|
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. If the bank is really small using |
||
| balance INTEGER | ||
| )`; | ||
|
|
||
| const CREATE_ACCOUNT_CHANGES_TABLE = ` | ||
| CREATE TABLE IF NOT EXISTS account_changes ( | ||
| change_number SERIAL PRIMARY KEY, | ||
| account_number SMALLINT, | ||
| amount INTEGER, | ||
| changed_date DATE, | ||
| remark VARCHAR(100), | ||
| CONSTRAINT fk_account_number FOREIGN KEY (account_number) REFERENCES account(account_number) ON DELETE CASCADE | ||
| )`; | ||
| try { | ||
| await client.connect(); | ||
| console.log("Connected to PostgreSQL database!"); | ||
|
|
||
| // Create tables | ||
| await client.query(CREATE_ACCOUNT_TABLE); | ||
| await client.query(CREATE_ACCOUNT_CHANGES_TABLE); | ||
| } catch (error) { | ||
| console.error("Error creating tables:", error); | ||
| } finally { | ||
| await client.end(); | ||
| } | ||
| } | ||
|
|
||
| createTables(client); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { accounts, account_changes } from "./data.js"; | ||
| import { Client } from "pg"; | ||
| const config = { | ||
| host: "localhost", | ||
| user: "hyfuser", | ||
| password: "hyfpassword", | ||
| database: "transactions_week3", | ||
| port: 5432, | ||
| }; | ||
| const client = new Client(config); | ||
|
|
||
| async function seedDatabase(client, accounts, account_changes) { | ||
| try { | ||
| await client.connect(); | ||
| console.log("Connected to PostgreSQL database!"); | ||
|
|
||
| for (const account of accounts) { | ||
| const INSERT_ACCOUNT_QUERY = { | ||
| text: "INSERT INTO account(account_number, balance) VALUES($1, $2) ON CONFLICT (account_number) DO NOTHING", | ||
| values: Object.values(account), | ||
| }; | ||
| await client.query(INSERT_ACCOUNT_QUERY); | ||
| } | ||
|
|
||
| for (const change of account_changes) { | ||
| const INSERT_CHANGE_QUERY = { | ||
| text: `INSERT INTO account_changes(account_number, amount, changed_date, remark) VALUES($1, $2, $3, $4)`, | ||
| values: Object.values(change), | ||
| }; | ||
| await client.query(INSERT_CHANGE_QUERY); | ||
| } | ||
| } catch (error) { | ||
| console.error("Error seeding database:", error); | ||
| } finally { | ||
| await client.end(); | ||
| } | ||
| } | ||
|
|
||
| seedDatabase(client, accounts, account_changes); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import { Client } from "pg"; | ||
| const config = { | ||
| host: "localhost", | ||
| user: "hyfuser", | ||
| password: "hyfpassword", | ||
| database: "world", | ||
| port: 5432, | ||
| }; | ||
| const conn = new Client(config); | ||
| await conn.connect(); | ||
| console.log("Connected to PostgreSQL database!"); | ||
|
|
||
| function getPopulation(Country, name, code, cb) { | ||
| // assuming that connection to the database is established and stored as conn | ||
| conn.query( | ||
| `SELECT Population FROM ${Country} WHERE Name = '${name}' and code = '${code}'`, | ||
| function (err, result) { | ||
| if (err) cb(err); | ||
| if (result.length == 0) cb(new Error("Not found")); | ||
| cb(null, result.rows[0].population); | ||
| } | ||
| ); | ||
| } | ||
|
|
||
| getPopulation("country", "Netherlands", "NLD", function (err, population) { | ||
| if (err) { | ||
| console.error("Error:", err); | ||
| } else { | ||
| console.log("Netherland's population (the regular query):", population); | ||
| } | ||
| }); | ||
|
|
||
| // The resulting query becomes: SELECT Population FROM country WHERE Name = = '' OR 1=1 --' and code = '' OR 1=1 --' | ||
| // Since 1=1 is always true, this condition matches all rows in the country table. However, because the code only retrieves the first row from the result set, it returns only the population of the first country in the table. | ||
| getPopulation( | ||
| "country", | ||
| "Netherlands", | ||
| "' OR 1=1 --", | ||
| function (err, population) { | ||
| if (err) { | ||
| console.error("Error:", err); | ||
| } else { | ||
| console.log("the hacker's query 1:", population); | ||
| } | ||
| } | ||
| ); | ||
|
|
||
| // The user's input is treated as data only and not executable code here. However, it is not possible to demonstrate the advantages of this approach, as the function getPopulation can only handle results that contain an array of objects with the population property. Although the SQL code is executed and yields the desired result, it is not possible to extract it without modifying the initial function, so it serves as an SQL injection prevention of some sort. | ||
| function getPopulationSQLinjectProof(Country, name, code, cb) { | ||
| const allowedTables = new Set(["country"]); | ||
| const table = String(Country).toLowerCase(); | ||
| if (!allowedTables.has(table)) return cb(new Error("Invalid table")); | ||
|
|
||
| const sql = `SELECT population FROM ${table} WHERE Name = $1 AND Code = $2`; | ||
|
|
||
| conn.query(sql, [name, code], function (err, result) { | ||
| if (err) return cb(err); | ||
| if (!result) return cb(new Error("Not found")); | ||
| cb(null, result.rows); | ||
| }); | ||
| } | ||
|
|
||
| getPopulationSQLinjectProof( | ||
| "country", | ||
| "' OR 1=1 --", | ||
| "' OR 1=1 --", | ||
| function (err, population) { | ||
| if (err) { | ||
| console.error("Error:", err); | ||
| } else { | ||
| console.log("the hacker's query 2:", population); | ||
| } | ||
| conn.end(); | ||
| } | ||
| ); |
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.
Suggestion: Same suggestion as week 2, better to use JSON file for pure data. You don't have to change your code but it is better to know.