feat: enforce configurable session expiration safely

This commit is contained in:
OpenClaw Agent
2026-09-21 16:14:28 +08:00
parent 62872220f9
commit f252a7a195
2 changed files with 51 additions and 39 deletions
+2 -1
View File
@@ -7,6 +7,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"chfs-ng/internal/api" "chfs-ng/internal/api"
"chfs-ng/internal/auth" "chfs-ng/internal/auth"
@@ -68,7 +69,7 @@ func main() {
if len(cfg.Path) == 1 { if len(cfg.Path) == 1 {
store = storage.Local{Root: root} store = storage.Local{Root: root}
} }
srv := &api.Server{Store: store, Auth: mgr, Sessions: map[string]*auth.Account{}, WebDAVRoot: root, Allow: cfg.Allow, MultiRoot: len(cfg.Path) > 1, RemoveMode: cfg.FileRemove, Log: opslog.New(cfg.Log)} srv := &api.Server{Store: store, Auth: mgr, Sessions: map[string]*auth.Account{}, SessionExpiry: map[string]time.Time{}, SessionTimeout: cfg.SessionTimeout, WebDAVRoot: root, Allow: cfg.Allow, MultiRoot: len(cfg.Path) > 1, RemoveMode: cfg.FileRemove, Log: opslog.New(cfg.Log)}
addr := ":" + itoa(cfg.Port) addr := ":" + itoa(cfg.Port)
log.Printf("chfs-ng listening on %s root=%s removeMode=%d", addr, filepath.Clean(root), cfg.FileRemove) log.Printf("chfs-ng listening on %s root=%s removeMode=%d", addr, filepath.Clean(root), cfg.FileRemove)
if cfg.SSLCert != "" && cfg.SSLKey != "" { if cfg.SSLCert != "" && cfg.SSLKey != "" {
+41 -30
View File
@@ -12,6 +12,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"time" "time"
"chfs-ng/internal/auth" "chfs-ng/internal/auth"
@@ -24,6 +25,9 @@ type Server struct {
Store storage.Storage Store storage.Storage
Auth *auth.Manager Auth *auth.Manager
Sessions map[string]*auth.Account Sessions map[string]*auth.Account
SessionExpiry map[string]time.Time
SessionMu sync.RWMutex
SessionTimeout int
WebDAVRoot string WebDAVRoot string
Allow string Allow string
MultiRoot bool MultiRoot bool
@@ -64,32 +68,23 @@ func (s *Server) Handler() http.Handler {
} }
func account(r *http.Request, s *Server) *auth.Account { func account(r *http.Request, s *Server) *auth.Account {
if c, e := r.Cookie("chfs_token"); e == nil { if c, e := r.Cookie("chfs_token"); e == nil {
if a := s.Sessions[c.Value]; a != nil { s.SessionMu.RLock()
a := s.Sessions[c.Value]
exp := s.SessionExpiry[c.Value]
s.SessionMu.RUnlock()
if a != nil && (exp.IsZero() || time.Now().Before(exp)) {
return a return a
} }
if a != nil {
s.SessionMu.Lock()
delete(s.Sessions, c.Value)
delete(s.SessionExpiry, c.Value)
s.SessionMu.Unlock()
}
} }
return s.Auth.Guest return s.Auth.Guest
} }
func (s *Server) logOp(r *http.Request, action string) {
if s.Log == nil {
return
}
a := account(r, s)
user := ""
if a != nil {
user = a.Username
}
_ = s.Log.Write(clientIPString(r), user, action)
}
func clientIPString(r *http.Request) string {
ip := clientIP(r)
if ip == nil {
return "-"
}
return ip.String()
}
func writeJSON(w http.ResponseWriter, v any) { func writeJSON(w http.ResponseWriter, v any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(v) json.NewEncoder(w).Encode(v)
@@ -233,31 +228,47 @@ func (s *Server) exist(w http.ResponseWriter, r *http.Request) {
} }
func (s *Server) session(w http.ResponseWriter, r *http.Request) { func (s *Server) session(w http.ResponseWriter, r *http.Request) {
if r.Method == "DELETE" { if r.Method == "DELETE" {
if c, e := r.Cookie("chfs_token"); e == nil {
s.SessionMu.Lock()
delete(s.Sessions, c.Value)
delete(s.SessionExpiry, c.Value)
s.SessionMu.Unlock()
}
http.SetCookie(w, &http.Cookie{Name: "chfs_token", Value: "", Path: "/", MaxAge: -1, HttpOnly: true}) http.SetCookie(w, &http.Cookie{Name: "chfs_token", Value: "", Path: "/", MaxAge: -1, HttpOnly: true})
writeJSON(w, map[string]any{"ok": true}) writeJSON(w, map[string]any{"ok": true})
return return
} }
if r.Method == "GET" { if r.Method != http.MethodPost {
user := "" http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
if c, e := r.Cookie("chfs_token"); e == nil {
if x := s.Sessions[c.Value]; x != nil {
user = x.Username
}
}
writeJSON(w, map[string]any{"login": user != "", "user": user})
return return
} }
user, pwd := r.FormValue("user"), r.FormValue("pwd") user, pwd := r.FormValue("user"), r.FormValue("pwd")
a := s.Auth.Authenticate(user, pwd) a := s.Auth.Authenticate(user, pwd)
if a == nil { if a == nil {
http.Error(w, "invalid account", 401) http.Error(w, "invalid account", http.StatusUnauthorized)
return return
} }
token := auth.NewToken() token := auth.NewToken()
exp := time.Time{}
maxAge := 0
if s.SessionTimeout > 0 {
exp = time.Now().Add(time.Duration(s.SessionTimeout) * time.Minute)
maxAge = s.SessionTimeout * 60
}
s.SessionMu.Lock()
if s.Sessions == nil {
s.Sessions = make(map[string]*auth.Account)
}
if s.SessionExpiry == nil {
s.SessionExpiry = make(map[string]time.Time)
}
s.Sessions[token] = a s.Sessions[token] = a
http.SetCookie(w, &http.Cookie{Name: "chfs_token", Value: token, Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode}) s.SessionExpiry[token] = exp
s.SessionMu.Unlock()
http.SetCookie(w, &http.Cookie{Name: "chfs_token", Value: token, Path: "/", MaxAge: maxAge, HttpOnly: true, SameSite: http.SameSiteLaxMode})
writeJSON(w, map[string]any{"ok": true, "user": a.Username}) writeJSON(w, map[string]any{"ok": true, "user": a.Username})
} }
func (s *Server) newtext(w http.ResponseWriter, r *http.Request) { func (s *Server) newtext(w http.ResponseWriter, r *http.Request) {
a := account(r, s) a := account(r, s)
dir := strings.Trim(strings.ReplaceAll(r.FormValue("filepath"), "\\", "/"), "/") dir := strings.Trim(strings.ReplaceAll(r.FormValue("filepath"), "\\", "/"), "/")