Use sessions instead of jwt

This commit is contained in:
Jan-Lukas Else 2021-05-14 18:24:02 +02:00
parent d7da17bda1
commit 1b2eed9897
9 changed files with 282 additions and 146 deletions

View File

@ -7,9 +7,7 @@ import (
"encoding/json"
"io"
"net/http"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/pquerna/otp/totp"
)
@ -19,10 +17,6 @@ func checkCredentials(username, password, totpPasscode string) bool {
(appConfig.User.TOTP == "" || totp.Validate(totpPasscode, appConfig.User.TOTP))
}
func checkUsernameTOTP(username string, totp bool) bool {
return username == appConfig.User.Nick && totp == (appConfig.User.TOTP != "")
}
func checkAppPasswords(username, password string) bool {
for _, apw := range appConfig.User.AppPasswords {
if apw.Username == username && apw.Password == password {
@ -38,22 +32,22 @@ 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
// 1. Check if already logged in
if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok && loggedIn {
next.ServeHTTP(w, r)
return
}
// 1. Check BasicAuth (just for app passwords)
// 2. Check BasicAuth (just for app passwords)
if username, password, ok := r.BasicAuth(); ok && checkAppPasswords(username, password) {
next.ServeHTTP(w, r)
return
}
// 2. Check JWT
if checkAuthToken(r) {
next.ServeHTTP(w, r)
// 3. Check login cookie
if checkLoginCookie(r) {
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), loggedInKey, true)))
return
}
// 3. Show login form
// 4. 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
@ -74,25 +68,11 @@ 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" &&
checkUsernameTOTP(claims.Username, claims.TOTP) {
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) {
if checkLoginCookie(r) {
next.ServeHTTP(rw, r.WithContext(context.WithValue(r.Context(), loggedInKey, true)))
return
}
@ -100,6 +80,16 @@ func checkLoggedIn(next http.Handler) http.Handler {
})
}
func checkLoginCookie(r *http.Request) bool {
ses, err := loginSessionsStore.Get(r, "l")
if err == nil && ses != nil {
if login, ok := ses.Values["login"]; ok && login.(bool) {
return true
}
}
return false
}
func checkIsLogin(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if !checkLogin(rw, r) {
@ -134,58 +124,31 @@ func checkLogin(w http.ResponseWriter, r *http.Request) bool {
req.Header[k] = v
}
// Cookie
tokenCookie, err := createTokenCookie()
ses, err := loginSessionsStore.Get(r, "l")
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return true
}
req.AddCookie(tokenCookie)
http.SetCookie(w, tokenCookie)
ses.Values["login"] = true
cookie, err := loginSessionsStore.SaveGetCookie(r, w, ses)
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return true
}
req.AddCookie(cookie)
// Serve original request
d.ServeHTTP(w, req)
return true
}
type authClaims struct {
*jwt.StandardClaims
TokenType string
Username string
TOTP bool
}
func createTokenCookie() (*http.Cookie, error) {
expiration := time.Now().Add(7 * 24 * time.Hour)
tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, &authClaims{
&jwt.StandardClaims{ExpiresAt: expiration.Unix()},
"login",
appConfig.User.Nick,
appConfig.User.TOTP != "",
}).SignedString(jwtKey())
if err != nil {
return nil, err
}
return &http.Cookie{
Name: "token",
Value: tokenString,
Expires: expiration,
Secure: httpsConfigured(),
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
}, nil
}
// Need to set auth middleware!
func serveLogin(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusFound)
}
func serveLogout(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{
Name: "token",
MaxAge: -1,
Secure: httpsConfigured(),
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
if ses, err := loginSessionsStore.Get(r, "l"); err == nil && ses != nil {
_ = loginSessionsStore.Delete(r, w, ses)
}
http.Redirect(w, r, "/", http.StatusFound)
}

View File

@ -6,20 +6,16 @@ import (
"encoding/json"
"io"
"net/http"
"time"
"github.com/dchest/captcha"
"github.com/dgrijalva/jwt-go"
)
func captchaMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. Check JWT
claims := &captchaClaims{}
if captchaCookie, err := r.Cookie("captcha"); err == nil {
if tkn, err := jwt.ParseWithClaims(captchaCookie.Value, claims, func(t *jwt.Token) (interface{}, error) {
return jwtKey(), nil
}); err == nil && tkn.Valid && claims.TokenType == "captcha" {
// 1. Check Cookie
ses, err := captchaSessionsStore.Get(r, "c")
if err == nil && ses != nil {
if captcha, ok := ses.Values["captcha"]; ok && captcha.(bool) {
next.ServeHTTP(w, r)
return
}
@ -54,59 +50,41 @@ func checkIsCaptcha(next http.Handler) http.Handler {
}
func checkCaptcha(w http.ResponseWriter, r *http.Request) bool {
if r.Method == http.MethodPost &&
r.Header.Get(contentType) == contentTypeWWWForm &&
r.FormValue("captchaaction") == "captcha" {
// Do original request
captchabody, _ := base64.StdEncoding.DecodeString(r.FormValue("captchabody"))
req, _ := http.NewRequest(r.FormValue("captchamethod"), r.RequestURI, bytes.NewReader(captchabody))
// Copy original headers
captchaheaders, _ := base64.StdEncoding.DecodeString(r.FormValue("captchaheaders"))
var headers http.Header
_ = json.Unmarshal(captchaheaders, &headers)
for k, v := range headers {
req.Header[k] = v
}
// Check captcha
if captcha.VerifyString(r.FormValue("captchaid"), r.FormValue("digits")) {
// Create cookie
captchaCookie, err := createCaptchaCookie()
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return true
}
// Add cookie to original request
req.AddCookie(captchaCookie)
// Send cookie
http.SetCookie(w, captchaCookie)
}
// Serve original request
d.ServeHTTP(w, req)
return true
if r.Method != http.MethodPost {
return false
}
return false
}
type captchaClaims struct {
*jwt.StandardClaims
TokenType string
}
func createCaptchaCookie() (*http.Cookie, error) {
expiration := time.Now().Add(24 * time.Hour)
tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, &captchaClaims{
&jwt.StandardClaims{ExpiresAt: expiration.Unix()},
"captcha",
}).SignedString(jwtKey())
if err != nil {
return nil, err
if r.Header.Get(contentType) != contentTypeWWWForm {
return false
}
return &http.Cookie{
Name: "captcha",
Value: tokenString,
Expires: expiration,
Secure: httpsConfigured(),
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
}, nil
if r.FormValue("captchaaction") != "captcha" {
return false
}
// Prepare original request
captchabody, _ := base64.StdEncoding.DecodeString(r.FormValue("captchabody"))
req, _ := http.NewRequest(r.FormValue("captchamethod"), r.RequestURI, bytes.NewReader(captchabody))
// Copy original headers
captchaheaders, _ := base64.StdEncoding.DecodeString(r.FormValue("captchaheaders"))
var headers http.Header
_ = json.Unmarshal(captchaheaders, &headers)
for k, v := range headers {
req.Header[k] = v
}
// Check captcha and create cookie
if captcha.VerifyString(r.FormValue("captchaid"), r.FormValue("digits")) {
ses, err := captchaSessionsStore.Get(r, "c")
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return true
}
ses.Values["captcha"] = true
cookie, err := captchaSessionsStore.SaveGetCookie(r, w, ses)
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return true
}
req.AddCookie(cookie)
}
// Serve original request
d.ServeHTTP(w, req)
return true
}

View File

@ -148,6 +148,15 @@ func migrateDb() error {
return err
},
},
&migrator.Migration{
Name: "00013",
Func: func(tx *sql.Tx) error {
_, err := tx.Exec(`
create table sessions (id integer primary key autoincrement, data text default '', created text default '', modified datetime default '', expires text default '');
`)
return err
},
},
),
)
if err != nil {

9
go.mod
View File

@ -11,7 +11,6 @@ require (
github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/dgraph-io/ristretto v0.0.4-0.20210504190834-0bf2acd73aa3
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/elnormous/contenttype v1.0.0
github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/go-chi/chi/v5 v5.0.3
@ -24,6 +23,8 @@ require (
github.com/gopherjs/gopherjs v0.0.0-20210202160940-bed99a852dfe // indirect
github.com/gorilla/feeds v1.1.1
github.com/gorilla/handlers v1.5.1
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.1
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/joncrlsn/dque v0.0.0-20200702023911-3e80e3146ce5
github.com/kaorimatz/go-opml v0.0.0-20210201121027-bc8e2852d7f9
@ -39,7 +40,7 @@ require (
github.com/miekg/dns v1.1.42 // indirect
github.com/mitchellh/go-server-timing v1.0.1
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/pelletier/go-toml v1.9.0 // indirect
github.com/pelletier/go-toml v1.9.1 // indirect
github.com/pquerna/otp v1.3.0
github.com/schollz/sqlite3dump v1.2.4
github.com/smartystreets/assertions v1.2.0 // indirect
@ -57,12 +58,12 @@ require (
github.com/yuin/goldmark-emoji v1.0.1
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.16.0 // indirect
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf // indirect
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // 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-20210510120150-4163338589ed
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/ini.v1 v1.62.0 // indirect

17
go.sum
View File

@ -63,7 +63,6 @@ github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
github.com/dgraph-io/ristretto v0.0.4-0.20210504190834-0bf2acd73aa3 h1:jU/wpYsEL+8JPLf/QcjkQKI5g0dOjSuwcMjkThxt5x0=
github.com/dgraph-io/ristretto v0.0.4-0.20210504190834-0bf2acd73aa3/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
@ -141,6 +140,10 @@ github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
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/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20170920190843-316c5e0ff04e/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@ -257,8 +260,8 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.0.1-0.20170904195809-1d6b12b7cb29/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.0 h1:NOd0BRdOKpPf0SxkL3HxSQOG7rNh+4kl6PHcBPFs7Q0=
github.com/pelletier/go-toml v1.9.0/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.1 h1:a6qW1EVNZWH9WGI6CsYdD8WAylkoXBS5yv0XHlh17Tc=
github.com/pelletier/go-toml v1.9.1/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -371,8 +374,8 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o=
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -458,8 +461,8 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

View File

@ -136,6 +136,7 @@ func main() {
}
initTelegram()
initBlogStats()
initSessions()
// Start cron hooks
startHourlyHooks()

177
sessions.go Normal file
View File

@ -0,0 +1,177 @@
package main
import (
"database/sql"
"errors"
"fmt"
"log"
"net/http"
"time"
"github.com/araddon/dateparse"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
)
var loginSessionsStore, captchaSessionsStore *dbSessionStore
const (
sessionCreatedOn = "created"
sessionModifiedOn = "modified"
sessionExpiresOn = "expires"
)
func initSessions() {
deleteExpiredSessions := func() {
if _, err := appDbExec("delete from sessions where expires < @now",
sql.Named("now", time.Now().Local().String())); err != nil {
log.Println("Failed to delete expired sessions:", err.Error())
}
}
deleteExpiredSessions()
hourlyHooks = append(hourlyHooks, deleteExpiredSessions)
loginSessionsStore = &dbSessionStore{
codecs: securecookie.CodecsFromPairs(jwtKey()),
options: &sessions.Options{
Secure: httpsConfigured(),
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
MaxAge: int((7 * 24 * time.Hour).Seconds()),
},
}
captchaSessionsStore = &dbSessionStore{
codecs: securecookie.CodecsFromPairs(jwtKey()),
options: &sessions.Options{
Secure: httpsConfigured(),
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
MaxAge: int((24 * time.Hour).Seconds()),
},
}
}
type dbSessionStore struct {
options *sessions.Options
codecs []securecookie.Codec
}
func (s *dbSessionStore) Get(r *http.Request, name string) (*sessions.Session, error) {
return sessions.GetRegistry(r).Get(s, name)
}
func (s *dbSessionStore) New(r *http.Request, name string) (session *sessions.Session, err error) {
session = sessions.NewSession(s, name)
opts := *s.options
session.Options = &opts
session.IsNew = true
if cook, errCookie := r.Cookie(name); errCookie == nil {
if err = securecookie.DecodeMulti(name, cook.Value, &session.ID, s.codecs...); err == nil {
session.IsNew = s.load(session) == nil
}
}
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) {
if ss.ID == "" {
if err = s.insert(ss); err != nil {
return nil, err
}
} else if err = s.save(ss); err != nil {
return nil, err
}
if encoded, err := securecookie.EncodeMulti(ss.Name(), ss.ID, s.codecs...); err != nil {
return nil, err
} else {
cookie = sessions.NewCookie(ss.Name(), encoded, ss.Options)
http.SetCookie(w, cookie)
return cookie, nil
}
}
func (s *dbSessionStore) Delete(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
options := *session.Options
options.MaxAge = -1
http.SetCookie(w, sessions.NewCookie(session.Name(), "", &options))
for k := range session.Values {
delete(session.Values, k)
}
if _, err := appDbExec("delete from sessions where id = @id", sql.Named("id", session.ID)); err != nil {
return err
}
return nil
}
func (s *dbSessionStore) load(session *sessions.Session) (err error) {
row, err := appDbQueryRow("select data, created, modified, expires from sessions where id = @id", sql.Named("id", session.ID))
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 {
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
}
session.Values[sessionCreatedOn] = created
session.Values[sessionModifiedOn] = modified
session.Values[sessionExpiresOn] = expires
return nil
}
func (s *dbSessionStore) insert(session *sessions.Session) (err error) {
created := time.Now()
modified := time.Now()
expires := time.Now().Add(time.Second * time.Duration(session.Options.MaxAge))
delete(session.Values, sessionCreatedOn)
delete(session.Values, sessionExpiresOn)
delete(session.Values, sessionModifiedOn)
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values, s.codecs...)
if err != nil {
return err
}
res, err := appDbExec("insert into sessions(data, created, modified, expires) values(@data, @created, @modified, @expires)",
sql.Named("data", encoded), sql.Named("created", created.Local().String()), sql.Named("modified", modified.Local().String()), sql.Named("expires", expires.Local().String()))
if err != nil {
return err
}
lastInserted, err := res.LastInsertId()
if err != nil {
return err
}
session.ID = fmt.Sprintf("%d", lastInserted)
return nil
}
func (s *dbSessionStore) save(session *sessions.Session) (err error) {
if session.IsNew {
return s.insert(session)
}
delete(session.Values, sessionCreatedOn)
delete(session.Values, sessionExpiresOn)
delete(session.Values, sessionModifiedOn)
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values, s.codecs...)
if err != nil {
return err
}
_, err = appDbExec("update sessions set data = @data, modified = @modified where id = @id",
sql.Named("data", encoded), sql.Named("modified", time.Now().Local().String()), sql.Named("id", session.ID))
if err != nil {
return err
}
return nil
}

View File

@ -12,7 +12,7 @@
<input type="hidden" name="captchaheaders" value="{{ .Data.captchaheaders }}">
<input type="hidden" name="captchabody" value="{{ .Data.captchabody }}">
<input type="hidden" name="captchaid" value="{{ .Data.captchaid }}">
<input type="text" name="digits" placeholder="{{ string .Blog.Lang "captchainstructions" }}">
<input type="text" name="digits" placeholder="{{ string .Blog.Lang "captchainstructions" }}" required>
<input class="fw" type="submit" value="{{ string .Blog.Lang "submit" }}">
</form>
</main>

View File

@ -2,12 +2,14 @@ package main
import (
"bytes"
"context"
"database/sql"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
@ -77,15 +79,17 @@ func (m *mention) verifyMention() error {
if err != nil {
return err
}
req.Header.Set(userAgent, appUserAgent)
var resp *http.Response
if strings.HasPrefix(m.Source, appConfig.Server.PublicAddress) {
// Set authentication
c, _ := createTokenCookie()
req.AddCookie(c)
}
resp, err := appHttpClient.Do(req)
if err != nil {
return err
rec := httptest.NewRecorder()
d.ServeHTTP(rec, req.WithContext(context.WithValue(req.Context(), loggedInKey, true)))
resp = rec.Result()
} else {
req.Header.Set(userAgent, appUserAgent)
resp, err = appHttpClient.Do(req)
if err != nil {
return err
}
}
err = m.verifyReader(resp.Body)
_ = resp.Body.Close()