diff --git a/activityStreams.go b/activityStreams.go
index d66b53c..a9b0f9e 100644
--- a/activityStreams.go
+++ b/activityStreams.go
@@ -15,7 +15,7 @@ var asContext = []string{"https://www.w3.org/ns/activitystreams"}
const asRequestKey requestContextKey = "asRequest"
-func manipulateAsPath(next http.Handler) http.Handler {
+func checkActivityStreamsRequest(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if ap := appConfig.ActivityPub; ap != nil && ap.Enabled {
if lowerAccept := strings.ToLower(r.Header.Get("Accept")); (strings.Contains(lowerAccept, contentTypeAS) || strings.Contains(lowerAccept, "application/ld+json")) && !strings.Contains(lowerAccept, contentTypeHTML) {
diff --git a/config.go b/config.go
index 5c772e3..2b25da7 100644
--- a/config.go
+++ b/config.go
@@ -20,6 +20,7 @@ type config struct {
PathRedirects []*configRegexRedirect `mapstructure:"pathRedirects"`
ActivityPub *configActivityPub `mapstructure:"activityPub"`
Notifications *configNotifications `mapstructure:"notifications"`
+ PrivateMode *configPrivateMode `mapstructure:"privateMode"`
}
type configServer struct {
@@ -190,6 +191,10 @@ type configTelegram struct {
InstantViewHash string `mapstructure:"instantViewHash"`
}
+type configPrivateMode struct {
+ Enabled bool `mapstructure:"enabled"`
+}
+
var appConfig = &config{}
func initConfig() error {
@@ -255,6 +260,9 @@ func initConfig() error {
}
appConfig.Micropub.MediaStorage.MediaURL = strings.TrimSuffix(appConfig.Micropub.MediaStorage.MediaURL, "/")
}
+ if pm := appConfig.PrivateMode; pm != nil && pm.Enabled {
+ appConfig.ActivityPub = &configActivityPub{Enabled: false}
+ }
return nil
}
diff --git a/go.mod b/go.mod
index d4d5306..76e2dac 100644
--- a/go.mod
+++ b/go.mod
@@ -44,6 +44,7 @@ require (
github.com/spf13/viper v1.7.1
github.com/tdewolff/minify/v2 v2.9.13
github.com/tdewolff/parse/v2 v2.5.11 // indirect
+ github.com/thoas/go-funk v0.7.1-0.20201128100912-5035611e402b
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2
github.com/yuin/goldmark v1.3.2
@@ -53,9 +54,9 @@ require (
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect
golang.org/x/mod v0.4.1 // indirect
- golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 // indirect
+ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
- golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 // indirect
+ golang.org/x/sys v0.0.0-20210227040730-b0d1d43c014d // indirect
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
golang.org/x/text v0.3.5 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
diff --git a/go.sum b/go.sum
index 7d0cfce..c94b9f3 100644
--- a/go.sum
+++ b/go.sum
@@ -288,6 +288,8 @@ github.com/tdewolff/parse/v2 v2.5.11 h1:Wq0x026IKZh9GPUB5Fp+v5bki/SNmpIkdltcnm6H
github.com/tdewolff/parse/v2 v2.5.11/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
+github.com/thoas/go-funk v0.7.1-0.20201128100912-5035611e402b h1:EBnJBNE7sw2Z7dg2inKlTQNO2se7PvDejZQjNaPAjg8=
+github.com/thoas/go-funk v0.7.1-0.20201128100912-5035611e402b/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
@@ -370,8 +372,8 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 h1:OgUuv8lsRpBibGNbSizVwKWlysjaNzmC9gYMhPVfqFM=
-golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -404,8 +406,8 @@ golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 h1:8qxJSnu+7dRq6upnbntrmriWByIakBuct5OM/MdQC1M=
-golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210227040730-b0d1d43c014d h1:9fH9JvLNoSpsDWcXJ4dSE3lZW99Z3OCUZLr07g60U6o=
+golang.org/x/sys v0.0.0-20210227040730-b0d1d43c014d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
diff --git a/http.go b/http.go
index 2a721a8..4fc1fb9 100644
--- a/http.go
+++ b/http.go
@@ -77,13 +77,21 @@ func reloadRouter() error {
return nil
}
+const paginationPath = "/page/{page:[0-9-]+}"
+const feedPath = ".{feed:rss|json|atom}"
+
func buildHandler() (http.Handler, error) {
-
- paginationPath := "/page/{page:[0-9-]+}"
- feedPath := ".{feed:rss|json|atom}"
-
r := chi.NewRouter()
+ // Private mode
+ privateMode := false
+ privateModeHandler := []func(http.Handler) http.Handler{}
+ if pm := appConfig.PrivateMode; pm != nil && pm.Enabled {
+ privateMode = true
+ privateModeHandler = append(privateModeHandler, authMiddleware)
+ }
+
+ // Basic middleware
if appConfig.Server.Logging {
r.Use(logMiddleware)
}
@@ -96,29 +104,37 @@ func buildHandler() (http.Handler, error) {
if !appConfig.Cache.Enable {
r.Use(middleware.NoCache)
}
+
+ // No Index Header
+ if privateMode {
+ r.Use(noIndexHeader)
+ }
+
+ // Login middleware etc.
r.Use(checkIsLogin)
r.Use(checkIsCaptcha)
r.Use(checkLoggedIn)
+ r.Use(checkActivityStreamsRequest)
// Logout
r.With(authMiddleware).Get("/login", serveLogin)
r.With(authMiddleware).Get("/logout", serveLogout)
// Micropub
- r.Route(micropubPath, func(mpRouter chi.Router) {
- mpRouter.Use(checkIndieAuth)
- mpRouter.Get("/", serveMicropubQuery)
- mpRouter.Post("/", serveMicropubPost)
- mpRouter.Post(micropubMediaSubPath, serveMicropubMedia)
+ r.Route(micropubPath, func(r chi.Router) {
+ r.Use(checkIndieAuth)
+ r.Get("/", serveMicropubQuery)
+ r.Post("/", serveMicropubPost)
+ r.Post(micropubMediaSubPath, serveMicropubMedia)
})
// IndieAuth
- r.Route("/indieauth", func(indieauthRouter chi.Router) {
- indieauthRouter.Get("/", indieAuthRequest)
- indieauthRouter.With(authMiddleware).Post("/accept", indieAuthAccept)
- indieauthRouter.Post("/", indieAuthVerification)
- indieauthRouter.Get("/token", indieAuthToken)
- indieauthRouter.Post("/token", indieAuthToken)
+ r.Route("/indieauth", func(r chi.Router) {
+ r.Get("/", indieAuthRequest)
+ r.With(authMiddleware).Post("/accept", indieAuthAccept)
+ r.Post("/", indieAuthVerification)
+ r.Get("/token", indieAuthToken)
+ r.Post("/token", indieAuthToken)
})
// ActivityPub and stuff
@@ -132,9 +148,9 @@ func buildHandler() (http.Handler, error) {
}
// Webmentions
- r.Route(webmentionPath, func(webmentionRouter chi.Router) {
- webmentionRouter.Post("/", handleWebmention)
- webmentionRouter.Group(func(r chi.Router) {
+ r.Route(webmentionPath, func(r chi.Router) {
+ r.Post("/", handleWebmention)
+ r.Group(func(r chi.Router) {
// Authenticated routes
r.Use(authMiddleware)
r.Get("/", webmentionAdmin)
@@ -159,7 +175,8 @@ func buildHandler() (http.Handler, error) {
return nil, err
}
r.Group(func(r chi.Router) {
- r.Use(manipulateAsPath, cacheMiddleware)
+ r.Use(privateModeHandler...)
+ r.Use(cacheMiddleware)
for _, path := range pp {
r.Get(path, servePost)
}
@@ -171,7 +188,7 @@ func buildHandler() (http.Handler, error) {
return nil, err
}
r.Group(func(r chi.Router) {
- r.Use(authMiddleware, cacheMiddleware)
+ r.Use(authMiddleware)
for _, path := range dp {
r.Get(path, servePost)
}
@@ -183,6 +200,7 @@ func buildHandler() (http.Handler, error) {
return nil, err
}
r.Group(func(r chi.Router) {
+ r.Use(privateModeHandler...)
r.Use(cacheMiddleware)
for _, path := range allPostAliases {
r.Get(path, servePostAlias)
@@ -200,13 +218,13 @@ func buildHandler() (http.Handler, error) {
}
// Media files
- r.Get(`/m/{file:[0-9a-fA-F]+(\.[0-9a-zA-Z]+)?}`, serveMediaFile)
+ r.With(privateModeHandler...).Get(`/m/{file:[0-9a-fA-F]+(\.[0-9a-zA-Z]+)?}`, serveMediaFile)
// Captcha
r.Handle("/captcha/*", captcha.Server(500, 250))
// Short paths
- r.With(cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", redirectToLongPath)
+ r.With(privateModeHandler...).With(cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", redirectToLongPath)
for blog, blogConfig := range appConfig.Blogs {
@@ -216,12 +234,9 @@ func buildHandler() (http.Handler, error) {
blogPath = ""
}
- r.Group(func(r chi.Router) {
-
- })
-
// Sections
r.Group(func(r chi.Router) {
+ r.Use(privateModeHandler...)
r.Use(cacheMiddleware)
for _, section := range blogConfig.Sections {
if section.Name != "" {
@@ -243,6 +258,7 @@ func buildHandler() (http.Handler, error) {
return nil, err
}
r.Group(func(r chi.Router) {
+ r.Use(privateModeHandler...)
r.Use(cacheMiddleware)
r.Get(taxPath, serveTaxonomy(blog, taxonomy))
for _, tv := range taxValues {
@@ -253,13 +269,13 @@ func buildHandler() (http.Handler, error) {
r.Get(vPath+paginationPath, handler)
}
})
-
}
}
// Photos
if blogConfig.Photos != nil && blogConfig.Photos.Enabled {
r.Group(func(r chi.Router) {
+ r.Use(privateModeHandler...)
r.Use(cacheMiddleware)
photoPath := blogPath + blogConfig.Photos.Path
handler := servePhotos(blog, photoPath)
@@ -272,6 +288,7 @@ func buildHandler() (http.Handler, error) {
// Search
if blogConfig.Search != nil && blogConfig.Search.Enabled {
r.Group(func(r chi.Router) {
+ r.Use(privateModeHandler...)
r.Use(cacheMiddleware)
searchPath := blogPath + blogConfig.Search.Path
handler := serveSearch(blog, searchPath)
@@ -288,7 +305,7 @@ func buildHandler() (http.Handler, error) {
// Stats
if blogConfig.BlogStats != nil && blogConfig.BlogStats.Enabled {
statsPath := blogPath + blogConfig.BlogStats.Path
- r.With(cacheMiddleware).Get(statsPath, serveBlogStats(blog, statsPath))
+ r.With(privateModeHandler...).With(cacheMiddleware).Get(statsPath, serveBlogStats(blog, statsPath))
}
// Year / month archives
@@ -297,6 +314,7 @@ func buildHandler() (http.Handler, error) {
return nil, err
}
r.Group(func(r chi.Router) {
+ r.Use(privateModeHandler...)
r.Use(cacheMiddleware)
for _, d := range dates {
// Year
@@ -334,19 +352,23 @@ func buildHandler() (http.Handler, error) {
// Blog
if !blogConfig.PostAsHome {
- handler := serveHome(blog, blogPath)
- r.With(manipulateAsPath, cacheMiddleware).Get(fullBlogPath, handler)
- r.With(cacheMiddleware).Get(fullBlogPath+feedPath, handler)
- r.With(cacheMiddleware).Get(blogPath+paginationPath, handler)
+ r.Group(func(r chi.Router) {
+ r.Use(privateModeHandler...)
+ r.Use(cacheMiddleware)
+ handler := serveHome(blog, blogPath)
+ r.Get(fullBlogPath, handler)
+ r.Get(fullBlogPath+feedPath, handler)
+ r.Get(blogPath+paginationPath, handler)
+ })
}
// Custom pages
for _, cp := range blogConfig.CustomPages {
handler := serveCustomPage(blogConfig, cp)
if cp.Cache {
- r.With(cacheMiddleware).Get(cp.Path, handler)
+ r.With(privateModeHandler...).With(cacheMiddleware).Get(cp.Path, handler)
} else {
- r.Get(cp.Path, handler)
+ r.With(privateModeHandler...).Get(cp.Path, handler)
}
}
@@ -356,20 +378,21 @@ func buildHandler() (http.Handler, error) {
if randomPath == "" {
randomPath = "/random"
}
- r.Get(blogPath+randomPath, redirectToRandomPost(blog))
+ r.With(privateModeHandler...).Get(blogPath+randomPath, redirectToRandomPost(blog))
}
// Editor
- r.Route(blogPath+"/editor", func(mpRouter chi.Router) {
- mpRouter.Use(authMiddleware)
- mpRouter.Get("/", serveEditor(blog))
- mpRouter.Post("/", serveEditorPost(blog))
+ r.Route(blogPath+"/editor", func(r chi.Router) {
+ r.Use(authMiddleware)
+ r.Get("/", serveEditor(blog))
+ r.Post("/", serveEditorPost(blog))
})
// Comments
if commentsConfig := blogConfig.Comments; commentsConfig != nil && commentsConfig.Enabled {
commentsPath := blogPath + "/comment"
r.Route(commentsPath, func(cr chi.Router) {
+ cr.Use(privateModeHandler...)
cr.With(cacheMiddleware).Get("/{id:[0-9]+}", serveComment(blog))
cr.With(captchaMiddleware).Post("/", createComment(blog, commentsPath))
// Admin
@@ -385,10 +408,14 @@ func buildHandler() (http.Handler, error) {
}
// Sitemap
- r.With(cacheMiddleware).Get(sitemapPath, serveSitemap)
+ r.With(privateModeHandler...).With(cacheMiddleware).Get(sitemapPath, serveSitemap)
// Robots.txt - doesn't need cache, because it's too simple
- r.Get("/robots.txt", serveRobotsTXT)
+ if !privateMode {
+ r.Get("/robots.txt", serveRobotsTXT)
+ } else {
+ r.Get("/robots.txt", servePrivateRobotsTXT)
+ }
// Check redirects, then serve 404
r.With(cacheMiddleware, checkRegexRedirects).NotFound(serve404)
@@ -411,12 +438,19 @@ func securityHeaders(next http.Handler) http.Handler {
extraCSPDomains += " " + strings.Join(appConfig.Server.CSPDomains, " ")
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Header().Add("Strict-Transport-Security", "max-age=31536000;")
- w.Header().Add("Referrer-Policy", "no-referrer")
- w.Header().Add("X-Content-Type-Options", "nosniff")
- w.Header().Add("X-Frame-Options", "SAMEORIGIN")
- w.Header().Add("X-Xss-Protection", "1; mode=block")
- w.Header().Add("Content-Security-Policy", "default-src 'self'"+extraCSPDomains)
+ w.Header().Set("Strict-Transport-Security", "max-age=31536000;")
+ w.Header().Set("Referrer-Policy", "no-referrer")
+ w.Header().Set("X-Content-Type-Options", "nosniff")
+ w.Header().Set("X-Frame-Options", "SAMEORIGIN")
+ w.Header().Set("X-Xss-Protection", "1; mode=block")
+ w.Header().Set("Content-Security-Policy", "default-src 'self'"+extraCSPDomains)
+ next.ServeHTTP(w, r)
+ })
+}
+
+func noIndexHeader(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("X-Robots-Tag", "noindex")
next.ServeHTTP(w, r)
})
}
diff --git a/robotstxt.go b/robotstxt.go
index 878b3dc..c33bf51 100644
--- a/robotstxt.go
+++ b/robotstxt.go
@@ -8,3 +8,7 @@ import (
func serveRobotsTXT(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(fmt.Sprintf("User-agent: *\nSitemap: %v", appConfig.Server.PublicAddress+sitemapPath)))
}
+
+func servePrivateRobotsTXT(w http.ResponseWriter, r *http.Request) {
+ _, _ = w.Write([]byte("User-agent: *\nDisallow: /"))
+}
diff --git a/templates/login.gohtml b/templates/login.gohtml
index dc75ac7..056cf01 100644
--- a/templates/login.gohtml
+++ b/templates/login.gohtml
@@ -14,6 +14,7 @@
+ {{ include "author" . }}
{{ end }}
diff --git a/webmentionSending.go b/webmentionSending.go
index 840b555..8a0d616 100644
--- a/webmentionSending.go
+++ b/webmentionSending.go
@@ -9,6 +9,7 @@ import (
"strings"
"github.com/PuerkitoBio/goquery"
+ "github.com/thoas/go-funk"
"github.com/tomnomnom/linkheader"
)
@@ -19,11 +20,12 @@ func (p *post) sendWebmentions() error {
return err
}
links = append(links, contentLinks...)
- links = append(links, p.firstParameter(appConfig.Micropub.LikeParam), p.firstParameter(appConfig.Micropub.ReplyParam), p.firstParameter(appConfig.Micropub.BookmarkParam))
- for _, link := range links {
+ links = append(links, p.firstParameter("link"), p.firstParameter(appConfig.Micropub.LikeParam), p.firstParameter(appConfig.Micropub.ReplyParam), p.firstParameter(appConfig.Micropub.BookmarkParam))
+ for _, link := range funk.UniqString(links) {
if link == "" {
continue
}
+ // Internal mention
if strings.HasPrefix(link, appConfig.Server.PublicAddress) {
// Save mention directly
if err := createWebmention(p.fullURL(), link); err != nil {
@@ -31,6 +33,11 @@ func (p *post) sendWebmentions() error {
}
continue
}
+ // External mention
+ if pm := appConfig.PrivateMode; pm != nil && pm.Enabled {
+ // Private mode, don't send external mentions
+ continue
+ }
endpoint := discoverEndpoint(link)
if endpoint == "" {
continue
diff --git a/webmentionVerification.go b/webmentionVerification.go
index 29b5af5..e905885 100644
--- a/webmentionVerification.go
+++ b/webmentionVerification.go
@@ -10,6 +10,7 @@ import (
"net/http"
"net/url"
"os"
+ "strings"
"github.com/PuerkitoBio/goquery"
"github.com/joncrlsn/dque"
@@ -73,6 +74,10 @@ func (m *mention) verifyMention() error {
return err
}
req.Header.Set(userAgent, appUserAgent)
+ if strings.HasPrefix(m.Source, appConfig.Server.PublicAddress) {
+ // Set authentication
+ req.SetBasicAuth(appConfig.User.Nick, appConfig.User.Password)
+ }
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err