228 lines
7.8 KiB
Go
228 lines
7.8 KiB
Go
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 {}
|
|
}
|