From 3496b1b3fa437b1df683639423860859ba5e70cd Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Fri, 4 Sep 2026 12:10:15 +0800 Subject: [PATCH 1/9] Update todo --- todo/target.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/todo/target.md b/todo/target.md index bfcb81d..e37c1d0 100644 --- a/todo/target.md +++ b/todo/target.md @@ -2,4 +2,4 @@ - login - signup -# Rows Range to Admin, User & Character(Admin Only) +# Add Normal User Api \ No newline at end of file From 231ad38bd2c6fd0d73a293bef2180a7f7b07a1b1 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Fri, 4 Sep 2026 13:34:41 +0800 Subject: [PATCH 2/9] Nothing changed,Maybe --- router/router.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/router/router.go b/router/router.go index 781f1ef..915a627 100644 --- a/router/router.go +++ b/router/router.go @@ -59,5 +59,8 @@ func ReturnRouter(config config.Config) (*gin.Engine, *gorm.DB) { v1.GET("/character/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character_range"), adminControllor.CharacterRange) } + { + //USER ONLY + } return router, db } From 65fa2943011d6159eda3601e64f68a59af331198 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 5 Sep 2026 10:26:46 +0800 Subject: [PATCH 3/9] Add config item --- example_config.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/example_config.json b/example_config.json index 7ef30cb..175c13b 100644 --- a/example_config.json +++ b/example_config.json @@ -10,6 +10,7 @@ "root_admin_uu_hash": "15de633d1f56bbcb1e8e81ccdea6cc8b710f0bcdc24af74f992b9b1ab420a7b3", "switch_config": { - "allow_multi_character": true + "allow_multi_character": true, + "allow_sign_up": true } } \ No newline at end of file From 40bedd55c32fbe1d5ea7f1146588fc997f6b1f8d Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 5 Sep 2026 11:05:58 +0800 Subject: [PATCH 4/9] Fix:Fix a bug,which let admin login will not add Set-cookie http head --- adminControllor/adminLogin.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/adminControllor/adminLogin.go b/adminControllor/adminLogin.go index d8bdcd3..c66a007 100644 --- a/adminControllor/adminLogin.go +++ b/adminControllor/adminLogin.go @@ -31,19 +31,23 @@ func AdminLogin(c *gin.Context) { //Root Admin Login if "root" == body.Username && config.RootAdminPasswordHash == hash.SHA256(body.Password) { token := hash.SHA256(body.Username + body.Password + time.Now().String()) - c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": "login success", "token": token}) var err error if body.IsRemember { - c.SetCookie("admin_token", token, 30*24*60*60, "/", "", false, true) err = sqlOperator.AddAdminToken(db, config.RootAdminUUHash, token, time.Now().Add(30*24*time.Hour)) } else { - c.SetCookie("admin_token", token, 0, "/", "", false, true) err = sqlOperator.AddAdminToken(db, config.RootAdminUUHash, token, time.Now().Add(1*time.Hour)) } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "message": "insert admin token failed"}) return } + //Set cookie (must be before c.JSON, otherwise headers are already flushed) + if body.IsRemember { + c.SetCookie("admin_token", token, 30*24*60*60, "/", "", false, true) + } else { + c.SetCookie("admin_token", token, 0, "/", "", false, true) + } + c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": "login success", "token": token}) return } //Normal Admin Login @@ -57,16 +61,20 @@ func AdminLogin(c *gin.Context) { return } token := hash.SHA256(body.Username + body.Password + time.Now().String()) - c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": "login success", "token": token}) if body.IsRemember { - c.SetCookie("admin_token", token, 30*24*60*60, "/", "", false, true) err = sqlOperator.AddAdminToken(db, admin.UUHash, token, time.Now().Add(30*24*time.Hour)) } else { - c.SetCookie("admin_token", token, 0, "/", "", false, true) err = sqlOperator.AddAdminToken(db, admin.UUHash, token, time.Now().Add(1*time.Hour)) } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "message": "insert admin token failed"}) return } + //Set cookie (must be before c.JSON, otherwise headers are already flushed) + if body.IsRemember { + c.SetCookie("admin_token", token, 30*24*60*60, "/", "", false, true) + } else { + c.SetCookie("admin_token", token, 0, "/", "", false, true) + } + c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": "login success", "token": token}) } From e24c8331f658420c2e298f520efb452d46244c14 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 5 Sep 2026 11:06:44 +0800 Subject: [PATCH 5/9] Add user login --- router/router.go | 6 +- sql/getCharacterByUUHash.go | 18 ++++++ sql/getUesrUUHash.go | 12 ++++ sql/getUser.go | 12 ++++ userControllor/login.go | 106 ++++++++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 sql/getCharacterByUUHash.go create mode 100644 sql/getUesrUUHash.go create mode 100644 sql/getUser.go create mode 100644 userControllor/login.go diff --git a/router/router.go b/router/router.go index 915a627..b6d0beb 100644 --- a/router/router.go +++ b/router/router.go @@ -5,6 +5,7 @@ import ( "github.com/TrafficLight6/GoAccountHub/config" "github.com/TrafficLight6/GoAccountHub/middleware" sqlOperator "github.com/TrafficLight6/GoAccountHub/sql" + "github.com/TrafficLight6/GoAccountHub/userControllor" "github.com/gin-gonic/gin" "gorm.io/gorm" ) @@ -31,7 +32,7 @@ func ReturnRouter(config config.Config) (*gin.Engine, *gorm.DB) { //Admin Api //Login & Logout - v1.GET("/admin/login", adminControllor.AdminLogin) + v1.POST("/admin/login", adminControllor.AdminLogin) v1.DELETE("/admin/logout", adminControllor.AdminLogout) //Admin Operation @@ -61,6 +62,9 @@ func ReturnRouter(config config.Config) (*gin.Engine, *gorm.DB) { { //USER ONLY + export := v1.Group("/export") + //Login + export.POST("/user/login", userControllor.Login) } return router, db } diff --git a/sql/getCharacterByUUHash.go b/sql/getCharacterByUUHash.go new file mode 100644 index 0000000..c5145a1 --- /dev/null +++ b/sql/getCharacterByUUHash.go @@ -0,0 +1,18 @@ +package sqlOperator + +import ( + "github.com/TrafficLight6/GoAccountHub/sqlTable" + "gorm.io/gorm" +) + +func GetCharacterByCharacterUUHash(db *gorm.DB, characterUUHash string) (sqlTable.Character, error) { + var character sqlTable.Character + err := db.Where("uu_hash = ?", characterUUHash).First(&character).Error + return character, err +} + +func GetCharacterByUserUUHash(db *gorm.DB, userUUHash string) ([]sqlTable.Character, error) { + var characters []sqlTable.Character + err := db.Where("user_uu_hash = ?", userUUHash).Find(&characters).Error + return characters, err +} diff --git a/sql/getUesrUUHash.go b/sql/getUesrUUHash.go new file mode 100644 index 0000000..a8fbc3d --- /dev/null +++ b/sql/getUesrUUHash.go @@ -0,0 +1,12 @@ +package sqlOperator + +import ( + "github.com/TrafficLight6/GoAccountHub/sqlTable" + "gorm.io/gorm" +) + +func GetUserUUHash(db *gorm.DB, userID uint) (string, error) { + var user sqlTable.User + err := db.Where("id = ?", userID).First(&user).Error + return user.UUHash, err +} diff --git a/sql/getUser.go b/sql/getUser.go new file mode 100644 index 0000000..3bb5712 --- /dev/null +++ b/sql/getUser.go @@ -0,0 +1,12 @@ +package sqlOperator + +import ( + "github.com/TrafficLight6/GoAccountHub/sqlTable" + "gorm.io/gorm" +) + +func GetUserByUsernameAndPassword(db *gorm.DB, username string, passwordHash string) (sqlTable.User, error) { + var user sqlTable.User + err := db.Where("username = ? AND password_hash = ?", username, passwordHash).First(&user).Error + return user, err +} diff --git a/userControllor/login.go b/userControllor/login.go new file mode 100644 index 0000000..8838a77 --- /dev/null +++ b/userControllor/login.go @@ -0,0 +1,106 @@ +package userControllor + +import ( + "errors" + "net/http" + "time" + + "github.com/TrafficLight6/GoAccountHub/hash" + sqlOperator "github.com/TrafficLight6/GoAccountHub/sql" + sqlTable "github.com/TrafficLight6/GoAccountHub/sqlTable" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type UserLoginRequestBody struct { + Username string `json:"username"` + CharacterName string `json:"character_name"` + Password string `json:"password"` + IsRemember bool `json:"is_remember"` +} + +func Login(c *gin.Context) { + var body UserLoginRequestBody + if err := c.ShouldBindJSON(&body); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "message": "Invalid body"}) + return + } + //Check request body + if body.Username == "" || body.CharacterName == "" || body.Password == "" { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "message": "Username, character name or password is empty"}) + return + } + //Get db + db := c.MustGet("db").(*gorm.DB) + _ = db + //Is user exist + user, err := sqlOperator.GetUserByUsernameAndPassword(db, body.Username, hash.SHA256(body.Password)) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "message": "Get user failed"}) + return + } + if user.ID == 0 { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "message": "Invalid credentials"}) + return + } + //Get user UUhash + userUUHash, err := sqlOperator.GetUserUUHash(db, user.ID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "message": "Get user UUhash failed"}) + return + } + //Get characters by user UUhash + characters, err := sqlOperator.GetCharacterByUserUUHash(db, userUUHash) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "message": "Get characters failed"}) + return + } + //Check character name exist + isCharacterExist := false + characterUUHash := "" + for _, character := range characters { + if character.CharacterName == body.CharacterName { + isCharacterExist = true + characterUUHash = character.UUHash + break + } + } + if !isCharacterExist { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "message": "Character name not exist"}) + return + } + //Generate token + token := hash.SHA256(body.Username + body.CharacterName + body.Password + time.Now().String()) + //Add token to db + var userToken sqlTable.UserLoginToken + if body.IsRemember { + userToken = sqlTable.UserLoginToken{ + UUHash: userUUHash, + CharacterUUHash: characterUUHash, + Token: token, + DeadTime: time.Now().Add(30 * 24 * time.Hour), + } + } else { + userToken = sqlTable.UserLoginToken{ + UUHash: userUUHash, + CharacterUUHash: characterUUHash, + Token: token, + DeadTime: time.Now().Add(1 * time.Hour), + } + } + //Add token to db + err = db.Create(&userToken).Error + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "message": "Add token failed"}) + return + } + //Set cookie (must be before c.JSON, otherwise headers are already flushed) + if body.IsRemember { + c.SetCookie("user_token", token, 30*24*60*60, "/", "", false, true) + } else { + c.SetCookie("user_token", token, 0, "/", "", false, true) + } + //Return token + c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": "Login success", "token": token}) + return +} From 5d72759601282225139111b299dcd0244671aee4 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 5 Sep 2026 11:13:29 +0800 Subject: [PATCH 6/9] fix(middleware): add admin token expiration check --- middleware/adminCheck.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/middleware/adminCheck.go b/middleware/adminCheck.go index 951afda..86af8cb 100644 --- a/middleware/adminCheck.go +++ b/middleware/adminCheck.go @@ -2,6 +2,7 @@ package middleware import ( "net/http" + "time" sqlOperator "github.com/TrafficLight6/GoAccountHub/sql" "github.com/gin-gonic/gin" @@ -22,6 +23,12 @@ func AdminCheckMiddleware() gin.HandlerFunc { c.Abort() return } + //Check token expiration + if !adminToken.DeadTime.After(time.Now()) { + c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "error": "admin_token expired"}) + c.Abort() + return + } c.Set("adminToken", adminToken) c.Next() } From 79d8b78ef868bc1d8ebcce0c51b75ade573dbd56 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 5 Sep 2026 11:21:02 +0800 Subject: [PATCH 7/9] nothing --- adminControllor/adminLogout.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/adminControllor/adminLogout.go b/adminControllor/adminLogout.go index e684099..b28c58d 100644 --- a/adminControllor/adminLogout.go +++ b/adminControllor/adminLogout.go @@ -8,8 +8,6 @@ import ( "gorm.io/gorm" ) -//db := sqlOperator.GetDB() - type AdminLogoutRequest struct { Token string `json:"token"` } From 4dbd5e22f9de6caadbf2c3cf308cabdc08112520 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 5 Sep 2026 11:34:51 +0800 Subject: [PATCH 8/9] Add user logout --- router/router.go | 3 ++- sql/checkUserToken.go | 14 ++++++++++++++ sql/deleteUserToken.go | 13 +++++++++++++ userControllor/logout.go | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 sql/checkUserToken.go create mode 100644 sql/deleteUserToken.go create mode 100644 userControllor/logout.go diff --git a/router/router.go b/router/router.go index b6d0beb..d351d20 100644 --- a/router/router.go +++ b/router/router.go @@ -63,8 +63,9 @@ func ReturnRouter(config config.Config) (*gin.Engine, *gorm.DB) { { //USER ONLY export := v1.Group("/export") - //Login + //Login & Logout export.POST("/user/login", userControllor.Login) + export.DELETE("/user/logout", userControllor.Logout) } return router, db } diff --git a/sql/checkUserToken.go b/sql/checkUserToken.go new file mode 100644 index 0000000..0f46b87 --- /dev/null +++ b/sql/checkUserToken.go @@ -0,0 +1,14 @@ +package sqlOperator + +import ( + "github.com/TrafficLight6/GoAccountHub/sqlTable" + "gorm.io/gorm" +) + +func CheckUserToken(db *gorm.DB, token string) bool { + var tokenRecord sqlTable.UserLoginToken + if err := db.Where("token = ?", token).First(&tokenRecord).Error; err != nil { + return false + } + return true +} diff --git a/sql/deleteUserToken.go b/sql/deleteUserToken.go new file mode 100644 index 0000000..f892d34 --- /dev/null +++ b/sql/deleteUserToken.go @@ -0,0 +1,13 @@ +package sqlOperator + +import ( + "github.com/TrafficLight6/GoAccountHub/sqlTable" + "gorm.io/gorm" +) + +func DeleteUserToken(db *gorm.DB, token string) error { + if err := db.Where("token = ?", token).Delete(&sqlTable.UserLoginToken{}).Error; err != nil { + return err + } + return nil +} diff --git a/userControllor/logout.go b/userControllor/logout.go new file mode 100644 index 0000000..c632df0 --- /dev/null +++ b/userControllor/logout.go @@ -0,0 +1,40 @@ +package userControllor + +import ( + "net/http" + + sqlOperator "github.com/TrafficLight6/GoAccountHub/sql" + "github.com/gin-gonic/gin" + + "gorm.io/gorm" +) + +type UserLogoutRequest struct { + Token string `json:"token"` +} + +func Logout(c *gin.Context) { + var body UserLogoutRequest + if err := c.ShouldBindJSON(&body); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Invalid request body"}) + return + } + //Validate Token + if body.Token == "" { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Token is empty"}) + return + } + //Get db + db := c.Value("db").(*gorm.DB) + //Check Token Exist + if !sqlOperator.CheckUserToken(db, body.Token) { + c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "error": "Token not found"}) + return + } + //Delete Token From Database(Logout) + if err := sqlOperator.DeleteUserToken(db, body.Token); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "error": "Internal server error"}) + return + } + c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": "Logout success"}) +} From 752dbeb1e3bad405c0d94dab2141c9d32ef814c5 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 5 Sep 2026 11:41:11 +0800 Subject: [PATCH 9/9] Add conifg field --- config/config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/config/config.go b/config/config.go index 39938a4..9d0cd82 100644 --- a/config/config.go +++ b/config/config.go @@ -22,6 +22,7 @@ type Config struct { type SwitchConfig struct { AllowMultiCharacter bool `json:"allow_multi_character"` + AllowAdminLogout bool `json:"allow_admin_logout"` } func GetConfig(path string) Config {