From 232e87ae368e0437f2f0d82f1d894741f4412ae0 Mon Sep 17 00:00:00 2001 From: NanamiAdmin Date: Tue, 3 Mar 2026 23:02:52 +0800 Subject: [PATCH] feat(auth): add logout functionality - Implement LogoutHandler to handle user logout requests - Add DeleteTokenInfo and GetUserIDFromToken functions to auth module - Update README.md with logout endpoint documentation --- README.md | 30 ++++++++++++++++++++++++++++-- auth.go | 26 +++++++++++++++++++++++++- router.go | 1 + userAct.go | 29 +++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b17e50d..24535f2 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ All API responses are returned in JSON format: --- -### 1. Register User +### Register User **Endpoint:** `/register` **Method:** POST @@ -122,7 +122,7 @@ X-Timestamp: 1704067200000 --- -### 2. Login +### Login **Endpoint:** `/login` **Method:** POST @@ -166,6 +166,32 @@ X-Timestamp: 1704067200000 --- +### Logout + +**Endpoint:** `/logout` +**Method:** GET +**Auth Required:** Yes (token) + +**Request Headers:** +``` +X-Token: your_token +X-Timestamp: 1704067200000 +``` +| Header | Type | Required | Description | +|--------|------|----------|-------------| +| X-Token | string | Yes | Authentication token | +| X-Timestamp | int64 | Yes | Client timestamp in milliseconds | + +**Response:** +```json +{ + "success": true, + "message": "Logout successful" +} +``` + +--- + ### 3. Create frpc Instance **Endpoint:** `/frpcAct/instanceMgr/create` diff --git a/auth.go b/auth.go index 98d6860..cf7707b 100644 --- a/auth.go +++ b/auth.go @@ -106,7 +106,7 @@ func GetTokenInfo(userID int) (*TokenInfo, error) { tokenInfo, exists := tokenMap[userID] if !exists { return nil, fmt.Errorf("Token not found for userID %d", userID) - + } return tokenInfo, nil @@ -199,3 +199,27 @@ func ValidateTimeStamp(header http.Header) bool { } return true } + +func GetUserIDFromToken(token string) (int, error) { + userID, err := extractUserIDFromToken(token) + if err != nil { + return 0, fmt.Errorf("Failed to extract userID from token: %w", err) + } + return userID, nil +} + +func DeleteTokenInfo(userID int) error { + tokenMux.Lock() + defer tokenMux.Unlock() + delete(tokenMap, userID) + postLog.Debug(fmt.Sprintf("[DeleteTokenInfo] Removed token for userID %d", userID)) + return nil +} + +func GetUsernameByID(userID int) (string) { + user, err := GetUserByID(userID) + if err != nil { + return "" + } + return user.Username +} diff --git a/router.go b/router.go index d0a438e..5b4b666 100644 --- a/router.go +++ b/router.go @@ -10,6 +10,7 @@ func setupRoutes() { postLog.Info("Setting up routes...") http.HandleFunc("/register", RegisterHandler) http.HandleFunc("/login", LoginHandler) + http.HandleFunc("/logout", LogoutHandler) http.HandleFunc("/frpcAct/instanceMgr/create", CreateInstanceHandler) http.HandleFunc("/frpcAct/instanceMgr/list", ListInstancesHandler) diff --git a/userAct.go b/userAct.go index cc5d28f..e60ec72 100644 --- a/userAct.go +++ b/userAct.go @@ -159,3 +159,32 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { }) postLog.Info(fmt.Sprintf("[LoginHandler] User \"%s\" Login successful", req.Username)) } + +func LogoutHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method") + postLog.Warning(fmt.Sprintf("[LogoutHandler] Invalid request method: %s", r.Method)) + return + } + + if !ValidateTimeStamp(r.Header) { + SendErrorResponse(w, http.StatusBadRequest, "Invalid or missing X-Timestamp in header") + return + } + + userID, err := GetUserIDFromToken(r.Header.Get("X-Token")) + if err != nil { + SendErrorResponse(w, http.StatusUnauthorized, "Invalid or missing token") + postLog.Warning(fmt.Sprintf("[LogoutHandler] Invalid or missing token: %v", err)) + return + } + + if err := DeleteTokenInfo(userID); 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 + } + + SendSuccessResponse(w, "Logout successful", nil) + postLog.Info(fmt.Sprintf("[LogoutHandler] User [%d]%s Logout successful", userID, GetUsernameByID(userID))) +} \ No newline at end of file