diff --git a/client/src/App.vue b/client/src/App.vue
index 1ed034c..26f31c4 100644
--- a/client/src/App.vue
+++ b/client/src/App.vue
@@ -36,24 +36,37 @@ async function tryLogin() {
}
async function tryLogout() {
- logout();
+ console.info("Logging out...");
authState = AuthState.Unauthenticated;
+ logout();
}
-function deAuthenticatedCallback() {
+function UnauthorizedCallback() {
console.info("Unauthenticated");
authState = AuthState.Unauthenticated;
}
-onMounted(async() => {
- setup(deAuthenticatedCallback);
+async function checkAuth() {
+ console.info("Checking Auth State...");
let res = await checkAuthentication();
authState = res.auth;
loginDisabled = false;
if (authState === AuthState.Authenticated) {
console.info("Already Authenticated ", authState);
+ } else if (res.error == null) {
+ console.info("Unauthorized");
}
else console.info("Check Authentication error",res.error);
+}
+
+onMounted(async() => {
+ setup(UnauthorizedCallback);
+ await checkAuth();
+ setInterval(function () {
+ if (authState === AuthState.Authenticated) {
+ checkAuth();
+ }
+ }.bind(this), 120000);
});
@@ -97,11 +110,11 @@ onMounted(async() => {
diff --git a/client/src/api.ts b/client/src/api.ts
index 9d89950..51d3c03 100644
--- a/client/src/api.ts
+++ b/client/src/api.ts
@@ -5,19 +5,24 @@ const httpTransport = new HTTPTransport("http://"+ window.location.host +"/api")
const manager = new RequestManager([httpTransport], () => crypto.randomUUID());
const client = new Client(manager);
-let deAuthenticatedCallback;
+let UnauthorizedCallback: Function;
-export function setup(_deAuthenticatedCallback: () => void) {
- deAuthenticatedCallback = _deAuthenticatedCallback;
+export function setup(_UnauthorizedCallback: () => void) {
+ UnauthorizedCallback = _UnauthorizedCallback;
}
export async function apiCall(method: string, params: Record): Promise{
+ console.debug("Starting API Call...");
try {
const result = await client.request({method, params});
console.debug("api call result", result);
return { Data: result, Error: null};
} catch (ex){
- console.debug("api call epic fail", ex);
+ if (ex == "Error: Unauthorized") {
+ UnauthorizedCallback();
+ } else {
+ console.debug("api call epic fail", ex);
+ }
return { Data: null, Error: ex};
}
}
@@ -57,7 +62,10 @@ export async function checkAuthentication() {
}
} else window.localStorage.setItem("commit_hash", response.data.commit_hash);
return {auth: 2, error: null};
- } catch (error) {
+ } catch (error: any) {
+ if (error.response.status == 401) {
+ return {auth: 0, error: null};
+ }
return {auth: 0, error: error};
}
}
\ No newline at end of file
diff --git a/cmd/main.go b/cmd/main.go
index 4d488d1..4919841 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -34,7 +34,7 @@ func main() {
os.Exit(1)
}
- slog.Info("Validating Config")
+ slog.Info("Validating Config...")
if *applyPtr {
slog.Info("Applying Config...")
@@ -54,7 +54,7 @@ func main() {
slog.Info("Starting Webserver...")
server.StartWebserver(conf, apiHandler)
- slog.Info("Ready")
+ slog.Info("Ready.")
// Handle Exit Signal
sigChan := make(chan os.Signal, 1)
diff --git a/pkg/definitions/config.go b/pkg/definitions/config.go
index f1faa7a..8061a42 100644
--- a/pkg/definitions/config.go
+++ b/pkg/definitions/config.go
@@ -14,7 +14,6 @@ type Config struct {
func ValidateConfig(conf *Config) error {
val := validator.New()
- slog.Info("Registering validator")
val.RegisterValidation("test", nilIfOtherNil)
return val.Struct(conf)
}
diff --git a/pkg/jsonrpc/handler.go b/pkg/jsonrpc/handler.go
index 7aae6f3..d6598fe 100644
--- a/pkg/jsonrpc/handler.go
+++ b/pkg/jsonrpc/handler.go
@@ -10,6 +10,7 @@ import (
"runtime/debug"
"golang.org/x/exp/slog"
+ "nfsense.net/nfsense/pkg/session"
)
type Handler struct {
@@ -25,7 +26,7 @@ func NewHandler(maxRequestSize int64) *Handler {
}
}
-func (h *Handler) HandleRequest(ctx context.Context, r io.Reader, w io.Writer) error {
+func (h *Handler) HandleRequest(ctx context.Context, s *session.Session, r io.Reader, w io.Writer) error {
defer func() {
if r := recover(); r != nil {
slog.Error("Recovered Panic Handling JSONRPC Request", fmt.Errorf("%v", r), "stack", debug.Stack())
@@ -52,6 +53,10 @@ func (h *Handler) HandleRequest(ctx context.Context, r io.Reader, w io.Writer) e
return respondError(w, req.ID, ErrMethodNotFound, fmt.Errorf("Unsupported Jsonrpc version %v", req.Jsonrpc))
}
+ if s == nil {
+ return respondError(w, req.ID, 401, fmt.Errorf("Unauthorized"))
+ }
+
method, ok := h.methods[req.Method]
if !ok {
return respondError(w, req.ID, ErrMethodNotFound, fmt.Errorf("Unknown Method %v", req.Method))
diff --git a/pkg/server/api.go b/pkg/server/api.go
index 4986853..eb5cd81 100644
--- a/pkg/server/api.go
+++ b/pkg/server/api.go
@@ -8,14 +8,17 @@ import (
"time"
"golang.org/x/exp/slog"
+ "nfsense.net/nfsense/pkg/session"
)
func HandleAPI(w http.ResponseWriter, r *http.Request) {
- _, s := GetSession(r)
+ slog.Info("Api Handler hit")
+ _, s := session.GetSession(r)
if s == nil {
+ // Fallthrough after so that jsonrpc can still deliver a valid jsonrpc error
w.WriteHeader(http.StatusUnauthorized)
- return
}
+
defer func() {
if r := recover(); r != nil {
slog.Error("Recovered Panic Handling HTTP API Request", fmt.Errorf("%v", r), "stack", debug.Stack())
@@ -23,10 +26,10 @@ func HandleAPI(w http.ResponseWriter, r *http.Request) {
return
}
}()
- ctx, cancel := context.WithTimeout(context.WithValue(r.Context(), SessionKey, s), time.Second*10)
+ ctx, cancel := context.WithTimeout(context.WithValue(r.Context(), session.SessionKey, s), time.Second*10)
defer cancel()
- err := apiHandler.HandleRequest(ctx, r.Body, w)
+ err := apiHandler.HandleRequest(ctx, s, r.Body, w)
if err != nil {
slog.Error("Handling HTTP API Request", err)
}
diff --git a/pkg/server/server.go b/pkg/server/server.go
index 51b99fe..25f4b6f 100644
--- a/pkg/server/server.go
+++ b/pkg/server/server.go
@@ -10,6 +10,7 @@ import (
"nfsense.net/nfsense/pkg/definitions"
"nfsense.net/nfsense/pkg/jsonrpc"
+ "nfsense.net/nfsense/pkg/session"
)
var server http.Server
@@ -32,7 +33,7 @@ func StartWebserver(conf *definitions.Config, _apiHandler *jsonrpc.Handler) {
stopCleanup = make(chan struct{})
- go CleanupSessions(stopCleanup)
+ go session.CleanupSessions(stopCleanup)
go func() {
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
diff --git a/pkg/server/session.go b/pkg/server/session.go
index 908174f..b785bb7 100644
--- a/pkg/server/session.go
+++ b/pkg/server/session.go
@@ -4,94 +4,17 @@ import (
"encoding/json"
"io"
"net/http"
- "runtime/debug"
- "sync"
"time"
- "github.com/google/uuid"
"golang.org/x/exp/slog"
+ "nfsense.net/nfsense/pkg/session"
)
-type SessionKeyType string
-
-const SessionKey SessionKeyType = "session"
-const SessionCookieName string = "session"
-
-type Session struct {
- Username string
- Expires time.Time
- // TODO Add []websocket.Conn pointer to close all active websockets, alternativly do this via context cancelation
-}
-
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
-type SessionResponse struct {
- CommitHash string `json:"commit_hash"`
-}
-
-var sessionsSync sync.Mutex
-var sessions map[string]*Session = map[string]*Session{}
-
-var CommitHash = func() string {
- if info, ok := debug.ReadBuildInfo(); ok {
- for _, setting := range info.Settings {
- if setting.Key == "vcs.revision" {
- return setting.Value
- }
- }
- }
- return "asd"
-}()
-
-func GetSession(r *http.Request) (string, *Session) {
- c, err := r.Cookie("session")
- if err != nil {
- return "", nil
- }
- s, ok := sessions[c.Value]
- if ok {
- return c.Value, s
- }
- return "", nil
-}
-
-func GenerateSession(w http.ResponseWriter, username string) {
- id := uuid.New().String()
- expires := time.Now().Add(time.Minute * 5)
- sessionsSync.Lock()
- defer sessionsSync.Unlock()
- sessions[id] = &Session{
- Username: username,
- Expires: expires,
- }
- http.SetCookie(w, &http.Cookie{Name: SessionCookieName, HttpOnly: true, SameSite: http.SameSiteStrictMode, Value: id, Expires: expires})
-}
-
-func CleanupSessions(stop chan struct{}) {
- tick := time.NewTicker(time.Minute)
- for {
- select {
- case <-tick.C:
- ids := []string{}
- sessionsSync.Lock()
- for id, s := range sessions {
- if time.Now().After(s.Expires) {
- ids = append(ids, id)
- }
- }
- for _, id := range ids {
- delete(sessions, id)
- }
- sessionsSync.Unlock()
- case <-stop:
- return
- }
- }
-}
-
func HandleLogin(w http.ResponseWriter, r *http.Request) {
buf, err := io.ReadAll(r.Body)
if err != nil {
@@ -106,7 +29,7 @@ func HandleLogin(w http.ResponseWriter, r *http.Request) {
}
if req.Username == "admin" && req.Password == "12345" {
slog.Info("User Login Successfull")
- GenerateSession(w, req.Username)
+ session.GenerateSession(w, req.Username)
w.WriteHeader(http.StatusOK)
return
}
@@ -114,25 +37,21 @@ func HandleLogin(w http.ResponseWriter, r *http.Request) {
}
func HandleLogout(w http.ResponseWriter, r *http.Request) {
- http.SetCookie(w, &http.Cookie{Name: SessionCookieName, HttpOnly: true, SameSite: http.SameSiteStrictMode, Value: "", Expires: time.Now()})
+ http.SetCookie(w, session.GetCookie("", time.Now()))
w.WriteHeader(http.StatusOK)
}
func HandleSession(w http.ResponseWriter, r *http.Request) {
- id, s := GetSession(r)
+ id, s := session.GetSession(r)
if s == nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
- sessionsSync.Lock()
- defer sessionsSync.Unlock()
- if s != nil {
- s.Expires = time.Now().Add(time.Minute * 5)
- }
- http.SetCookie(w, &http.Cookie{Name: SessionCookieName, HttpOnly: true, SameSite: http.SameSiteStrictMode, Value: id, Expires: s.Expires})
+ session.ExtendSession(s)
+ http.SetCookie(w, session.GetCookie(id, s.Expires))
w.WriteHeader(http.StatusOK)
- resp := SessionResponse{
- CommitHash: CommitHash,
+ resp := session.SessionResponse{
+ CommitHash: session.CommitHash,
}
res, err := json.Marshal(resp)
if err != nil {
diff --git a/pkg/server/websocket.go b/pkg/server/websocket.go
index 92e60cb..86299a6 100644
--- a/pkg/server/websocket.go
+++ b/pkg/server/websocket.go
@@ -9,17 +9,18 @@ import (
"time"
"golang.org/x/exp/slog"
+ "nfsense.net/nfsense/pkg/session"
"nhooyr.io/websocket"
)
func HandleWebsocketAPI(w http.ResponseWriter, r *http.Request) {
- _, s := GetSession(r)
+ _, s := session.GetSession(r)
if s == nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
- ctx, cancel := context.WithCancel(context.WithValue(r.Context(), SessionKey, s))
+ ctx, cancel := context.WithCancel(context.WithValue(r.Context(), session.SessionKey, s))
defer cancel()
c, err := websocket.Accept(w, r, nil)
if err != nil {
@@ -51,7 +52,7 @@ func HandleWebsocketAPI(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel()
- err := apiHandler.HandleRequest(ctx, bytes.NewReader(m), w)
+ err := apiHandler.HandleRequest(ctx, s, bytes.NewReader(m), w)
if err != nil {
slog.Error("Handling Websocket API Request", err)
}
diff --git a/pkg/session/cookie.go b/pkg/session/cookie.go
new file mode 100644
index 0000000..32da76e
--- /dev/null
+++ b/pkg/session/cookie.go
@@ -0,0 +1,10 @@
+package session
+
+import (
+ "net/http"
+ "time"
+)
+
+func GetCookie(value string, expires time.Time) *http.Cookie {
+ return &http.Cookie{Name: SessionCookieName, HttpOnly: true, SameSite: http.SameSiteStrictMode, Value: value, Expires: expires}
+}
diff --git a/pkg/session/session.go b/pkg/session/session.go
new file mode 100644
index 0000000..83b7b9d
--- /dev/null
+++ b/pkg/session/session.go
@@ -0,0 +1,93 @@
+package session
+
+import (
+ "net/http"
+ "runtime/debug"
+ "sync"
+ "time"
+
+ "github.com/google/uuid"
+)
+
+type SessionKeyType string
+
+const SessionKey SessionKeyType = "session"
+const SessionCookieName string = "session"
+
+type Session struct {
+ Username string
+ Expires time.Time
+ // TODO Add []websocket.Conn pointer to close all active websockets, alternativly do this via context cancelation
+}
+
+type SessionResponse struct {
+ CommitHash string `json:"commit_hash"`
+}
+
+var sessionsSync sync.Mutex
+var sessions map[string]*Session = map[string]*Session{}
+
+var CommitHash = func() string {
+ if info, ok := debug.ReadBuildInfo(); ok {
+ for _, setting := range info.Settings {
+ if setting.Key == "vcs.revision" {
+ return setting.Value
+ }
+ }
+ }
+ return "asd"
+}()
+
+func ExtendSession(s *Session) {
+ sessionsSync.Lock()
+ defer sessionsSync.Unlock()
+ if s != nil {
+ s.Expires = time.Now().Add(time.Minute * 5)
+ }
+}
+
+func GetSession(r *http.Request) (string, *Session) {
+ c, err := r.Cookie("session")
+ if err != nil {
+ return "", nil
+ }
+ s, ok := sessions[c.Value]
+ if ok {
+ return c.Value, s
+ }
+ return "", nil
+}
+
+func GenerateSession(w http.ResponseWriter, username string) {
+ id := uuid.New().String()
+ expires := time.Now().Add(time.Minute * 5)
+ sessionsSync.Lock()
+ defer sessionsSync.Unlock()
+ sessions[id] = &Session{
+ Username: username,
+ Expires: expires,
+ }
+ http.SetCookie(w, &http.Cookie{Name: SessionCookieName, HttpOnly: true, SameSite: http.SameSiteStrictMode, Value: id, Expires: expires})
+}
+
+func CleanupSessions(stop chan struct{}) {
+ tick := time.NewTicker(time.Minute)
+ for {
+ select {
+ case <-tick.C:
+ ids := []string{}
+ sessionsSync.Lock()
+ for id, s := range sessions {
+ if time.Now().After(s.Expires) {
+ ids = append(ids, id)
+ }
+ }
+ for _, id := range ids {
+ delete(sessions, id)
+ }
+ sessionsSync.Unlock()
+ case <-stop:
+ return
+ }
+ }
+}