fulgens/services-src/auth/resources/wasm/login/main.go
2024-09-28 19:41:34 +01:00

258 lines
9.7 KiB
Go

package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
"syscall/js"
"time"
"golang.org/x/crypto/argon2"
)
var currentInputType = 0
func hashPassword(password string, salt []byte) string {
return base64.StdEncoding.EncodeToString(
argon2.IDKey(
[]byte(password),
salt,
32,
19264,
1,
32,
),
)
}
func showInput(inputType int, inputContainer js.Value, usernameBox js.Value, signupButton js.Value, passwordBox js.Value, backButton js.Value, inputNameBox js.Value, statusBox js.Value, nextButton js.Value) {
if inputType == 0 {
// Show login
inputContainer.Get("classList").Call("remove", "hidden")
usernameBox.Get("classList").Call("remove", "hidden")
signupButton.Get("classList").Call("remove", "hidden")
passwordBox.Get("classList").Call("add", "hidden")
backButton.Get("classList").Call("add", "hidden")
inputNameBox.Set("innerText", "Username:")
// Get the current service name
serviceName := js.Global().Get("document").Call("getElementById", "passThrough").Get("innerText").String()
// Set the service name
statusBox.Set("innerText", "Login to your "+serviceName+" account!")
// Set the current input type
currentInputType = 0
} else if inputType == 1 {
inputContainer.Get("classList").Call("remove", "hidden")
signupButton.Get("classList").Call("add", "hidden")
usernameBox.Get("classList").Call("add", "hidden")
passwordBox.Get("classList").Call("remove", "hidden")
backButton.Get("classList").Call("remove", "hidden")
inputNameBox.Get("classList").Call("remove", "hidden")
nextButton.Get("classList").Call("remove", "hidden")
inputNameBox.Get("classList").Call("remove", "hidden")
inputNameBox.Set("innerText", "Password:")
currentInputType = 1
} else if inputType == 2 {
signupButton.Get("classList").Call("add", "hidden")
nextButton.Get("classList").Call("add", "hidden")
backButton.Get("classList").Call("add", "hidden")
inputContainer.Get("classList").Call("add", "hidden")
inputNameBox.Get("classList").Call("add", "hidden")
passwordBox.Get("classList").Call("add", "hidden")
usernameBox.Get("classList").Call("add", "hidden")
currentInputType = 2
}
}
func main() {
// Redirect to app if already signed in
localStorage := js.Global().Get("localStorage")
if !localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
js.Global().Get("window").Get("location").Call("replace", "/authorize"+js.Global().Get("window").Get("location").Get("search").String())
}
var usernameBox = js.Global().Get("document").Call("getElementById", "usernameBox")
var passwordBox = js.Global().Get("document").Call("getElementById", "passwordBox")
var statusBox = js.Global().Get("document").Call("getElementById", "statusBox")
var nextButton = js.Global().Get("document").Call("getElementById", "nextButton")
var backButton = js.Global().Get("document").Call("getElementById", "backButton")
var signupButton = js.Global().Get("document").Call("getElementById", "signupButton")
var inputNameBox = js.Global().Get("document").Call("getElementById", "inputNameBox")
var inputContainer = js.Global().Get("document").Call("getElementById", "inputContainer")
// Show the login screen
showInput(0, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
nextButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
go func() {
if currentInputType == 0 {
if usernameBox.Get("value").IsNull() {
statusBox.Set("innerText", "A username is required!")
return
} else {
statusBox.Set("innerText", "Welcome back, "+usernameBox.Get("value").String()+"!")
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
}
} else if currentInputType == 1 {
password := passwordBox.Get("value").String()
username := usernameBox.Get("value").String()
if passwordBox.Get("value").IsNull() {
statusBox.Set("innerText", "A password is required!")
return
}
showInput(2, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
// Hash the password
statusBox.Set("innerText", "Hashing password...")
fmt.Println("Hashing password...")
// Fetch the salt from the server
body, err := json.Marshal(map[string]interface{}{
"username": username,
})
if err != nil {
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Error marshaling salt body: "+err.Error())
return
}
requestUri, err := url.JoinPath(js.Global().Get("window").Get("location").Get("origin").String(), "/api/loginChallenge")
if err != nil {
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Error joining URL: "+err.Error())
return
}
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
if err != nil {
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Error contacting server: "+err.Error())
return
}
// Get all our ducks in a row
var responseMap map[string]interface{}
// Read the response
decoder := json.NewDecoder(response.Body)
err = decoder.Decode(&responseMap)
if err != nil {
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Error decoding server response: "+err.Error())
return
}
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
// Decode the salt
salt, err := base64.StdEncoding.DecodeString(responseMap["salt"].(string))
if err != nil {
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Error decoding salt: "+err.Error())
return
}
hashedPassword := hashPassword(password, salt)
// Hashed password computed, contact server
statusBox.Set("innerText", "Contacting server...")
signupBody := map[string]interface{}{
"username": username,
"password": hashedPassword,
}
// Marshal the body
body, err = json.Marshal(signupBody)
if err != nil {
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Error marshaling signup body: "+err.Error())
return
}
// Send the password to the server
requestUri, err = url.JoinPath(js.Global().Get("window").Get("location").Get("origin").String(), "/api/login")
if err != nil {
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Error joining URL: "+err.Error())
return
}
// Send the request
fmt.Println("Sending request to", requestUri)
response, err = http.Post(requestUri, "application/json", bytes.NewReader(body))
if err != nil {
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Error contacting server: "+err.Error())
return
}
// Read the response
fmt.Println("Reading response...")
decoder = json.NewDecoder(response.Body)
err = decoder.Decode(&responseMap)
if err != nil {
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Error decoding server response: "+err.Error())
return
}
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
if response.StatusCode == 200 {
// Logged in
fmt.Println("Logged in!")
statusBox.Set("innerText", "Setting up encryption keys...")
localStorage.Call("setItem", "DONOTSHARE-secretKey", responseMap["key"].(string))
localStorage.Call("setItem", "DONOTSHARE-clientKey", hashPassword(password, []byte("fg-auth-client")))
// Redirect to app
statusBox.Set("innerText", "Welcome!")
time.Sleep(time.Second)
js.Global().Get("window").Get("location").Call("replace", "/authorize"+js.Global().Get("window").Get("location").Get("search").String())
} else if response.StatusCode == 401 {
// Login failed
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Username or password incorrect!")
} else {
// Unknown error
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Something went wrong! (error code: "+responseMap["code"].(string)+")")
}
}
}()
return nil
}))
backButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
go func() {
showInput(0, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
return
}()
return nil
}))
signupButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
js.Global().Get("window").Get("location").Call("replace", "/signup"+js.Global().Get("window").Get("location").Get("search").String())
return nil
}))
// Wait for events
select {}
}