Browse Source

Private mode

master
Jan-Lukas Else 2 months ago
parent
commit
e25199b2a3
9 changed files with 118 additions and 56 deletions
  1. +1
    -1
      activityStreams.go
  2. +8
    -0
      config.go
  3. +3
    -2
      go.mod
  4. +6
    -4
      go.sum
  5. +81
    -47
      http.go
  6. +4
    -0
      robotstxt.go
  7. +1
    -0
      templates/login.gohtml
  8. +9
    -2
      webmentionSending.go
  9. +5
    -0
      webmentionVerification.go

+ 1
- 1
activityStreams.go View File

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


+ 8
- 0
config.go View File

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


+ 3
- 2
go.mod View File

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


+ 6
- 4
go.sum View File

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


+ 81
- 47
http.go View File

@ -77,13 +77,21 @@ func reloadRouter() error {
return nil
}
func buildHandler() (http.Handler, error) {
paginationPath := "/page/{page:[0-9-]+}"
feedPath := ".{feed:rss|json|atom}"
const paginationPath = "/page/{page:[0-9-]+}"
const feedPath = ".{feed:rss|json|atom}"
func buildHandler() (http.Handler, error) {
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)
})
}


+ 4
- 0
robotstxt.go View File

@ -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: /"))
}

+ 1
- 0
templates/login.gohtml View File

@ -14,6 +14,7 @@
<input type="password" name="password" placeholder="{{ string .Blog.Lang "password" }}">
<input class="fw" type="submit" value="{{ string .Blog.Lang "login" }}">
</form>
{{ include "author" . }}
</main>
{{ end }}


+ 9
- 2
webmentionSending.go View File

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


+ 5
- 0
webmentionVerification.go View File

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


Loading…
Cancel
Save