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 <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2026-03-26 11:54:23 +08:00
committed by GitHub
parent ebf36950e4
commit e705ac6643
8 changed files with 98 additions and 32 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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++ {

View File

@ -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":

View File

@ -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":

View File

@ -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:

View File

@ -26,6 +26,7 @@ type Command struct {
const (
// Keywords
TokenLogin = iota
TokenLogout
TokenRegister
TokenList
TokenServices

View File

@ -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")