diff --git a/.gitignore b/.gitignore
index fb3dc3d..21465f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ main
dsn
*.pem
api_key.pem
+admin_hash.pem
diff --git a/bootstrap/routes.go b/bootstrap/routes.go
index ca46cb5..8341e60 100644
--- a/bootstrap/routes.go
+++ b/bootstrap/routes.go
@@ -18,38 +18,19 @@ along with this program. If not, see .Rawley Fow
*/
import (
- "net/http"
- "strings"
-
"github.com/gin-gonic/gin"
"gitlab.com/rawleyifowler/site-rework/controllers"
+ "gitlab.com/rawleyifowler/site-rework/utils"
)
// Initializes all routes from the controller package
func InitializeRoutes(router *gin.Engine) {
- router.NoRoute(ServePage("not_found.tmpl"))
+ router.NoRoute(utils.ServePage("not_found.tmpl"))
blogGroup := router.Group("/blog")
controllers.RegisterBlogGroup(blogGroup)
- router.GET("/", ServePage("index.tmpl"))
- router.GET("/resume", ServePage("resume.tmpl"))
- router.GET("/contact", ServePage("contact.tmpl"))
-}
-
-// Returns a handler function to serve a page based on the template that is inputted
-func ServePage(temp string) gin.HandlerFunc {
- title := strings.Split(temp, ".")[0]
- if title == "index" {
- // Index is the home page so we don't care
- title = ""
- } else {
- // Else title it accordingly
- title = " | " + title
- }
- return func(c *gin.Context) {
- c.HTML(
- http.StatusOK,
- temp,
- struct{ Title string }{Title: title},
- )
- }
+ adminGroup := router.Group("/admin")
+ controllers.RegisterAdminGroup(adminGroup)
+ router.GET("/", utils.ServePage("index.tmpl"))
+ router.GET("/resume", utils.ServePage("resume.tmpl"))
+ router.GET("/contact", utils.ServePage("contact.tmpl"))
}
diff --git a/controllers/admin.go b/controllers/admin.go
new file mode 100644
index 0000000..6d09ec0
--- /dev/null
+++ b/controllers/admin.go
@@ -0,0 +1,87 @@
+package controllers
+
+import (
+ "crypto/sha256"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "gitlab.com/rawleyifowler/site-rework/models"
+ "gitlab.com/rawleyifowler/site-rework/utils"
+)
+
+var (
+ key string = utils.LoadApiKey("api_key.pem")
+ admin_hash string = utils.LoadAdminHash("admin_hash.pem")
+ att map[string]uint32 = make(map[string]uint32)
+)
+
+// Ensure is logged in
+func AdminOnly() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ k, err := c.Cookie("admin_key")
+ if err != nil {
+ c.Redirect(http.StatusPermanentRedirect, "/admin/")
+ }
+ if k == key {
+ c.Next()
+ }
+ }
+}
+
+func RegisterAdminGroup(r *gin.RouterGroup) {
+ r.GET("/", utils.ServePage("login.tmpl"))
+ r.POST("/login", HandleLogin)
+ r.GET("/post", AdminOnly(), utils.ServePage("create_post.tmpl"))
+ r.POST("/post", AdminOnly(), CreatePost)
+}
+
+func HandleLogin(c *gin.Context) {
+ if att[c.ClientIP()] > 5 {
+ c.Redirect(http.StatusPermanentRedirect, "/admin/")
+ }
+ err := c.Request.ParseForm()
+ if err != nil {
+ c.Redirect(http.StatusPermanentRedirect, "/admin/")
+ }
+ f := c.Request.Form
+ for _, v := range []string{"username", "password"} {
+ if !f.Has(v) {
+ c.AbortWithStatus(http.StatusNotAcceptable)
+ }
+ }
+ str := f.Get("username") + f.Get("password")
+ s := sha256.New()
+ s.Sum([]byte(str))
+ var h []byte
+ s.Write(h)
+ if string(h) == admin_hash {
+ c.SetCookie("admin_key", key, 1, "/", "rawley.xyz", true, true)
+ c.Redirect(http.StatusTemporaryRedirect, "/admin/post")
+ } else {
+ att[c.ClientIP()]++
+ c.Redirect(http.StatusTemporaryRedirect, "/admin/")
+ }
+}
+
+func CreatePost(c *gin.Context) {
+ err := c.Request.ParseForm()
+ if err != nil {
+ c.AbortWithStatus(http.StatusBadRequest)
+ }
+ f := c.Request.Form
+ for _, v := range []string{"title", "content", "url"} {
+ if !f.Has(v) {
+ c.AbortWithStatus(http.StatusNotAcceptable)
+ }
+ }
+ b := AddBlogPost(&models.BlogPost{
+ Title: f.Get("title"),
+ Content: f.Get("content"),
+ Url: f.Get("url"),
+ })
+ if b {
+ c.Redirect(http.StatusTemporaryRedirect, "/blog/post/"+f.Get("url"))
+ } else {
+ c.Redirect(http.StatusTemporaryRedirect, "/admin/post")
+ }
+}
diff --git a/controllers/blog.go b/controllers/blog.go
index 6eb95fa..889b72e 100644
--- a/controllers/blog.go
+++ b/controllers/blog.go
@@ -16,8 +16,6 @@ along with this program. If not, see .Rawley Fow
*/
import (
- "encoding/json"
- "io/ioutil"
"net/http"
"strconv"
@@ -34,7 +32,6 @@ type CommentDto struct {
var (
db *gorm.DB
- apiKey string
recentPosters map[string]uint
captchaVals [2]int
)
@@ -44,8 +41,6 @@ func RegisterBlogGroup(r *gin.RouterGroup) {
recentPosters = make(map[string]uint)
// Load dsn and initialize database
dsn := utils.LoadDSN("dsn")
- // Load and initialize api key
- apiKey = utils.LoadApiKey("api_key.pem")
var err error
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
@@ -54,7 +49,7 @@ func RegisterBlogGroup(r *gin.RouterGroup) {
utils.PerformMigrations(db)
r.GET("/", RenderBlogPage)
r.GET("/post/:url", RenderIndividualBlogPost)
- r.POST("/post", CreateBlogPost)
+ r.POST("/post")
r.POST("/post/comment", CreateComment)
// Clear the commenters cache every 15 minutes
go utils.TimedClearMap(&recentPosters, 180000*5)
@@ -86,40 +81,9 @@ func RenderIndividualBlogPost(c *gin.Context) {
}
}
-func CreateBlogPost(c *gin.Context) {
- reqKey, err := c.Request.Cookie("APK")
- if err != nil {
- c.Status(http.StatusForbidden)
- return
- }
- if reqKey.Value != apiKey {
- c.Status(http.StatusForbidden)
- return
- }
- raw, err := ioutil.ReadAll(c.Request.Body)
- if err != nil {
- c.Status(http.StatusNotAcceptable)
- return
- }
- var data models.BlogPost
- // Write the data from the request to the data variable
- if json.Unmarshal([]byte(raw), &data) != nil {
- c.Status(http.StatusNotAcceptable)
- return
- }
- // Create the blog post in the database
- err = db.Create(&data).Error
- if err != nil {
- c.JSON(http.StatusNotAcceptable, "{ \"error\": "+err.Error()+" }")
- } else {
- c.Status(http.StatusAccepted)
- }
-}
-
func CreateComment(c *gin.Context) {
if c.Request.ParseForm() != nil {
- c.Status(http.StatusNotAcceptable)
- return
+ c.AbortWithStatus(http.StatusNotAcceptable)
}
a := []string{c.Request.Form.Get("author"),
c.Request.Form.Get("content"),
@@ -128,7 +92,7 @@ func CreateComment(c *gin.Context) {
// If the length of the comment is great enough, and the comment already exists we can safely assume it is spam.
if len(a[1]) > 20 && len(*GetCommentsByContent(a[1], a[2])) > 0 {
c.HTML(http.StatusNotAcceptable, "comment_post_failed.tmpl", CommentDto{Url: a[2]})
- return
+ c.Abort()
}
i, err := strconv.ParseInt(a[3], 10, 32)
if err != nil || int(i) != (captchaVals[0]+captchaVals[1]) {
@@ -160,6 +124,11 @@ func GetNumberOfRecentPosts(c *gin.Context) uint {
return recentPosters[c.ClientIP()]
}
+func AddBlogPost(bp *models.BlogPost) bool {
+ err := db.Create(bp).Error
+ return err == nil
+}
+
func DeleteBlogPostById(id string) bool {
err := db.Model(&models.BlogPost{}).Delete(&models.BlogPost{Url: id}).Error
return err == nil
diff --git a/e b/e
new file mode 100644
index 0000000..ff07b46
--- /dev/null
+++ b/e
@@ -0,0 +1,106 @@
+
+
+[31m2022/03/02 07:29:33 [Recovery] 2022/03/02 - 07:29:33 panic recovered:
+GET /admin/post HTTP/1.1
+Host: localhost:8080
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
+Accept-Encoding: gzip, deflate
+Accept-Language: en-US,en;q=0.5
+Cache-Control: max-age=0
+Connection: keep-alive
+Sec-Fetch-Dest: document
+Sec-Fetch-Mode: navigate
+Sec-Fetch-Site: none
+Sec-Fetch-User: ?1
+Sec-Gpc: 1
+Upgrade-Insecure-Requests: 1
+User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
+
+
+Cannot redirect with status code 406
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/render/redirect.go:22 (0x7ff947)
+ Redirect.Render: panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:927 (0x806ea6)
+ (*Context).Render: if err := r.Render(c.Writer); err != nil {
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:1008 (0x8cab1d)
+ (*Context).Redirect: c.Render(-1, render.Redirect{
+/home/rf/Projects/personal/site-rework/controllers/admin.go:23 (0x8caabd)
+ AdminOnly.func1: c.Redirect(http.StatusNotAcceptable, "/admin/")
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:168 (0x80f321)
+ (*Context).Next: c.handlers[c.index](c)
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/recovery.go:99 (0x80f30c)
+ CustomRecoveryWithWriter.func1: c.Next()
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:168 (0x80f321)
+ (*Context).Next: c.handlers[c.index](c)
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/recovery.go:99 (0x80f30c)
+ CustomRecoveryWithWriter.func1: c.Next()
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:168 (0x80e586)
+ (*Context).Next: c.handlers[c.index](c)
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/logger.go:241 (0x80e569)
+ LoggerWithConfig.func1: c.Next()
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:168 (0x80dad0)
+ (*Context).Next: c.handlers[c.index](c)
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/gin.go:555 (0x80d738)
+ (*Engine).handleHTTPRequest: c.Next()
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/gin.go:511 (0x80d271)
+ (*Engine).ServeHTTP: engine.handleHTTPRequest(c)
+/usr/lib/go/src/net/http/server.go:2879 (0x6b66da)
+ serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
+/usr/lib/go/src/net/http/server.go:1930 (0x6b1d87)
+ (*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
+/usr/lib/go/src/runtime/asm_amd64.s:1581 (0x464d80)
+ goexit: BYTE $0x90 // NOP
+[0m
+
+
+[31m2022/03/02 07:29:33 [Recovery] 2022/03/02 - 07:29:33 panic recovered:
+GET /admin/post HTTP/1.1
+Host: localhost:8080
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
+Accept-Encoding: gzip, deflate
+Accept-Language: en-US,en;q=0.5
+Cache-Control: max-age=0
+Connection: keep-alive
+Sec-Fetch-Dest: document
+Sec-Fetch-Mode: navigate
+Sec-Fetch-Site: none
+Sec-Fetch-User: ?1
+Sec-Gpc: 1
+Upgrade-Insecure-Requests: 1
+User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
+
+
+Cannot redirect with status code 406
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/render/redirect.go:22 (0x7ff947)
+ Redirect.Render: panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:927 (0x806ea6)
+ (*Context).Render: if err := r.Render(c.Writer); err != nil {
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:1008 (0x8cab1d)
+ (*Context).Redirect: c.Render(-1, render.Redirect{
+/home/rf/Projects/personal/site-rework/controllers/admin.go:23 (0x8caabd)
+ AdminOnly.func1: c.Redirect(http.StatusNotAcceptable, "/admin/")
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:168 (0x80f321)
+ (*Context).Next: c.handlers[c.index](c)
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/recovery.go:99 (0x80f30c)
+ CustomRecoveryWithWriter.func1: c.Next()
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:168 (0x80f321)
+ (*Context).Next: c.handlers[c.index](c)
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/recovery.go:99 (0x80f30c)
+ CustomRecoveryWithWriter.func1: c.Next()
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:168 (0x80e586)
+ (*Context).Next: c.handlers[c.index](c)
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/logger.go:241 (0x80e569)
+ LoggerWithConfig.func1: c.Next()
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:168 (0x80dad0)
+ (*Context).Next: c.handlers[c.index](c)
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/gin.go:555 (0x80d738)
+ (*Engine).handleHTTPRequest: c.Next()
+/home/rf/.go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/gin.go:511 (0x80d271)
+ (*Engine).ServeHTTP: engine.handleHTTPRequest(c)
+/usr/lib/go/src/net/http/server.go:2879 (0x6b66da)
+ serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
+/usr/lib/go/src/net/http/server.go:1930 (0x6b1d87)
+ (*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
+/usr/lib/go/src/runtime/asm_amd64.s:1581 (0x464d80)
+ goexit: BYTE $0x90 // NOP
+[0m
diff --git a/templates/create_post.tmpl b/templates/create_post.tmpl
new file mode 100644
index 0000000..1b7d72b
--- /dev/null
+++ b/templates/create_post.tmpl
@@ -0,0 +1,10 @@
+{{ template "header.tmpl" . }}
+
Create post
+
+{{ template "footer.tmpl" . }}
diff --git a/templates/login.tmpl b/templates/login.tmpl
new file mode 100644
index 0000000..63d24a7
--- /dev/null
+++ b/templates/login.tmpl
@@ -0,0 +1,8 @@
+{{ template "header.tmpl" . }}
+Admin Login
+
+{{ template "footer.tmpl" . }}
diff --git a/utils/admin_hash.go b/utils/admin_hash.go
new file mode 100644
index 0000000..51e167b
--- /dev/null
+++ b/utils/admin_hash.go
@@ -0,0 +1,20 @@
+package utils
+
+import (
+ "bufio"
+ "os"
+)
+
+func LoadAdminHash(path string) string {
+ file, err := os.Open(path)
+ if err != nil {
+ panic("Could not find DSN for database...")
+ }
+ defer file.Close()
+ reader := bufio.NewScanner(file)
+ // The dsn should be the first line of the file
+ if reader.Scan() {
+ return reader.Text()
+ }
+ panic(path + " is empty...")
+}
diff --git a/utils/serve_page.go b/utils/serve_page.go
new file mode 100644
index 0000000..2aacc61
--- /dev/null
+++ b/utils/serve_page.go
@@ -0,0 +1,27 @@
+package utils
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+)
+
+// Returns a handler function to serve a page based on the template that is inputted
+func ServePage(temp string) gin.HandlerFunc {
+ title := strings.Split(temp, ".")[0]
+ if title == "index" {
+ // Index is the home page so we don't care
+ title = ""
+ } else {
+ // Else title it accordingly
+ title = " | " + title
+ }
+ return func(c *gin.Context) {
+ c.HTML(
+ http.StatusOK,
+ temp,
+ struct{ Title string }{Title: title},
+ )
+ }
+}