Feat:Using Go to implement user registration logic (#13431)

### What problem does this PR solve?

Feat:Using Go to implement user registration logic

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
chanx
2026-03-06 16:42:49 +08:00
committed by GitHub
parent 37eb533fea
commit 81fd1811b8
21 changed files with 660 additions and 207 deletions

View File

@@ -0,0 +1,40 @@
//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package common
type ErrorCode int
const (
CodeSuccess ErrorCode = 0
CodeNotEffective ErrorCode = 10
CodeExceptionError ErrorCode = 100
CodeArgumentError ErrorCode = 101
CodeDataError ErrorCode = 102
CodeOperatingError ErrorCode = 103
CodeTimeoutError ErrorCode = 104
CodeConnectionError ErrorCode = 105
CodeRunning ErrorCode = 106
CodeResourceExhausted ErrorCode = 107
CodePermissionError ErrorCode = 108
CodeAuthenticationError ErrorCode = 109
CodeBadRequest ErrorCode = 400
CodeUnauthorized ErrorCode = 401
CodeForbidden ErrorCode = 403
CodeNotFound ErrorCode = 404
CodeConflict ErrorCode = 409
CodeServerError ErrorCode = 500
)

View File

@@ -18,6 +18,7 @@ package dao
import (
"fmt"
"ragflow/internal/model"
"ragflow/internal/server"
"time"
@@ -77,9 +78,15 @@ func InitDB() error {
sqlDB.SetConnMaxLifetime(time.Hour)
// Auto migrate
//if err := DB.AutoMigrate(&model.User{}, &model.Document{}); err != nil {
// return fmt.Errorf("failed to migrate database: %w", err)
//}
if err := DB.AutoMigrate(
&model.User{},
&model.Tenant{},
&model.UserTenant{},
&model.File{},
&model.File2Document{},
); err != nil {
return fmt.Errorf("failed to migrate database: %w", err)
}
logger.Info("Database connected and migrated successfully")
return nil

View File

@@ -195,6 +195,11 @@ func (dao *FileDAO) GetAllParentFolders(startID string) ([]*model.File, error) {
return parentFolders, nil
}
// Create creates a new file
func (dao *FileDAO) Create(file *model.File) error {
return DB.Create(file).Error
}
// generateUUID generates a UUID
func generateUUID() string {
id := uuid.New().String()

View File

@@ -88,3 +88,13 @@ func (dao *TenantDAO) GetByID(id string) (*model.Tenant, error) {
}
return &tenant, nil
}
// Create creates a new tenant
func (dao *TenantDAO) Create(tenant *model.Tenant) error {
return DB.Create(tenant).Error
}
// Delete deletes a tenant by ID (soft delete)
func (dao *TenantDAO) Delete(id string) error {
return DB.Model(&model.Tenant{}).Where("id = ?", id).Update("status", "0").Error
}

View File

@@ -101,3 +101,8 @@ func (dao *UserDAO) List(offset, limit int) ([]*model.User, int64, error) {
func (dao *UserDAO) Delete(id uint) error {
return DB.Delete(&model.User{}, id).Error
}
// DeleteByID delete user by string ID
func (dao *UserDAO) DeleteByID(id string) error {
return DB.Model(&model.User{}).Where("id = ?", id).Update("status", "0").Error
}

View File

@@ -59,11 +59,11 @@ func (h *ChatHandler) ListChats(c *gin.Context) {
}
// Get user by access token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}
@@ -112,11 +112,11 @@ func (h *ChatHandler) ListChatsNext(c *gin.Context) {
}
// Get user by access token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}
@@ -196,11 +196,11 @@ func (h *ChatHandler) SetDialog(c *gin.Context) {
}
// Get user by access token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}
@@ -268,11 +268,11 @@ func (h *ChatHandler) RemoveChats(c *gin.Context) {
}
// Get user by access token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}

View File

@@ -61,11 +61,11 @@ func (h *ChatSessionHandler) SetChatSession(c *gin.Context) {
}
// Get user by access token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}
@@ -124,11 +124,11 @@ func (h *ChatSessionHandler) RemoveChatSessions(c *gin.Context) {
}
// Get user by access token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}
@@ -190,11 +190,11 @@ func (h *ChatSessionHandler) ListChatSessions(c *gin.Context) {
}
// Get user by access token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}
@@ -270,11 +270,11 @@ func (h *ChatSessionHandler) Completion(c *gin.Context) {
}
// Get user by access token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}

View File

@@ -59,11 +59,11 @@ func (h *ChunkHandler) RetrievalTest(c *gin.Context) {
}
// Get user by access token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}

View File

@@ -58,11 +58,11 @@ func (h *ConnectorHandler) ListConnectors(c *gin.Context) {
}
// Get user by access token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}

View File

@@ -65,11 +65,11 @@ func (h *FileHandler) ListFiles(c *gin.Context) {
}
// Get user by access token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}
@@ -141,11 +141,11 @@ func (h *FileHandler) GetRootFolder(c *gin.Context) {
}
// Get user by access token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}
@@ -189,11 +189,11 @@ func (h *FileHandler) GetParentFolder(c *gin.Context) {
}
// Get user by access token (for validation)
_, err := h.userService.GetUserByToken(token)
_, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}
@@ -246,11 +246,11 @@ func (h *FileHandler) GetAllParentFolders(c *gin.Context) {
}
// Get user by access token (for validation)
_, err := h.userService.GetUserByToken(token)
_, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}

View File

@@ -130,11 +130,11 @@ func (h *KnowledgebaseHandler) ListKbs(c *gin.Context) {
}
// Get user by access token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}

View File

@@ -71,10 +71,11 @@ func (h *LLMHandler) GetMyLLMs(c *gin.Context) {
}
// Get user by token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}
@@ -127,10 +128,11 @@ func (h *LLMHandler) Factories(c *gin.Context) {
}
// Get user by token
_, err := h.userService.GetUserByToken(token)
_, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}
@@ -207,11 +209,11 @@ func (h *LLMHandler) ListApp(c *gin.Context) {
}
// Get user by token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}

View File

@@ -65,11 +65,11 @@ func (h *SearchHandler) ListSearchApps(c *gin.Context) {
}
// Get user by access token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}

View File

@@ -58,10 +58,11 @@ func (h *TenantHandler) TenantInfo(c *gin.Context) {
return
}
// Get user by token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}
@@ -109,11 +110,11 @@ func (h *TenantHandler) TenantList(c *gin.Context) {
}
// Get user by token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
"code": code,
"message": err.Error(),
})
return
}

