feature: persist local sessions across restart (#522)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
parent
f3bc82311d
commit
7788f58aac
|
|
@ -43,6 +43,16 @@ func (cfg *Config) FindAcl(aclTitle string) *AccessControlList {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Config) FindUserByUsername(searchUsername string) *LocalUser {
|
||||
for _, user := range cfg.AuthLocalUsers.Users {
|
||||
if user.Username == searchUsername {
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Config) SetDir(dir string) {
|
||||
cfg.usedConfigDir = dir
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ func (e *Executor) GetLogTrackingIds(startOffset int64, pageCount int64) ([]*Int
|
|||
"total": totalLogCount,
|
||||
"startIndex": startIndex,
|
||||
"endIndex": endIndex,
|
||||
}).Infof("GetLogTrackingIds")
|
||||
}).Tracef("GetLogTrackingIds")
|
||||
|
||||
trackingIds := make([]*InternalLogEntry, 0, pageCount)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package filehelper
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var writeFileMutex sync.Mutex
|
||||
|
||||
func WriteFile(filename string, out []byte) {
|
||||
writeFileMutex.Lock()
|
||||
|
||||
defer writeFileMutex.Unlock()
|
||||
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
handle, err := os.Create(filename)
|
||||
handle.Close()
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Errorf("Failed to create %v", filename)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err := os.WriteFile(filename, out, 0600)
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Errorf("Failed to write session to %v", filename)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -107,12 +107,16 @@ func forwardResponseHandlerLogout(md metadata.MD, w http.ResponseWriter) {
|
|||
http.SetCookie(
|
||||
w,
|
||||
&http.Cookie{
|
||||
Name: "olivetin-sid-oauth",
|
||||
Value: "",
|
||||
Name: "olivetin-sid-oauth",
|
||||
MaxAge: 31556952, // 1 year
|
||||
Value: "",
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
},
|
||||
)
|
||||
|
||||
delete(localUserSessions, sid)
|
||||
deleteLocalUserSession("local", sid)
|
||||
|
||||
http.SetCookie(
|
||||
w,
|
||||
&http.Cookie{
|
||||
|
|
@ -147,6 +151,8 @@ func SetGlobalRestConfig(config *config.Config) {
|
|||
func startRestAPIServer(globalConfig *config.Config) error {
|
||||
cfg = globalConfig
|
||||
|
||||
loadUserSessions()
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"address": cfg.ListenAddressRestActions,
|
||||
}).Info("Starting REST API")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,177 @@
|
|||
package httpservers
|
||||
|
||||
import (
|
||||
"github.com/OliveTin/OliveTin/internal/config"
|
||||
"github.com/OliveTin/OliveTin/internal/filehelper"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sessionStorageMutex sync.Mutex
|
||||
|
||||
type UserSession struct {
|
||||
Username string
|
||||
Expiry int64
|
||||
}
|
||||
|
||||
type SessionProvider struct {
|
||||
Sessions map[string]*UserSession
|
||||
}
|
||||
|
||||
type SessionStorage struct {
|
||||
Providers map[string]*SessionProvider
|
||||
}
|
||||
|
||||
var (
|
||||
sessionStorage *SessionStorage
|
||||
)
|
||||
|
||||
func registerSessionProviders() {
|
||||
sessionStorage = &SessionStorage{
|
||||
Providers: make(map[string]*SessionProvider),
|
||||
}
|
||||
|
||||
registerSessionProvider("local")
|
||||
registerSessionProvider("oauth2")
|
||||
}
|
||||
|
||||
func registerSessionProvider(provider string) {
|
||||
sessionStorage.Providers[provider] = &SessionProvider{
|
||||
Sessions: make(map[string]*UserSession),
|
||||
}
|
||||
}
|
||||
|
||||
func deleteLocalUserSession(provider string, sid string) {
|
||||
sessionStorageMutex.Lock()
|
||||
|
||||
deleteLocalUserSessionBatch(provider, sid)
|
||||
|
||||
sessionStorageMutex.Unlock()
|
||||
|
||||
saveUserSessions()
|
||||
}
|
||||
|
||||
func deleteLocalUserSessionBatch(provider string, sid string) {
|
||||
log.WithFields(log.Fields{
|
||||
"sid": sid,
|
||||
"provider": provider,
|
||||
}).Debug("Deleting user session")
|
||||
|
||||
if _, ok := sessionStorage.Providers[provider]; !ok {
|
||||
return
|
||||
}
|
||||
|
||||
delete(sessionStorage.Providers[provider].Sessions, sid)
|
||||
}
|
||||
|
||||
func registerUserSession(provider string, sid string, username string) {
|
||||
sessionStorageMutex.Lock()
|
||||
sessionStorage.Providers[provider].Sessions[sid] = &UserSession{
|
||||
Username: username,
|
||||
Expiry: time.Now().Unix() + 31556952, // 1 year
|
||||
}
|
||||
sessionStorageMutex.Unlock()
|
||||
|
||||
saveUserSessions()
|
||||
}
|
||||
|
||||
func saveUserSessions() {
|
||||
sessionStorageMutex.Lock()
|
||||
defer sessionStorageMutex.Unlock()
|
||||
|
||||
filename := filepath.Join(cfg.GetDir(), "sessions.db.yaml")
|
||||
|
||||
out, err := yaml.Marshal(sessionStorage)
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Errorf("Failed to marshal session data to %v", filename)
|
||||
return
|
||||
}
|
||||
|
||||
filehelper.WriteFile(filename, out)
|
||||
}
|
||||
|
||||
func loadUserSessions() {
|
||||
registerSessionProviders()
|
||||
|
||||
filename := filepath.Join(cfg.GetDir(), "sessions.db.yaml")
|
||||
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filename)
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Errorf("Failed to read %v", filename)
|
||||
return
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(data, &sessionStorage)
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Error("Failed to unmarshal sessions.local.db")
|
||||
return
|
||||
}
|
||||
|
||||
deleteExpiredSessions()
|
||||
}
|
||||
|
||||
func deleteExpiredSessions() {
|
||||
sessionStorageMutex.Lock()
|
||||
|
||||
for provider, sessions := range sessionStorage.Providers {
|
||||
for sid, session := range sessions.Sessions {
|
||||
if session.Expiry < time.Now().Unix() {
|
||||
deleteLocalUserSessionBatch(provider, sid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sessionStorageMutex.Unlock()
|
||||
|
||||
saveUserSessions()
|
||||
}
|
||||
|
||||
func getUserFromSession(providerName string, sid string) *config.LocalUser {
|
||||
provider, ok := sessionStorage.Providers[providerName]
|
||||
|
||||
if !ok {
|
||||
log.WithFields(log.Fields{
|
||||
"provider": providerName,
|
||||
}).Warnf("Provider not found")
|
||||
return nil
|
||||
}
|
||||
|
||||
session, ok := provider.Sessions[sid]
|
||||
|
||||
if !ok {
|
||||
log.WithFields(log.Fields{
|
||||
"sid": sid,
|
||||
"provider": providerName,
|
||||
}).Warnf("Stale session")
|
||||
return nil
|
||||
}
|
||||
|
||||
user := cfg.FindUserByUsername(session.Username)
|
||||
|
||||
if user == nil {
|
||||
log.WithFields(log.Fields{
|
||||
"sid": sid,
|
||||
"provider": providerName,
|
||||
}).Warnf("User not found")
|
||||
return nil
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
|
@ -4,13 +4,7 @@ import (
|
|||
"google.golang.org/grpc/metadata"
|
||||
"net/http"
|
||||
|
||||
"github.com/OliveTin/OliveTin/internal/config"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
localUserSessions = make(map[string]*config.LocalUser)
|
||||
)
|
||||
|
||||
func parseLocalUserCookie(req *http.Request) (string, string, string) {
|
||||
|
|
@ -22,41 +16,27 @@ func parseLocalUserCookie(req *http.Request) (string, string, string) {
|
|||
|
||||
cookieValue := cookie.Value
|
||||
|
||||
user, ok := localUserSessions[cookieValue]
|
||||
user := getUserFromSession("local", cookieValue)
|
||||
|
||||
if !ok {
|
||||
log.WithFields(log.Fields{
|
||||
"sid": cookieValue,
|
||||
"provider": "local",
|
||||
}).Warnf("Stale session")
|
||||
if user == nil {
|
||||
return "", "", ""
|
||||
}
|
||||
|
||||
return user.Username, user.Usergroup, cookie.Value
|
||||
}
|
||||
|
||||
func findUserByUsername(searchUsername string) *config.LocalUser {
|
||||
for _, user := range cfg.AuthLocalUsers.Users {
|
||||
if user.Username == searchUsername {
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func forwardResponseHandlerLoginLocalUser(md metadata.MD, w http.ResponseWriter) error {
|
||||
setUsername := getMetadataKeyOrEmpty(md, "set-username")
|
||||
|
||||
if setUsername != "" {
|
||||
user := findUserByUsername(setUsername)
|
||||
user := cfg.FindUserByUsername(setUsername)
|
||||
|
||||
if user == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sid := uuid.NewString()
|
||||
localUserSessions[sid] = user
|
||||
registerUserSession("local", sid, user.Username)
|
||||
|
||||
http.SetCookie(
|
||||
w,
|
||||
|
|
|
|||
Loading…
Reference in New Issue