sermoni

"Service monitor" / cronjob status service
Log | Files | Refs

commit 5e06ffde58df78dfe605c3200bd67e8c96e1b0c3
parent 1c670e02d93922b38d35a4b521078ef5e5efd75b
Author: Vetle Haflan <vetle@haflan.dev>
Date:   Mon, 13 Apr 2020 00:02:14 +0200

Start work on requests and their handlers (+init request)

Diffstat:
Minternal/http/auth.go | 55+++++++++++++++++++++++++++++++++++++++++++++++++++----
Minternal/http/http.go | 2+-
Mui/src/App.vue | 13+++++++++++--
Aui/src/requests.js | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 153 insertions(+), 7 deletions(-)

diff --git a/internal/http/auth.go b/internal/http/auth.go @@ -1,18 +1,44 @@ package http import ( + "encoding/json" "log" "net/http" + "math/rand" + "strings" + "time" + "github.com/gorilla/securecookie" "github.com/gorilla/sessions" ) -func authorized(session *sessions.Session) bool { - val := session.Values["authenticated"] - auth, ok := val.(bool) - return ok && auth +// Deal with login, logout, and general security stuff + +// initHandler checks two things: +// 1. If a CSRF token exists for the given session. Otherwise it creates it +// 2. Whether the session is authenticated +// It then returns an object on the form {"auth": true, "csrftoken": "<long string>"} +// This is requested immediately when the website is loaded. +func initHandler(w http.ResponseWriter, r *http.Request) { + session, _ := store.Get(r, "session") + val := session.Values["csrftoken"] + token, ok := val.(string) + if !ok { + token = string(securecookie.GenerateRandomKey(32)) + session.Values["csrftoken"] = token + session.Save(r, w) // TODO: Error handling, as always + } + b, _ := json.Marshal(struct { + CSRFToken string `json:"csrftoken"` + Authenticated bool `json:"authenticated"` + }{ + token, + authorized(session), + }) + w.Write(b) } + func loginHandler(w http.ResponseWriter, r *http.Request) { session, _ := store.Get(r, "session") if authorized(session) { @@ -34,6 +60,12 @@ func logoutHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Logged out")) } +func authorized(session *sessions.Session) bool { + val := session.Values["authenticated"] + auth, ok := val.(bool) + return ok && auth +} + // Middleware for the simple sermoni authentication scheme func auth(handler http.HandlerFunc) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -47,3 +79,18 @@ func auth(handler http.HandlerFunc) http.Handler { handler.ServeHTTP(w, r) }) } + +// not cryptosecure, only for testing! +// thanks: https://yourbasic.org/golang/generate-random-string/ +func temporary32CharRandomString() string { + rand.Seed(time.Now().UnixNano()) + chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZÅÄÖ" + + "abcdefghijklmnopqrstuvwxyzåäö" + + "0123456789") + length := 32 + var b strings.Builder + for i := 0; i < length; i++ { + b.WriteRune(chars[rand.Intn(len(chars))]) + } + return b.String() +} diff --git a/internal/http/http.go b/internal/http/http.go @@ -22,8 +22,8 @@ func StartServer(port int) { router := mux.NewRouter() router.HandleFunc("/", homeHandler) + router.HandleFunc("/init", initHandler) router.HandleFunc("/login", loginHandler) - //router.HandleFunc("/logout", logoutHandler) router.Handle("/logout", auth(logoutHandler)) router.Handle("/services", auth(getServices)).Methods("GET") diff --git a/ui/src/App.vue b/ui/src/App.vue @@ -19,6 +19,7 @@ import Login from "./Login.vue"; import Events from "./Events.vue"; import Services from "./Services.vue"; + import api from "./requests.js"; export default { name: "App", components: {Login, Eye, Events, Services}, @@ -44,8 +45,16 @@ } }, mounted() { - // TODO: Send request to server to figure out if an authenticated - // session is active + api.init( + successData => { + console.log(successData); + api.login() + }, + errorData => { + console.log(errorData); + // TODO: set color of header to '#f5c6cb' + } + ) } } </script> diff --git a/ui/src/requests.js b/ui/src/requests.js @@ -0,0 +1,90 @@ + +/** + * Create URL parameters from a JSON object + */ +function params(parameterObject) { + const parameters = new URLSearchParams(); + for (let key in parameterObject) { + let value = parameterObject[key]; + if (Array.isArray(value)) { + for (let i = 0; i < value.length; i++) { + parameters.append(key, value[i]); + } + } else { + parameters.set(key, value); + } + } + return parameters.toString(); +} + +var csrfToken; + +// Send a request to server. Takes request description, jsonRequest, +// on the following format +// { +// url: "/services" , +// method: "POST", +// expectedStatus: 201, +// error: errData => { console.log(errData); }, +// success: successData => { console.log(successData); } +// } +function request(jsonRequest) { + if (!jsonRequest.url) { + console.error("No URL provided in the request object"); + return; + } + let xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (!jsonRequest.expectedStatus) { + jsonRequest.expectedStatus = 200 + } + if (this.readyState === XMLHttpRequest.DONE) { + if (this.status !== jsonRequest.expectedStatus) { + if (jsonRequest.error) { + jsonRequest.error(this.status, JSON.parse(this.responseText)); + } else { + console.error(this.status + ": " + this.responseText); + } + } else if (jsonRequest.success) { + jsonRequest.success(JSON.parse(this.responseText)); + } + } + }; + if (jsonRequest.method) { + xhttp.open(jsonRequest.method, jsonRequest.url, true); + } else { + xhttp.open("GET", jsonRequest.url, true); + } + xhttp.setRequestHeader("X-CSRFToken", csrfToken); + if (jsonRequest.data) { + xhttp.send(JSON.stringify(jsonRequest.data)); + } else { + xhttp.send(); + } +} + +function init(successHandler, errorHandler) { + request({ + url: "/init", + success: (data) => { + csrfToken = data.csrftoken; + if (successHandler) { + successHandler(data); + } + }, + error: errorHandler ? errorHandler : (data) => console.log(data) + }); +} + +function login(successHandler, errorHandler) { + request({ + url: "/login", + success: successHandler, + error: errorHandler + }); +} + +export default { + init, + login, +}