feat(session): add session list API

- Move token-related functions from auth.go to new session.go
- Add session tracking with expiration and cleanup
- Implement session list API endpoint
- Update login/logout handlers to use session system
- Add hourly cleanup of expired tokens and sessions
This commit is contained in:
2026-03-05 18:24:19 +08:00
parent efdaefa6f5
commit e400cc1869
6 changed files with 155 additions and 9 deletions
+43 -6
View File
@@ -161,6 +161,12 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
return
}
if err := JoinSession(user.UserID, user.Username, token); err != nil {
SendErrorResponse(w, http.StatusInternalServerError, "Failed to create session")
postLog.Error(fmt.Sprintf("[LoginHandler] Failed to create session for user \"%s\": %v", req.Username, err))
return
}
SendSuccessResponse(w, "Login successful", map[string]interface{}{
"token": token,
"userID": user.UserID,
@@ -189,7 +195,7 @@ func LogoutHandler(w http.ResponseWriter, r *http.Request) {
return
}
if err := DeleteTokenInfo(userID); err != nil {
if err := RemoveSession(userID, r.Header.Get("X-Token")); err != nil {
SendErrorResponse(w, http.StatusInternalServerError, "Failed to logout")
postLog.Error(fmt.Sprintf("[LogoutHandler] Failed to logout user [%d]%s: %v", userID, GetUsernameByID(userID), err))
return
@@ -227,7 +233,7 @@ func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
postLog.Warning(fmt.Sprintf("[CreateUserHandler] Permission denied: non-superuser token for user [%d]%s", userID, GetUsernameByID(userID)))
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
@@ -242,13 +248,13 @@ func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
postLog.Warning(fmt.Sprintf("[CreateUserHandler] Invalid request format: %v", err))
return
}
if req.Username == "" || req.Passwd == "" || req.Type == "" {
SendErrorResponse(w, http.StatusBadRequest, "Username, password, and type are required")
postLog.Warning("[CreateUserHandler] CreateUser failed: username, password, or type is empty")
return
}
if req.Type != "admin" && req.Type != "user" && req.Type != "superuser" {
SendErrorResponse(w, http.StatusBadRequest, "Invalid type: must be 'admin' or 'user' or 'superuser'")
postLog.Warning(fmt.Sprintf("[CreateUserHandler] CreateUser failed: invalid type: %s", req.Type))
@@ -304,7 +310,7 @@ func RemoveUserHandler(w http.ResponseWriter, r *http.Request) {
postLog.Warning(fmt.Sprintf("[RemoveUserHandler] Permission denied: non-superuser token for user [%d]%s", userID, GetUsernameByID(userID)))
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
@@ -367,4 +373,35 @@ func ListUserHandler(w http.ResponseWriter, r *http.Request) {
return
}
SendSuccessResponse(w, "User list retrieved successfully", userList)
}
}
func ListActiveSessionsHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
postLog.Warning(fmt.Sprintf("[ListActiveSessionsHandler] Invalid request method: %s", r.Method))
return
}
if !ValidateTimeStamp(r.Header) {
SendErrorResponse(w, http.StatusBadRequest, "Invalid or missing X-Timestamp in header")
postLog.Warning("[ListActiveSessionsHandler] Invalid or missing X-Timestamp in header")
return
}
userID, err := extractUserIDFromToken(r.Header.Get("X-Token"))
if err != nil {
SendErrorResponse(w, http.StatusUnauthorized, "Invalid or missing token")
postLog.Warning(fmt.Sprintf("[ListActiveSessionsHandler] Invalid or missing token: %v", err))
return
}
if err := CheckPermission(userID, "superuser"); err != nil {
SendErrorResponse(w, http.StatusForbidden, "Permission denied")
postLog.Warning(fmt.Sprintf("[ListActiveSessionsHandler] Permission denied for user [%d]%s: %v", userID, GetUsernameByID(userID), err))
return
}
sessions := ListActiveSessions()
postLog.Debug(fmt.Sprintf("[ListActiveSessionsHandler] User [%d]%s listed %d active sessions", userID, GetUsernameByID(userID), len(sessions)))
SendSuccessResponse(w, "Active sessions listed", sessions)
}