package main import ( "context" "encoding/json" "fmt" "log" "net/http" "time" "github.com/jackc/pgx/v5" ) type CreateIdentityRequest struct { Username string `json:"username"` Password string `json:"password"` FirstName string `json:"first_name"` LastName string `json:"last_name"` } func createLocalHandler(w http.ResponseWriter, r *http.Request) { var hashText string var salt []byte var err error var checkExisting pgx.Row var req CreateIdentityRequest var accountid string response := map[string]interface{}{} 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) response["status"] = "error" response["message"] = "HTTP POST Method not allowed" goto ExitAPICall } // XXX Temporary Account Handler accountid = r.Header.Get("Account") if accountid == "" { w.WriteHeader(http.StatusUnauthorized) response["status"] = "error" response["message"] = "Missing Authentication header" goto ExitAPICall } // Optional: enforce content type if r.Header.Get("Content-Type") != "application/json" { w.WriteHeader(http.StatusUnsupportedMediaType) response["status"] = "error" response["message"] = "Content-Type must be application/json" goto ExitAPICall } // 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 err = json.NewDecoder(r.Body).Decode(&req) if err != nil { w.WriteHeader(http.StatusInternalServerError) response["status"] = "error" response["message"] = "Invalid JSON: " + err.Error() goto ExitAPICall } log.Println("Received Identity Request: AccountID:", accountid, "Username:", req.Username, "Password:", req.Password, "First Name:", req.FirstName, "LastName:", req.LastName) // Check if policy with the same name already exists for the account checkExisting = pool.QueryRow(context.Background(), "SELECT FROM identities WHERE accountid = $1 AND provider_user_id = $2", accountid, req.Username) err = checkExisting.Scan() if err == nil { response["status"] = "fail" response["message"] = "Identity " + req.Username + " already exists: " w.WriteHeader(http.StatusConflict) goto ExitAPICall } salt, _ = generateSalt() hashText = hashPassword(req.Password, salt) _, err = pool.Exec(context.Background(), "INSERT INTO identities (accountid, provider, provider_user_id, password_hash) VALUES($1, $2, $3, $4) ON CONFLICT DO NOTHING", accountid, "local", req.Username, hashText) if err != nil { response["status"] = "fail" response["message"] = "Internal Server Error: " + err.Error() w.WriteHeader(http.StatusInternalServerError) goto ExitAPICall } w.WriteHeader(http.StatusOK) response["status"] = "success" ExitAPICall: response["timestamp"] = fmt.Sprintf("%d", time.Now().Unix()) json.NewEncoder(w).Encode(response) }