fix: address branch review issues (json tags, share lockout, session expiry, file unlink)

This commit is contained in:
2026-08-26 23:44:19 +08:00
parent b7af194492
commit d988d35881
6 changed files with 67 additions and 17 deletions
+6
View File
@@ -80,9 +80,15 @@ func (s *Server) handleDownload(w http.ResponseWriter, r *http.Request) {
func (s *Server) handleDeleteFile(w http.ResponseWriter, r *http.Request) { func (s *Server) handleDeleteFile(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") id := r.PathValue("id")
f, err := s.store.FileGet(id)
if err != nil {
http.Error(w, "Not found", http.StatusNotFound)
return
}
if err := s.store.FileDelete(id); err != nil { if err := s.store.FileDelete(id); err != nil {
http.Error(w, "Failed to delete", http.StatusInternalServerError) http.Error(w, "Failed to delete", http.StatusInternalServerError)
return return
} }
os.Remove(filepath.Join(s.workspace, f.Dir, f.StorageName))
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
} }
+19 -2
View File
@@ -2,8 +2,11 @@ package server
import ( import (
"net/http" "net/http"
"time"
) )
const sessionMaxAge = 7 * 86400
func (s *Server) requireAuth(next http.HandlerFunc) http.HandlerFunc { func (s *Server) requireAuth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("ydropbox_session") cookie, err := r.Cookie("ydropbox_session")
@@ -13,7 +16,14 @@ func (s *Server) requireAuth(next http.HandlerFunc) http.HandlerFunc {
} }
s.mu.Lock() s.mu.Lock()
_, exists := s.sessions[cookie.Value] createdAt, exists := s.sessions[cookie.Value]
if exists && time.Now().Unix()-createdAt > sessionMaxAge {
delete(s.sessions, cookie.Value)
exists = false
}
if exists {
s.sessions[cookie.Value] = time.Now().Unix()
}
s.mu.Unlock() s.mu.Unlock()
if !exists { if !exists {
@@ -34,7 +44,14 @@ func (s *Server) requireAuthPage(next http.HandlerFunc) http.HandlerFunc {
} }
s.mu.Lock() s.mu.Lock()
_, exists := s.sessions[cookie.Value] createdAt, exists := s.sessions[cookie.Value]
if exists && time.Now().Unix()-createdAt > sessionMaxAge {
delete(s.sessions, cookie.Value)
exists = false
}
if exists {
s.sessions[cookie.Value] = time.Now().Unix()
}
s.mu.Unlock() s.mu.Unlock()
if !exists { if !exists {
+12 -8
View File
@@ -11,14 +11,16 @@ import (
) )
type Server struct { type Server struct {
store *store.Store store *store.Store
accessToken string accessToken string
sessions map[string]int64 sessions map[string]int64
shareSessions map[string]string shareSessions map[string]string
loginAttempts map[string]int loginAttempts map[string]int
loginLockout map[string]time.Time loginLockout map[string]time.Time
mu sync.Mutex shareAttempts map[string]int
workspace string shareLockout map[string]time.Time
mu sync.Mutex
workspace string
} }
func New(store *store.Store, accessToken string, workspace string) *Server { func New(store *store.Store, accessToken string, workspace string) *Server {
@@ -29,6 +31,8 @@ func New(store *store.Store, accessToken string, workspace string) *Server {
shareSessions: make(map[string]string), shareSessions: make(map[string]string),
loginAttempts: make(map[string]int), loginAttempts: make(map[string]int),
loginLockout: make(map[string]time.Time), loginLockout: make(map[string]time.Time),
shareAttempts: make(map[string]int),
shareLockout: make(map[string]time.Time),
workspace: workspace, workspace: workspace,
} }
} }
+23
View File
@@ -108,12 +108,35 @@ func (s *Server) handleSharePassword(w http.ResponseWriter, r *http.Request) {
return return
} }
key := share.ID
s.mu.Lock()
if lockTime, locked := s.shareLockout[key]; locked && time.Now().Before(lockTime) {
s.mu.Unlock()
http.Error(w, "Too many attempts, locked for 60s", http.StatusForbidden)
return
}
s.mu.Unlock()
time.Sleep(500 * time.Millisecond)
password := r.FormValue("password") password := r.FormValue("password")
if err := bcrypt.CompareHashAndPassword([]byte(share.PasswordHash), []byte(password)); err != nil { if err := bcrypt.CompareHashAndPassword([]byte(share.PasswordHash), []byte(password)); err != nil {
s.mu.Lock()
s.shareAttempts[key]++
if s.shareAttempts[key] >= 5 {
s.shareLockout[key] = time.Now().Add(60 * time.Second)
s.shareAttempts[key] = 0
}
s.mu.Unlock()
http.Error(w, "Wrong password", http.StatusForbidden) http.Error(w, "Wrong password", http.StatusForbidden)
return return
} }
s.mu.Lock()
delete(s.shareAttempts, key)
delete(s.shareLockout, key)
s.mu.Unlock()
cookieVal := generateSessionID() cookieVal := generateSessionID()
s.mu.Lock() s.mu.Lock()
if s.shareSessions == nil { if s.shareSessions == nil {
+6 -6
View File
@@ -3,12 +3,12 @@ package store
import "database/sql" import "database/sql"
type File struct { type File struct {
ID string ID string `json:"id"`
OriginalName string OriginalName string `json:"name"`
StorageName string StorageName string `json:"-"`
Dir string Dir string `json:"dir"`
Size int64 Size int64 `json:"size"`
CreatedAt int64 CreatedAt int64 `json:"created_at"`
} }
func (s *Store) FileCreate(f *File) error { func (s *Store) FileCreate(f *File) error {
+1 -1
View File
@@ -60,7 +60,7 @@ func TestIntegration(t *testing.T) {
var uploadResp map[string]any var uploadResp map[string]any
json.NewDecoder(uploadRes.Body).Decode(&uploadResp) json.NewDecoder(uploadRes.Body).Decode(&uploadResp)
file := uploadResp["file"].(map[string]any) file := uploadResp["file"].(map[string]any)
fileID := file["ID"].(string) fileID := file["id"].(string)
listReq, _ := http.NewRequest("GET", ts.URL+"/api/files?dir=inbox", nil) listReq, _ := http.NewRequest("GET", ts.URL+"/api/files?dir=inbox", nil)
for _, cookie := range loginRes.Cookies() { for _, cookie := range loginRes.Cookies() {