mirror of
https://github.com/syssecfsu/witty.git
synced 2025-01-26 03:54:19 +01:00
add toast messages
This commit is contained in:
parent
481e5473aa
commit
033d9b3f14
@ -14,6 +14,17 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="text-center">
|
<body class="text-center">
|
||||||
|
<div class="toast bg-primary text-white border-0" role="alert" aria-live="assertive" aria-atomic="true" id="authMsg"
|
||||||
|
style="position: absolute;top: 0px; right: 10px; z-index:1;">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="toast-body">
|
||||||
|
{{.msg}}
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"
|
||||||
|
aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<main class="form-signin">
|
<main class="form-signin">
|
||||||
<form action="/login" method="post">
|
<form action="/login" method="post">
|
||||||
<img class="mb-4" src="/assets/img/keyboard.svg" alt="" width="64">
|
<img class="mb-4" src="/assets/img/keyboard.svg" alt="" width="64">
|
||||||
@ -31,6 +42,19 @@
|
|||||||
<p class="mt-5 mb-3 text-muted">WiTTY: Web-based Interactive TTY</p>
|
<p class="mt-5 mb-3 text-muted">WiTTY: Web-based Interactive TTY</p>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
var element = document.getElementById("authMsg");
|
||||||
|
var toast = new bootstrap.Toast(element);
|
||||||
|
|
||||||
|
toast.show()
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.hide()
|
||||||
|
}, 1500)
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -248,8 +248,8 @@ out:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to marshal record", err)
|
log.Println("Failed to marshal record", err)
|
||||||
} else {
|
} else {
|
||||||
tc.record.Write(jbuf)
|
|
||||||
tc.record.Write([]byte(",")) // write a deliminator
|
tc.record.Write([]byte(",")) // write a deliminator
|
||||||
|
tc.record.Write(jbuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
tc.lastRecTime = time.Now()
|
tc.lastRecTime = time.Now()
|
||||||
@ -259,7 +259,7 @@ out:
|
|||||||
var err error
|
var err error
|
||||||
if cmd == recordCmd {
|
if cmd == recordCmd {
|
||||||
// use the session ID and current as file name
|
// 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)
|
tc.record, err = os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -268,16 +268,14 @@ out:
|
|||||||
}
|
}
|
||||||
|
|
||||||
tc.record.Write([]byte("[")) // write a [ for an array of json objs
|
tc.record.Write([]byte("[")) // write a [ for an array of json objs
|
||||||
|
|
||||||
|
// write a dummy record to clear the screen.
|
||||||
tc.lastRecTime = time.Now()
|
tc.lastRecTime = time.Now()
|
||||||
|
jbuf, _ := json.Marshal(WriteRecord{Dur: time.Since(tc.lastRecTime), Data: []byte("\033[2J\033[H")})
|
||||||
|
tc.record.Write(jbuf)
|
||||||
|
|
||||||
} else {
|
} 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.Close()
|
||||||
tc.record = nil
|
tc.record = nil
|
||||||
}
|
}
|
||||||
|
53
web/auth.go
53
web/auth.go
@ -9,9 +9,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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) {
|
func login(c *gin.Context) {
|
||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
|
|
||||||
@ -20,64 +27,66 @@ func login(c *gin.Context) {
|
|||||||
|
|
||||||
// Validate form input
|
// Validate form input
|
||||||
if strings.Trim(username, " ") == "" || strings.Trim(passwd, " ") == "" {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for username and password match, usually from a database
|
// Check for username and password match, usually from a database
|
||||||
if username != "hello" || passwd != "world" {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the username in the session
|
// 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 {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
host = &c.Request.Host
|
host = &c.Request.Host
|
||||||
|
|
||||||
c.Redirect(http.StatusSeeOther, "/")
|
c.Redirect(http.StatusSeeOther, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func logout(c *gin.Context) {
|
func logout(c *gin.Context) {
|
||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
|
|
||||||
user := session.Get(userkey)
|
user := session.Get(userkey)
|
||||||
if user == nil {
|
if user != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid session token"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
session.Delete(userkey)
|
session.Delete(userkey)
|
||||||
if err := session.Save(); err != nil {
|
session.Save()
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save session"})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
leftLoginMsg(c, "Welcome to WiTTY")
|
||||||
c.Redirect(http.StatusFound, "/login")
|
c.Redirect(http.StatusFound, "/login")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthRequired is a simple middleware to check the session
|
// AuthRequired is a simple middleware to check the session
|
||||||
func AuthRequired(c *gin.Context) {
|
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)
|
session := sessions.Default(c)
|
||||||
user := session.Get(userkey)
|
user := session.Get(userkey)
|
||||||
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
// Abort the request with the appropriate error code
|
leftLoginMsg(c, "Not authorized, login first")
|
||||||
c.Redirect(http.StatusTemporaryRedirect, "/login")
|
c.Redirect(http.StatusTemporaryRedirect, "/login")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Continue down the chain to handler etc
|
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
func loginPage(c *gin.Context) {
|
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})
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ func collectRecords(c *gin.Context, cmd string) (records []RecordedSession) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
for _, finfo := range files {
|
for _, finfo := range files {
|
||||||
fname := finfo.Name()
|
fname := finfo.Name()
|
||||||
if !strings.HasSuffix(fname, ".rec") {
|
if !strings.HasSuffix(fname, ".scr") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fsize := finfo.Size() / 1024
|
fsize := finfo.Size() / 1024
|
||||||
|
@ -44,44 +44,44 @@ func StartWeb(fp *os.File, cmd []string) {
|
|||||||
// so login can survive server reboot
|
// so login can survive server reboot
|
||||||
store := sessions.NewCookieStore([]byte(uniuri.NewLen(32)))
|
store := sessions.NewCookieStore([]byte(uniuri.NewLen(32)))
|
||||||
rt.Use(sessions.Sessions("witty-session", store))
|
rt.Use(sessions.Sessions("witty-session", store))
|
||||||
rt.Use(AuthRequired)
|
|
||||||
|
|
||||||
rt.SetTrustedProxies(nil)
|
rt.SetTrustedProxies(nil)
|
||||||
rt.LoadHTMLGlob("./assets/template/*")
|
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
|
// handle static files
|
||||||
rt.Static("/assets", "./assets")
|
rt.Static("/assets", "./assets")
|
||||||
rt.Static("/records", "./records")
|
rt.Static("/records", "./records")
|
||||||
rt.GET("/favicon.ico", favIcon)
|
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)
|
term_conn.Init(checkOrigin)
|
||||||
rt.RunTLS(":8080", "./tls/cert.pem", "./tls/private-key.pem")
|
rt.RunTLS(":8080", "./tls/cert.pem", "./tls/private-key.pem")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user