From 033d9b3f145bc7a4382d8210161d223afa943f66 Mon Sep 17 00:00:00 2001 From: Zhi Wang Date: Fri, 21 Jan 2022 17:53:23 -0500 Subject: [PATCH] add toast messages --- assets/template/login.html | 26 ++++++++++++++++- term_conn/relay.go | 18 +++++------- web/auth.go | 55 +++++++++++++++++++--------------- web/record.go | 2 +- web/routing.go | 60 +++++++++++++++++++------------------- 5 files changed, 96 insertions(+), 65 deletions(-) diff --git a/assets/template/login.html b/assets/template/login.html index c836d16..7a71760 100644 --- a/assets/template/login.html +++ b/assets/template/login.html @@ -14,8 +14,19 @@ + +
-
+
@@ -31,6 +42,19 @@

WiTTY: Web-based Interactive TTY

+ + + \ No newline at end of file diff --git a/term_conn/relay.go b/term_conn/relay.go index 882bc61..41e6ef1 100644 --- a/term_conn/relay.go +++ b/term_conn/relay.go @@ -248,8 +248,8 @@ out: if err != nil { log.Println("Failed to marshal record", err) } else { - tc.record.Write(jbuf) tc.record.Write([]byte(",")) // write a deliminator + tc.record.Write(jbuf) } tc.lastRecTime = time.Now() @@ -259,7 +259,7 @@ out: var err error if cmd == recordCmd { // use the session ID and current as file name - fname := "./records/" + tc.Name + "_" + strconv.FormatInt(time.Now().Unix(), 16) + ".rec" + fname := "./records/" + tc.Name + "_" + strconv.FormatInt(time.Now().Unix(), 16) + ".scr" tc.record, err = os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { @@ -268,16 +268,14 @@ out: } tc.record.Write([]byte("[")) // write a [ for an array of json objs + + // write a dummy record to clear the screen. tc.lastRecTime = time.Now() + jbuf, _ := json.Marshal(WriteRecord{Dur: time.Since(tc.lastRecTime), Data: []byte("\033[2J\033[H")}) + tc.record.Write(jbuf) + } else { - fsinfo, err := tc.record.Stat() - - if err == nil { - tc.record.Truncate(fsinfo.Size() - 1) - tc.record.Seek(0, 2) // truncate does not change read/write location - tc.record.Write([]byte("]")) - } - + tc.record.Write([]byte("]")) tc.record.Close() tc.record = nil } diff --git a/web/auth.go b/web/auth.go index 50ae9c6..d3fad53 100644 --- a/web/auth.go +++ b/web/auth.go @@ -9,9 +9,16 @@ import ( ) const ( - userkey = "user" + userkey = "authorized_user" + loginKey = "login_msg" ) +func leftLoginMsg(c *gin.Context, msg string) { + session := sessions.Default(c) + session.Set(loginKey, msg) + session.Save() +} + func login(c *gin.Context) { session := sessions.Default(c) @@ -20,64 +27,66 @@ func login(c *gin.Context) { // Validate form input if strings.Trim(username, " ") == "" || strings.Trim(passwd, " ") == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "Username/password can't be empty"}) + leftLoginMsg(c, "User name or password cannot be empty") + c.Redirect(http.StatusSeeOther, "/login") return } // Check for username and password match, usually from a database if username != "hello" || passwd != "world" { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication failed"}) + leftLoginMsg(c, "Username/password does not match") + c.Redirect(http.StatusSeeOther, "/login") return } // Save the username in the session - session.Set(userkey, username) // In real world usage you'd set this to the users ID + session.Set(userkey, username) if err := session.Save(); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save session"}) + leftLoginMsg(c, "Failed to save session data") + c.Redirect(http.StatusSeeOther, "/login") return } host = &c.Request.Host - c.Redirect(http.StatusSeeOther, "/") } func logout(c *gin.Context) { session := sessions.Default(c) + user := session.Get(userkey) - if user == nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid session token"}) - return - } - session.Delete(userkey) - if err := session.Save(); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save session"}) - return + if user != nil { + session.Delete(userkey) + session.Save() } + + leftLoginMsg(c, "Welcome to WiTTY") c.Redirect(http.StatusFound, "/login") } // AuthRequired is a simple middleware to check the session func AuthRequired(c *gin.Context) { - if (c.Request.URL.String() == "/login") || - strings.HasPrefix(c.Request.URL.String(), "/assets") { - c.Next() - return - } - session := sessions.Default(c) user := session.Get(userkey) + if user == nil { - // Abort the request with the appropriate error code + leftLoginMsg(c, "Not authorized, login first") c.Redirect(http.StatusTemporaryRedirect, "/login") c.Abort() return } - // Continue down the chain to handler etc + c.Next() } func loginPage(c *gin.Context) { - c.HTML(http.StatusOK, "login.html", gin.H{}) + session := sessions.Default(c) + msg := session.Get(loginKey) + + if msg == nil { + msg = "Login first" + } + + c.HTML(http.StatusOK, "login.html", gin.H{"msg": msg}) } diff --git a/web/record.go b/web/record.go index 7dac4fa..c58cb0f 100644 --- a/web/record.go +++ b/web/record.go @@ -63,7 +63,7 @@ func collectRecords(c *gin.Context, cmd string) (records []RecordedSession) { if err == nil { for _, finfo := range files { fname := finfo.Name() - if !strings.HasSuffix(fname, ".rec") { + if !strings.HasSuffix(fname, ".scr") { continue } fsize := finfo.Size() / 1024 diff --git a/web/routing.go b/web/routing.go index c418715..831f044 100644 --- a/web/routing.go +++ b/web/routing.go @@ -44,44 +44,44 @@ func StartWeb(fp *os.File, cmd []string) { // so login can survive server reboot store := sessions.NewCookieStore([]byte(uniuri.NewLen(32))) rt.Use(sessions.Sessions("witty-session", store)) - rt.Use(AuthRequired) rt.SetTrustedProxies(nil) rt.LoadHTMLGlob("./assets/template/*") - - // Fill in the index page - rt.GET("/", indexPage) - rt.GET("/login", loginPage) - - rt.POST("/login", login) - rt.GET("/logout", logout) - - // to update the tabs of current interactive and saved sessions - rt.GET("/update/:active", updateIndex) - - // create a new interactive session - rt.GET("/new", newInteractive) - rt.GET("/ws_new/:id", newTermConn) - - // create a viewer of an interactive session - rt.GET("/view/:id", viewPage) - rt.GET("/ws_view/:id", newViewWS) - - // start/stop recording the session - rt.GET("/record/:id", startRecord) - rt.GET("/stop/:id", stopRecord) - - // create a viewer of an interactive session - rt.GET("/replay/:id", replayPage) - - // delete a recording - rt.GET("/delete/:fname", delRec) - // handle static files rt.Static("/assets", "./assets") rt.Static("/records", "./records") rt.GET("/favicon.ico", favIcon) + rt.GET("/login", loginPage) + rt.POST("/login", login) + + g1 := rt.Group("/", AuthRequired) + + // Fill in the index page + g1.GET("/", indexPage) + g1.GET("/logout", logout) + + // to update the tabs of current interactive and saved sessions + g1.GET("/update/:active", updateIndex) + + // create a new interactive session + g1.GET("/new", newInteractive) + g1.GET("/ws_new/:id", newTermConn) + + // create a viewer of an interactive session + g1.GET("/view/:id", viewPage) + g1.GET("/ws_view/:id", newViewWS) + + // start/stop recording the session + g1.GET("/record/:id", startRecord) + g1.GET("/stop/:id", stopRecord) + + // create a viewer of an interactive session + g1.GET("/replay/:id", replayPage) + + // delete a recording + g1.GET("/delete/:fname", delRec) + term_conn.Init(checkOrigin) rt.RunTLS(":8080", "./tls/cert.pem", "./tls/private-key.pem") }