From e705ac6643a3a1d7947a3a4e97726d84b92f2023 Mon Sep 17 00:00:00 2001 From: Jin Hai Date: Thu, 26 Mar 2026 11:54:23 +0800 Subject: [PATCH] Add logout (#13796) ### What problem does this PR solve? Add command: logout ### Type of change - [x] New Feature (non-breaking change which adds functionality) Signed-off-by: Jin Hai --- internal/admin/router.go | 3 +- internal/cli/admin_parser.go | 10 ++++++ internal/cli/cli.go | 67 +++++++++++++++++++----------------- internal/cli/client.go | 33 ++++++++++++++++++ internal/cli/lexer.go | 2 ++ internal/cli/parser.go | 4 +++ internal/cli/types.go | 1 + internal/cli/user_parser.go | 10 ++++++ 8 files changed, 98 insertions(+), 32 deletions(-) diff --git a/internal/admin/router.go b/internal/admin/router.go index 413b5e4b62..b1b7a71c2d 100644 --- a/internal/admin/router.go +++ b/internal/admin/router.go @@ -43,7 +43,6 @@ func (r *Router) Setup(engine *gin.Engine) { // Public routes admin.GET("/ping", r.handler.Ping) admin.POST("/login", r.handler.Login) - admin.GET("/logout", r.handler.Logout) admin.POST("/reports", r.handler.Reports) @@ -51,6 +50,8 @@ func (r *Router) Setup(engine *gin.Engine) { protected := admin.Group("") protected.Use(r.handler.AuthMiddleware()) { + + protected.GET("/logout", r.handler.Logout) // Auth protected.GET("/auth", r.handler.AuthCheck) diff --git a/internal/cli/admin_parser.go b/internal/cli/admin_parser.go index 1cfd96a277..503931dd20 100644 --- a/internal/cli/admin_parser.go +++ b/internal/cli/admin_parser.go @@ -27,6 +27,16 @@ func (p *Parser) parseAdminLoginUser() (*Command, error) { return cmd, nil } +func (p *Parser) parseAdminLogout() (*Command, error) { + cmd := NewCommand("logout") + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + func (p *Parser) parseAdminPingServer() (*Command, error) { cmd := NewCommand("ping") p.nextToken() diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 7c77ad68f1..b0d7a3848b 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -110,7 +110,7 @@ func parseHostPort(hostPort string) (string, int, error) { func ParseConnectionArgs(args []string) (*ConnectionArgs, error) { // First, scan args to check for help, config file, and admin mode var configFilePath string - + var adminMode bool = false for i := 0; i < len(args); i++ { arg := args[i] if arg == "--help" || arg == "-help" { @@ -118,6 +118,8 @@ func ParseConnectionArgs(args []string) (*ConnectionArgs, error) { } else if (arg == "-f" || arg == "--config") && i+1 < len(args) { configFilePath = args[i+1] i++ + } else if arg == "--admin" { + adminMode = true } } @@ -125,45 +127,48 @@ func ParseConnectionArgs(args []string) (*ConnectionArgs, error) { var config *ConfigFile var err error - if configFilePath != "" { - // User specified config file via -f - config, err = LoadConfigFileFromPath(configFilePath) - if err != nil { - return nil, err - } - } else { - // Try default rf.yml - config, err = LoadDefaultConfigFile() - if err != nil { - return nil, err - } - } - // Parse arguments manually to support both short and long forms // and to handle priority: command line > config file > defaults // Build result from config file first (if exists), then override with command line flags result := &ConnectionArgs{} + if !adminMode { + // Only user mode read config file + if configFilePath != "" { + // User specified config file via -f + config, err = LoadConfigFileFromPath(configFilePath) + if err != nil { + return nil, err + } + } else { + // Try default rf.yml + config, err = LoadDefaultConfigFile() + if err != nil { + return nil, err + } + } + + // Apply config file values first (lower priority) + if config != nil { + // Parse host:port from config file + if config.Host != "" { + h, port, err := parseHostPort(config.Host) + if err != nil { + return nil, fmt.Errorf("invalid host in config file: %v", err) + } + result.Host = h + result.Port = port + } + result.UserName = config.UserName + result.Password = config.Password + result.APIToken = config.APIToken + } + } + // Get non-flag arguments (command to execute) var nonFlagArgs []string - // Apply config file values first (lower priority) - if config != nil { - // Parse host:port from config file - if config.Host != "" { - h, port, err := parseHostPort(config.Host) - if err != nil { - return nil, fmt.Errorf("invalid host in config file: %v", err) - } - result.Host = h - result.Port = port - } - result.UserName = config.UserName - result.Password = config.Password - result.APIToken = config.APIToken - } - // Override with command line flags (higher priority) // Handle both short and long forms manually for i := 0; i < len(args); i++ { diff --git a/internal/cli/client.go b/internal/cli/client.go index 10795baadf..0445adc368 100644 --- a/internal/cli/client.go +++ b/internal/cli/client.go @@ -234,6 +234,35 @@ func (c *RAGFlowClient) loginUser(email, password string) (string, error) { return token, nil } +func (c *RAGFlowClient) Logout() (ResponseIf, error) { + if c.HTTPClient.LoginToken == "" { + return nil, fmt.Errorf("not logged in") + } + + var path string + if c.ServerType == "admin" { + path = "/admin/logout" + } else { + path = "/user/logout" + } + + resp, err := c.HTTPClient.Request("GET", path, c.ServerType == "admin", "web", nil, nil) + if err != nil { + return nil, err + } + + var result SimpleResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("login failed: invalid JSON (%w)", err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("login failed: %s", result.Message) + } + + return &result, nil +} + // readPassword reads password from terminal without echoing func readPassword() (string, error) { // Check if stdin is a terminal by trying to get terminal size @@ -301,6 +330,8 @@ func (c *RAGFlowClient) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { switch cmd.Type { case "login_user": return nil, c.LoginUser(cmd) + case "logout": + return c.Logout() case "ping": return c.PingAdmin(cmd) case "benchmark": @@ -350,6 +381,8 @@ func (c *RAGFlowClient) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { return c.RegisterUser(cmd) case "login_user": return nil, c.LoginUser(cmd) + case "logout": + return c.Logout() case "ping": return c.PingServer(cmd) case "benchmark": diff --git a/internal/cli/lexer.go b/internal/cli/lexer.go index f0cbeee6ed..2cbcec633a 100644 --- a/internal/cli/lexer.go +++ b/internal/cli/lexer.go @@ -149,6 +149,8 @@ func (l *Lexer) lookupIdent(ident string) Token { switch upper { case "LOGIN": return Token{Type: TokenLogin, Value: ident} + case "LOGOUT": + return Token{Type: TokenLogout, Value: ident} case "REGISTER": return Token{Type: TokenRegister, Value: ident} case "LIST": diff --git a/internal/cli/parser.go b/internal/cli/parser.go index d4afadf9aa..0c15345ab0 100644 --- a/internal/cli/parser.go +++ b/internal/cli/parser.go @@ -81,6 +81,8 @@ func (p *Parser) parseAdminCommand() (*Command, error) { switch p.curToken.Type { case TokenLogin: return p.parseAdminLoginUser() + case TokenLogout: + return p.parseAdminLogout() case TokenPing: return p.parseAdminPingServer() case TokenList: @@ -131,6 +133,8 @@ func (p *Parser) parseUserCommand() (*Command, error) { switch p.curToken.Type { case TokenLogin: return p.parseLoginUser() + case TokenLogout: + return p.parseLogout() case TokenPing: return p.parsePingServer() case TokenList: diff --git a/internal/cli/types.go b/internal/cli/types.go index 5d90d54394..1da0ed2eea 100644 --- a/internal/cli/types.go +++ b/internal/cli/types.go @@ -26,6 +26,7 @@ type Command struct { const ( // Keywords TokenLogin = iota + TokenLogout TokenRegister TokenList TokenServices diff --git a/internal/cli/user_parser.go b/internal/cli/user_parser.go index 4190597fc7..98d729ffff 100644 --- a/internal/cli/user_parser.go +++ b/internal/cli/user_parser.go @@ -3,6 +3,16 @@ package cli import "fmt" // Command parsers +func (p *Parser) parseLogout() (*Command, error) { + cmd := NewCommand("logout") + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + func (p *Parser) parseLoginUser() (*Command, error) { cmd := NewCommand("login_user")