View File

@@ -17,7 +17,9 @@
package handler
import (
"fmt"
"net/http"
"ragflow/internal/common"
"ragflow/internal/server"
"ragflow/internal/utility"
"strconv"
@@ -47,31 +49,51 @@ func NewUserHandler(userService *service.UserService) *UserHandler {
// @Produce json
// @Param request body service.RegisterRequest true "registration info"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/users/register [post]
// @Router /v1/user/register [post]
func (h *UserHandler) Register(c *gin.Context) {
var req service.RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
c.JSON(http.StatusOK, gin.H{
"code": common.CodeBadRequest,
"message": err.Error(),
"data": false,
})
return
}
user, err := h.userService.Register(&req)
user, code, err := h.userService.Register(&req)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
"data": false,
})
return
}
variables := server.GetVariables()
secretKey := variables.SecretKey
authToken, err := utility.DumpAccessToken(*user.AccessToken, secretKey)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": common.CodeServerError,
"message": "Failed to generate auth token",
"data": false,
})
return
}
c.Header("Authorization", authToken)
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "*")
c.Header("Access-Control-Allow-Headers", "*")
c.Header("Access-Control-Expose-Headers", "Authorization")
profile := h.userService.GetUserProfile(user)
c.JSON(http.StatusOK, gin.H{
"message": "registration successful",
"data": gin.H{
"id": user.ID,
"nickname": user.Nickname,
"email": user.Email,
},
"code": common.CodeSuccess,
"message": fmt.Sprintf("%s, welcome aboard!", req.Nickname),
"data": profile,
})
}
@@ -87,18 +109,20 @@ func (h *UserHandler) Register(c *gin.Context) {
func (h *UserHandler) Login(c *gin.Context) {
var req service.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
c.JSON(http.StatusOK, gin.H{
"code": common.CodeBadRequest,
"message": err.Error(),
"data": false,
})
return
}
user, err := h.userService.Login(&req)
user, code, err := h.userService.Login(&req)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
"data": false,
})
return
}
@@ -114,7 +138,7 @@ func (h *UserHandler) Login(c *gin.Context) {
c.Header("Access-Control-Expose-Headers", "Authorization")
c.JSON(http.StatusOK, gin.H{
"code": 0,
"code": common.CodeSuccess,
"message": "Welcome back!",
"data": user,
})
@@ -132,18 +156,20 @@ func (h *UserHandler) Login(c *gin.Context) {
func (h *UserHandler) LoginByEmail(c *gin.Context) {
var req service.EmailLoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
c.JSON(http.StatusOK, gin.H{
"code": common.CodeBadRequest,
"message": err.Error(),
"data": false,
})
return
}
user, err := h.userService.LoginByEmail(&req)
user, code, err := h.userService.LoginByEmail(&req)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
"data": false,
})
return
}
@@ -151,21 +177,26 @@ func (h *UserHandler) LoginByEmail(c *gin.Context) {
variables := server.GetVariables()
secretKey := variables.SecretKey
authToken, err := utility.DumpAccessToken(*user.AccessToken, secretKey)
// Set Authorization header with access_token
if user.AccessToken != nil {
c.Header("Authorization", authToken)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": common.CodeServerError,
"message": "Failed to generate auth token",
"data": false,
})
return
}
// Set CORS headers
c.Header("Authorization", authToken)
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "*")
c.Header("Access-Control-Allow-Headers", "*")
c.Header("Access-Control-Expose-Headers", "Authorization")
profile := h.userService.GetUserProfile(user)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"code": common.CodeSuccess,
"message": "Welcome back!",
"data": user,
"data": profile,
})
}
@@ -182,22 +213,28 @@ func (h *UserHandler) GetUserByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "invalid user id",
c.JSON(http.StatusOK, gin.H{
"code": common.CodeBadRequest,
"message": "invalid user id",
"data": false,
})
return
}
user, err := h.userService.GetUserByID(uint(id))
user, code, err := h.userService.GetUserByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{
"error": "user not found",
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
"data": false,
})
return
}
c.JSON(http.StatusOK, gin.H{
"data": user,
"code": common.CodeSuccess,
"message": "success",
"data": user,
})
}
@@ -222,15 +259,19 @@ func (h *UserHandler) ListUsers(c *gin.Context) {
pageSize = 10
}
users, total, err := h.userService.ListUsers(page, pageSize)
users, total, code, err := h.userService.ListUsers(page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "failed to get users",
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
"data": false,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": common.CodeSuccess,
"message": "success",
"data": gin.H{
"items": users,
"total": total,
@@ -253,34 +294,38 @@ func (h *UserHandler) Logout(c *gin.Context) {
// Extract token from request
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
c.JSON(http.StatusOK, gin.H{
"code": common.CodeUnauthorized,
"message": "Missing Authorization header",
"data": false,
})
return
}
// Get user by token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"message": "Invalid access token",
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
"data": false,
})
return
}
// Logout user
if err := h.userService.Logout(user); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
code, err = h.userService.Logout(user)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
"data": false,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"code": common.CodeSuccess,
"data": true,
"message": "success",
})
@@ -299,19 +344,21 @@ func (h *UserHandler) Info(c *gin.Context) {
// Extract token from request
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
c.JSON(http.StatusOK, gin.H{
"code": common.CodeUnauthorized,
"message": "Missing Authorization header",
"data": false,
})
return
}
// Get user by token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"error": "Invalid access token",
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
"data": false,
})
return
}
@@ -320,8 +367,9 @@ func (h *UserHandler) Info(c *gin.Context) {
profile := h.userService.GetUserProfile(user)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": profile,
"code": common.CodeSuccess,
"message": "success",
"data": profile,
})
}
@@ -339,18 +387,21 @@ func (h *UserHandler) Setting(c *gin.Context) {
// Extract token from request
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
c.JSON(http.StatusOK, gin.H{
"code": common.CodeUnauthorized,
"message": "Missing Authorization header",
"data": false,
})
return
}
// Get user by token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Invalid access token",
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
"data": false,
})
return
}
@@ -358,22 +409,29 @@ func (h *UserHandler) Setting(c *gin.Context) {
// Parse request
var req service.UpdateSettingsRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
c.JSON(http.StatusOK, gin.H{
"code": common.CodeBadRequest,
"message": err.Error(),
"data": false,
})
return
}
// Update user settings
if err := h.userService.UpdateUserSettings(user, &req); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
code, err = h.userService.UpdateUserSettings(user, &req)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
"data": false,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": common.CodeSuccess,
"message": "settings updated successfully",
"data": true,
})
}
@@ -391,18 +449,21 @@ func (h *UserHandler) ChangePassword(c *gin.Context) {
// Extract token from request
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
c.JSON(http.StatusOK, gin.H{
"code": common.CodeUnauthorized,
"message": "Missing Authorization header",
"data": false,
})
return
}
// Get user by token
user, err := h.userService.GetUserByToken(token)
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Invalid access token",
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
"data": false,
})
return
}
@@ -410,22 +471,29 @@ func (h *UserHandler) ChangePassword(c *gin.Context) {
// Parse request
var req service.ChangePasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
c.JSON(http.StatusOK, gin.H{
"code": common.CodeBadRequest,
"message": err.Error(),
"data": false,
})
return
}
// Change password
if err := h.userService.ChangePassword(user, &req); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
code, err = h.userService.ChangePassword(user, &req)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
"data": false,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": common.CodeSuccess,
"message": "password changed successfully",
"data": true,
})
}
@@ -438,10 +506,10 @@ func (h *UserHandler) ChangePassword(c *gin.Context) {
// @Success 200 {object} map[string]interface{}
// @Router /v1/user/login/channels [get]
func (h *UserHandler) GetLoginChannels(c *gin.Context) {
channels, err := h.userService.GetLoginChannels()
channels, code, err := h.userService.GetLoginChannels()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": "Load channels failure, error: " + err.Error(),
"data": []interface{}{},
})
@@ -449,7 +517,7 @@ func (h *UserHandler) GetLoginChannels(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"code": common.CodeSuccess,
"message": "success",
"data": channels,
})

