diff --git a/agent/pkg/api/socket_routes.go b/agent/pkg/api/socket_routes.go index 3118e8696..d4c1568ca 100644 --- a/agent/pkg/api/socket_routes.go +++ b/agent/pkg/api/socket_routes.go @@ -47,9 +47,9 @@ func init() { } func WebSocketRoutes(app *gin.Engine, eventHandlers EventHandlers, startTime int64) { - app.GET("/ws", func(c *gin.Context) { + app.GET("/ws", middlewares.RequiresAuth(), func(c *gin.Context) { websocketHandler(c.Writer, c.Request, eventHandlers, false, startTime) - }, middlewares.RequiresAuth()) + }) app.GET("/wsTapper", func(c *gin.Context) { // TODO: add m2m authentication to this route websocketHandler(c.Writer, c.Request, eventHandlers, true, startTime) diff --git a/agent/pkg/controllers/user_controller.go b/agent/pkg/controllers/user_controller.go index 80453d5be..c06bf2c6b 100644 --- a/agent/pkg/controllers/user_controller.go +++ b/agent/pkg/controllers/user_controller.go @@ -1,7 +1,9 @@ package controllers import ( + "errors" "mizuserver/pkg/providers" + "net/http" "github.com/gin-gonic/gin" "github.com/up9inc/mizu/shared/logger" @@ -11,20 +13,44 @@ func Login(c *gin.Context) { if token, err := providers.PerformLogin(c.PostForm("username"), c.PostForm("password"), c.Request.Context()); err != nil { c.AbortWithStatusJSON(401, gin.H{"error": "bad login"}) } else { - c.JSON(200, gin.H{"token": token}) + c.SetSameSite(http.SameSiteLaxMode) + c.SetCookie("x-session-token", *token, 3600, "/", "", false, true) + c.JSON(200, "") } } func Logout(c *gin.Context) { - token := c.GetHeader("x-session-token") - if err := providers.Logout(token, c.Request.Context()); err != nil { + token, err := c.Cookie("x-session-token") + if err != nil { + if errors.Is(err, http.ErrNoCookie) { + c.AbortWithStatusJSON(401, gin.H{"error": "could not find session cookie"}) + } else { + logger.Log.Errorf("error reading cookie in logout %s", err) + c.AbortWithStatusJSON(500, gin.H{"error": "error occured while logging out, the session might still be valid"}) + } + + return + } + + if err = providers.Logout(token, c.Request.Context()); err != nil { c.AbortWithStatusJSON(500, gin.H{"error": "error occured while logging out, the session might still be valid"}) } else { + c.SetCookie("x-session-token", "", -1, "/", "", false, true) c.JSON(200, "") } } func Register(c *gin.Context) { + // only allow one user to be created without authentication + if IsInstallNeeded, err := providers.IsInstallNeeded(); err != nil { + logger.Log.Errorf("unknown internal while checking if install is needed %s", err) + c.AbortWithStatusJSON(500, gin.H{"error": "internal error occured while checking if install is needed"}) + return + } else if !IsInstallNeeded { + c.AbortWithStatusJSON(401, gin.H{"error": "cannot register when install is not needed"}) + return + } + if token, _, err, formErrorMessages := providers.RegisterUser(c.PostForm("username"), c.PostForm("password"), c.Request.Context()); err != nil { if formErrorMessages != nil { logger.Log.Infof("user attempted to register but had form errors %v %v", formErrorMessages, err) @@ -34,6 +60,8 @@ func Register(c *gin.Context) { c.AbortWithStatusJSON(500, gin.H{"error": "internal error occured while registering"}) } } else { - c.JSON(200, gin.H{"token": token}) + c.SetSameSite(http.SameSiteLaxMode) + c.SetCookie("x-session-token", *token, 3600, "/", "", false, true) + c.JSON(200, "") } } diff --git a/agent/pkg/middlewares/requiresAuth.go b/agent/pkg/middlewares/requiresAuth.go index 08c5079b4..477d4363e 100644 --- a/agent/pkg/middlewares/requiresAuth.go +++ b/agent/pkg/middlewares/requiresAuth.go @@ -1,49 +1,51 @@ package middlewares import ( + "errors" "mizuserver/pkg/config" "mizuserver/pkg/providers" - "time" + "net/http" "github.com/gin-gonic/gin" - "github.com/patrickmn/go-cache" "github.com/up9inc/mizu/shared/logger" ) -const cachedValidTokensRetainmentTime = time.Minute * 1 - -var cachedValidTokens = cache.New(cachedValidTokensRetainmentTime, cachedValidTokensRetainmentTime) +const errorMessage = "unknown authentication error occured" func RequiresAuth() gin.HandlerFunc { return func(c *gin.Context) { - // auth is irrelevant for ephermeral mizu + // authentication is irrelevant for ephermeral mizu if !config.Config.StandaloneMode { c.Next() return } - token := c.GetHeader("x-session-token") - if token == "" { - c.AbortWithStatusJSON(401, gin.H{"error": "token header is empty"}) + token, err := c.Cookie("x-session-token") + if err != nil { + if errors.Is(err, http.ErrNoCookie) { + c.AbortWithStatusJSON(401, gin.H{"error": "could not find session cookie"}) + } else { + logger.Log.Errorf("error reading cookie %s", err) + c.AbortWithStatusJSON(500, gin.H{"error": errorMessage}) + } + return } - if _, isTokenCached := cachedValidTokens.Get(token); isTokenCached { - c.Next() + if token == "" { + c.AbortWithStatusJSON(401, gin.H{"error": "token cookie is empty"}) return } if isTokenValid, err := providers.VerifyToken(token, c.Request.Context()); err != nil { logger.Log.Errorf("error verifying token %s", err) - c.AbortWithStatusJSON(401, gin.H{"error": "unknown auth error occured"}) + c.AbortWithStatusJSON(500, gin.H{"error": errorMessage}) return } else if !isTokenValid { c.AbortWithStatusJSON(401, gin.H{"error": "invalid token"}) return } - cachedValidTokens.Set(token, true, cachedValidTokensRetainmentTime) - c.Next() } } diff --git a/ui/src/helpers/api.js b/ui/src/helpers/api.js index ae85c78a6..e380d8e24 100644 --- a/ui/src/helpers/api.js +++ b/ui/src/helpers/api.js @@ -22,8 +22,6 @@ export default class Api { } constructor() { - this.token = localStorage.getItem("token"); - this.client = this.getAxiosClient(); this.source = null; } @@ -143,7 +141,6 @@ export default class Api { try { const response = await this.client.post(`/user/register`, form); - this.persistToken(response.data.token); return response; } catch (e) { if (e.response.status === 400) { @@ -164,32 +161,18 @@ export default class Api { form.append('password', password); const response = await this.client.post(`/user/login`, form); - if (response.status >= 200 && response.status < 300) { - this.persistToken(response.data.token); - } return response; } - persistToken = (token) => { - this.token = token; - this.client = this.getAxiosClient(); - localStorage.setItem('token', token); - } - logout = async () => { await this.client.post(`/user/logout`); - this.persistToken(null); } getAxiosClient = () => { const headers = { Accept: "application/json" } - - if (this.token) { - headers['x-session-token'] = `${this.token}`; // we use `x-session-token` instead of `Authorization` because the latter is reserved by kubectl proxy, making mizu view not work - } return axios.create({ baseURL: apiURL, timeout: 31000,