added admin control panel for making posts, with basic authentication

This commit is contained in:
rawleyIfowler
2022-03-02 10:59:56 -06:00
parent a03c75a3e2
commit 8bac0d8407
8 changed files with 109 additions and 22 deletions

View File

@@ -2,6 +2,7 @@ package controllers
import ( import (
"crypto/sha256" "crypto/sha256"
"fmt"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -11,19 +12,25 @@ import (
var ( var (
key string = utils.LoadApiKey("api_key.pem") key string = utils.LoadApiKey("api_key.pem")
admin_hash string = utils.LoadAdminHash("admin_hash.pem") admin_hash [32]byte = utils.LoadAdminHash("admin_hash.pem")
att map[string]uint32 = make(map[string]uint32) att map[string]uint32 = make(map[string]uint32)
) )
// Ensure is logged in // Ensure is logged in
func AdminOnly() gin.HandlerFunc { func AdminOnly() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
var status uint = http.StatusOK
k, err := c.Cookie("admin_key") k, err := c.Cookie("admin_key")
if err != nil { if err != nil {
c.Redirect(http.StatusPermanentRedirect, "/admin/") status = http.StatusForbidden
} }
if k == key { fmt.Println(k)
if k == key && status == http.StatusOK {
c.Next() c.Next()
} else {
c.HTML(int(status), "forbidden.tmpl", models.Page{Title: " | forbidden"})
c.Abort()
return
} }
} }
} }
@@ -37,41 +44,48 @@ func RegisterAdminGroup(r *gin.RouterGroup) {
func HandleLogin(c *gin.Context) { func HandleLogin(c *gin.Context) {
if att[c.ClientIP()] > 5 { if att[c.ClientIP()] > 5 {
c.Redirect(http.StatusPermanentRedirect, "/admin/") c.HTML(http.StatusForbidden, "forbidden.tmpl", models.Page{Title: " | forbidden"})
return
} }
err := c.Request.ParseForm() err := c.Request.ParseForm()
if err != nil { if err != nil {
c.Redirect(http.StatusPermanentRedirect, "/admin/") c.HTML(http.StatusForbidden, "forbidden.tmpl", models.Page{Title: " | forbidden"})
return
} }
f := c.Request.Form f := c.Request.Form
for _, v := range []string{"username", "password"} { for _, v := range []string{"username", "password"} {
if !f.Has(v) { if !f.Has(v) {
c.AbortWithStatus(http.StatusNotAcceptable) c.AbortWithStatus(http.StatusNotAcceptable)
return
} }
} }
str := f.Get("username") + f.Get("password") str := f.Get("username") + f.Get("password")
s := sha256.New() s := sha256.Sum256([]byte(str))
s.Sum([]byte(str)) var success bool
var h []byte if s == admin_hash {
s.Write(h) c.SetCookie("admin_key", key, 3600, "/", "rawley.xyz", false, true)
if string(h) == admin_hash { success = true
c.SetCookie("admin_key", key, 1, "/", "rawley.xyz", true, true)
c.Redirect(http.StatusTemporaryRedirect, "/admin/post")
} else { } else {
att[c.ClientIP()]++ att[c.ClientIP()]++
c.Redirect(http.StatusTemporaryRedirect, "/admin/") success = false
} }
c.HTML(http.StatusOK, "admin_success_redirect.tmpl", struct {
Success bool
Title string
}{Success: success, Title: "login"})
} }
func CreatePost(c *gin.Context) { func CreatePost(c *gin.Context) {
err := c.Request.ParseForm() err := c.Request.ParseForm()
if err != nil { if err != nil {
c.AbortWithStatus(http.StatusBadRequest) c.AbortWithStatus(http.StatusBadRequest)
return
} }
f := c.Request.Form f := c.Request.Form
for _, v := range []string{"title", "content", "url"} { for _, v := range []string{"title", "content", "url"} {
if !f.Has(v) { if !f.Has(v) {
c.AbortWithStatus(http.StatusNotAcceptable) c.AbortWithStatus(http.StatusNotAcceptable)
return
} }
} }
b := AddBlogPost(&models.BlogPost{ b := AddBlogPost(&models.BlogPost{
@@ -79,9 +93,8 @@ func CreatePost(c *gin.Context) {
Content: f.Get("content"), Content: f.Get("content"),
Url: f.Get("url"), Url: f.Get("url"),
}) })
if b { c.HTML(http.StatusOK, "post_success.tmpl", struct {
c.Redirect(http.StatusTemporaryRedirect, "/blog/post/"+f.Get("url")) Url string
} else { Success bool
c.Redirect(http.StatusTemporaryRedirect, "/admin/post") }{Url: f.Get("url"), Success: b})
}
} }

View File

@@ -84,6 +84,7 @@ func RenderIndividualBlogPost(c *gin.Context) {
func CreateComment(c *gin.Context) { func CreateComment(c *gin.Context) {
if c.Request.ParseForm() != nil { if c.Request.ParseForm() != nil {
c.AbortWithStatus(http.StatusNotAcceptable) c.AbortWithStatus(http.StatusNotAcceptable)
return
} }
a := []string{c.Request.Form.Get("author"), a := []string{c.Request.Form.Get("author"),
c.Request.Form.Get("content"), c.Request.Form.Get("content"),
@@ -92,7 +93,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 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 { if len(a[1]) > 20 && len(*GetCommentsByContent(a[1], a[2])) > 0 {
c.HTML(http.StatusNotAcceptable, "comment_post_failed.tmpl", CommentDto{Url: a[2]}) c.HTML(http.StatusNotAcceptable, "comment_post_failed.tmpl", CommentDto{Url: a[2]})
c.Abort() return
} }
i, err := strconv.ParseInt(a[3], 10, 32) i, err := strconv.ParseInt(a[3], 10, 32)
if err != nil || int(i) != (captchaVals[0]+captchaVals[1]) { if err != nil || int(i) != (captchaVals[0]+captchaVals[1]) {

43
e Normal file
View File

@@ -0,0 +1,43 @@
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] Loaded HTML Templates (20):
- forbidden.tmpl
- not_found.tmpl
- create_post.tmpl
- footer.tmpl
- blog_post.tmpl
- comment_post.tmpl
- contact.tmpl
- index.tmpl
- login.tmpl
- resume.tmpl
-
- blog.tmpl
- wip.tmpl
- comment_post_spam.tmpl
- footer_no_banners.tmpl
- header.tmpl
- internal_server_error.tmpl
- nav.tmpl
- admin_success_redirect.tmpl
- comment_post_failed.tmpl
[GIN-debug] GET /blog/ --> gitlab.com/rawleyifowler/site-rework/controllers.RenderBlogPage (4 handlers)
[GIN-debug] GET /blog/post/:url --> gitlab.com/rawleyifowler/site-rework/controllers.RenderIndividualBlogPost (4 handlers)
[GIN-debug] POST /blog/post --> github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1 (3 handlers)
[GIN-debug] POST /blog/post/comment --> gitlab.com/rawleyifowler/site-rework/controllers.CreateComment (4 handlers)
[GIN-debug] GET /admin/ --> gitlab.com/rawleyifowler/site-rework/utils.ServePage.func1 (4 handlers)
[GIN-debug] POST /admin/login --> gitlab.com/rawleyifowler/site-rework/controllers.HandleLogin (4 handlers)
[GIN-debug] GET /admin/post --> gitlab.com/rawleyifowler/site-rework/utils.ServePage.func1 (5 handlers)
[GIN-debug] POST /admin/post --> gitlab.com/rawleyifowler/site-rework/controllers.CreatePost (5 handlers)
[GIN-debug] GET / --> gitlab.com/rawleyifowler/site-rework/utils.ServePage.func1 (4 handlers)
[GIN-debug] GET /resume --> gitlab.com/rawleyifowler/site-rework/utils.ServePage.func1 (4 handlers)
[GIN-debug] GET /contact --> gitlab.com/rawleyifowler/site-rework/utils.ServePage.func1 (4 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080
signal: interrupt

View File

@@ -0,0 +1,10 @@
{{ template "header.tmpl" . }}
{{ if .Success }}
<h2>Login was <u>successful</u>!</h2>
<p>Click <a href="/admin/post">here</a> to go to the post creation area.</p>
{{ else }}
<h2>Login was <u>not</u> successful!</h2>
<p>Click <a href="/admin/">here</a> to return to the login page to try again.</p>
{{ end }}
</body>
</html>

View File

@@ -3,7 +3,7 @@
<form action="/admin/post" method="POST"> <form action="/admin/post" method="POST">
<input type="text" placeholder="url" name="url" /> <input type="text" placeholder="url" name="url" />
<input type="text" placeholder="title" name="title" /> <input type="text" placeholder="title" name="title" />
<textarea placeholder="post" name="content" /> <textarea placeholder="post" name="content"></textarea>
<button type="submit">Make post</button> <button type="submit">Make post</button>
<button type="clear">Clear form</button> <button type="clear">Clear form</button>
</form> </form>

5
templates/forbidden.tmpl Normal file
View File

@@ -0,0 +1,5 @@
{{ template "header.tmpl" . }}
<h2>403 You're not allowed here >:(</h2>
<p>Click <a href="/">here</a> to return home.</p>
</body>
</html>

View File

@@ -0,0 +1,11 @@
{{ template "header.tmpl" . }}
{{ if .Success }}
<h2>Post created successfully!</h2>
<p>Click <a href="/blog/post/{{ .Url }}">here</a> to view the post.</p>
<p>Click <a href="/admin/post">here</a> to return to the posting page.</p>
{{ else }}
<h2>Post failed to be created...</h2>
<p>Click <a href="/admin/post">here</a> to return to the posting page.</p>
{{ end }}
</body>
</html>

View File

@@ -5,7 +5,7 @@ import (
"os" "os"
) )
func LoadAdminHash(path string) string { func LoadAdminHash(path string) [32]byte {
file, err := os.Open(path) file, err := os.Open(path)
if err != nil { if err != nil {
panic("Could not find DSN for database...") panic("Could not find DSN for database...")
@@ -14,7 +14,11 @@ func LoadAdminHash(path string) string {
reader := bufio.NewScanner(file) reader := bufio.NewScanner(file)
// The dsn should be the first line of the file // The dsn should be the first line of the file
if reader.Scan() { if reader.Scan() {
return reader.Text() var buff [32]byte
for i, b := range reader.Bytes() {
buff[i] = b
}
return buff
} }
panic(path + " is empty...") panic(path + " is empty...")
} }