From e74afac82947cf06b175288d948551d539c2706e Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Sat, 20 Feb 2021 23:35:16 +0100 Subject: [PATCH] Show admin links when logged in --- authentication.go | 45 ++++++++++++++++++++----- blogstats.go | 4 +-- cache.go | 73 +++++++++++++++++++++++------------------ captcha.go | 2 +- comments.go | 2 +- commentsAdmin.go | 4 +-- customPages.go | 6 ++-- editor.go | 8 ++--- errors.go | 2 +- http.go | 1 + indieAuthServer.go | 2 +- notifications.go | 2 +- posts.go | 4 +-- render.go | 19 +++++++---- search.go | 2 +- taxonomies.go | 2 +- templates/header.gohtml | 10 +++++- webmentionAdmin.go | 2 +- 18 files changed, 121 insertions(+), 69 deletions(-) diff --git a/authentication.go b/authentication.go index 7a5f7f8..f0ecd41 100644 --- a/authentication.go +++ b/authentication.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "context" "encoding/base64" "encoding/json" "io" @@ -25,20 +26,20 @@ func jwtKey() []byte { func authMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Check if already logged in + if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok && loggedIn { + next.ServeHTTP(w, r) + return + } // 1. Check BasicAuth if username, password, ok := r.BasicAuth(); ok && checkCredentials(username, password) { next.ServeHTTP(w, r) return } // 2. Check JWT - if tokenCookie, err := r.Cookie("token"); err == nil { - claims := &authClaims{} - if tkn, err := jwt.ParseWithClaims(tokenCookie.Value, claims, func(t *jwt.Token) (interface{}, error) { - return jwtKey(), nil - }); err == nil && tkn.Valid && claims.TokenType == "login" && checkUsername(claims.Username) { - next.ServeHTTP(w, r) - return - } + if checkAuthToken(r) { + next.ServeHTTP(w, r) + return } // 3. Show login form w.WriteHeader(http.StatusUnauthorized) @@ -50,7 +51,7 @@ func authMiddleware(next http.Handler) http.Handler { _ = r.ParseForm() b = []byte(r.PostForm.Encode()) } - render(w, templateLogin, &renderData{ + render(w, r, templateLogin, &renderData{ Data: map[string]string{ "loginmethod": r.Method, "loginheaders": base64.StdEncoding.EncodeToString(h), @@ -60,6 +61,32 @@ func authMiddleware(next http.Handler) http.Handler { }) } +func checkAuthToken(r *http.Request) bool { + if tokenCookie, err := r.Cookie("token"); err == nil { + claims := &authClaims{} + if tkn, err := jwt.ParseWithClaims(tokenCookie.Value, claims, func(t *jwt.Token) (interface{}, error) { + return jwtKey(), nil + }); err == nil && tkn.Valid && + claims.TokenType == "login" && + checkUsername(claims.Username) { + return true + } + } + return false +} + +const loggedInKey requestContextKey = "loggedIn" + +func checkLoggedIn(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + if checkAuthToken(r) { + next.ServeHTTP(rw, r.WithContext(context.WithValue(r.Context(), loggedInKey, true))) + return + } + next.ServeHTTP(rw, r) + }) +} + func checkIsLogin(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { if !checkLogin(rw, r) { diff --git a/blogstats.go b/blogstats.go index d4d07d2..5068bf2 100644 --- a/blogstats.go +++ b/blogstats.go @@ -4,7 +4,7 @@ import ( "net/http" ) -func serveBlogStats(blog, statsPath string) func(w http.ResponseWriter, r *http.Request) { +func serveBlogStats(blog, statsPath string) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { // Build query query, params := buildPostsQuery(&postsRequestConfig{ @@ -36,7 +36,7 @@ func serveBlogStats(blog, statsPath string) func(w http.ResponseWriter, r *http. counts = append(counts, count) } } - render(w, templateBlogStats, &renderData{ + render(w, r, templateBlogStats, &renderData{ BlogString: blog, Canonical: statsPath, Data: map[string]interface{}{ diff --git a/cache.go b/cache.go index 9b79f20..f3b7658 100644 --- a/cache.go +++ b/cache.go @@ -31,43 +31,52 @@ func initCache() (err error) { func cacheMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if appConfig.Cache.Enable && - // check method - (r.Method == http.MethodGet || r.Method == http.MethodHead) && - // check bypass query - !(r.URL.Query().Get("cache") == "0" || r.URL.Query().Get("cache") == "false") { - key := cacheKey(r) - // Get cache or render it - cacheInterface, _, _ := cacheGroup.Do(key, func() (interface{}, error) { - return getCache(key, next, r), nil - }) - cache := cacheInterface.(*cacheItem) - // copy cached headers - for k, v := range cache.header { - w.Header()[k] = v - } - setCacheHeaders(w, cache) - // check conditional request - if ifNoneMatchHeader := r.Header.Get("If-None-Match"); ifNoneMatchHeader != "" && ifNoneMatchHeader == cache.eTag { + // Do checks + if !appConfig.Cache.Enable { + next.ServeHTTP(w, r) + return + } + if !(r.Method == http.MethodGet || r.Method == http.MethodHead) { + next.ServeHTTP(w, r) + return + } + if r.URL.Query().Get("cache") == "0" || r.URL.Query().Get("cache") == "false" { + next.ServeHTTP(w, r) + return + } + if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok && loggedIn { + next.ServeHTTP(w, r) + return + } + // Search and serve cache + key := cacheKey(r) + // Get cache or render it + cacheInterface, _, _ := cacheGroup.Do(key, func() (interface{}, error) { + return getCache(key, next, r), nil + }) + cache := cacheInterface.(*cacheItem) + // copy cached headers + for k, v := range cache.header { + w.Header()[k] = v + } + setCacheHeaders(w, cache) + // check conditional request + if ifNoneMatchHeader := r.Header.Get("If-None-Match"); ifNoneMatchHeader != "" && ifNoneMatchHeader == cache.eTag { + // send 304 + w.WriteHeader(http.StatusNotModified) + return + } + if ifModifiedSinceHeader := r.Header.Get("If-Modified-Since"); ifModifiedSinceHeader != "" { + if t, err := dateparse.ParseAny(ifModifiedSinceHeader); err == nil && t.After(cache.creationTime) { // send 304 w.WriteHeader(http.StatusNotModified) return } - if ifModifiedSinceHeader := r.Header.Get("If-Modified-Since"); ifModifiedSinceHeader != "" { - t, err := dateparse.ParseAny(ifModifiedSinceHeader) - if err == nil && t.After(cache.creationTime) { - // send 304 - w.WriteHeader(http.StatusNotModified) - return - } - } - // set status code - w.WriteHeader(cache.code) - // write cached body - _, _ = w.Write(cache.body) - return } - next.ServeHTTP(w, r) + // set status code + w.WriteHeader(cache.code) + // write cached body + _, _ = w.Write(cache.body) }) } diff --git a/captcha.go b/captcha.go index f0fc965..0f08ece 100644 --- a/captcha.go +++ b/captcha.go @@ -34,7 +34,7 @@ func captchaMiddleware(next http.Handler) http.Handler { _ = r.ParseForm() b = []byte(r.PostForm.Encode()) } - render(w, templateCaptcha, &renderData{ + render(w, r, templateCaptcha, &renderData{ Data: map[string]string{ "captchamethod": r.Method, "captchaheaders": base64.StdEncoding.EncodeToString(h), diff --git a/comments.go b/comments.go index 1670de8..1e6d306 100644 --- a/comments.go +++ b/comments.go @@ -41,7 +41,7 @@ func serveComment(blog string) func(http.ResponseWriter, *http.Request) { return } w.Header().Set("X-Robots-Tag", "noindex") - render(w, templateComment, &renderData{ + render(w, r, templateComment, &renderData{ BlogString: blog, Canonical: appConfig.Server.PublicAddress + appConfig.Blogs[blog].getRelativePath(fmt.Sprintf("/comment/%d", id)), Data: comment, diff --git a/commentsAdmin.go b/commentsAdmin.go index 8384273..451873d 100644 --- a/commentsAdmin.go +++ b/commentsAdmin.go @@ -33,7 +33,7 @@ func (p *commentsPaginationAdapter) Slice(offset, length int, data interface{}) return err } -func commentsAdmin(blog, commentPath string) func(w http.ResponseWriter, r *http.Request) { +func commentsAdmin(blog, commentPath string) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { // Adapter pageNoString := chi.URLParam(r, "page") @@ -69,7 +69,7 @@ func commentsAdmin(blog, commentPath string) func(w http.ResponseWriter, r *http } nextPath = fmt.Sprintf("%s/page/%d", commentPath, nextPage) // Render - render(w, templateCommentsAdmin, &renderData{ + render(w, r, templateCommentsAdmin, &renderData{ BlogString: blog, Data: map[string]interface{}{ "Comments": comments, diff --git a/customPages.go b/customPages.go index e935ff6..4b9c37c 100644 --- a/customPages.go +++ b/customPages.go @@ -2,8 +2,8 @@ package main import "net/http" -func serveCustomPage(blog *configBlog, page *customPage) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, _ *http.Request) { +func serveCustomPage(blog *configBlog, page *customPage) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { if appConfig.Cache != nil && appConfig.Cache.Enable && page.Cache { if page.CacheExpiration != 0 { setInternalCacheExpirationHeader(w, page.CacheExpiration) @@ -11,7 +11,7 @@ func serveCustomPage(blog *configBlog, page *customPage) func(w http.ResponseWri setInternalCacheExpirationHeader(w, int(appConfig.Cache.Expiration)) } } - render(w, page.Template, &renderData{ + render(w, r, page.Template, &renderData{ Blog: blog, Canonical: appConfig.Server.PublicAddress + page.Path, Data: page.Data, diff --git a/editor.go b/editor.go index 8bf4633..cda0378 100644 --- a/editor.go +++ b/editor.go @@ -11,9 +11,9 @@ import ( const editorPath = "/editor" -func serveEditor(blog string) func(w http.ResponseWriter, _ *http.Request) { - return func(w http.ResponseWriter, _ *http.Request) { - render(w, templateEditor, &renderData{ +func serveEditor(blog string) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + render(w, r, templateEditor, &renderData{ BlogString: blog, Data: map[string]interface{}{ "Drafts": loadDrafts(blog), @@ -38,7 +38,7 @@ func serveEditorPost(blog string) func(w http.ResponseWriter, r *http.Request) { return } mf := post.toMfItem() - render(w, templateEditor, &renderData{ + render(w, r, templateEditor, &renderData{ BlogString: blog, Data: map[string]interface{}{ "UpdatePostURL": parsedURL.String(), diff --git a/errors.go b/errors.go index 4790516..6f04989 100644 --- a/errors.go +++ b/errors.go @@ -24,7 +24,7 @@ func serveError(w http.ResponseWriter, r *http.Request, message string, status i if message == "" { message = http.StatusText(status) } - render(w, templateError, &renderData{ + render(w, r, templateError, &renderData{ Data: &errorData{ Title: title, Message: message, diff --git a/http.go b/http.go index bad7ef9..71190e2 100644 --- a/http.go +++ b/http.go @@ -98,6 +98,7 @@ func buildHandler() (http.Handler, error) { } r.Use(checkIsLogin) r.Use(checkIsCaptcha) + r.Use(checkLoggedIn) // Profiler if appConfig.Server.Debug { diff --git a/indieAuthServer.go b/indieAuthServer.go index 3f2ad2d..b5b360d 100644 --- a/indieAuthServer.go +++ b/indieAuthServer.go @@ -53,7 +53,7 @@ func indieAuthRequest(w http.ResponseWriter, r *http.Request) { serveError(w, r, "state must not be empty", http.StatusBadRequest) return } - render(w, "indieauth", &renderData{ + render(w, r, "indieauth", &renderData{ Data: data, }) } diff --git a/notifications.go b/notifications.go index 04cf99d..5804682 100644 --- a/notifications.go +++ b/notifications.go @@ -144,7 +144,7 @@ func notificationsAdmin(notificationPath string) func(http.ResponseWriter, *http } nextPath = fmt.Sprintf("%s/page/%d", notificationPath, nextPage) // Render - render(w, templateNotificationsAdmin, &renderData{ + render(w, r, templateNotificationsAdmin, &renderData{ Data: map[string]interface{}{ "Notifications": notifications, "HasPrev": hasPrev, diff --git a/posts.go b/posts.go index 5c70c46..da8d5e8 100644 --- a/posts.go +++ b/posts.go @@ -66,7 +66,7 @@ func servePost(w http.ResponseWriter, r *http.Request) { template = templateStaticHome } w.Header().Add("Link", fmt.Sprintf("<%s>; rel=shortlink", p.shortURL())) - render(w, template, &renderData{ + render(w, r, template, &renderData{ BlogString: p.Blog, Canonical: canonical, Data: p, @@ -285,7 +285,7 @@ func serveIndex(ic *indexConfig) func(w http.ResponseWriter, r *http.Request) { if summaryTemplate == "" { summaryTemplate = templateSummary } - render(w, templateIndex, &renderData{ + render(w, r, templateIndex, &renderData{ BlogString: ic.blog, Canonical: appConfig.Server.PublicAddress + path, Data: map[string]interface{}{ diff --git a/render.go b/render.go index f838c2d..2449cd4 100644 --- a/render.go +++ b/render.go @@ -39,6 +39,7 @@ const ( templateCaptcha = "captcha" templateCommentsAdmin = "commentsadmin" templateNotificationsAdmin = "notificationsadmin" + templateWebmentionAdmin = "webmentionadmin" ) var templates map[string]*template.Template @@ -238,9 +239,10 @@ type renderData struct { Canonical string Blog *configBlog Data interface{} + LoggedIn bool } -func render(w http.ResponseWriter, template string, data *renderData) { +func render(w http.ResponseWriter, r *http.Request, template string, data *renderData) { // Check render data if data.Blog == nil { if len(data.BlogString) == 0 { @@ -259,15 +261,20 @@ func render(w http.ResponseWriter, template string, data *renderData) { if data.Data == nil { data.Data = map[string]interface{}{} } - // We need to use a buffer here to enable minification - var buffer bytes.Buffer - err := templates[template].ExecuteTemplate(&buffer, template, data) + // Check login + if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok && loggedIn { + data.LoggedIn = true + } + // Minify and write response + mw := minifier.Writer(contentTypeHTML, w) + defer func() { + _ = mw.Close() + }() + err := templates[template].ExecuteTemplate(mw, template, data) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Set content type w.Header().Set(contentType, contentTypeHTMLUTF8) - // Write buffered response - _, _ = writeMinified(w, contentTypeHTML, buffer.Bytes()) } diff --git a/search.go b/search.go index 05a4db4..76cce3d 100644 --- a/search.go +++ b/search.go @@ -21,7 +21,7 @@ func serveSearch(blog string, servePath string) func(w http.ResponseWriter, r *h http.Redirect(w, r, path.Join(servePath, searchEncode(q)), http.StatusFound) return } - render(w, templateSearch, &renderData{ + render(w, r, templateSearch, &renderData{ BlogString: blog, Canonical: appConfig.Server.PublicAddress + servePath, }) diff --git a/taxonomies.go b/taxonomies.go index faee1d3..ce5c91e 100644 --- a/taxonomies.go +++ b/taxonomies.go @@ -9,7 +9,7 @@ func serveTaxonomy(blog string, tax *taxonomy) func(w http.ResponseWriter, r *ht serveError(w, r, err.Error(), http.StatusInternalServerError) return } - render(w, templateTaxonomy, &renderData{ + render(w, r, templateTaxonomy, &renderData{ BlogString: blog, Canonical: appConfig.Server.PublicAddress + r.URL.Path, Data: map[string]interface{}{ diff --git a/templates/header.gohtml b/templates/header.gohtml index 3b57ae1..55f23ef 100644 --- a/templates/header.gohtml +++ b/templates/header.gohtml @@ -4,8 +4,16 @@ {{ with .Blog.Description }}

{{ . }}

{{ end }} + {{ if .LoggedIn }} + + {{ end }} {{ end }} \ No newline at end of file diff --git a/webmentionAdmin.go b/webmentionAdmin.go index 451d6b3..122cf90 100644 --- a/webmentionAdmin.go +++ b/webmentionAdmin.go @@ -67,7 +67,7 @@ func webmentionAdmin(w http.ResponseWriter, r *http.Request) { } nextPath = fmt.Sprintf("%s/page/%d", webmentionPath, nextPage) // Render - render(w, "webmentionadmin", &renderData{ + render(w, r, templateWebmentionAdmin, &renderData{ Data: map[string]interface{}{ "Mentions": mentions, "HasPrev": hasPrev,