276 lines
8.8 KiB
Go
276 lines
8.8 KiB
Go
package sql_injection
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"regexp"
|
|
|
|
"github.com/Vomitblood/cspj-application/server/internal/db"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
// secure register endpoint
|
|
func UnsecureRegisterSql(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
|
|
// construct the sql query using concatenation, BAD
|
|
emailCheckSQL := fmt.Sprintf("SELECT COUNT(*) FROM users WHERE email = '%s'", credentials.Email)
|
|
var existingUserCount int
|
|
err := db.DbPool.QueryRow(context.Background(), emailCheckSQL).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)
|
|
fmt.Println(hashedPassword)
|
|
if err != nil {
|
|
http.Error(w, "Error hashing password", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// over here the validations has passed, so insert into the db
|
|
// also use concatenation here
|
|
insertSQL := fmt.Sprintf("INSERT INTO users (email, password, role) VALUES ('%s', '%s', 'user')", credentials.Email, string(hashedPassword))
|
|
_, err = db.DbPool.Exec(context.Background(), insertSQL)
|
|
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 register endpoint
|
|
func SecureRegisterSql(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
|
|
// use parameterization
|
|
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
|
|
// use parameterization
|
|
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)
|
|
}
|
|
|
|
// very secure login endpoint
|
|
func UnsecureLoginSql(w http.ResponseWriter, r *http.Request) {
|
|
// decode the json body
|
|
var requestData struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
// declare new json decoder with custom property
|
|
jsonDecoder := json.NewDecoder(r.Body)
|
|
// rejects any unknown fields in the json, more strict
|
|
jsonDecoder.DisallowUnknownFields()
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&requestData); err != nil {
|
|
http.Error(w, "Invalid request format", http.StatusBadRequest)
|
|
log.Println("Failed to decode JSON body or extra fields present:", err)
|
|
return
|
|
}
|
|
|
|
// validate that user is provided
|
|
if requestData.Email == "" || requestData.Password == "" {
|
|
http.Error(w, "Invalid request format", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// retrieve the user data
|
|
// construct sql command using concatenation
|
|
query := fmt.Sprintf("SELECT id, email, password FROM users WHERE email = '%s'", requestData.Email)
|
|
var id int
|
|
var email string
|
|
var hashedPassword string
|
|
err := db.DbPool.QueryRow(context.Background(), query).Scan(&id, &email, &hashedPassword)
|
|
if err != nil {
|
|
http.Error(w, "Invalid email or password", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// compare the provided password with the stored hashed password
|
|
err = bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(requestData.Password))
|
|
if err != nil {
|
|
http.Error(w, "Invalid email or password", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// send back the user data as a JSON response
|
|
response := map[string]interface{}{
|
|
"id": id,
|
|
"email": email,
|
|
}
|
|
if err := json.NewEncoder(w).Encode(response); err != nil {
|
|
http.Error(w, "Failed to encode response as JSON", http.StatusInternalServerError)
|
|
log.Printf("JSON encoding error: %v", err)
|
|
}
|
|
}
|
|
|
|
// very secure login endpoint
|
|
func SecureLoginSql(w http.ResponseWriter, r *http.Request) {
|
|
// decode the json body
|
|
var requestData struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
// declare new json decoder with custom property
|
|
jsonDecoder := json.NewDecoder(r.Body)
|
|
// rejects any unknown fields in the json, more strict
|
|
jsonDecoder.DisallowUnknownFields()
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&requestData); err != nil {
|
|
http.Error(w, "Invalid request format", http.StatusBadRequest)
|
|
log.Println("Failed to decode JSON body or extra fields present:", err)
|
|
return
|
|
}
|
|
|
|
// validate that user is provided
|
|
if requestData.Email == "" || requestData.Password == "" {
|
|
http.Error(w, "Invalid request format", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// retrieve list of existing emails from db
|
|
existingEmails, err := db.FetchEmails()
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
log.Printf("Failed to fetch emails: %v", err)
|
|
return
|
|
}
|
|
|
|
// check if the email exists in the allowed list
|
|
// this step is crucial
|
|
// server will reject ANYTHING that does not match the list
|
|
if !existingEmails[requestData.Email] {
|
|
http.Error(w, "Invalid email or password", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// retrieve the email and password from the database
|
|
var id int
|
|
var email string
|
|
var hashedPassword string
|
|
query := "SELECT id, email, password FROM users WHERE email = $1"
|
|
err = db.DbPool.QueryRow(context.Background(), query, requestData.Email).Scan(&id, &email, &hashedPassword)
|
|
if err != nil {
|
|
http.Error(w, "Invalid email or password", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// compare the provided password and the stored password hashes
|
|
err = bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(requestData.Password))
|
|
if err != nil {
|
|
http.Error(w, "Invalid email or password", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// send back the user data as a json response
|
|
response := map[string]interface{}{
|
|
"id": id,
|
|
"email": email,
|
|
}
|
|
if err := json.NewEncoder(w).Encode(response); err != nil {
|
|
http.Error(w, "Failed to encode response as JSON", http.StatusInternalServerError)
|
|
log.Printf("JSON encoding error: %v", err)
|
|
}
|
|
}
|