diff --git a/agent/pkg/api/socket_routes.go b/agent/pkg/api/socket_routes.go index d4c1568ca..3118e8696 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", middlewares.RequiresAuth(), func(c *gin.Context) { + app.GET("/ws", 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 c06bf2c6b..80453d5be 100644 --- a/agent/pkg/controllers/user_controller.go +++ b/agent/pkg/controllers/user_controller.go @@ -1,9 +1,7 @@ package controllers import ( - "errors" "mizuserver/pkg/providers" - "net/http" "github.com/gin-gonic/gin" "github.com/up9inc/mizu/shared/logger" @@ -13,44 +11,20 @@ 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.SetSameSite(http.SameSiteLaxMode) - c.SetCookie("x-session-token", *token, 3600, "/", "", false, true) - c.JSON(200, "") + c.JSON(200, gin.H{"token": token}) } } func Logout(c *gin.Context) { - 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 { + token := c.GetHeader("x-session-token") + 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) @@ -60,8 +34,6 @@ func Register(c *gin.Context) { c.AbortWithStatusJSON(500, gin.H{"error": "internal error occured while registering"}) } } else { - c.SetSameSite(http.SameSiteLaxMode) - c.SetCookie("x-session-token", *token, 3600, "/", "", false, true) - c.JSON(200, "") + c.JSON(200, gin.H{"token": token}) } } diff --git a/agent/pkg/middlewares/requiresAuth.go b/agent/pkg/middlewares/requiresAuth.go index 477d4363e..08c5079b4 100644 --- a/agent/pkg/middlewares/requiresAuth.go +++ b/agent/pkg/middlewares/requiresAuth.go @@ -1,51 +1,49 @@ package middlewares import ( - "errors" "mizuserver/pkg/config" "mizuserver/pkg/providers" - "net/http" + "time" "github.com/gin-gonic/gin" + "github.com/patrickmn/go-cache" "github.com/up9inc/mizu/shared/logger" ) -const errorMessage = "unknown authentication error occured" +const cachedValidTokensRetainmentTime = time.Minute * 1 + +var cachedValidTokens = cache.New(cachedValidTokensRetainmentTime, cachedValidTokensRetainmentTime) func RequiresAuth() gin.HandlerFunc { return func(c *gin.Context) { - // authentication is irrelevant for ephermeral mizu + // auth is irrelevant for ephermeral mizu if !config.Config.StandaloneMode { c.Next() return } - 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}) - } - + token := c.GetHeader("x-session-token") + if token == "" { + c.AbortWithStatusJSON(401, gin.H{"error": "token header is empty"}) return } - if token == "" { - c.AbortWithStatusJSON(401, gin.H{"error": "token cookie is empty"}) + if _, isTokenCached := cachedValidTokens.Get(token); isTokenCached { + c.Next() return } if isTokenValid, err := providers.VerifyToken(token, c.Request.Context()); err != nil { logger.Log.Errorf("error verifying token %s", err) - c.AbortWithStatusJSON(500, gin.H{"error": errorMessage}) + c.AbortWithStatusJSON(401, gin.H{"error": "unknown auth error occured"}) 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 e380d8e24..ae85c78a6 100644 --- a/ui/src/helpers/api.js +++ b/ui/src/helpers/api.js @@ -22,6 +22,8 @@ export default class Api { } constructor() { + this.token = localStorage.getItem("token"); + this.client = this.getAxiosClient(); this.source = null; } @@ -141,6 +143,7 @@ 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) { @@ -161,18 +164,32 @@ 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,