From a16ade224ec1566263aa888ee77a37cb332a1d87 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Sun, 4 Apr 2021 02:25:23 +0100 Subject: [PATCH] Invalidate sessions when password is changed Store a session key in the session that we regenerate every time the password changes. If they don't match then the session is invalid. If the user changes their own password, we'll update the current session with the new key but any others will invalidate. If an admin changes a password via /wiki/users, all sessions for that user will be invalidated. Closes #75 --- config/users.go | 24 ++++++++++++++++++++++-- handlers_session.go | 16 +++++++++++----- handlers_user.go | 2 ++ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/config/users.go b/config/users.go index f9f7d80..0d5e2dc 100644 --- a/config/users.go +++ b/config/users.go @@ -43,6 +43,7 @@ type User struct { Name string Salt []byte Password []byte + SessionKey []byte Permissions Permission } @@ -82,6 +83,7 @@ func (a *UserManager) load() error { return err } + dirty := false hasAdmin := false for i := range settings.Users { u := settings.Users[i] @@ -89,6 +91,18 @@ func (a *UserManager) load() error { if u.Has(PermissionAdmin) { hasAdmin = true } + if u.SessionKey == nil { + key, err := a.randomBytes() + if err != nil { + return err + } + u.SessionKey = key + dirty = true + } + } + + if dirty { + _ = a.save("System", "Migration: adding session keys") } if len(a.users) > 0 && !hasAdmin { @@ -212,7 +226,7 @@ func (a *UserManager) canRemoveAdmin(user *User) bool { return false } -func (a *UserManager) generateSalt() ([]byte, error) { +func (a *UserManager) randomBytes() ([]byte, error) { res := make([]byte, 16) n, err := rand.Read(res) @@ -224,7 +238,7 @@ func (a *UserManager) generateSalt() ([]byte, error) { } func (a *UserManager) setPassword(u *User, password string) error { - salt, err := a.generateSalt() + salt, err := a.randomBytes() if err != nil { return err } @@ -235,7 +249,13 @@ func (a *UserManager) setPassword(u *User, password string) error { return err } + key, err := a.randomBytes() + if err != nil { + return err + } + u.Salt = salt u.Password = hash + u.SessionKey = key return nil } diff --git a/handlers_session.go b/handlers_session.go index ab05f5d..4fd69a7 100644 --- a/handlers_session.go +++ b/handlers_session.go @@ -2,6 +2,7 @@ package main import ( "context" + "fmt" "log" "net/http" @@ -10,15 +11,18 @@ import ( ) const ( - sessionName = "wiki" - sessionUserKey = "user" - sessionNoticeKey = "notice" - sessionErrorKey = "error" + sessionName = "wiki" + sessionUserKey = "user" + sessionSessionKey = "session" + sessionNoticeKey = "notice" + sessionErrorKey = "error" contextUserKey = "user" contextErrorKey = "error" contextNoticeKey = "notice" contextSessionKey = "session" + + sessionKeyFormat = "wiki:%x" ) type UserProvider interface { @@ -33,7 +37,9 @@ func SessionHandler(up UserProvider, store sessions.Store) func(http.Handler) ht if username, ok := s.Values[sessionUserKey]; ok { user := up.User(username.(string)) if user != nil { - request = request.WithContext(context.WithValue(request.Context(), contextUserKey, user)) + if key := s.Values[sessionSessionKey]; fmt.Sprintf(sessionKeyFormat, user.SessionKey) == key { + request = request.WithContext(context.WithValue(request.Context(), contextUserKey, user)) + } } } diff --git a/handlers_user.go b/handlers_user.go index 7b87568..db774b9 100644 --- a/handlers_user.go +++ b/handlers_user.go @@ -57,6 +57,7 @@ func LoginHandler(auth Authenticator) http.HandlerFunc { putSessionKey(writer, request, sessionErrorKey, fmt.Sprintf("Failed to login: %v", err)) } else { putSessionKey(writer, request, sessionUserKey, user.Name) + putSessionKey(writer, request, sessionSessionKey, fmt.Sprintf(sessionKeyFormat, user.SessionKey)) } writer.Header().Set("location", redirect) writer.WriteHeader(http.StatusSeeOther) @@ -199,6 +200,7 @@ func ModifyAccountHandler(pu PasswordUpdater) http.HandlerFunc { putSessionKey(writer, request, sessionErrorKey, fmt.Sprintf("Unable to set password: %v", err)) } else { putSessionKey(writer, request, sessionNoticeKey, "Your password has been updated") + putSessionKey(writer, request, sessionSessionKey, fmt.Sprintf(sessionKeyFormat, user.SessionKey)) } } else { writer.WriteHeader(http.StatusBadRequest)