mirror of https://github.com/jlelse/GoBlog
Show admin links when logged in
This commit is contained in:
parent
bb73d4831c
commit
e74afac829
|
@ -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) {
|
||||
|
|
|
@ -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{}{
|
||||
|
|
73
cache.go
73
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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
1
http.go
1
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 {
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
4
posts.go
4
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{}{
|
||||
|
|
19
render.go
19
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())
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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{}{
|
||||
|
|
|
@ -4,8 +4,16 @@
|
|||
{{ with .Blog.Description }}<p><i>{{ . }}</i></p>{{ end }}
|
||||
<nav>
|
||||
{{ with menu .Blog "main" }}
|
||||
{{ range $i, $item := .Items }}{{ if ne $i 0 }} • {{ end }}<a href="{{ $item.Link }}">{{ $item.Title }}</a>{{ end }}
|
||||
{{ range $i, $item := .Items }}{{ if ne $i 0 }} • {{ end }}<a href="{{ $item.Link }}">{{ $item.Title }}</a>{{ end }}
|
||||
{{ end }}
|
||||
</nav>
|
||||
{{ if .LoggedIn }}
|
||||
<nav>
|
||||
<a href="{{ blogrelative .Blog "/editor" }}">{{ string .Blog.Lang "editor" }}</a>
|
||||
• <a href="/notifications">{{ string .Blog.Lang "notifications" }}</a>
|
||||
• <a href="/webmention">{{ string .Blog.Lang "webmentions" }}</a>
|
||||
• <a href="{{ blogrelative .Blog "/comment" }}">{{ string .Blog.Lang "comments" }}</a>
|
||||
</nav>
|
||||
{{ end }}
|
||||
</header>
|
||||
{{ end }}
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue