Refactor and test authentication middleware

pull/7/head
Jan-Lukas Else 1 year ago
parent 6429d64b0a
commit c2dada5d50
  1. 94
      authentication.go
  2. 194
      authentication_test.go
  3. 7
      blogroll.go
  4. 4
      blogstats.go
  5. 10
      cache.go
  6. 26
      captcha.go
  7. 4
      customPages.go
  8. 1
      dbmigrations/00022.sql
  9. 2
      geoMap.go
  10. 2
      go.mod
  11. 36
      go.sum
  12. 64
      http.go
  13. 5
      posts.go
  14. 6
      render.go
  15. 34
      sessions.go
  16. 4
      webmentionVerification.go

@ -7,17 +7,22 @@ import (
"encoding/json"
"io"
"net/http"
"strings"
"github.com/pquerna/otp/totp"
"go.goblog.app/app/pkgs/contenttype"
)
const loggedInKey contextKey = "loggedIn"
// Check if credentials are correct
func (a *goBlog) checkCredentials(username, password, totpPasscode string) bool {
return username == a.cfg.User.Nick &&
password == a.cfg.User.Password &&
(a.cfg.User.TOTP == "" || totp.Validate(totpPasscode, a.cfg.User.TOTP))
}
// Check if app passwords are correct
func (a *goBlog) checkAppPasswords(username, password string) bool {
for _, apw := range a.cfg.User.AppPasswords {
if apw.Username == username && apw.Password == password {
@ -27,31 +32,29 @@ func (a *goBlog) checkAppPasswords(username, password string) bool {
return false
}
func (a *goBlog) jwtKey() []byte {
return []byte(a.cfg.Server.JWTSecret)
// Check if cookie is known and logged in
func (a *goBlog) checkLoginCookie(r *http.Request) bool {
ses, err := a.loginSessions.Get(r, "l")
if err == nil && ses != nil {
if login, ok := ses.Values["login"]; ok && login.(bool) {
return true
}
}
return false
}
// Middleware to force login
func (a *goBlog) authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. Check if already logged in
if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok && loggedIn {
next.ServeHTTP(w, r)
return
}
// 2. Check BasicAuth (just for app passwords)
if username, password, ok := r.BasicAuth(); ok && a.checkAppPasswords(username, password) {
// Check if already logged in
if a.isLoggedIn(r) {
next.ServeHTTP(w, r)
return
}
// 3. Check login cookie
if a.checkLoginCookie(r) {
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), loggedInKey, true)))
return
}
// 4. Show login form
// Show login form
w.Header().Set("Cache-Control", "no-store,max-age=0")
h, _ := json.Marshal(r.Header.Clone())
b, _ := io.ReadAll(io.LimitReader(r.Body, 2000000)) // Only allow 20 Megabyte
h, _ := json.Marshal(r.Header)
b, _ := io.ReadAll(io.LimitReader(r.Body, 20*1000*1000)) // Only allow 20 MB
_ = r.Body.Close()
if len(b) == 0 {
// Maybe it's a form
@ -69,28 +72,7 @@ func (a *goBlog) authMiddleware(next http.Handler) http.Handler {
})
}
const loggedInKey contextKey = "loggedIn"
func (a *goBlog) checkLoggedIn(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if a.checkLoginCookie(r) {
next.ServeHTTP(rw, r.WithContext(context.WithValue(r.Context(), loggedInKey, true)))
return
}
next.ServeHTTP(rw, r)
})
}
func (a *goBlog) checkLoginCookie(r *http.Request) bool {
ses, err := a.loginSessions.Get(r, "l")
if err == nil && ses != nil {
if login, ok := ses.Values["login"]; ok && login.(bool) {
return true
}
}
return false
}
// Middleware to check if the request is a login request
func (a *goBlog) checkIsLogin(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if !a.checkLogin(rw, r) {
@ -99,11 +81,12 @@ func (a *goBlog) checkIsLogin(next http.Handler) http.Handler {
})
}
// Checks login and returns true if it already served an error
func (a *goBlog) checkLogin(w http.ResponseWriter, r *http.Request) bool {
if r.Method != http.MethodPost {
return false
}
if r.Header.Get(contentType) != contenttype.WWWForm {
if !strings.Contains(r.Header.Get(contentType), contenttype.WWWForm) {
return false
}
if r.FormValue("loginaction") != "login" {
@ -131,22 +114,49 @@ func (a *goBlog) checkLogin(w http.ResponseWriter, r *http.Request) bool {
return true
}
ses.Values["login"] = true
cookie, err := a.loginSessions.SaveGetCookie(r, w, ses)
err = a.loginSessions.Save(r, w, ses)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return true
}
req.AddCookie(cookie)
// Serve original request
setLoggedIn(req)
a.d.ServeHTTP(w, req)
return true
}
func (a *goBlog) isLoggedIn(r *http.Request) bool {
// Check if context key already set
if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok && loggedIn {
return true
}
// Check app passwords
if username, password, ok := r.BasicAuth(); ok && a.checkAppPasswords(username, password) {
setLoggedIn(r)
return true
}
// Check session cookie
if a.checkLoginCookie(r) {
setLoggedIn(r)
return true
}
// Not logged in
return false
}
// Set request context value
func setLoggedIn(r *http.Request) {
newRequest := r.WithContext(context.WithValue(r.Context(), loggedInKey, true))
(*r) = *newRequest
}
// HandlerFunc to redirect to home after login
// Need to set auth middleware!
func serveLogin(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusFound)
}
// HandlerFunc to delete login session and cookie
func (a *goBlog) serveLogout(w http.ResponseWriter, r *http.Request) {
if ses, err := a.loginSessions.Get(r, "l"); err == nil && ses != nil {
_ = a.loginSessions.Delete(r, w, ses)

@ -0,0 +1,194 @@
package main
import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"path/filepath"
"strings"
"testing"
"github.com/justinas/alice"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.goblog.app/app/pkgs/contenttype"
)
func Test_authMiddleware(t *testing.T) {
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
Server: &configServer{
PublicAddress: "https://example.com",
},
Blogs: map[string]*configBlog{
"en": {
Lang: "en",
},
},
DefaultBlog: "en",
User: &configUser{
Nick: "test",
Password: "pass",
AppPasswords: []*configAppPassword{
{
Username: "app1",
Password: "pass1",
},
},
},
},
}
_ = app.initDatabase(false)
app.initSessions()
_ = app.initTemplateStrings()
_ = app.initRendering()
app.d = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
_, _ = rw.Write([]byte("ABC Test"))
if app.isLoggedIn(r) {
_, _ = rw.Write([]byte("Logged in"))
}
})
h := alice.New(app.checkIsLogin, app.authMiddleware).Then(app.d)
t.Run("Login required", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/abc", nil)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
res := rec.Result()
resBody, _ := io.ReadAll(res.Body)
_ = res.Body.Close()
resString := string(resBody)
// Login form
assert.Equal(t, http.StatusOK, res.StatusCode)
assert.Contains(t, res.Header.Get("Content-Type"), contenttype.HTML)
assert.Contains(t, resString, "name=loginmethod value=POST")
})
t.Run("Login with app password", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/abc", nil)
req.SetBasicAuth("app1", "pass1")
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
res := rec.Result()
resBody, _ := io.ReadAll(res.Body)
_ = res.Body.Close()
resString := string(resBody)
assert.Equal(t, http.StatusOK, res.StatusCode)
assert.Contains(t, resString, "ABC Test")
assert.Contains(t, resString, "Logged in")
})
t.Run("Login with wrong app password", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/abc", nil)
req.SetBasicAuth("app1", "wrong")
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
res := rec.Result()
resBody, _ := io.ReadAll(res.Body)
_ = res.Body.Close()
resString := string(resBody)
// Login form
assert.Equal(t, http.StatusOK, res.StatusCode)
assert.Contains(t, res.Header.Get("Content-Type"), contenttype.HTML)
assert.Contains(t, resString, "name=loginmethod value=POST")
})
t.Run("Login with form", func(t *testing.T) {
// First request
data := url.Values{}
data.Add("loginaction", "login")
data.Add("loginmethod", "GET")
data.Add("username", "test")
data.Add("password", "pass")
req := httptest.NewRequest(http.MethodPost, "/abc", strings.NewReader(data.Encode()))
req.Header.Add("Content-Type", contenttype.WWWForm)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
res := rec.Result()
resBody, _ := io.ReadAll(res.Body)
_ = res.Body.Close()
resString := string(resBody)
assert.Equal(t, http.StatusOK, res.StatusCode)
assert.Contains(t, resString, "ABC Test")
assert.Contains(t, resString, "Logged in")
// Second request using cookie
require.Len(t, res.Cookies(), 1)
req = httptest.NewRequest(http.MethodPost, "/abc", nil)
req.AddCookie(res.Cookies()[0])
rec = httptest.NewRecorder()
h.ServeHTTP(rec, req)
res = rec.Result()
resBody, _ = io.ReadAll(res.Body)
_ = res.Body.Close()
resString = string(resBody)
assert.Equal(t, http.StatusOK, res.StatusCode)
assert.Contains(t, resString, "ABC Test")
assert.Contains(t, resString, "Logged in")
})
t.Run("Login with wrong credentials", func(t *testing.T) {
data := url.Values{}
data.Add("loginaction", "login")
data.Add("loginmethod", "GET")
data.Add("username", "test")
data.Add("password", "wrong")
req := httptest.NewRequest(http.MethodPost, "/abc", strings.NewReader(data.Encode()))
req.Header.Add("Content-Type", contenttype.WWWForm)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
res := rec.Result()
resBody, _ := io.ReadAll(res.Body)
_ = res.Body.Close()
resString := string(resBody)
assert.Equal(t, http.StatusUnauthorized, res.StatusCode)
assert.Contains(t, res.Header.Get("Content-Type"), contenttype.HTML)
assert.Contains(t, resString, "Incorrect credentials")
})
}
func Test_setLoggedIn(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/abc", nil)
setLoggedIn(req)
loggedIn, ok := req.Context().Value(loggedInKey).(bool)
assert.True(t, ok)
assert.True(t, loggedIn)
}

@ -11,7 +11,6 @@ import (
"time"
"github.com/kaorimatz/go-opml"
servertiming "github.com/mitchellh/go-server-timing"
"github.com/thoas/go-funk"
"go.goblog.app/app/pkgs/contenttype"
)
@ -20,18 +19,16 @@ const defaultBlogrollPath = "/blogroll"
func (a *goBlog) serveBlogroll(w http.ResponseWriter, r *http.Request) {
blog := r.Context().Value(blogContextKey).(string)
t := servertiming.FromContext(r.Context()).NewMetric("bg").Start()
outlines, err, _ := a.blogrollCacheGroup.Do(blog, func() (interface{}, error) {
return a.getBlogrollOutlines(blog)
})
t.Stop()
if err != nil {
log.Printf("Failed to get outlines: %v", err)
a.serveError(w, r, "", http.StatusInternalServerError)
return
}
if a.cfg.Cache != nil && a.cfg.Cache.Enable {
setInternalCacheExpirationHeader(w, r, int(a.cfg.Cache.Expiration))
a.setInternalCacheExpirationHeader(w, r, int(a.cfg.Cache.Expiration))
}
c := a.cfg.Blogs[blog].Blogroll
can := a.getRelativePath(blog, defaultIfEmpty(c.Path, defaultBlogrollPath))
@ -58,7 +55,7 @@ func (a *goBlog) serveBlogrollExport(w http.ResponseWriter, r *http.Request) {
return
}
if a.cfg.Cache != nil && a.cfg.Cache.Enable {
setInternalCacheExpirationHeader(w, r, int(a.cfg.Cache.Expiration))
a.setInternalCacheExpirationHeader(w, r, int(a.cfg.Cache.Expiration))
}
w.Header().Set(contentType, contenttype.XMLUTF8)
var opmlBytes bytes.Buffer

@ -5,8 +5,6 @@ import (
"database/sql"
"encoding/gob"
"net/http"
servertiming "github.com/mitchellh/go-server-timing"
)
const defaultBlogStatsPath = "/statistics"
@ -35,11 +33,9 @@ func (a *goBlog) serveBlogStats(w http.ResponseWriter, r *http.Request) {
func (a *goBlog) serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
blog := r.Context().Value(blogContextKey).(string)
t := servertiming.FromContext(r.Context()).NewMetric("bs").Start()
data, err, _ := a.blogStatsCacheGroup.Do(blog, func() (interface{}, error) {
return a.db.getBlogStats(blog)
})
t.Stop()
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return

@ -40,7 +40,8 @@ func (a *goBlog) initCache() (err error) {
return
}
func (c *cache) cacheMiddleware(next http.Handler) http.Handler {
func (a *goBlog) cacheMiddleware(next http.Handler) http.Handler {
c := a.cache
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if c.c == nil {
// No cache configured
@ -56,7 +57,7 @@ func (c *cache) cacheMiddleware(next http.Handler) http.Handler {
next.ServeHTTP(w, r)
return
}
if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok && loggedIn {
if a.isLoggedIn(r) {
next.ServeHTTP(w, r)
return
}
@ -179,7 +180,6 @@ func (c *cache) getCache(key string, next http.Handler, r *http.Request) (item *
result.Header.Del("Accept-Ranges")
result.Header.Del("ETag")
result.Header.Del("Last-Modified")
result.Header.Del("Server-Timing")
// Create cache item
item = &cacheItem{
expiration: exp,
@ -205,8 +205,8 @@ func (c *cache) purge() {
c.c.Clear()
}
func setInternalCacheExpirationHeader(w http.ResponseWriter, r *http.Request, expiration int) {
if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok && loggedIn {
func (a *goBlog) setInternalCacheExpirationHeader(w http.ResponseWriter, r *http.Request, expiration int) {
if a.isLoggedIn(r) {
return
}
w.Header().Set(cacheInternalExpirationHeader, strconv.Itoa(expiration))

@ -2,28 +2,38 @@ package main
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"io"
"net/http"
"strings"
"github.com/dchest/captcha"
"go.goblog.app/app/pkgs/contenttype"
)
const captchaSolvedKey contextKey = "captchaSolved"
func (a *goBlog) captchaMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. Check Cookie
// Check if captcha already solved
if solved, ok := r.Context().Value(captchaSolvedKey).(bool); ok && solved {
next.ServeHTTP(w, r)
return
}
// Check Cookie
ses, err := a.captchaSessions.Get(r, "c")
if err == nil && ses != nil {
if captcha, ok := ses.Values["captcha"]; ok && captcha.(bool) {
next.ServeHTTP(w, r)
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), captchaSolvedKey, true)))
return
}
}
// 2. Show Captcha
h, _ := json.Marshal(r.Header.Clone())
b, _ := io.ReadAll(io.LimitReader(r.Body, 2000000)) // Only allow 20 Megabyte
// Show Captcha
w.Header().Set("Cache-Control", "no-store,max-age=0")
h, _ := json.Marshal(r.Header)
b, _ := io.ReadAll(io.LimitReader(r.Body, 20*1000*1000)) // Only allow 20 MB
_ = r.Body.Close()
if len(b) == 0 {
// Maybe it's a form
@ -53,7 +63,7 @@ func (a *goBlog) checkCaptcha(w http.ResponseWriter, r *http.Request) bool {
if r.Method != http.MethodPost {
return false
}
if r.Header.Get(contentType) != contenttype.WWWForm {
if !strings.Contains(r.Header.Get(contentType), contenttype.WWWForm) {
return false
}
if r.FormValue("captchaaction") != "captcha" {
@ -77,12 +87,12 @@ func (a *goBlog) checkCaptcha(w http.ResponseWriter, r *http.Request) bool {
return true
}
ses.Values["captcha"] = true
cookie, err := a.captchaSessions.SaveGetCookie(r, w, ses)
err = a.captchaSessions.Save(r, w, ses)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return true
}
req.AddCookie(cookie)
req = req.WithContext(context.WithValue(req.Context(), captchaSolvedKey, true))
}
// Serve original request
a.d.ServeHTTP(w, req)

@ -8,9 +8,9 @@ func (a *goBlog) serveCustomPage(w http.ResponseWriter, r *http.Request) {
page := r.Context().Value(customPageContextKey).(*configCustomPage)
if a.cfg.Cache != nil && a.cfg.Cache.Enable && page.Cache {
if page.CacheExpiration != 0 {
setInternalCacheExpirationHeader(w, r, page.CacheExpiration)
a.setInternalCacheExpirationHeader(w, r, page.CacheExpiration)
} else {
setInternalCacheExpirationHeader(w, r, int(a.cfg.Cache.Expiration))
a.setInternalCacheExpirationHeader(w, r, int(a.cfg.Cache.Expiration))
}
}
a.render(w, r, page.Template, &renderData{

@ -0,0 +1 @@
create index sessions_exp on sessions (expires);

@ -4,7 +4,6 @@ import (
"embed"
"encoding/json"
"io"
"log"
"net/http"
"net/http/httptest"
"net/http/httputil"
@ -82,7 +81,6 @@ var leafletFiles embed.FS
func (a *goBlog) serveLeaflet(basePath string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
fileName := strings.TrimPrefix(r.URL.Path, basePath)
log.Println(basePath, fileName)
fb, err := leafletFiles.ReadFile(fileName)
if err != nil {
a.serve404(w, r)

@ -20,7 +20,6 @@ require (
github.com/go-chi/chi/v5 v5.0.3
github.com/go-fed/httpsig v1.1.0
github.com/go-sql-driver/mysql v1.5.0 // indirect
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f // indirect
github.com/golang/glog v0.0.0-20210429001901-424d2337a529 // indirect
github.com/gopherjs/gopherjs v0.0.0-20210202160940-bed99a852dfe // indirect
github.com/gorilla/handlers v1.5.1
@ -38,7 +37,6 @@ require (
github.com/lopezator/migrator v0.3.0
github.com/mattn/go-sqlite3 v1.14.8
github.com/microcosm-cc/bluemonday v1.0.15
github.com/mitchellh/go-server-timing v1.0.1
github.com/paulmach/go.geojson v1.4.0
github.com/pquerna/otp v1.3.0
github.com/schollz/sqlite3dump v1.3.0

@ -1,4 +1,3 @@
cloud.google.com/go v0.16.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@ -68,7 +67,6 @@ github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqO
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2 h1:t8KYCwSKsOEZBFELI4Pn/phbp38iJ1RRAkDFNin1aak=
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -108,14 +106,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/felixge/httpsnoop v1.0.0/go.mod h1:3+D9sFq0ahK/JeJPhCBUV1xlf4/eIYrUQaxulT0VzX8=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.4.3-0.20170329110642-4da3e2cfbabc/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/garyburd/redigo v1.1.1-0.20170914051019-70e1b1943d4f/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/gchaincl/sqlhooks/v2 v2.0.1 h1:j9WZAq1Tx/xngDfEdsgUww+o9iY3rLOYGYhYWcFxdmI=
github.com/gchaincl/sqlhooks/v2 v2.0.1/go.mod h1:Qv7HXjGB9TehamVK52yW5H+a0RRhprIj3ESTcWOG2jw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -129,19 +124,14 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4=
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg=
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f/go.mod h1:ijRvpgDJDI262hYq/IQVYgf8hd8IHUs93Ol0kvMBAx4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v0.0.0-20210429001901-424d2337a529 h1:2voWjNECnrZRbfwXxHB1/j8wa6xdKn85B5NzgVL/pTU=
github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20170918230701-e5d664eb928e/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@ -167,10 +157,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.1.1-0.20171103154506-982329095285/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -200,7 +188,6 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -214,7 +201,6 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gregjones/httpcache v0.0.0-20170920190843-316c5e0ff04e/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
@ -231,7 +217,6 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v0.0.0-20170914154624-68e816d1c783/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
@ -240,7 +225,6 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/log15 v0.0.0-20170622235902-74a0988b5f80/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jlaffaye/ftp v0.0.0-20210307004419-5d4190119067 h1:P2S26PMwXl8+ZGuOG3C69LG4be5vHafUayZm9VPw3tU=
@ -262,7 +246,6 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -283,13 +266,10 @@ github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lopezator/migrator v0.3.0 h1:VW/rR+J8NYwPdkBxjrFdjwejpgvP59LbmANJxXuNbuk=
github.com/lopezator/migrator v0.3.0/go.mod h1:bpVAVPkWSvTw8ya2Pk7E/KiNAyDWNImgivQY79o8/8I=
github.com/magiconair/properties v1.7.4-0.20170902060319-8d7837e64d3c/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.0.10-0.20170816031813-ad5389df28cd/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@ -302,13 +282,10 @@ github.com/microcosm-cc/bluemonday v1.0.15/go.mod h1:ZLvAzeakRwrGnzQEvstVzVt3Zpq
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-server-timing v1.0.1 h1:f00/aIe8T3MrnLhQHu3tSWvnwc5GV/p5eutuu3hF/tE=
github.com/mitchellh/go-server-timing v1.0.1/go.mod h1:Mo6GKi9FSLwWFAMn3bqVPWe20y5ri5QGQuO9D9MCOxk=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@ -319,7 +296,6 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/paulmach/go.geojson v1.4.0 h1:5x5moCkCtDo5x8af62P9IOAYGQcYHtxz2QJ3x1DoCgY=
github.com/paulmach/go.geojson v1.4.0/go.mod h1:YaKx1hKpWF+T2oj2lFJPsW/t1Q5e1jQI61eoQSTwpIs=
github.com/pelletier/go-toml v1.0.1-0.20170904195809-1d6b12b7cb29/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -349,19 +325,14 @@ github.com/snabb/diagio v1.0.0 h1:kovhQ1rDXoEbmpf/T5N2sUp2iOdxEg+TcqzbYVHV2V0=
github.com/snabb/diagio v1.0.0/go.mod h1:ZyGaWFhfBVqstGUw6laYetzeTwZ2xxVPqTALx1QQa1w=
github.com/snabb/sitemap v1.0.0 h1:7vJeNPAaaj7fQSRS3WYuJHzUjdnhLdSLLpvVtnhbzC0=
github.com/snabb/sitemap v1.0.0/go.mod h1:Id8uz1+WYdiNmSjEi4BIvL5UwNPYLsTHzRbjmDwNDzA=
github.com/spf13/afero v0.0.0-20170901052352-ee1bd8ee15a1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.1.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/jwalterweatherman v0.0.0-20170901151539-12bd96e66386/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.1-0.20170901120850-7aff26db30c1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -496,7 +467,6 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210716203947-853a461950ff h1:j2EK/QoxYNBsXI4R7fQkkRUk8y6wnOBI+6hgPdP/6Ds=
golang.org/x/net v0.0.0-20210716203947-853a461950ff/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
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=
@ -509,7 +479,6 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20170517211232-f52d1811a629/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -579,7 +548,6 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -638,7 +606,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20170921000349-586095a6e407/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@ -668,7 +635,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20170918111702-1e559d0a00ee/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -710,7 +676,6 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/grpc v1.2.1-0.20170921194603-d4b75ebd4f9f/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -745,7 +710,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=

@ -17,7 +17,6 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/justinas/alice"
servertiming "github.com/mitchellh/go-server-timing"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
"golang.org/x/net/context"
@ -38,17 +37,15 @@ func (a *goBlog) startServer() (err error) {
}
a.d = fixHTTPHandler(router)
// Set basic middlewares
var finalHandler http.Handler = a.d
if a.cfg.Server.PublicHTTPS || a.cfg.Server.SecurityHeaders {
finalHandler = a.securityHeaders(finalHandler)
}
finalHandler = servertiming.Middleware(finalHandler, nil)
finalHandler = middleware.Heartbeat("/ping")(finalHandler)
finalHandler = middleware.Compress(flate.DefaultCompression)(finalHandler)
finalHandler = middleware.Recoverer(finalHandler)
h := alice.New()
if a.cfg.Server.Logging {
finalHandler = a.logMiddleware(finalHandler)
h = h.Append(a.logMiddleware)
}
h = h.Append(middleware.Recoverer, middleware.Compress(flate.DefaultCompression), middleware.Heartbeat("/ping"))
if a.cfg.Server.PublicHTTPS || a.cfg.Server.SecurityHeaders {
h = h.Append(a.securityHeaders)
}
finalHandler := h.Then(a.d)
// Start Onion service
if a.cfg.Server.Tor {
go func() {
@ -154,10 +151,9 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
r.Use(noIndexHeader)
}
// Login middleware etc.
// Login and captcha middleware
r.Use(a.checkIsLogin)
r.Use(a.checkIsCaptcha)
r.Use(a.checkLoggedIn)
// Logout
r.With(a.authMiddleware).Get("/login", serveLogin)
@ -187,7 +183,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
r.Post("/{blog}/inbox", a.apHandleInbox)
})
r.Group(func(r chi.Router) {
r.Use(a.cache.cacheMiddleware)
r.Use(a.cacheMiddleware)
r.Get("/.well-known/webfinger", a.apHandleWebfinger)
r.Get("/.well-known/host-meta", handleWellKnownHostMeta)
r.Get("/.well-known/nodeinfo", a.serveNodeInfoDiscover)
@ -236,7 +232,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
r.Handle("/captcha/*", captcha.Server(500, 250))
// Short paths
r.With(privateModeHandler...).With(a.cache.cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", a.redirectToLongPath)
r.With(privateModeHandler...).With(a.cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", a.redirectToLongPath)
for blog, blogConfig := range a.cfg.Blogs {
sbm := middleware.WithValue(blogContextKey, blog)
@ -244,7 +240,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
// Sections
r.Group(func(r chi.Router) {
r.Use(privateModeHandler...)
r.Use(a.cache.cacheMiddleware, sbm)
r.Use(a.cacheMiddleware, sbm)
for _, section := range blogConfig.Sections {
if section.Name != "" {
r.Group(func(r chi.Router) {
@ -264,7 +260,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
// Taxonomies
r.Group(func(r chi.Router) {
r.Use(privateModeHandler...)
r.Use(a.cache.cacheMiddleware, sbm)
r.Use(a.cacheMiddleware, sbm)
for _, taxonomy := range blogConfig.Taxonomies {
if taxonomy.Name != "" {
r.Group(func(r chi.Router) {
@ -285,7 +281,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
r.Group(func(r chi.Router) {
photoPath := blogConfig.getRelativePath(defaultIfEmpty(pc.Path, defaultPhotosPath))
r.Use(privateModeHandler...)
r.Use(a.cache.cacheMiddleware, sbm, middleware.WithValue(indexConfigKey, &indexConfig{
r.Use(a.cacheMiddleware, sbm, middleware.WithValue(indexConfigKey, &indexConfig{
path: photoPath,
parameter: pc.Parameter,
title: pc.Title,
@ -308,7 +304,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
))
r.Group(func(r chi.Router) {
r.Use(privateModeHandler...)
r.Use(a.cache.cacheMiddleware)
r.Use(a.cacheMiddleware)
r.Get("/", a.serveSearch)
r.Post("/", a.serveSearch)
searchResultPath := "/" + searchPlaceholder
@ -316,7 +312,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
r.Get(searchResultPath+feedPath, a.serveSearchResult)
r.Get(searchResultPath+paginationPath, a.serveSearchResult)
})
r.With(a.cache.cacheMiddleware).Get("/opensearch.xml", a.serveOpenSearch)
r.With(a.cacheMiddleware).Get("/opensearch.xml", a.serveOpenSearch)
})
}
@ -325,7 +321,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
statsPath := blogConfig.getRelativePath(defaultIfEmpty(bsc.Path, defaultBlogStatsPath))
r.Group(func(r chi.Router) {
r.Use(privateModeHandler...)
r.Use(a.cache.cacheMiddleware, sbm)
r.Use(a.cacheMiddleware, sbm)
r.Get(statsPath, a.serveBlogStats)
r.Get(statsPath+".table.html", a.serveBlogStatsTable)
})
@ -334,7 +330,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
// Date archives
r.Group(func(r chi.Router) {
r.Use(privateModeHandler...)
r.Use(a.cache.cacheMiddleware, sbm)
r.Use(a.cacheMiddleware, sbm)
yearRegex := `/{year:x|\d\d\d\d}`
monthRegex := `/{month:x|\d\d}`
@ -361,9 +357,9 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
r.Group(func(r chi.Router) {
r.Use(privateModeHandler...)
r.Use(sbm)
r.With(a.checkActivityStreamsRequest, a.cache.cacheMiddleware).Get(blogConfig.getRelativePath(""), a.serveHome)
r.With(a.cache.cacheMiddleware).Get(blogConfig.getRelativePath("")+feedPath, a.serveHome)
r.With(a.cache.cacheMiddleware).Get(blogConfig.getRelativePath(paginationPath), a.serveHome)
r.With(a.checkActivityStreamsRequest, a.cacheMiddleware).Get(blogConfig.getRelativePath(""), a.serveHome)
r.With(a.cacheMiddleware).Get(blogConfig.getRelativePath("")+feedPath, a.serveHome)
r.With(a.cacheMiddleware).Get(blogConfig.getRelativePath(paginationPath), a.serveHome)
})
}
@ -371,7 +367,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
for _, cp := range blogConfig.CustomPages {
scp := middleware.WithValue(customPageContextKey, cp)
if cp.Cache {
r.With(privateModeHandler...).With(a.cache.cacheMiddleware, sbm, scp).Get(cp.Path, a.serveCustomPage)
r.With(privateModeHandler...).With(a.cacheMiddleware, sbm, scp).Get(cp.Path, a.serveCustomPage)
} else {
r.With(privateModeHandler...).With(sbm, scp).Get(cp.Path, a.serveCustomPage)
}
@ -407,7 +403,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
r.Route(commentsPath, func(r chi.Router) {
r.Use(sbm, middleware.WithValue(pathContextKey, commentsPath))
r.Use(privateModeHandler...)
r.With(a.cache.cacheMiddleware, noIndexHeader).Get("/{id:[0-9]+}", a.serveComment)
r.With(a.cacheMiddleware, noIndexHeader).Get("/{id:[0-9]+}", a.serveComment)
r.With(a.captchaMiddleware).Post("/", a.createComment)
r.Group(func(r chi.Router) {
// Admin
@ -424,7 +420,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
brPath := blogConfig.getRelativePath(defaultIfEmpty(brConfig.Path, defaultBlogrollPath))
r.Group(func(r chi.Router) {
r.Use(privateModeHandler...)
r.Use(a.cache.cacheMiddleware, sbm)
r.Use(a.cacheMiddleware, sbm)
r.Get(brPath, a.serveBlogroll)
r.Get(brPath+".opml", a.serveBlogrollExport)
})
@ -436,7 +432,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
r.Route(mapPath, func(r chi.Router) {
r.Use(privateModeHandler...)
r.Group(func(r chi.Router) {
r.Use(a.cache.cacheMiddleware, sbm)
r.Use(a.cacheMiddleware, sbm)
r.Get("/", a.serveGeoMap)
r.HandleFunc("/leaflet/*", a.serveLeaflet(mapPath+"/"))
})
@ -449,7 +445,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
contactPath := blogConfig.getRelativePath(defaultIfEmpty(cc.Path, defaultContactPath))
r.Route(contactPath, func(r chi.Router) {
r.Use(privateModeHandler...)
r.Use(a.cache.cacheMiddleware, sbm)
r.Use(a.cacheMiddleware, sbm)
r.Get("/", a.serveContactForm)
r.With(a.captchaMiddleware).Post("/", a.sendContactSubmission)
})
@ -458,7 +454,7 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
}
// Sitemap
r.With(privateModeHandler...).With(a.cache.cacheMiddleware).Get(sitemapPath, a.serveSitemap)
r.With(privateModeHandler...).With(a.cacheMiddleware).Get(sitemapPath, a.serveSitemap)
// Robots.txt - doesn't need cache, because it's too simple
if !privateMode {
@ -517,7 +513,7 @@ func (a *goBlog) servePostsAliasesRedirects(pmh ...func(http.Handler) http.Handl
// Is post, check status
switch postStatus(value) {
case statusPublished, statusUnlisted:
alicePrivate.Append(a.checkActivityStreamsRequest, a.cache.cacheMiddleware).ThenFunc(a.servePost).ServeHTTP(w, r)
alicePrivate.Append(a.checkActivityStreamsRequest, a.cacheMiddleware).ThenFunc(a.servePost).ServeHTTP(w, r)
return
case statusDraft, statusPrivate:
alice.New(a.authMiddleware).ThenFunc(a.servePost).ServeHTTP(w, r)
@ -525,20 +521,20 @@ func (a *goBlog) servePostsAliasesRedirects(pmh ...func(http.Handler) http.Handl
}
case "alias":
// Is alias, redirect
alicePrivate.Append(a.cache.cacheMiddleware).ThenFunc(func(w http.ResponseWriter, r *http.Request) {
alicePrivate.Append(a.cacheMiddleware).ThenFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, value, http.StatusFound)
}).ServeHTTP(w, r)
return
case "deleted":
// Is deleted, serve 410
alicePrivate.Append(a.cache.cacheMiddleware).ThenFunc(func(w http.ResponseWriter, r *http.Request) {
alicePrivate.Append(a.cacheMiddleware).ThenFunc(func(w http.ResponseWriter, r *http.Request) {
a.serve410(w, r)
}).ServeHTTP(w, r)
return
}
}
// No post, check regex redirects or serve 404 error
alice.New(a.cache.cacheMiddleware, a.checkRegexRedirects).ThenFunc(a.serve404).ServeHTTP(w, r)
alice.New(a.cacheMiddleware, a.checkRegexRedirects).ThenFunc(a.serve404).ServeHTTP(w, r)
}
}

@ -13,7 +13,6 @@ import (
"github.com/go-chi/chi/v5"
"github.com/microcosm-cc/bluemonday"
servertiming "github.com/mitchellh/go-server-timing"
"github.com/vcraescu/go-paginator"
)
@ -46,9 +45,7 @@ const (
)
func (a *goBlog) servePost(w http.ResponseWriter, r *http.Request) {
t := servertiming.FromContext(r.Context()).NewMetric("gp").Start()
p, err := a.db.getPost(r.URL.Path)
t.Stop()
if err == errPostNotFound {
a.serve404(w, r)
return
@ -253,9 +250,7 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
}, db: a.db}, a.cfg.Blogs[blog].Pagination)
p.SetPage(pageNo)
var posts []*post
t := servertiming.FromContext(r.Context()).NewMetric("gp").Start()
err := p.Results(&posts)
t.Stop()
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return

@ -11,7 +11,6 @@ import (
"path/filepath"
"strings"
servertiming "github.com/mitchellh/go-server-timing"
"go.goblog.app/app/pkgs/contenttype"
)
@ -115,9 +114,6 @@ func (a *goBlog) render(w http.ResponseWriter, r *http.Request, template string,
}
func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, statusCode int, template string, data *renderData) {
// Server timing
t := servertiming.FromContext(r.Context()).NewMetric("r").Start()
defer t.Stop()
// Check render data
a.checkRenderData(r, data)
// Set content type
@ -165,7 +161,7 @@ func (a *goBlog) checkRenderData(r *http.Request, data *renderData) {
data.TorUsed = true
}
// Check login
if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok && loggedIn {
if a.isLoggedIn(r) {
data.LoggedIn = true
}
// Check if comments enabled

@ -2,7 +2,6 @@ package main
import (
"database/sql"
"errors"
"fmt"
"log"
"net/http"
@ -31,7 +30,7 @@ func (a *goBlog) initSessions() {
deleteExpiredSessions()
a.hourlyHooks = append(a.hourlyHooks, deleteExpiredSessions)
a.loginSessions = &dbSessionStore{
codecs: securecookie.CodecsFromPairs(a.jwtKey()),
codecs: securecookie.CodecsFromPairs([]byte(a.cfg.Server.JWTSecret)),
options: &sessions.Options{
Secure: a.httpsConfigured(),
HttpOnly: true,
@ -42,7 +41,7 @@ func (a *goBlog) initSessions() {
db: a.db,
}
a.captchaSessions = &dbSessionStore{
codecs: securecookie.CodecsFromPairs(a.jwtKey()),
codecs: securecookie.CodecsFromPairs([]byte(a.cfg.Server.JWTSecret)),
options: &sessions.Options{
Secure: a.httpsConfigured(),
HttpOnly: true,
@ -77,25 +76,19 @@ func (s *dbSessionStore) New(r *http.Request, name string) (session *sessions.Se
return session, err
}
func (s *dbSessionStore) Save(r *http.Request, w http.ResponseWriter, ss *sessions.Session) error {
_, err := s.SaveGetCookie(r, w, ss)
return err
}
func (s *dbSessionStore) SaveGetCookie(r *http.Request, w http.ResponseWriter, ss *sessions.Session) (cookie *http.Cookie, err error) {
func (s *dbSessionStore) Save(r *http.Request, w http.ResponseWriter, ss *sessions.Session) (err error) {
if ss.ID == "" {
if err = s.insert(ss); err != nil {
return nil, err
return err
}
} else if err = s.save(ss); err != nil {
return nil, err
return err
}
if encoded, err := securecookie.EncodeMulti(ss.Name(), ss.ID, s.codecs...); err != nil {
return nil, err
return err
} else {
cookie = sessions.NewCookie(ss.Name(), encoded, ss.Options)
http.SetCookie(w, cookie)
return cookie, nil
http.SetCookie(w, sessions.NewCookie(ss.Name(), encoded, ss.Options))
return nil
}
}
@ -113,22 +106,17 @@ func (s *dbSessionStore) Delete(r *http.Request, w http.ResponseWriter, session
}
func (s *dbSessionStore) load(session *sessions.Session) (err error) {
row, err := s.db.queryRow("select data, created, modified, expires from sessions where id = @id", sql.Named("id", session.ID))
row, err := s.db.queryRow("select data, created, modified, expires from sessions where id = @id and expires > @now", sql.Named("id", session.ID), sql.Named("now", utcNowString()))
if err != nil {
return err
}
var data, createdStr, modifiedStr, expiresStr string
if err = row.Scan(&data, &createdStr, &modifiedStr, &expiresStr); err == sql.ErrNoRows {
return nil
} else if err != nil {
if err = row.Scan(&data, &createdStr, &modifiedStr, &expiresStr); err != nil {
return err
}
created, _ := dateparse.ParseLocal(createdStr)
modified, _ := dateparse.ParseLocal(modifiedStr)
expires, _ := dateparse.ParseLocal(expiresStr)
if expires.Before(time.Now()) {
return errors.New("session expired")
}
if err = securecookie.DecodeMulti(session.Name(), data, &session.Values, s.codecs...); err != nil {
return err
}
@ -149,7 +137,7 @@ func (s *dbSessionStore) insert(session *sessions.Session) (err error) {
if err != nil {
return err
}
res, err := s.db.exec("insert into sessions(data, created, modified, expires) values(@data, @created, @modified, @expires)",
res, err := s.db.exec("insert or replace into sessions(data, created, modified, expires) values(@data, @created, @modified, @expires)",
sql.Named("data", encoded), sql.Named("created", created.Format(time.RFC3339)), sql.Named("modified", modified.Format(time.RFC3339)), sql.Named("expires", exp