package main import ( "bytes" "strings" "time" "encoding/json" "net/url" "syscall/js" "git.ailur.dev/ailur/jsFetch" ) func authorize(deny bool, query url.Values, sleepTime time.Duration) { // Get the token from local storage localStorage := js.Global().Get("localStorage") token := localStorage.Call("getItem", "DONOTSHARE-secretKey").String() // Fetch /api/authorize requestUri, err := url.JoinPath(js.Global().Get("window").Get("location").Get("origin").String(), "/api/authorize") if err != nil { js.Global().Get("document").Call("getElementById", "statusBox").Set("innerText", "Error joining URL: "+err.Error()) return } authorizeBody := map[string]interface{}{ "token": token, "deny": deny, "appId": query.Get("client_id"), "redirectUri": query.Get("redirect_uri"), } // Append the nonce if it exists if query.Has("nonce") { authorizeBody["nonce"] = query.Get("nonce") } // Append the PKCE code challenge if it exists if query.Has("code_challenge") { authorizeBody["PKCECode"] = query.Get("code_challenge") authorizeBody["PKCEMethod"] = query.Get("code_challenge_method") } // Marshal the body body, err := json.Marshal(authorizeBody) if err != nil { js.Global().Get("document").Call("getElementById", "statusBox").Set("innerText", "Error marshaling authorize body: "+err.Error()) return } // Send the request response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body)) if err != nil { js.Global().Get("document").Call("getElementById", "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 { js.Global().Get("document").Call("getElementById", "statusBox").Set("innerText", "Error decoding server response: "+err.Error()) return } // Close the response body err = response.Body.Close() if err != nil { println("Could not close response body: " + err.Error() + ", memory leaks may occur") } if response.StatusCode == 200 { if deny { // Redirect to the redirect_uri with an error denyUri := query.Get("redirect_uri") + "?error=access_denied" if query.Has("state") { denyUri += "&state=" + query.Get("state") } js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate") time.Sleep(sleepTime) js.Global().Get("window").Get("location").Call("replace", denyUri) } else { // Redirect to the redirect_uri with the code allowUri := query.Get("redirect_uri") + "?code=" + responseMap["exchangeCode"].(string) if query.Has("state") { allowUri += "&state=" + query.Get("state") } js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate") time.Sleep(sleepTime) js.Global().Get("window").Get("location").Call("replace", allowUri) } } else if response.StatusCode == 401 { js.Global().Get("document").Call("getElementById", "statusBox").Set("innerText", "OAuth screening failed! We could have just saved you from a bad actor!") } else if response.StatusCode != 500 { js.Global().Get("document").Call("getElementById", "statusBox").Set("innerText", responseMap["error"].(string)) } else { js.Global().Get("document").Call("getElementById", "statusBox").Set("innerText", "Something went wrong! (error code: "+responseMap["code"].(string)+")") } } func main() { // Transition in js.Global().Get("document").Get("documentElement").Get("style").Set("display", "initial") js.Global().Get("swipe-out").Get("classList").Call("add", "swipe-out-animate") var sleepTime = 200 * time.Millisecond if js.Global().Get("window").Call("matchMedia", "(prefers-reduced-motion: reduce)").Get("matches").Bool() { sleepTime = 500 * time.Millisecond } time.Sleep(sleepTime) // Redirect to log-in if not signed in localStorage := js.Global().Get("localStorage") if localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() { js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate") time.Sleep(sleepTime) js.Global().Get("window").Get("location").Call("replace", "/login"+js.Global().Get("window").Get("location").Get("search").String()) } var query url.Values // Parse the url parameters using url.ParseQuery var err error query, err = url.ParseQuery(strings.TrimPrefix(js.Global().Get("window").Get("location").Get("search").String(), "?")) if err != nil { js.Global().Get("document").Call("getElementById", "statusBox").Set("innerText", "Error parsing URL query: "+err.Error()) return } var statusBox = js.Global().Get("document").Call("getElementById", "statusBox") var autoAccept = js.Global().Get("document").Call("getElementById", "autoAccept") // Check if the token is valid requestUri, err := url.JoinPath(js.Global().Get("window").Get("location").Get("origin").String(), "/api/loggedIn") if err != nil { statusBox.Set("innerText", "Error joining URL: "+err.Error()) return } loggedInBody := map[string]interface{}{ "token": localStorage.Call("getItem", "DONOTSHARE-secretKey").String(), } // Marshal the body body, err := json.Marshal(loggedInBody) if err != nil { statusBox.Set("innerText", "Error marshaling signup body: "+err.Error()) return } response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body)) if err != nil { statusBox.Set("innerText", "Error contacting server: "+err.Error()) return } // Check if the response is 200 if response.StatusCode == 401 { // Close the response body err = response.Body.Close() if err != nil { println("Could not close response body: " + err.Error() + ", memory leaks may occur") } // Redirect to log-out if not signed in js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate") time.Sleep(sleepTime) js.Global().Get("window").Get("location").Call("replace", "/logout"+js.Global().Get("window").Get("location").Get("search").String()) return } else if response.StatusCode == 500 { // Read the response var responseMap map[string]interface{} decoder := json.NewDecoder(response.Body) err = decoder.Decode(&responseMap) if err != nil { js.Global().Call("alert", "Error decoding server response: "+err.Error()) return } // Close the response body err = response.Body.Close() if err != nil { println("Could not close response body: " + err.Error() + ", memory leaks may occur") } // Alert the user if the server is down js.Global().Call("alert", "Something went wrong! (error code: "+responseMap["code"].(string)+")") return } // Close the response body err = response.Body.Close() if err != nil { println("Could not close response body: " + err.Error() + ", memory leaks may occur") } if autoAccept.Get("innerText").String() == "0" { // Change the status box to the authorization dialog statusBox.Set("innerText", "Would you like to allow "+js.Global().Get("document").Call("getElementById", "passThrough").Get("innerText").String()+" to access your user information? You will be redirected to "+query.Get("redirect_uri")+" after you make your decision.") // Add an event listener to the Deny button js.Global().Get("document").Call("getElementById", "denyButton").Call("addEventListener", "click", js.FuncOf(func(this js.Value, p []js.Value) interface{} { // We still partially authorize the user to prevent open redirects go authorize(true, query, sleepTime) return nil })) // Add an event listener to the Allow button js.Global().Get("document").Call("getElementById", "allowButton").Call("addEventListener", "click", js.FuncOf(func(this js.Value, p []js.Value) interface{} { go authorize(false, query, sleepTime) return nil })) } else { // Auto-accept the request, as it's from an internal service go authorize(false, query, sleepTime) } // Wait for events select {} }