From d949b3decbd8a68bc84a8c73ba33589945c14f42 Mon Sep 17 00:00:00 2001 From: Kato Twofold Date: Thu, 2 Jun 2022 23:53:32 +0300 Subject: [PATCH 1/6] Working on the admin interface + Implemented login token + Routing + Admin controller + Login Page * Updated `.env` example --- .env.example | 6 ++- lib/controllers/controllers_admin.go | 19 +++++++ lib/helper/helper_main.go | 15 ++++++ lib/routes/routes_admin.go | 80 ++++++++++++++++++++++++++++ main.go | 3 ++ templates/pages/admin_login.html | 42 +++++++++++++++ 6 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 lib/controllers/controllers_admin.go create mode 100644 lib/routes/routes_admin.go create mode 100644 templates/pages/admin_login.html diff --git a/.env.example b/.env.example index 1b7bad1..329fb7f 100644 --- a/.env.example +++ b/.env.example @@ -21,4 +21,8 @@ CLEANUP_MAX_AGE=43200 # Default is a month # Maximum pad file lenght, this is in characters, a character is one byte. # Default: 524288 ( 500kb ) -MAXIMUM_PAD_SIZE=524288 \ No newline at end of file +MAXIMUM_PAD_SIZE=524288 + +# Your admin access token +# If the value is not defined the admin interface will not be available +# ADMIN_TOKEN=SUPER_SECRET_ADMIN_TOKEN \ No newline at end of file diff --git a/lib/controllers/controllers_admin.go b/lib/controllers/controllers_admin.go new file mode 100644 index 0000000..e08221c --- /dev/null +++ b/lib/controllers/controllers_admin.go @@ -0,0 +1,19 @@ +package controllers + +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +func AdminMiddleware(router *gin.RouterGroup) { + + // Handl + router.Use(func(ctx *gin.Context) { + + // Check which route we are accessing + fmt.Println(`Accesing: `, ctx.Request.RequestURI) + + }) + +} diff --git a/lib/helper/helper_main.go b/lib/helper/helper_main.go index ef5f905..7c1f158 100644 --- a/lib/helper/helper_main.go +++ b/lib/helper/helper_main.go @@ -72,3 +72,18 @@ func GetCacheMapLimit() int { return rez } + +// Get the admin token used to authenticate as an admin +func GetAdminToken() string { + // Get the admin login from the environment + adminToken, exists := os.LookupEnv("ADMIN_TOKEN") + + // Check if the admin token was defined + if !exists { + // The admin token was not defined, disable admin logins + return "" + } + + // Return the admin token + return adminToken +} diff --git a/lib/routes/routes_admin.go b/lib/routes/routes_admin.go new file mode 100644 index 0000000..fc1fbce --- /dev/null +++ b/lib/routes/routes_admin.go @@ -0,0 +1,80 @@ +package routes + +import ( + "encoding/hex" + "fmt" + "net/http" + + "github.com/JustKato/FreePad/lib/controllers" + "github.com/JustKato/FreePad/lib/helper" + "github.com/gin-gonic/gin" + + "crypto/sha512" +) + +var adminLoginToken string = "" + +func AdminRoutes(router *gin.RouterGroup) { + + adminLoginToken = helper.GetAdminToken() + + // Apply the admin middleware for identification + controllers.AdminMiddleware(router) + + // Admin login route + router.GET("/login", func(ctx *gin.Context) { + ctx.HTML(200, "admin_login.html", gin.H{ + "title": "Login Login", + "domain_base": helper.GetDomainBase(), + }) + }) + + router.POST("/login", func(ctx *gin.Context) { + + // Get the value of the admin token + adminToken := ctx.PostForm("admin-token") + + // Check if the input admin token matches our admin token + if adminLoginToken != "" && adminLoginToken == adminToken { + + sha512Hasher := sha512.New() + sha512Hasher.Write([]byte(adminToken)) + + // Set the cookie to be an admin + hashHexToken := sha512Hasher.Sum(nil) + hashToken := hex.EncodeToString(hashHexToken) + + fmt.Println(hashToken) + + // Set the cookie + ctx.SetCookie("admin_token", hashToken, 60*60, "/", helper.GetDomainBase(), true, true) + + ctx.Request.Method = "GET" + + // Redirect the user to the admin page + ctx.Redirect(http.StatusTemporaryRedirect, "/admin") + return + } else { + ctx.Request.Method = "GET" + + // Redirect the user to the admin page + ctx.Redirect(http.StatusTemporaryRedirect, "/admin/login?fail") + return + } + + }) + + // Admin view route + router.GET("/", func(ctx *gin.Context) { + + adminToken, err := ctx.Cookie("admin_token") + if err != nil { + adminToken = "" + } + + ctx.JSON(200, gin.H{ + `adminToken`: adminToken, + }) + }) + +} diff --git a/main.go b/main.go index 534bff1..6549a86 100644 --- a/main.go +++ b/main.go @@ -46,6 +46,9 @@ func main() { // Implement the rate limiter controllers.DoRateLimit(router) + // Admin Routing + routes.AdminRoutes(router.Group("/admin")) + // Add Routes routes.HomeRoutes(router) diff --git a/templates/pages/admin_login.html b/templates/pages/admin_login.html new file mode 100644 index 0000000..c2b8c7f --- /dev/null +++ b/templates/pages/admin_login.html @@ -0,0 +1,42 @@ +{{ template "inc/header.html" .}} + + + +
+
+ + + Logo + + +
+
+ + + + +
+ Access the admin interface for FreePad, this can only be done through the Admin Token. +
+
+ + + +
+ + {{ template "inc/theme-toggle.html" .}} + + +{{ template "inc/footer.html" .}} \ No newline at end of file From c3c9aacac32b1fbc4f02416d6c50f5da5f2a41b7 Mon Sep 17 00:00:00 2001 From: Kato Twofold Date: Fri, 3 Jun 2022 22:56:19 +0300 Subject: [PATCH 2/6] + Admin interface + Pad Listing @TODO: Add pagination --- lib/routes/routes_admin.go | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/routes/routes_admin.go b/lib/routes/routes_admin.go index fc1fbce..9573448 100644 --- a/lib/routes/routes_admin.go +++ b/lib/routes/routes_admin.go @@ -7,6 +7,7 @@ import ( "github.com/JustKato/FreePad/lib/controllers" "github.com/JustKato/FreePad/lib/helper" + "github.com/JustKato/FreePad/lib/objects" "github.com/gin-gonic/gin" "crypto/sha512" @@ -44,37 +45,40 @@ func AdminRoutes(router *gin.RouterGroup) { hashHexToken := sha512Hasher.Sum(nil) hashToken := hex.EncodeToString(hashHexToken) - fmt.Println(hashToken) - // Set the cookie ctx.SetCookie("admin_token", hashToken, 60*60, "/", helper.GetDomainBase(), true, true) ctx.Request.Method = "GET" // Redirect the user to the admin page - ctx.Redirect(http.StatusTemporaryRedirect, "/admin") + ctx.Redirect(http.StatusFound, "/admin/view") return } else { ctx.Request.Method = "GET" // Redirect the user to the admin page - ctx.Redirect(http.StatusTemporaryRedirect, "/admin/login?fail") + ctx.Redirect(http.StatusFound, "/admin/login?fail") return } }) // Admin view route - router.GET("/", func(ctx *gin.Context) { + router.GET("/view", func(ctx *gin.Context) { - adminToken, err := ctx.Cookie("admin_token") - if err != nil { - adminToken = "" - } + // Get all of the pads as a listing + padList := objects.GetAllPosts() - ctx.JSON(200, gin.H{ - `adminToken`: adminToken, + fmt.Println(padList) + + padList[0].LastModified + + ctx.HTML(200, "admin_view.html", gin.H{ + "title": "Admin", + "padList": padList, + "domain_base": helper.GetDomainBase(), }) + }) } From b710d24a2d1cebe8ab8c7c3ee760ff7804dcc755 Mon Sep 17 00:00:00 2001 From: Kato Twofold Date: Fri, 3 Jun 2022 22:56:25 +0300 Subject: [PATCH 3/6] * Previous commit --- lib/controllers/controllers_admin.go | 43 +++++++++++++++ lib/objects/objects_post.go | 27 ++++++++++ templates/pages/admin_view.html | 78 ++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 templates/pages/admin_view.html diff --git a/lib/controllers/controllers_admin.go b/lib/controllers/controllers_admin.go index e08221c..d0818b6 100644 --- a/lib/controllers/controllers_admin.go +++ b/lib/controllers/controllers_admin.go @@ -1,8 +1,12 @@ package controllers import ( + "crypto/sha512" + "encoding/hex" "fmt" + "net/http" + "github.com/JustKato/FreePad/lib/helper" "github.com/gin-gonic/gin" ) @@ -14,6 +18,45 @@ func AdminMiddleware(router *gin.RouterGroup) { // Check which route we are accessing fmt.Println(`Accesing: `, ctx.Request.RequestURI) + // Check if the request is other than the login request + if ctx.Request.RequestURI != "/admin/login" { + // Check if the user is logged-in + + fmt.Println(`Checking if admin`) + + if !IsAdmin(ctx) { + // Not an admin, redirect to homepage + ctx.Redirect(http.StatusTemporaryRedirect, "/") + ctx.Abort() + + fmt.Println(`Not an admin!`) + return + } + + } + }) } + +func IsAdmin(ctx *gin.Context) bool { + adminToken, err := ctx.Cookie("admin_token") + if err != nil { + return false + } + + // Encode the real token + sha512Hasher := sha512.New() + sha512Hasher.Write([]byte(helper.GetAdminToken())) + hashHexToken := sha512Hasher.Sum(nil) + trueToken := hex.EncodeToString(hashHexToken) + + // Check if the user's admin token matches the token + if adminToken != "" && adminToken == trueToken { + // Yep, it's the admin! + return true + } + + // Definitely not an admin + return false +} diff --git a/lib/objects/objects_post.go b/lib/objects/objects_post.go index b66518b..eb8a5a9 100644 --- a/lib/objects/objects_post.go +++ b/lib/objects/objects_post.go @@ -295,3 +295,30 @@ func CleanupPosts(age int) { } } + +func GetAllPosts() []Post { + // Initialize the list of posts + postList := []Post{} + + // Get the posts storage directory + storageDir := getStorageDirectory() + + // Read the directory listing + files, err := os.ReadDir(storageDir) + // Check if thereh as been an issues with reading the directory contents + if err != nil { + // Log the error + fmt.Println("Error::GetAllPosts:", err) + // Return an empty list to have a clean fallback + return []Post{} + } + + // Go through all of the files + for _, v := range files { + // Process the file into a pad + postList = append(postList, GetPost(v.Name())) + } + + // Return the post list + return postList +} diff --git a/templates/pages/admin_view.html b/templates/pages/admin_view.html new file mode 100644 index 0000000..5da8e85 --- /dev/null +++ b/templates/pages/admin_view.html @@ -0,0 +1,78 @@ +{{ template "inc/header.html" .}} + + + + + +
+
+ + + Logo + + +
+ +
+
+ Pad Name +
+
+ Create Date +
+
+ Actions +
+
+ +
+ {{ range $indx, $element := .padList }} + +
+ +
+ {{ $element.LastModified }} +
+ +
+ + {{ end }} +
+ +
+
+ +
+ + {{ template "inc/theme-toggle.html" .}} + + +{{ template "inc/footer.html" .}} \ No newline at end of file From d056a4d429563b65ee039b5786936028c65f0a64 Mon Sep 17 00:00:00 2001 From: Kato Twofold Date: Fri, 3 Jun 2022 22:56:53 +0300 Subject: [PATCH 4/6] * Removed debug line --- lib/routes/routes_admin.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/routes/routes_admin.go b/lib/routes/routes_admin.go index 9573448..c6eb2ce 100644 --- a/lib/routes/routes_admin.go +++ b/lib/routes/routes_admin.go @@ -71,8 +71,6 @@ func AdminRoutes(router *gin.RouterGroup) { fmt.Println(padList) - padList[0].LastModified - ctx.HTML(200, "admin_view.html", gin.H{ "title": "Admin", "padList": padList, From faff1ab5271238b845e46d2c62235ed898117a48 Mon Sep 17 00:00:00 2001 From: Kato Twofold Date: Fri, 3 Jun 2022 22:59:44 +0300 Subject: [PATCH 5/6] + Deletion Confirmation --- lib/routes/routes_admin.go | 4 ++++ templates/pages/admin_view.html | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/routes/routes_admin.go b/lib/routes/routes_admin.go index c6eb2ce..5162728 100644 --- a/lib/routes/routes_admin.go +++ b/lib/routes/routes_admin.go @@ -63,6 +63,10 @@ func AdminRoutes(router *gin.RouterGroup) { }) + router.GET("/delete/:padname", func(ctx *gin.Context) { + + }) + // Admin view route router.GET("/view", func(ctx *gin.Context) { diff --git a/templates/pages/admin_view.html b/templates/pages/admin_view.html index 5da8e85..9a092e5 100644 --- a/templates/pages/admin_view.html +++ b/templates/pages/admin_view.html @@ -58,9 +58,9 @@ {{ $element.LastModified }} @@ -75,4 +75,14 @@ {{ template "inc/theme-toggle.html" .}} + + {{ template "inc/footer.html" .}} \ No newline at end of file From bf1d032e68788a0cb2027967deb395663d34ca9b Mon Sep 17 00:00:00 2001 From: Kato Twofold Date: Fri, 3 Jun 2022 23:15:20 +0300 Subject: [PATCH 6/6] + Deletion implemented --- lib/objects/objects_post.go | 21 +++++++++++++++------ lib/routes/routes_admin.go | 13 +++++++++++-- lib/routes/routes_home.go | 2 +- templates/pages/admin_view.html | 12 +++++++++--- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/lib/objects/objects_post.go b/lib/objects/objects_post.go index eb8a5a9..bd9f571 100644 --- a/lib/objects/objects_post.go +++ b/lib/objects/objects_post.go @@ -26,6 +26,13 @@ type Post struct { Views uint32 `json:"views"` } +func (p *Post) Delete() error { + filePath := path.Join(getStorageDirectory(), p.Name) + + // Remove the file and return the result + return os.Remove(filePath) +} + // Get the path to the views JSON func getViewsFilePath() (string, error) { // Get the path to the storage then append the const name for the storage file @@ -94,7 +101,7 @@ func LoadViewsCache() error { return nil } -func AddViewToPost(postName string) uint32 { +func AddViewToPost(postName string, incrementViews bool) uint32 { // Lock the viewers mapping viewersLock.Lock() @@ -104,8 +111,10 @@ func AddViewToPost(postName string) uint32 { ViewsCache[postName] = 0 } - // Add to the counter - ViewsCache[postName]++ + if incrementViews { + // Add to the counter + ViewsCache[postName]++ + } // Unlock viewersLock.Unlock() @@ -175,7 +184,7 @@ func getStorageDirectory() string { } // Get a post from the file system -func GetPost(fileName string) Post { +func GetPost(fileName string, incrementViews bool) Post { // Get the base storage directory and make sure it exists storageDir := getStorageDirectory() @@ -183,7 +192,7 @@ func GetPost(fileName string) Post { filePath := fmt.Sprintf("%s%s", storageDir, fileName) // Get the post views and add 1 to them - postViews := AddViewToPost(fileName) + postViews := AddViewToPost(fileName, incrementViews) p := Post{ Name: fileName, @@ -316,7 +325,7 @@ func GetAllPosts() []Post { // Go through all of the files for _, v := range files { // Process the file into a pad - postList = append(postList, GetPost(v.Name())) + postList = append(postList, GetPost(v.Name(), false)) } // Return the post list diff --git a/lib/routes/routes_admin.go b/lib/routes/routes_admin.go index 5162728..e12aca3 100644 --- a/lib/routes/routes_admin.go +++ b/lib/routes/routes_admin.go @@ -64,7 +64,18 @@ func AdminRoutes(router *gin.RouterGroup) { }) router.GET("/delete/:padname", func(ctx *gin.Context) { + // Get the pad name that we bout' to delete + padName := ctx.Param("padname") + // Try and get the pad, check if valid + pad := objects.GetPost(padName, false) + + // Delete the pad + err := pad.Delete() + fmt.Println(err) + + // Redirect the user to the admin page + ctx.Redirect(http.StatusFound, "/admin/view") }) // Admin view route @@ -73,8 +84,6 @@ func AdminRoutes(router *gin.RouterGroup) { // Get all of the pads as a listing padList := objects.GetAllPosts() - fmt.Println(padList) - ctx.HTML(200, "admin_view.html", gin.H{ "title": "Admin", "padList": padList, diff --git a/lib/routes/routes_home.go b/lib/routes/routes_home.go index d512015..e7d8be0 100644 --- a/lib/routes/routes_home.go +++ b/lib/routes/routes_home.go @@ -41,7 +41,7 @@ func HomeRoutes(router *gin.Engine) { } postName = sanitize.XSS(sanitize.SingleLine(postName)) - post := objects.GetPost(postName) + post := objects.GetPost(postName, true) c.HTML(200, "page.html", gin.H{ "title": postName, diff --git a/templates/pages/admin_view.html b/templates/pages/admin_view.html index 9a092e5..18df451 100644 --- a/templates/pages/admin_view.html +++ b/templates/pages/admin_view.html @@ -37,7 +37,10 @@
Pad Name
-
+
+ Views +
+
Create Date
@@ -54,11 +57,14 @@ {{ $element.Name }}
-
+
+ {{ $element.Views }} +
+
{{ $element.LastModified }}
-
+
Delete