From aca9734b146417763159aa86a21eaac68f3c2de1 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Wed, 11 Apr 2018 19:34:37 -0700 Subject: [PATCH] More config settings and age gating --- blog.go | 1 + internal/controllers/admin/settings.go | 10 +++- internal/controllers/authctl/age-gate.go | 35 ++++++++++++++ internal/forms/settings.go | 3 ++ internal/middleware/age-gate.go | 59 ++++++++++++++++++++++++ models/settings/settings.go | 9 ++++ root/.age-gate.gohtml | 34 ++++++++++++++ root/admin/settings.gohtml | 32 +++++++++++++ 8 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 internal/controllers/authctl/age-gate.go create mode 100644 internal/middleware/age-gate.go create mode 100644 root/.age-gate.gohtml diff --git a/blog.go b/blog.go index 5ac4146..77075a1 100644 --- a/blog.go +++ b/blog.go @@ -133,6 +133,7 @@ func (b *Blog) SetupHTTP() { negroni.HandlerFunc(sessions.Middleware), negroni.HandlerFunc(middleware.CSRF(responses.Forbidden)), negroni.HandlerFunc(auth.Middleware), + negroni.HandlerFunc(middleware.AgeGate(authctl.AgeGate)), ) n.UseHandler(r) diff --git a/internal/controllers/admin/settings.go b/internal/controllers/admin/settings.go index 17e78f6..cce1010 100644 --- a/internal/controllers/admin/settings.go +++ b/internal/controllers/admin/settings.go @@ -5,9 +5,9 @@ import ( "strconv" "github.com/kirsle/blog/internal/forms" - "github.com/kirsle/blog/models/settings" "github.com/kirsle/blog/internal/render" "github.com/kirsle/blog/internal/responses" + "github.com/kirsle/blog/models/settings" ) func settingsHandler(w http.ResponseWriter, r *http.Request) { @@ -21,11 +21,16 @@ func settingsHandler(w http.ResponseWriter, r *http.Request) { redisPort, _ := strconv.Atoi(r.FormValue("redis-port")) redisDB, _ := strconv.Atoi(r.FormValue("redis-db")) mailPort, _ := strconv.Atoi(r.FormValue("mail-port")) + ppp, _ := strconv.Atoi(r.FormValue("posts-per-page")) + ppf, _ := strconv.Atoi(r.FormValue("posts-per-feed")) form := &forms.Settings{ Title: r.FormValue("title"), Description: r.FormValue("description"), AdminEmail: r.FormValue("admin-email"), URL: r.FormValue("url"), + NSFW: r.FormValue("nsfw") == "true", + PostsPerPage: ppp, + PostsPerFeed: ppf, RedisEnabled: len(r.FormValue("redis-enabled")) > 0, RedisHost: r.FormValue("redis-host"), RedisPort: redisPort, @@ -45,6 +50,9 @@ func settingsHandler(w http.ResponseWriter, r *http.Request) { settings.Site.Description = form.Description settings.Site.AdminEmail = form.AdminEmail settings.Site.URL = form.URL + settings.Site.NSFW = form.NSFW + settings.Blog.PostsPerPage = form.PostsPerPage + settings.Blog.PostsPerFeed = form.PostsPerFeed settings.Redis.Enabled = form.RedisEnabled settings.Redis.Host = form.RedisHost settings.Redis.Port = form.RedisPort diff --git a/internal/controllers/authctl/age-gate.go b/internal/controllers/authctl/age-gate.go new file mode 100644 index 0000000..7f72f91 --- /dev/null +++ b/internal/controllers/authctl/age-gate.go @@ -0,0 +1,35 @@ +package authctl + +import ( + "net/http" + + "github.com/kirsle/blog/internal/log" + "github.com/kirsle/blog/internal/render" + "github.com/kirsle/blog/internal/responses" + "github.com/kirsle/blog/internal/sessions" +) + +// AgeGate handles age verification for NSFW blogs. +func AgeGate(w http.ResponseWriter, r *http.Request) { + next := r.FormValue("next") + if next == "" { + next = "/" + } + v := map[string]interface{}{ + "Next": next, + } + + if r.Method == http.MethodPost { + confirm := r.FormValue("confirm") + log.Info("confirm: %s", confirm) + if r.FormValue("confirm") == "true" { + session := sessions.Get(r) + session.Values["age-ok"] = true + session.Save(r, w) + responses.Redirect(w, next) + return + } + } + + render.Template(w, r, ".age-gate.gohtml", v) +} diff --git a/internal/forms/settings.go b/internal/forms/settings.go index f49d9f4..95d1ecd 100644 --- a/internal/forms/settings.go +++ b/internal/forms/settings.go @@ -11,6 +11,9 @@ type Settings struct { Description string AdminEmail string URL string + NSFW bool + PostsPerPage int + PostsPerFeed int RedisEnabled bool RedisHost string RedisPort int diff --git a/internal/middleware/age-gate.go b/internal/middleware/age-gate.go new file mode 100644 index 0000000..3a6fc0f --- /dev/null +++ b/internal/middleware/age-gate.go @@ -0,0 +1,59 @@ +package middleware + +import ( + "net/http" + "strings" + + "github.com/kirsle/blog/internal/responses" + "github.com/kirsle/blog/internal/sessions" + "github.com/kirsle/blog/models/settings" + "github.com/urfave/negroni" +) + +var ageGateSuffixes = []string{ + ".js", + ".css", + ".txt", + ".ico", + ".png", + ".jpg", + ".jpeg", + ".gif", +} + +// AgeGate is a middleware generator that does age verification for NSFW sites. +func AgeGate(verifyHandler func(http.ResponseWriter, *http.Request)) negroni.HandlerFunc { + middleware := func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + s, _ := settings.Load() + if !s.Site.NSFW { + next(w, r) + return + } + + path := r.URL.Path + if strings.HasPrefix(path, "/age-verify") { + verifyHandler(w, r) // defer to the age gate handler itself. + return + } + + // Allow static files and things through. + for _, prefix := range ageGateSuffixes { + if strings.HasSuffix(path, prefix) { + next(w, r) + return + } + } + + // See if they've been cleared. + session := sessions.Get(r) + if val, _ := session.Values["age-ok"].(bool); !val { + // They haven't been verified. + responses.Redirect(w, "/age-verify?next="+r.URL.Path) + return + } + + next(w, r) + } + + return middleware +} diff --git a/models/settings/settings.go b/models/settings/settings.go index 9407c3f..e012ff8 100644 --- a/models/settings/settings.go +++ b/models/settings/settings.go @@ -20,6 +20,7 @@ type Settings struct { Title string `json:"title"` Description string `json:"description"` AdminEmail string `json:"adminEmail"` + NSFW bool `json:"nsfw"` URL string `json:"url"` } `json:"site"` @@ -29,6 +30,12 @@ type Settings struct { HashCost int `json:"hashCost"` // Bcrypt hash cost for passwords } `json:"security"` + // Blog settings. + Blog struct { + PostsPerPage int `json:"postsPerPage"` + PostsPerFeed int `json:"postsPerFeed"` + } `json:"blog"` + // Redis settings for caching in JsonDB. Redis struct { Enabled bool `json:"enabled"` @@ -58,6 +65,8 @@ func Defaults() *Settings { s.Site.Title = "Untitled Site" s.Security.HashCost = 14 s.Security.SecretKey = RandomKey() + s.Blog.PostsPerPage = 10 + s.Blog.PostsPerFeed = 10 s.Redis.Host = "localhost" s.Redis.Port = 6379 s.Redis.DB = 0 diff --git a/root/.age-gate.gohtml b/root/.age-gate.gohtml new file mode 100644 index 0000000..05df523 --- /dev/null +++ b/root/.age-gate.gohtml @@ -0,0 +1,34 @@ +{{ define "title" }}Age Verification{{ end }} +{{ define "content" }} +
+
+
+ + + + +

Restricted Content

+ +

+ This website has been marked NSFW + by its owner. It may contain nudity or content not suited for users + under the age of 18. +

+ +

+ To proceed, you must verify you are at least 18 years or older. +

+ + + + Get me out of here! + + +
+
+
+{{ end }} diff --git a/root/admin/settings.gohtml b/root/admin/settings.gohtml index 5d2973b..deb4692 100644 --- a/root/admin/settings.gohtml +++ b/root/admin/settings.gohtml @@ -50,6 +50,38 @@ placeholder="https://www.example.com/"> + NSFW Website +
+ +
+ +

Blog Settings

+ +
+ + +
+ +
+ + +
+

Redis Cache