package main import ( "bytes" "time" "crypto/ed25519" "encoding/base64" "encoding/json" "syscall/js" "git.ailur.dev/ailur/jsFetch" "golang.org/x/crypto/argon2" ) func main() { localStorage := js.Global().Get("localStorage") status := js.Global().Get("document").Call("getElementById", "status") loginButton := js.Global().Get("document").Call("getElementById", "login") username := js.Global().Get("document").Call("getElementById", "username") password := js.Global().Get("document").Call("getElementById", "password") if localStorage.Call("getItem", "server").IsNull() { localStorage.Call("setItem", "server", "https://chat.ailur.dev:1974") } if !localStorage.Call("getItem", "token").IsNull() { js.Global().Get("location").Set("href", "../") return } loginButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} { go func() { username.Set("disabled", true) password.Set("disabled", true) loginButton.Set("disabled", true) status.Set("innerText", "Generating encryption keys...") hash := argon2.IDKey( []byte(password.Get("value").String()), []byte(username.Get("value").String()), 32, 19264, 1, 32, ) status.Set("innerText", "Retrieving challenge from server...") jsonBody, err := json.Marshal(map[string]interface{}{ "username": username.Get("value").String(), }) if err != nil { status.Set("innerText", "Failed to encode request: "+err.Error()) username.Set("disabled", false) password.Set("disabled", false) loginButton.Set("disabled", false) return } response, err := jsFetch.Post(localStorage.Call("getItem", "server").String()+"/api/loginChallenge", "application/json", bytes.NewReader(jsonBody)) if err != nil { status.Set("innerText", "Failed to contact server: "+err.Error()) username.Set("disabled", false) password.Set("disabled", false) loginButton.Set("disabled", false) return } if response.StatusCode != 200 { if response.StatusCode == 500 { status.Set("innerText", "Something went wrong on our end. Please try again later.") } else { var body map[string]interface{} err := json.NewDecoder(response.Body).Decode(&body) if err != nil { status.Set("innerText", "Failed to decode response: "+err.Error()) } else { status.Set("innerText", body["error"].(string)) } } username.Set("disabled", false) password.Set("disabled", false) loginButton.Set("disabled", false) return } status.Set("innerText", "Signing challenge...") var body map[string]interface{} err = json.NewDecoder(response.Body).Decode(&body) if err != nil { status.Set("innerText", "Failed to decode response: "+err.Error()) username.Set("disabled", false) password.Set("disabled", false) loginButton.Set("disabled", false) return } challenge, err := base64.StdEncoding.DecodeString(body["nonce"].(string)) if err != nil { status.Set("innerText", "Failed to decode challenge: "+err.Error()) username.Set("disabled", false) password.Set("disabled", false) loginButton.Set("disabled", false) return } signature := ed25519.Sign(ed25519.NewKeyFromSeed(hash), challenge) status.Set("innerText", "Contacting server...") jsonBody, err = json.Marshal(map[string]interface{}{ "username": username.Get("value").String(), "signature": base64.StdEncoding.EncodeToString(signature), "challenge": body["token"].(string), }) if err != nil { status.Set("innerText", "Failed to encode request: "+err.Error()) username.Set("disabled", false) password.Set("disabled", false) loginButton.Set("disabled", false) return } response, err = jsFetch.Post(localStorage.Call("getItem", "server").String()+"/api/login", "application/json", bytes.NewReader(jsonBody)) if err != nil { status.Set("innerText", "Failed to contact server: "+err.Error()) username.Set("disabled", false) password.Set("disabled", false) loginButton.Set("disabled", false) return } if response.StatusCode != 200 { if response.StatusCode == 500 { status.Set("innerText", "Something went wrong on our end. Please try again later.") } else { var body map[string]interface{} err := json.NewDecoder(response.Body).Decode(&body) if err != nil { status.Set("innerText", "Failed to decode response: "+err.Error()) } else { status.Set("innerText", body["error"].(string)) } } username.Set("disabled", false) password.Set("disabled", false) loginButton.Set("disabled", false) return } err = json.NewDecoder(response.Body).Decode(&body) if err != nil { status.Set("innerText", "Failed to decode response: "+err.Error()) username.Set("disabled", false) password.Set("disabled", false) loginButton.Set("disabled", false) return } localStorage.Call("setItem", "token", body["token"].(string)) localStorage.Call("setItem", "username", username.Get("value").String()) localStorage.Call("setItem", "userId", body["userId"].(string)) status.Set("innerText", "Welcome, "+username.Get("value").String()+", to LChat!") time.Sleep(300 * time.Millisecond) js.Global().Get("location").Set("href", "../") }() return nil })) select {} }