Unified · Split
| @@ -1745,6 +1745,17 @@ func (h *Handler) RequestIFlowCookieToken(c *gin.Context) { · 1744 unmodified lines | |||
| 1745 | return | 1745 | return |
| 1746 | } | 1746 | } |
| 1747 | 1747 | ||
| 1748 | // Check for duplicate BXAuth before authentication | ||
| 1749 | bxAuth := iflowauth.ExtractBXAuth(cookieValue) | ||
| 1750 | if existingFile, err := iflowauth.CheckDuplicateBXAuth(h.cfg.AuthDir, bxAuth); err != nil { | ||
| 1751 | c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "error": "failed to check duplicate"}) | ||
| 1752 | return | ||
| 1753 | } else if existingFile != "" { | ||
| 1754 | existingFileName := filepath.Base(existingFile) | ||
| 1755 | c.JSON(http.StatusConflict, gin.H{"status": "error", "error": "duplicate BXAuth found", "existing_file": existingFileName}) | ||
| 1756 | return | ||
| 1757 | } | ||
| 1758 | |||
| 1748 | authSvc := iflowauth.NewIFlowAuth(h.cfg) | 1759 | authSvc := iflowauth.NewIFlowAuth(h.cfg) |
| 1749 | tokenData, errAuth := authSvc.AuthenticateWithCookie(ctx, cookieValue) | 1760 | tokenData, errAuth := authSvc.AuthenticateWithCookie(ctx, cookieValue) |
| 1750 | if errAuth != nil { | 1761 | if errAuth != nil { |
| @@ -1767,11 +1778,12 @@ func (h *Handler) RequestIFlowCookieToken(c *gin.Context) { · 16 unmodified lines | |||
| 1767 | } | 1778 | } |
| 1768 | 1779 | ||
| 1769 | tokenStorage.Email = email | 1780 | tokenStorage.Email = email |
| 1781 | timestamp := time.Now().Unix() | ||
| 1770 | 1782 | ||
| 1771 | record := &coreauth.Auth{ | 1783 | record := &coreauth.Auth{ |
| 1772 | ID: fmt.Sprintf("iflow-%s.json", fileName), | 1784 | ID: fmt.Sprintf("iflow-%s-%d.json", fileName, timestamp), |
| 1773 | Provider: "iflow", | 1785 | Provider: "iflow", |
| 1774 | FileName: fmt.Sprintf("iflow-%s.json", fileName), | 1786 | FileName: fmt.Sprintf("iflow-%s-%d.json", fileName, timestamp), |
| 1775 | Storage: tokenStorage, | 1787 | Storage: tokenStorage, |
| 1776 | Metadata: map[string]any{ | 1788 | Metadata: map[string]any{ |
| 1777 | "email": email, | 1789 | "email": email, |
| @@ -1,7 +1,10 @@ | |||
| 1 | package iflow | 1 | package iflow |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "encoding/json" | ||
| 4 | "fmt" | 5 | "fmt" |
| 6 | "os" | ||
| 7 | "path/filepath" | ||
| 5 | "strings" | 8 | "strings" |
| 6 | ) | 9 | ) |
| 7 | 10 | ||
| @@ -36,3 +39,61 @@ func SanitizeIFlowFileName(raw string) string { · 28 unmodified lines | |||
| 36 | } | 39 | } |
| 37 | return strings.TrimSpace(result.String()) | 40 | return strings.TrimSpace(result.String()) |
| 38 | } | 41 | } |
| 42 | |||
| 43 | // ExtractBXAuth extracts the BXAuth value from a cookie string. | ||
| 44 | func ExtractBXAuth(cookie string) string { | ||
| 45 | parts := strings.Split(cookie, ";") | ||
| 46 | for _, part := range parts { | ||
| 47 | part = strings.TrimSpace(part) | ||
| 48 | if strings.HasPrefix(part, "BXAuth=") { | ||
| 49 | return strings.TrimPrefix(part, "BXAuth=") | ||
| 50 | } | ||
| 51 | } | ||
| 52 | return "" | ||
| 53 | } | ||
| 54 | |||
| 55 | // CheckDuplicateBXAuth checks if the given BXAuth value already exists in any iflow auth file. | ||
| 56 | // Returns the path of the existing file if found, empty string otherwise. | ||
| 57 | func CheckDuplicateBXAuth(authDir, bxAuth string) (string, error) { | ||
| 58 | if bxAuth == "" { | ||
| 59 | return "", nil | ||
| 60 | } | ||
| 61 | |||
| 62 | entries, err := os.ReadDir(authDir) | ||
| 63 | if err != nil { | ||
| 64 | if os.IsNotExist(err) { | ||
| 65 | return "", nil | ||
| 66 | } | ||
| 67 | return "", fmt.Errorf("read auth dir failed: %w", err) | ||
| 68 | } | ||
| 69 | |||
| 70 | for _, entry := range entries { | ||
| 71 | if entry.IsDir() { | ||
| 72 | continue | ||
| 73 | } | ||
| 74 | name := entry.Name() | ||
| 75 | if !strings.HasPrefix(name, "iflow-") || !strings.HasSuffix(name, ".json") { | ||
| 76 | continue | ||
| 77 | } | ||
| 78 | |||
| 79 | filePath := filepath.Join(authDir, name) | ||
| 80 | data, err := os.ReadFile(filePath) | ||
| 81 | if err != nil { | ||
| 82 | continue | ||
| 83 | } | ||
| 84 | |||
| 85 | var tokenData struct { | ||
| 86 | Cookie string `json:"cookie"` | ||
| 87 | } | ||
| 88 | if err := json.Unmarshal(data, &tokenData); err != nil { | ||
| 89 | continue | ||
| 90 | } | ||
| 91 | |||
| 92 | existingBXAuth := ExtractBXAuth(tokenData.Cookie) | ||
| 93 | if existingBXAuth != "" && existingBXAuth == bxAuth { | ||
| 94 | return filePath, nil | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | return "", nil | ||
| 99 | } | ||
| @@ -506,11 +506,18 @@ func (ia *IFlowAuth) CreateCookieTokenStorage(data *IFlowTokenData) *IFlowTokenS · 505 unmodified lines | |||
| 506 | return nil | 506 | return nil |
| 507 | } | 507 | } |
| 508 | 508 | ||
| 509 | // Only save the BXAuth field from the cookie | ||
| 510 | bxAuth := ExtractBXAuth(data.Cookie) | ||
| 511 | cookieToSave := "" | ||
| 512 | if bxAuth != "" { | ||
| 513 | cookieToSave = "BXAuth=" + bxAuth + ";" | ||
| 514 | } | ||
| 515 | |||
| 509 | return &IFlowTokenStorage{ | 516 | return &IFlowTokenStorage{ |
| 510 | APIKey: data.APIKey, | 517 | APIKey: data.APIKey, |
| 511 | Email: data.Email, | 518 | Email: data.Email, |
| 512 | Expire: data.Expire, | 519 | Expire: data.Expire, |
| 513 | Cookie: data.Cookie, | 520 | Cookie: cookieToSave, |
| 514 | LastRefresh: time.Now().Format(time.RFC3339), | 521 | LastRefresh: time.Now().Format(time.RFC3339), |
| 515 | Type: "iflow", | 522 | Type: "iflow", |
| 516 | } | 523 | } |
| @@ -5,7 +5,9 @@ import ( · 4 unmodified lines | |||
| 5 | "context" | 5 | "context" |
| 6 | "fmt" | 6 | "fmt" |
| 7 | "os" | 7 | "os" |
| 8 | "path/filepath" | ||
| 8 | "strings" | 9 | "strings" |
| 10 | "time" | ||
| 9 | 11 | ||
| 10 | "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/iflow" | 12 | "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/iflow" |
| 11 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" | 13 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| @@ -37,6 +39,16 @@ func DoIFlowCookieAuth(cfg *config.Config, options *LoginOptions) { · 25 unmodified lines | |||
| 37 | return | 39 | return |
| 38 | } | 40 | } |
| 39 | 41 | ||
| 42 | // Check for duplicate BXAuth before authentication | ||
| 43 | bxAuth := iflow.ExtractBXAuth(cookie) | ||
| 44 | if existingFile, err := iflow.CheckDuplicateBXAuth(cfg.AuthDir, bxAuth); err != nil { | ||
| 45 | fmt.Printf("Failed to check duplicate: %v\n", err) | ||
| 46 | return | ||
| 47 | } else if existingFile != "" { | ||
| 48 | fmt.Printf("Duplicate BXAuth found, authentication already exists: %s\n", filepath.Base(existingFile)) | ||
| 49 | return | ||
| 50 | } | ||
| 51 | |||
| 40 | // Authenticate with cookie | 52 | // Authenticate with cookie |
| 41 | auth := iflow.NewIFlowAuth(cfg) | 53 | auth := iflow.NewIFlowAuth(cfg) |
| 42 | ctx := context.Background() | 54 | ctx := context.Background() |
| @@ -82,5 +94,5 @@ func promptForCookie(promptFn func(string) (string, error)) (string, error) { · 39 unmodified lines | |||
| 82 | // getAuthFilePath returns the auth file path for the given provider and email | 94 | // getAuthFilePath returns the auth file path for the given provider and email |
| 83 | func getAuthFilePath(cfg *config.Config, provider, email string) string { | 95 | func getAuthFilePath(cfg *config.Config, provider, email string) string { |
| 84 | fileName := iflow.SanitizeIFlowFileName(email) | 96 | fileName := iflow.SanitizeIFlowFileName(email) |
| 85 | return fmt.Sprintf("%s/%s-%s.json", cfg.AuthDir, provider, fileName) | 97 | return fmt.Sprintf("%s/%s-%s-%d.json", cfg.AuthDir, provider, fileName, time.Now().Unix()) |
| 86 | } | 98 | } |
| @@ -107,7 +107,7 @@ func (a *IFlowAuthenticator) Login(ctx context.Context, cfg *config.Config, opts · 106 unmodified lines | |||
| 107 | return nil, fmt.Errorf("iflow authentication failed: missing account identifier") | 107 | return nil, fmt.Errorf("iflow authentication failed: missing account identifier") |
| 108 | } | 108 | } |
| 109 | 109 | ||
| 110 | fileName := fmt.Sprintf("iflow-%s.json", email) | 110 | fileName := fmt.Sprintf("iflow-%s-%d.json", email, time.Now().Unix()) |
| 111 | metadata := map[string]any{ | 111 | metadata := map[string]any{ |
| 112 | "email": email, | 112 | "email": email, |
| 113 | "api_key": tokenStorage.APIKey, | 113 | "api_key": tokenStorage.APIKey, |