View File

@@ -86,6 +86,7 @@ func (r *Router) Setup(engine *gin.Engine) {
// User login by email endpoint
engine.POST("/v1/user/login", r.userHandler.LoginByEmail)
engine.POST("/v1/user/register", r.userHandler.Register)
// User login channels endpoint
engine.GET("/v1/user/login/channels", r.userHandler.GetLoginChannels)
// User logout endpoint

View File

@@ -25,7 +25,9 @@ import (
"errors"
"fmt"
"os"
"ragflow/internal/common"
"ragflow/internal/server"
"regexp"
"strconv"
"strings"
"time"
@@ -52,9 +54,8 @@ func NewUserService() *UserService {
// RegisterRequest registration request
type RegisterRequest struct {
Username string `json:"username" binding:"required,min=3,max=50"`
Password string `json:"password" binding:"required,min=6"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=6"`
Nickname string `json:"nickname"`
}
@@ -96,125 +97,220 @@ type UserResponse struct {
}
// Register user registration
func (s *UserService) Register(req *RegisterRequest) (*model.User, error) {
// Check if email exists
func (s *UserService) Register(req *RegisterRequest) (*model.User, common.ErrorCode, error) {
cfg := server.GetConfig()
if cfg.RegisterEnabled == 0 {
return nil, common.CodeOperatingError, fmt.Errorf("User registration is disabled!")
}
emailRegex := regexp.MustCompile(`^[\w\._-]+@([\w_-]+\.)+[\w-]{2,}$`)
if !emailRegex.MatchString(req.Email) {
return nil, common.CodeOperatingError, fmt.Errorf("Invalid email address: %s!", req.Email)
}
existUser, _ := s.userDAO.GetByEmail(req.Email)
if existUser != nil {
return nil, errors.New("email already exists")
return nil, common.CodeOperatingError, fmt.Errorf("Email: %s has already registered!", req.Email)
}
// Generate password hash
hashedPassword, err := s.HashPassword(req.Password)
decryptedPassword, err := s.decryptPassword(req.Password)
if err != nil {
return nil, fmt.Errorf("failed to hash password: %w", err)
return nil, common.CodeServerError, fmt.Errorf("Fail to decrypt password")
}
// Create user
status := "1"
user := &model.User{
Password: &hashedPassword,
Email: req.Email,
Nickname: req.Nickname,
Status: &status,
hashedPassword, err := s.HashPassword(decryptedPassword)
if err != nil {
return nil, common.CodeServerError, fmt.Errorf("failed to hash password: %w", err)
}
userID := s.GenerateToken()
accessToken := s.GenerateToken()
status := "1"
loginChannel := "password"
isSuperuser := false
user := &model.User{
ID: userID,
AccessToken: &accessToken,
Email: req.Email,
Nickname: req.Nickname,
Password: &hashedPassword,
Status: &status,
IsActive: "1",
IsAuthenticated: "1",
IsAnonymous: "0",
LoginChannel: &loginChannel,
IsSuperuser: &isSuperuser,
}
now := time.Now().Unix()
user.CreateTime = now
user.UpdateTime = &now
now_date := time.Now()
user.CreateDate = &now_date
user.UpdateDate = &now_date
user.LastLoginTime = &now_date
tenantName := req.Nickname + "'s Kingdom"
tenant := &model.Tenant{
ID: userID,
Name: &tenantName,
LLMID: cfg.Server.Mode,
EmbDID: cfg.Server.Mode,
ASRID: cfg.Server.Mode,
Img2TxtID: cfg.Server.Mode,
RerankID: cfg.Server.Mode,
ParserIDs: "naive:General,Q&A:Q&A,manual:Manual,table:Table,paper:Research Paper,book:Book,laws:Laws,presentation:Presentation,picture:Picture,one:One,audio:Audio,email:Email,tag:Tag",
}
tenant.CreateTime = now
tenant.UpdateTime = &now
tenant.CreateDate = &now_date
tenant.UpdateDate = &now_date
userTenantID := s.GenerateToken()
userTenant := &model.UserTenant{
ID: userTenantID,
UserID: userID,
TenantID: userID,
Role: "owner",
InvitedBy: userID,
Status: &status,
}
userTenant.CreateTime = now
userTenant.UpdateTime = &now
userTenant.CreateDate = &now_date
userTenant.UpdateDate = &now_date
fileID := s.GenerateToken()
rootFile := &model.File{
ID: fileID,
ParentID: fileID,
TenantID: userID,
CreatedBy: userID,
Name: "/",
Type: "folder",
Size: 0,
}
rootFile.CreateTime = now
rootFile.UpdateTime = &now
rootFile.CreateDate = &now_date
rootFile.UpdateDate = &now_date
tenantDAO := dao.NewTenantDAO()
userTenantDAO := dao.NewUserTenantDAO()
fileDAO := dao.NewFileDAO()
if err := s.userDAO.Create(user); err != nil {
return nil, fmt.Errorf("failed to create user: %w", err)
return nil, common.CodeServerError, fmt.Errorf("failed to create user: %w", err)
}
return user, nil
if err := tenantDAO.Create(tenant); err != nil {
s.userDAO.DeleteByID(userID)
return nil, common.CodeServerError, fmt.Errorf("failed to create tenant: %w", err)
}
if err := userTenantDAO.Create(userTenant); err != nil {
s.userDAO.DeleteByID(userID)
tenantDAO.Delete(userID)
return nil, common.CodeServerError, fmt.Errorf("failed to create user tenant relation: %w", err)
}
if err := fileDAO.Create(rootFile); err != nil {
s.userDAO.DeleteByID(userID)
tenantDAO.Delete(userID)
userTenantDAO.Delete(userTenantID)
return nil, common.CodeServerError, fmt.Errorf("failed to create root folder: %w", err)
}
return user, common.CodeSuccess, nil
}
// Login user login
func (s *UserService) Login(req *LoginRequest) (*model.User, error) {
func (s *UserService) Login(req *LoginRequest) (*model.User, common.ErrorCode, error) {
// Get user by email (using username field as email)
user, err := s.userDAO.GetByEmail(req.Username)
if err != nil {
return nil, errors.New("invalid email or password")
return nil, common.CodeAuthenticationError, fmt.Errorf("invalid email or password")
}
// Decrypt password using RSA
decryptedPassword, err := s.decryptPassword(req.Password)
if err != nil {
return nil, fmt.Errorf("failed to decrypt password: %w", err)
return nil, common.CodeServerError, fmt.Errorf("failed to decrypt password: %w", err)
}
// Verify password
if user.Password == nil || !s.VerifyPassword(*user.Password, decryptedPassword) {
return nil, errors.New("invalid username or password")
return nil, common.CodeAuthenticationError, fmt.Errorf("invalid username or password")
}
// Check user status
if user.Status == nil || *user.Status != "1" {
return nil, errors.New("user is disabled")
return nil, common.CodeForbidden, fmt.Errorf("user is disabled")
}
// Generate new access token
token := s.GenerateToken()
if err := s.UpdateUserAccessToken(user, token); err != nil {
return nil, fmt.Errorf("failed to update access token: %w", err)
return nil, common.CodeServerError, fmt.Errorf("failed to update access token: %w", err)
}
// Update timestamp
now := time.Now().Unix()
user.UpdateTime = &now
if err := s.userDAO.Update(user); err != nil {
return nil, fmt.Errorf("failed to update user: %w", err)
return nil, common.CodeServerError, fmt.Errorf("failed to update user: %w", err)
}
return user, nil
return user, common.CodeSuccess, nil
}
// LoginByEmail user login by email
func (s *UserService) LoginByEmail(req *EmailLoginRequest) (*model.User, error) {
// Check for default admin account
// Returns user on success, or error with specific code:
// - CodeAuthenticationError (109): Email not registered or password mismatch
// - CodeServerError (500): Password decryption failure
// - CodeForbidden (403): Account disabled
func (s *UserService) LoginByEmail(req *EmailLoginRequest) (*model.User, common.ErrorCode, error) {
if req.Email == "admin@ragflow.io" {
return nil, errors.New("default admin account cannot be used to login normal services")
return nil, common.CodeAuthenticationError, fmt.Errorf("default admin account cannot be used to login normal services")
}
// Get user by email
user, err := s.userDAO.GetByEmail(req.Email)
if err != nil {
return nil, errors.New("invalid email or password")
return nil, common.CodeAuthenticationError, fmt.Errorf("Email: %s is not registered!", req.Email)
}
// Decrypt password using RSA
decryptedPassword, err := s.decryptPassword(req.Password)
if err != nil {
return nil, fmt.Errorf("failed to decrypt password: %w", err)
return nil, common.CodeServerError, fmt.Errorf("Fail to crypt password")
}
// Verify password
if user.Password == nil || !s.VerifyPassword(*user.Password, decryptedPassword) {
return nil, errors.New("invalid email or password")
return nil, common.CodeAuthenticationError, fmt.Errorf("Email and password do not match!")
}
// Check user status
if user.Status == nil || *user.Status != "1" {
return nil, errors.New("user is disabled")
if user.IsActive == "0" {
return nil, common.CodeForbidden, fmt.Errorf("This account has been disabled, please contact the administrator!")
}
// Generate new access token
token := s.GenerateToken()
user.AccessToken = &token
// Update timestamp
now := time.Now().Unix()
user.UpdateTime = &now
now_date := time.Now()
user.UpdateDate = &now_date
if err := s.userDAO.Update(user); err != nil {
return nil, fmt.Errorf("failed to update user: %w", err)
return nil, common.CodeServerError, fmt.Errorf("failed to update user: %w", err)
}
return user, nil
return user, common.CodeSuccess, nil
}
// GetUserByID get user by ID
func (s *UserService) GetUserByID(id uint) (*UserResponse, error) {
func (s *UserService) GetUserByID(id uint) (*UserResponse, common.ErrorCode, error) {
user, err := s.userDAO.GetByID(id)
if err != nil {
return nil, err
return nil, common.CodeNotFound, err
}
return &UserResponse{
@@ -223,15 +319,15 @@ func (s *UserService) GetUserByID(id uint) (*UserResponse, error) {
Nickname: user.Nickname,
Status: user.Status,
CreatedAt: time.Unix(user.CreateTime, 0).Format("2006-01-02 15:04:05"),
}, nil
}, common.CodeSuccess, nil
}
// ListUsers list users
func (s *UserService) ListUsers(page, pageSize int) ([]*UserResponse, int64, error) {
func (s *UserService) ListUsers(page, pageSize int) ([]*UserResponse, int64, common.ErrorCode, error) {
offset := (page - 1) * pageSize
users, total, err := s.userDAO.List(offset, pageSize)
if err != nil {
return nil, 0, err
return nil, 0, common.CodeServerError, err
}
responses := make([]*UserResponse, len(users))
@@ -245,7 +341,7 @@ func (s *UserService) ListUsers(page, pageSize int) ([]*UserResponse, int64, err
}
}
return responses, total, nil
return responses, total, common.CodeSuccess, nil
}
// HashPassword generate password hash
@@ -399,7 +495,7 @@ func (s *UserService) GenerateToken() string {
// GetUserByToken gets user by authorization header
// The token parameter is the authorization header value, which needs to be decrypted
// using itsdangerous URLSafeTimedSerializer to get the actual access_token
func (s *UserService) GetUserByToken(authorization string) (*model.User, error) {
func (s *UserService) GetUserByToken(authorization string) (*model.User, common.ErrorCode, error) {
// Get secret key from config
variables := server.GetVariables()
secretKey := variables.SecretKey
@@ -408,16 +504,21 @@ func (s *UserService) GetUserByToken(authorization string) (*model.User, error)
// Equivalent to: access_token = str(jwt.loads(authorization)) in Python
accessToken, err := utility.ExtractAccessToken(authorization, secretKey)
if err != nil {
return nil, fmt.Errorf("invalid authorization token: %w", err)
return nil, common.CodeUnauthorized, fmt.Errorf("invalid authorization token: %w", err)
}
// Validate token format (should be at least 32 chars, UUID format)
if len(accessToken) < 32 {
return nil, errors.New("invalid access token format")
return nil, common.CodeUnauthorized, fmt.Errorf("invalid access token format")
}
// Get user by access token
return s.userDAO.GetByAccessToken(accessToken)
user, err := s.userDAO.GetByAccessToken(accessToken)
if err != nil {
return nil, common.CodeUnauthorized, err
}
return user, common.CodeSuccess, nil
}
// UpdateUserAccessToken updates user's access token
@@ -426,11 +527,15 @@ func (s *UserService) UpdateUserAccessToken(user *model.User, token string) erro
}
// Logout invalidates user's access token
func (s *UserService) Logout(user *model.User) error {
func (s *UserService) Logout(user *model.User) (common.ErrorCode, error) {
// Invalidate token by setting it to an invalid value
// Similar to Python implementation: "INVALID_" + secrets.token_hex(16)
invalidToken := "INVALID_" + s.GenerateToken()
return s.UpdateUserAccessToken(user, invalidToken)
err := s.UpdateUserAccessToken(user, invalidToken)
if err != nil {
return common.CodeServerError, err
}
return common.CodeSuccess, nil
}
// GetUserProfile returns user profile information
@@ -539,7 +644,7 @@ func (s *UserService) GetUserProfile(user *model.User) map[string]interface{} {
}
// UpdateUserSettings updates user settings
func (s *UserService) UpdateUserSettings(user *model.User, req *UpdateSettingsRequest) error {
func (s *UserService) UpdateUserSettings(user *model.User, req *UpdateSettingsRequest) (common.ErrorCode, error) {
// Update fields if provided
if req.Nickname != nil {
user.Nickname = *req.Nickname
@@ -562,15 +667,18 @@ func (s *UserService) UpdateUserSettings(user *model.User, req *UpdateSettingsRe
}
// Save updated user
return s.userDAO.Update(user)
if err := s.userDAO.Update(user); err != nil {
return common.CodeServerError, err
}
return common.CodeSuccess, nil
}
// ChangePassword changes user password
func (s *UserService) ChangePassword(user *model.User, req *ChangePasswordRequest) error {
func (s *UserService) ChangePassword(user *model.User, req *ChangePasswordRequest) (common.ErrorCode, error) {
// If password is provided, verify current password
if req.Password != nil {
if user.Password == nil || !s.VerifyPassword(*user.Password, *req.Password) {
return errors.New("current password is incorrect")
return common.CodeBadRequest, fmt.Errorf("current password is incorrect")
}
}
@@ -578,13 +686,16 @@ func (s *UserService) ChangePassword(user *model.User, req *ChangePasswordReques
if req.NewPassword != nil {
hashedPassword, err := s.HashPassword(*req.NewPassword)
if err != nil {
return fmt.Errorf("failed to hash new password: %w", err)
return common.CodeServerError, fmt.Errorf("failed to hash new password: %w", err)
}
user.Password = &hashedPassword
}
// Save updated user
return s.userDAO.Update(user)
if err := s.userDAO.Update(user); err != nil {
return common.CodeServerError, err
}
return common.CodeSuccess, nil
}
// LoginChannel represents a login channel response
@@ -595,7 +706,7 @@ type LoginChannel struct {
}
// GetLoginChannels gets all supported authentication channels
func (s *UserService) GetLoginChannels() ([]*LoginChannel, error) {
func (s *UserService) GetLoginChannels() ([]*LoginChannel, common.ErrorCode, error) {
cfg := server.GetConfig()
channels := make([]*LoginChannel, 0)
@@ -617,5 +728,5 @@ func (s *UserService) GetLoginChannels() ([]*LoginChannel, error) {
})
}
return channels, nil
return channels, common.CodeSuccess, nil
}