diff --git a/client/src-tauri/capabilities/default.json b/client/src-tauri/capabilities/default.json index c6ca25e..8848aad 100644 --- a/client/src-tauri/capabilities/default.json +++ b/client/src-tauri/capabilities/default.json @@ -19,6 +19,9 @@ }, { "url": "https://*" + }, + { + "url": "http://localhost:5000" } ], "identifier": "http:default" diff --git a/client/src/components/HeaderBar/ServerStatus.tsx b/client/src/components/HeaderBar/ServerStatus.tsx index 3a2a44c..c0f003a 100644 --- a/client/src/components/HeaderBar/ServerStatus.tsx +++ b/client/src/components/HeaderBar/ServerStatus.tsx @@ -33,15 +33,12 @@ export const ServerStatus = () => { try { const response = await fetch(serverUrl + "/health"); if (response.ok) { - console.log("connected"); setServerConnection("connected"); } else { - console.log("disconnected"); await sleep(500); setServerConnection("disconnected"); } } catch (e) { - console.log("disconnected", e); await sleep(500); setServerConnection("disconnected"); } @@ -60,13 +57,11 @@ export const ServerStatus = () => { useEffect(() => { // only start interval if server is connected if (serverConnection === "connected") { - const intervalId = setInterval(checkServerConnection, 2000); - console.log("Started server ping interval"); + const intervalId = setInterval(checkServerConnection, 5000); // cleanup interval on disconnection or component unmount return () => { clearInterval(intervalId); - console.log("Stopped server ping interval"); }; } }, [serverConnection, serverUrl]); @@ -82,7 +77,7 @@ export const ServerStatus = () => { > {serverConnection === "connecting" && ( )} @@ -90,7 +85,7 @@ export const ServerStatus = () => { { }} > diff --git a/client/src/components/Pages/SqlInjection/SqlInjection.tsx b/client/src/components/Pages/SqlInjection/SqlInjection.tsx index 8b305ea..25284e5 100644 --- a/client/src/components/Pages/SqlInjection/SqlInjection.tsx +++ b/client/src/components/Pages/SqlInjection/SqlInjection.tsx @@ -9,7 +9,7 @@ export const SqlInjection = () => { const theme = useTheme(); // states - const [subTabValue, setSubTabValue] = useState("style"); + const [subTabValue, setSubTabValue] = useState("register"); // logic for switching tabs const subTabChangeEvent = (newTabValue: string) => { diff --git a/client/src/components/Pages/SqlInjection/SqlInjectionRegister.tsx b/client/src/components/Pages/SqlInjection/SqlInjectionRegister.tsx index b1e95be..5633a92 100644 --- a/client/src/components/Pages/SqlInjection/SqlInjectionRegister.tsx +++ b/client/src/components/Pages/SqlInjection/SqlInjectionRegister.tsx @@ -4,18 +4,21 @@ import { fetch } from "@tauri-apps/plugin-http"; import { useAtom } from "jotai"; import { useState } from "react"; import { serverUrlAtom } from "../../../lib/jotai"; -import { sleep } from "../../../lib/utility"; import { HeaderLogo } from "../../Generic/HeaderLogo"; +import { useNotification } from "../../../contexts/NotificationContext"; export const SqlInjectionRegister = () => { + // contexts + const { openNotification } = useNotification(); + // atoms const [serverUrl, setServerUrl] = useAtom(serverUrlAtom); // states const [emailValueRaw, setEmailValueRaw] = useState(""); + const [errorMsg, setErrorMsg] = useState(""); const [passwordValueRaw, setPasswordValueRaw] = useState(""); const [rePasswordValueRaw, setRePasswordValueRaw] = useState(""); - const [passwordErrorMsg, setPasswordErrorMsg] = useState(""); const [passwordStrength, setPasswordStrength] = useState(0); const [passwordStrengthColor, setPasswordStrengthColor] = useState<"primary" | "error" | "warning" | "success">( "primary", @@ -86,16 +89,45 @@ export const SqlInjectionRegister = () => { }; const nextClickEvent = async () => { - // make a request to the server - setRegisterLoading(true); - // remove trailing slash + // reset the error messages + setErrorMsg(""); + + // ensure that the server url does not end with a trailing slash setServerUrl(serverUrl.replace(/\/$/, "")); + + // construct the request body + const requestBody = { + email: emailValueRaw, + password: passwordValueRaw, + rePassword: rePasswordValueRaw, + }; + + // start loading indicator + setRegisterLoading(true); + try { - const response = await fetch(serverUrl + "/sql-register"); - console.log; + // make request good + const response = await fetch(serverUrl + "/register-sql", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(requestBody), + }); + + // check if registration was successful + if (!response.ok) { + const errorMessage = await response.text(); + console.log("Registration failed:", errorMessage); + setErrorMsg(errorMessage); + } else { + openNotification("Registration successful"); + } } catch (e) { - console.log("failed", e); - await sleep(500); + // Log the error and handle failure + console.log("Request failed", e); + } finally { + // Stop loading indicator regardless of success/failure setRegisterLoading(false); } }; @@ -133,6 +165,7 @@ export const SqlInjectionRegister = () => { }} > { variant="outlined" /> passwordInputEvent(e.target.value)} @@ -157,9 +189,9 @@ export const SqlInjectionRegister = () => { variant="outlined" /> rePasswordInputEvent(e.target.value)} diff --git a/client/src/components/Testing/Testing.tsx b/client/src/components/Testing/Testing.tsx index 63edbde..e199448 100644 --- a/client/src/components/Testing/Testing.tsx +++ b/client/src/components/Testing/Testing.tsx @@ -17,7 +17,10 @@ export const Testing = () => { const close = () => setOpenState(false); const testing = () => { - fetch("https://ip.vomitblood.com/ping").then((response) => { + fetch("http://localhost:5000/nuke-db").then((response) => { + console.log(response); + }); + fetch("http://localhost:5000/setup-demo-db").then((response) => { console.log(response); }); }; @@ -53,14 +56,14 @@ export const Testing = () => { openButton={ setOpenState(true)} - size='small' + size="small" > } openState={openState} setMaximisedState={setMaximisedState} - title='Testing' + title="Testing" /> ); }; diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index c940833..35eef03 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -10,3 +10,5 @@ services: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data +volumes: + postgres_data: diff --git a/server/go.mod b/server/go.mod index 48a3f08..8c95d1c 100644 --- a/server/go.mod +++ b/server/go.mod @@ -7,7 +7,7 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.7.1 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/text v0.21.0 // indirect ) diff --git a/server/go.sum b/server/go.sum index 133a427..b293ba4 100644 --- a/server/go.sum +++ b/server/go.sum @@ -13,9 +13,15 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/server/internal/db/db.go b/server/internal/db/db.go index 4da16f5..8f2dec0 100644 --- a/server/internal/db/db.go +++ b/server/internal/db/db.go @@ -21,7 +21,6 @@ const ( ) var DbPool *pgxpool.Pool -var allowedUsernames map[string]bool // initialize connection to db func ConnectToDb() (*pgxpool.Pool, error) { @@ -79,19 +78,19 @@ func SetupDemoDb(w http.ResponseWriter, r *http.Request) { // create table and insert demo data createTableSQL := ` CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - username VARCHAR(50) UNIQUE NOT NULL, - email VARCHAR(100) NOT NULL, - password VARCHAR(100) NOT NULL + id SERIAL PRIMARY KEY, + email VARCHAR(100) NOT NULL, + password VARCHAR(100) NOT NULL, + role VARCHAR(50) DEFAULT 'user' );` - // also avoid duplicate entries + // avoid duplicate entries and specify roles insertDataSQL := ` - INSERT INTO users (username, email, password) VALUES - ('alice', 'alice@example.com', 'asdfalicepassword'), - ('bob', 'bob@example.com', 'asdfbobpassword'), - ('charlie', 'charlie@example.com', 'asdfcharliepassword') - ON CONFLICT (username) DO NOTHING;` + INSERT INTO users (email, password, role) VALUES + ('alice@example.com', 'asdfalicepassword', 'user'), + ('bob@example.com', 'asdfbobpassword', 'user'), + ('charlie@example.com', 'asdfcharliepassword', 'admin') + ` // execute create table _, err := DbPool.Exec(context.Background(), createTableSQL) diff --git a/server/internal/http_server/http_server.go b/server/internal/http_server/http_server.go index 01902bd..3fa596f 100644 --- a/server/internal/http_server/http_server.go +++ b/server/internal/http_server/http_server.go @@ -24,6 +24,7 @@ func ServeApi() { http.HandleFunc("/execute-sql", sql_injection.ExecuteSql) http.HandleFunc("/login-sql", sql_injection.LoginSql) http.HandleFunc("/secure-execute-sql", sql_injection.SecureExecuteSql) + http.HandleFunc("/register-sql", sql_injection.RegisterSql) http.HandleFunc("/secure-login-sql", sql_injection.SecureLoginSql) http.HandleFunc("/secure-get-user", sql_injection.SecureGetUser) log.Println("Server is running on http://localhost:5000") diff --git a/server/internal/sql_injection/sql_injection.go b/server/internal/sql_injection/sql_injection.go index a6dfdc4..b34ae0b 100644 --- a/server/internal/sql_injection/sql_injection.go +++ b/server/internal/sql_injection/sql_injection.go @@ -7,9 +7,11 @@ import ( "io" "log" "net/http" + "regexp" "strings" "github.com/Vomitblood/cspj-application/server/internal/db" + "golang.org/x/crypto/bcrypt" ) // unsecure version @@ -147,6 +149,72 @@ func SecureExecuteSql(w http.ResponseWriter, r *http.Request) { w.Write(jsonResp) } +// register endpoint +func RegisterSql(w http.ResponseWriter, r *http.Request) { + // read the request body + var credentials struct { + Email string `json:"email"` + Password string `json:"password"` + RePassword string `json:"rePassword"` + } + + if err := json.NewDecoder(r.Body).Decode(&credentials); err != nil { + http.Error(w, "Invalid request format", http.StatusBadRequest) + return + } + defer r.Body.Close() + + // check if the email is an email using regex, if not reject + emailRegex := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` + re := regexp.MustCompile(emailRegex) + if !re.MatchString(credentials.Email) { + http.Error(w, "Invalid email format", http.StatusBadRequest) + return + } + + // check if the password matches, if not reject + if credentials.Password != credentials.RePassword { + http.Error(w, "Passwords do not match", http.StatusBadRequest) + return + } + + // get the number of emails that matches + var existingUserCount int + emailCheckSQL := `SELECT COUNT(*) FROM users WHERE email = $1` + err := db.DbPool.QueryRow(context.Background(), emailCheckSQL, credentials.Email).Scan(&existingUserCount) + if err != nil { + http.Error(w, "Error checking email in the database", http.StatusInternalServerError) + log.Printf("Error checking email: %v", err) + return + } + // if there is more than 0 matches, that means email already exists, reject + if existingUserCount > 0 { + http.Error(w, "Email already exists", http.StatusConflict) + return + } + + // hash the password + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(credentials.Password), bcrypt.DefaultCost) + if err != nil { + http.Error(w, "Error hashing password", http.StatusInternalServerError) + return + } + + // over here the validations has passed, so insert into the db + insertSQL := `INSERT INTO users (email, password, role) VALUES ($1, $2, $3)` + _, err = db.DbPool.Exec(context.Background(), insertSQL, credentials.Email, hashedPassword, "user") + if err != nil { + http.Error(w, "Error inserting user into the database", http.StatusInternalServerError) + log.Printf("Error inserting user: %v", err) + return + } + + // send back status ok + w.WriteHeader(http.StatusOK) + w.Write([]byte("User registered successfully")) + log.Println("User registered successfully:", credentials.Email) +} + // secure login func SecureLoginSql(w http.ResponseWriter, r *http.Request) { var credentials struct {