1

I'm trying to create a web app using vanilla Golang (not Gin), and SvelteJS for the frontend. The web app is supposed to login to an external website (my stock broker's website), and grab the session cookie and JWT from that login request to use in subsequent requests to their API endpoint. The problem I'm facing with my current code is, the server console window shows a [2023-04-07T06:23:17.916Z] "POST /api/broker-login" Error (404): "Not found" error, yet the Chrome > Network for the same call says 405 Method Not Allowed?

What you'll find (I think) is that my file paths and handlers ARE setup correctly and pointing to the right places, AND I'm sending a POST request to my /api/broker-login endpoint. Keep in mind, I'm a newbie when it comes to JS frontend stuff, especially Svelte as this is my first crack at it. I'll try and do my best to showcase my project as concisely as possible below:

Project structure: projectStructure

main.go:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "html/template"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "strings"
    "time"

    "github.com/joho/godotenv"
)

func generateDeviceID() string {
    // return uuid.New().String()
    return "xxxxxxxxxxxxxxxxxxxxx" // static for testing
}

type sessionData struct {
    Jwt     string
    Cookies []*http.Cookie
}

// Global variable to store the session data
var currentSessionData sessionData

// LoginRequest represents the payload for the login request
type LoginPayload struct {
    Username string `json:"username"`
    Password string `json:"password"`
    DeviceID string `json:"deviceId"`
}

type LoginResponse struct {
    Data struct {
        Jwt string `json:"jwt"`
    } `json:"Data"`
}

func main() {

    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }

    http.HandleFunc("/", homeHandler)
    http.HandleFunc("/api/broker-login", brokerLoginHandler)

    log.Fatal(http.ListenAndServe(":8081", nil))
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    t, err := template.ParseFiles("./frontend/public/index.html")
    if err != nil {
        http.Error(w, "Error loading template", http.StatusInternalServerError)
        return
    }
    t.Execute(w, nil)
}

func brokerLoginHandler(w http.ResponseWriter, r *http.Request) {

    if r.Method != "POST" {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    loginURL := "https://api.xxx.co/v1/portaldata/api/login?dev=false&target=null"

    // Use the credentials from the .env file instead of getting them from the request
    email := os.Getenv("BROKER_EMAIL")
    password := os.Getenv("BROKER_PASSWORD")

    fmt.Println(email, password)

    // Generate a deviceID and store it in a cookie
    deviceID := generateDeviceID()
    deviceIDCookie := &http.Cookie{
        Name:    "device_id",
        Value:   deviceID,
        Expires: time.Now().Add(365 * 24 * time.Hour), // Set the cookie to expire in 1 year
    }
    http.SetCookie(w, deviceIDCookie)

    // Prepare the request payload for the broker's API
    payload := LoginPayload{
        Username: email,
        Password: password,
        DeviceID: deviceID,
    }
    fmt.Println(payload)

    // Convert the payload to JSON
    payloadBytes, err := json.Marshal(payload)
    if err != nil {
        http.Error(w, fmt.Sprintf("failed to marshal payload: %v", err), http.StatusInternalServerError)
        return
    }
    fmt.Println(payload)

    // Send the login request to the broker's API
    resp, err := http.Post(loginURL, "application/json", strings.NewReader(string(payloadBytes)))
    fmt.Println(resp)
    if err != nil {
        log.Printf("Error in brokerLoginHandler: %v\n", err)
        http.Error(w, "Failed to log in to broker", http.StatusInternalServerError)
        return
    }
    defer resp.Body.Close()

    bodyBytes, _ := ioutil.ReadAll(resp.Body)
    var loginResponse LoginResponse
    json.Unmarshal(bodyBytes, &loginResponse)

    jwt := loginResponse.Data.Jwt
    cookies := resp.Cookies()

    newSessionData := sessionData{
        Jwt:     jwt,
        Cookies: cookies,
    }

    // Update the global session data
    currentSessionData = newSessionData

    fmt.Printf("Login Successful!\n")
}

main.js:

// Add event listener to the "Login to Broker" button
export async function loginToBroker() {
    try {
        const response = await fetch("/api/broker-login", { method: "POST" });
        console.log(response) // trying to test...

        if (response.ok) {
            // Change the session status text to green
            const statusText = document.querySelector("#broker-status");
            statusText.textContent = "Connected to broker";
            statusText.classList.remove("disconnected");
            statusText.classList.add("connected");
        } else {
            console.error("Failed to log in to broker:", await response.text());
        }
    } catch (error) {
        console.error("Error logging in to broker:", error);
    }
}

App.svelte:

<script>
    import OrderForm from "./components/OrderForm.svelte";
</script>

<main>
    <h1>Stock App</h1>
    <SessionStatus />
</main>

ChromeTraceback

I saw in another answer that I might have been missing the export on the JS async function, so I added it, same issue with and without the export.

Does someone know what's going on here? Could it be a CORS issue? And if so, how to fix it? I've made this exact same program in Python using the requests library without issue, so I'm not sure what's going on here.

I should clarify, I run the backend server on port 8081 and the front end on the same port, and navigate to the localhost:8081 endpoint to see my login to broker button. Clicking it gives this grief. I've tried adding in some fmt.Println's to the brokerLoginHandler method as you see, but those aren't even triggered, so it's like the brokerLoginHandler isn't even being hit? Hopefully that helps someone.

3
  • Sounds very much like a problem with developer.mozilla.org/en-US/docs/Glossary/Preflight_request Commented Apr 7, 2023 at 6:59
  • what happens if you put fmt.Println(r.Method) as the first line in brokerLoginHandler? You're getting a 405 back, which makes me think you're either a) not actually getting a post request, or b) possibly have an old version of your server running that is checking for a different method. I don't see where the server would be logging requests so that's a little confusing as well when you say 'the server console window shows a [2023-04-07T06:23:17.916Z] "POST /api/broker-login" Error (404): "Not found" error' Commented Apr 7, 2023 at 16:36
  • I figured out the issue had to do with the way I was trying to access the index.html file from within the folder structure, as well as the export clause was screwing things up. Why this didn't happen yesterday was what was bugging me, maybe it had to do with the browser or server cache? Not sure, but I'll post my answer here shortly. Thanks for the insight! Commented Apr 7, 2023 at 19:48

1 Answer 1

0

After a restart of the computer, I was hit with another issue that helped me figure this out. It wasn't to do with CORS, however it's good to know that I noticed an OPTION API call happening prior to the POST request that gets sent to the broker during a real login attempt, but that doesn't have anything to do with this issue.

I noticed that in my homeHandler, I needed to change this line:

t, err := template.ParseFiles("./frontend/public/index.html")

to

t, err := template.ParseFiles("../frontend/public/index.html")

...because of how my folder structure is set up (D'OH).

Then, I took out the export in the main.js method as that was triggering an unexpected token 'export' error. Restarting both backend/frontend servers, clearing browser cache and refreshing the page, I clicked my login button and voila, it worked. Thank goodness it wasn't a CORS issue lol. Thanks everyone!

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.