package main import ( "context" "crypto/rsa" "encoding/json" "fmt" "libshared" "log" "net/http" "github.com/jackc/pgx/v5" ) type AuthenticateRequest struct { Accountid int64 `json:"accountid,string"` Username string `json:"username"` Password string `json:"password"` } type AuthenticateResponse struct { Token string `json:"token"` } var privateKey *rsa.PrivateKey func authenticateHandler(w http.ResponseWriter, r *http.Request) { var authenticaterequest AuthenticateRequest var err error var checkExisting pgx.Row var hashText string var ok bool var token string //secret := []byte("super-secret-key") w.Header().Set("Content-Type", "application/json") // Only allow POST method if r.Method != http.MethodPost { //http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) w.WriteHeader(http.StatusMethodNotAllowed) w.Header().Set("Content-Type", "application/json") apiresponse := libshared.NewAPIResponse("fail", "POST method required", AuthenticateResponse{}) json.NewEncoder(w).Encode(apiresponse) return } // Optional: enforce content type if r.Header.Get("Content-Type") != "application/json" { apiresponse := libshared.NewAPIResponse("fail", "Content-Type must be application/json", AuthenticateResponse{}) w.WriteHeader(http.StatusUnsupportedMediaType) //response["status"] = "error" //response["message"] = "Content-Type must be application/json" w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(apiresponse) return } // Read body with size limit (protect against huge requests) const maxBodyBytes = 1 << 20 // 1 MB r.Body = http.MaxBytesReader(w, r.Body, maxBodyBytes) // Decode JSON from request body directly into struct log.Println(r.Body) err = json.NewDecoder(r.Body).Decode(&authenticaterequest) if err != nil { apiresponse := libshared.NewAPIResponse("fail", "Invalid JSON: "+err.Error(), AuthenticateResponse{}) w.WriteHeader(http.StatusInternalServerError) //response["status"] = "error" //response["message"] = "Invalid JSON: " + err.Error() w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(apiresponse) return } log.Println(authenticaterequest) checkExisting = libshared.Pool.QueryRow(context.Background(), "SELECT password_hash FROM identities WHERE accountid = $1 AND provider = $2 AND provider_user_id = $3", authenticaterequest.Accountid, "local", authenticaterequest.Username) log.Println("Received Authentication Request: AccountID:", authenticaterequest.Accountid, "Username:", authenticaterequest.Username, "Password:", authenticaterequest.Password) err = checkExisting.Scan(&hashText) if err != nil { apiresponse := libshared.NewAPIResponse("fail", "User account does not exist", AuthenticateResponse{}) log.Println(err) w.WriteHeader(http.StatusUnauthorized) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(apiresponse) return } log.Println(hashText) ok = verifyPassword(authenticaterequest.Password, hashText) if ok == false { apiresponse := libshared.NewAPIResponse("fail", "Incorrect username or password", AuthenticateResponse{}) //response["message"] = "Bad password" w.WriteHeader(http.StatusUnauthorized) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(apiresponse) return } token, err = libshared.CreateJWT(privateKey, fmt.Sprintf("%d", authenticaterequest.Accountid), authenticaterequest.Username, "user") if err != nil { w.WriteHeader(http.StatusInternalServerError) apiresponse := libshared.NewAPIResponse("fail", "Failed to create JWT", AuthenticateResponse{}) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(apiresponse) return } apiresponse := libshared.NewAPIResponse("success", "Authentication successful", AuthenticateResponse{Token: token}) w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(apiresponse) }