More config settings and age gating

pull/5/head
Noah 2018-04-11 19:34:37 -07:00
parent 532135094c
commit aca9734b14
8 changed files with 182 additions and 1 deletions

View File

@ -133,6 +133,7 @@ func (b *Blog) SetupHTTP() {
negroni.HandlerFunc(sessions.Middleware), negroni.HandlerFunc(sessions.Middleware),
negroni.HandlerFunc(middleware.CSRF(responses.Forbidden)), negroni.HandlerFunc(middleware.CSRF(responses.Forbidden)),
negroni.HandlerFunc(auth.Middleware), negroni.HandlerFunc(auth.Middleware),
negroni.HandlerFunc(middleware.AgeGate(authctl.AgeGate)),
) )
n.UseHandler(r) n.UseHandler(r)

View File

@ -5,9 +5,9 @@ import (
"strconv" "strconv"
"github.com/kirsle/blog/internal/forms" "github.com/kirsle/blog/internal/forms"
"github.com/kirsle/blog/models/settings"
"github.com/kirsle/blog/internal/render" "github.com/kirsle/blog/internal/render"
"github.com/kirsle/blog/internal/responses" "github.com/kirsle/blog/internal/responses"
"github.com/kirsle/blog/models/settings"
) )
func settingsHandler(w http.ResponseWriter, r *http.Request) { 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")) redisPort, _ := strconv.Atoi(r.FormValue("redis-port"))
redisDB, _ := strconv.Atoi(r.FormValue("redis-db")) redisDB, _ := strconv.Atoi(r.FormValue("redis-db"))
mailPort, _ := strconv.Atoi(r.FormValue("mail-port")) 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{ form := &forms.Settings{
Title: r.FormValue("title"), Title: r.FormValue("title"),
Description: r.FormValue("description"), Description: r.FormValue("description"),
AdminEmail: r.FormValue("admin-email"), AdminEmail: r.FormValue("admin-email"),
URL: r.FormValue("url"), URL: r.FormValue("url"),
NSFW: r.FormValue("nsfw") == "true",
PostsPerPage: ppp,
PostsPerFeed: ppf,
RedisEnabled: len(r.FormValue("redis-enabled")) > 0, RedisEnabled: len(r.FormValue("redis-enabled")) > 0,
RedisHost: r.FormValue("redis-host"), RedisHost: r.FormValue("redis-host"),
RedisPort: redisPort, RedisPort: redisPort,
@ -45,6 +50,9 @@ func settingsHandler(w http.ResponseWriter, r *http.Request) {
settings.Site.Description = form.Description settings.Site.Description = form.Description
settings.Site.AdminEmail = form.AdminEmail settings.Site.AdminEmail = form.AdminEmail
settings.Site.URL = form.URL 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.Enabled = form.RedisEnabled
settings.Redis.Host = form.RedisHost settings.Redis.Host = form.RedisHost
settings.Redis.Port = form.RedisPort settings.Redis.Port = form.RedisPort

View File

@ -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)
}

View File

@ -11,6 +11,9 @@ type Settings struct {
Description string Description string
AdminEmail string AdminEmail string
URL string URL string
NSFW bool
PostsPerPage int
PostsPerFeed int
RedisEnabled bool RedisEnabled bool
RedisHost string RedisHost string
RedisPort int RedisPort int

View File

@ -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
}

View File

@ -20,6 +20,7 @@ type Settings struct {
Title string `json:"title"` Title string `json:"title"`
Description string `json:"description"` Description string `json:"description"`
AdminEmail string `json:"adminEmail"` AdminEmail string `json:"adminEmail"`
NSFW bool `json:"nsfw"`
URL string `json:"url"` URL string `json:"url"`
} `json:"site"` } `json:"site"`
@ -29,6 +30,12 @@ type Settings struct {
HashCost int `json:"hashCost"` // Bcrypt hash cost for passwords HashCost int `json:"hashCost"` // Bcrypt hash cost for passwords
} `json:"security"` } `json:"security"`
// Blog settings.
Blog struct {
PostsPerPage int `json:"postsPerPage"`
PostsPerFeed int `json:"postsPerFeed"`
} `json:"blog"`
// Redis settings for caching in JsonDB. // Redis settings for caching in JsonDB.
Redis struct { Redis struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
@ -58,6 +65,8 @@ func Defaults() *Settings {
s.Site.Title = "Untitled Site" s.Site.Title = "Untitled Site"
s.Security.HashCost = 14 s.Security.HashCost = 14
s.Security.SecretKey = RandomKey() s.Security.SecretKey = RandomKey()
s.Blog.PostsPerPage = 10
s.Blog.PostsPerFeed = 10
s.Redis.Host = "localhost" s.Redis.Host = "localhost"
s.Redis.Port = 6379 s.Redis.Port = 6379
s.Redis.DB = 0 s.Redis.DB = 0

34
root/.age-gate.gohtml Normal file
View File

@ -0,0 +1,34 @@
{{ define "title" }}Age Verification{{ end }}
{{ define "content" }}
<div class="card">
<div class="card-body">
<form action="/age-verify" method="POST">
<input type="hidden" name="_csrf" value="{{ .CSRF }}">
<input type="hidden" name="next" value="{{ .Data.Next }}">
<input type="hidden" name="confirm" value="true">
<h1>Restricted Content</h1>
<p>
This website has been marked <abbr title="Not Safe For Work">NSFW</abbr>
by its owner. It may contain nudity or content not suited for users
under the age of 18.
</p>
<p>
To proceed, you must verify you are at least 18 years or older.
</p>
<button type="submit"
class="btn btn-danger">
I am 18 years or older
</button>
<a class="btn btn-primary"
href="https://duckduckgo.com/">
Get me out of here!
</a>
</form>
</div>
</div>
{{ end }}

View File

@ -50,6 +50,38 @@
placeholder="https://www.example.com/"> placeholder="https://www.example.com/">
</div> </div>
<strong>NSFW Website</strong>
<div class="form-check mb-4">
<label class="form-check-label">
<input type="checkbox"
class="form-check-input"
name="nsfw"
value="true"
{{ if .Site.NSFW }}checked{{ end }}>
Website is NSFW. Requires an age verification to enter.
</label>
</div>
<h3>Blog Settings</h3>
<div class="form-group">
<label for="admin-email">Posts Per Page</label>
<input type="text"
class="form-control"
name="posts-per-page"
value="{{ .Blog.PostsPerPage }}"
placeholder="https://www.example.com/">
</div>
<div class="form-group">
<label for="admin-email">Posts Per (RSS) Feed</label>
<input type="text"
class="form-control"
name="posts-per-page"
value="{{ .Blog.PostsPerFeed }}"
placeholder="https://www.example.com/">
</div>
<h3>Redis Cache</h3> <h3>Redis Cache</h3>
<p> <p>