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 {