mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 13:00:52 +08:00
feat(auth): Use user info for iFlow auth identifier
This commit is contained in:
@@ -1013,13 +1013,17 @@ func (h *Handler) RequestIFlowToken(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tokenStorage := authSvc.CreateTokenStorage(tokenData)
|
tokenStorage := authSvc.CreateTokenStorage(tokenData)
|
||||||
tokenStorage.Email = fmt.Sprintf("iflow-%d", time.Now().UnixMilli())
|
identifier := strings.TrimSpace(tokenStorage.Email)
|
||||||
|
if identifier == "" {
|
||||||
|
identifier = fmt.Sprintf("iflow-%d", time.Now().UnixMilli())
|
||||||
|
tokenStorage.Email = identifier
|
||||||
|
}
|
||||||
record := &coreauth.Auth{
|
record := &coreauth.Auth{
|
||||||
ID: fmt.Sprintf("iflow-%s.json", tokenStorage.Email),
|
ID: fmt.Sprintf("iflow-%s.json", identifier),
|
||||||
Provider: "iflow",
|
Provider: "iflow",
|
||||||
FileName: fmt.Sprintf("iflow-%s.json", tokenStorage.Email),
|
FileName: fmt.Sprintf("iflow-%s.json", identifier),
|
||||||
Storage: tokenStorage,
|
Storage: tokenStorage,
|
||||||
Metadata: map[string]any{"email": tokenStorage.Email, "api_key": tokenStorage.APIKey},
|
Metadata: map[string]any{"email": identifier, "api_key": tokenStorage.APIKey},
|
||||||
Attributes: map[string]string{"api_key": tokenStorage.APIKey},
|
Attributes: map[string]string{"api_key": tokenStorage.APIKey},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -141,61 +141,69 @@ func (ia *IFlowAuth) doTokenRequest(ctx context.Context, req *http.Request) (*IF
|
|||||||
return nil, fmt.Errorf("iflow token: missing access token in response")
|
return nil, fmt.Errorf("iflow token: missing access token in response")
|
||||||
}
|
}
|
||||||
|
|
||||||
apiKey, errAPI := ia.FetchAPIKey(ctx, tokenResp.AccessToken)
|
info, errAPI := ia.FetchUserInfo(ctx, tokenResp.AccessToken)
|
||||||
if errAPI != nil {
|
if errAPI != nil {
|
||||||
return nil, fmt.Errorf("iflow token: fetch api key failed: %w", errAPI)
|
return nil, fmt.Errorf("iflow token: fetch user info failed: %w", errAPI)
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(apiKey) == "" {
|
if strings.TrimSpace(info.APIKey) == "" {
|
||||||
return nil, fmt.Errorf("iflow token: empty api key returned")
|
return nil, fmt.Errorf("iflow token: empty api key returned")
|
||||||
}
|
}
|
||||||
data.APIKey = apiKey
|
email := strings.TrimSpace(info.Email)
|
||||||
|
if email == "" {
|
||||||
|
email = strings.TrimSpace(info.Phone)
|
||||||
|
}
|
||||||
|
if email == "" {
|
||||||
|
return nil, fmt.Errorf("iflow token: missing account email/phone in user info")
|
||||||
|
}
|
||||||
|
data.APIKey = info.APIKey
|
||||||
|
data.Email = email
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchAPIKey retrieves the account API key associated with the provided access token.
|
// FetchUserInfo retrieves account metadata (including API key) for the provided access token.
|
||||||
func (ia *IFlowAuth) FetchAPIKey(ctx context.Context, accessToken string) (string, error) {
|
func (ia *IFlowAuth) FetchUserInfo(ctx context.Context, accessToken string) (*userInfoData, error) {
|
||||||
if strings.TrimSpace(accessToken) == "" {
|
if strings.TrimSpace(accessToken) == "" {
|
||||||
return "", fmt.Errorf("iflow api key: access token is empty")
|
return nil, fmt.Errorf("iflow api key: access token is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint := fmt.Sprintf("%s?accessToken=%s", iFlowUserInfoEndpoint, url.QueryEscape(accessToken))
|
endpoint := fmt.Sprintf("%s?accessToken=%s", iFlowUserInfoEndpoint, url.QueryEscape(accessToken))
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("iflow api key: create request failed: %w", err)
|
return nil, fmt.Errorf("iflow api key: create request failed: %w", err)
|
||||||
}
|
}
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
resp, err := ia.httpClient.Do(req)
|
resp, err := ia.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("iflow api key: request failed: %w", err)
|
return nil, fmt.Errorf("iflow api key: request failed: %w", err)
|
||||||
}
|
}
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("iflow api key: read response failed: %w", err)
|
return nil, fmt.Errorf("iflow api key: read response failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
log.Debugf("iflow api key failed: status=%d body=%s", resp.StatusCode, string(body))
|
log.Debugf("iflow api key failed: status=%d body=%s", resp.StatusCode, string(body))
|
||||||
return "", fmt.Errorf("iflow api key: %d %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
return nil, fmt.Errorf("iflow api key: %d %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||||
}
|
}
|
||||||
|
|
||||||
var result userInfoResponse
|
var result userInfoResponse
|
||||||
if err = json.Unmarshal(body, &result); err != nil {
|
if err = json.Unmarshal(body, &result); err != nil {
|
||||||
return "", fmt.Errorf("iflow api key: decode body failed: %w", err)
|
return nil, fmt.Errorf("iflow api key: decode body failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !result.Success {
|
if !result.Success {
|
||||||
return "", fmt.Errorf("iflow api key: request not successful")
|
return nil, fmt.Errorf("iflow api key: request not successful")
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.Data.APIKey == "" {
|
if result.Data.APIKey == "" {
|
||||||
return "", fmt.Errorf("iflow api key: missing api key in response")
|
return nil, fmt.Errorf("iflow api key: missing api key in response")
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.Data.APIKey, nil
|
return &result.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateTokenStorage converts token data into persistence storage.
|
// CreateTokenStorage converts token data into persistence storage.
|
||||||
@@ -209,6 +217,7 @@ func (ia *IFlowAuth) CreateTokenStorage(data *IFlowTokenData) *IFlowTokenStorage
|
|||||||
LastRefresh: time.Now().Format(time.RFC3339),
|
LastRefresh: time.Now().Format(time.RFC3339),
|
||||||
Expire: data.Expire,
|
Expire: data.Expire,
|
||||||
APIKey: data.APIKey,
|
APIKey: data.APIKey,
|
||||||
|
Email: data.Email,
|
||||||
TokenType: data.TokenType,
|
TokenType: data.TokenType,
|
||||||
Scope: data.Scope,
|
Scope: data.Scope,
|
||||||
}
|
}
|
||||||
@@ -226,6 +235,9 @@ func (ia *IFlowAuth) UpdateTokenStorage(storage *IFlowTokenStorage, data *IFlowT
|
|||||||
if data.APIKey != "" {
|
if data.APIKey != "" {
|
||||||
storage.APIKey = data.APIKey
|
storage.APIKey = data.APIKey
|
||||||
}
|
}
|
||||||
|
if data.Email != "" {
|
||||||
|
storage.Email = data.Email
|
||||||
|
}
|
||||||
storage.TokenType = data.TokenType
|
storage.TokenType = data.TokenType
|
||||||
storage.Scope = data.Scope
|
storage.Scope = data.Scope
|
||||||
}
|
}
|
||||||
@@ -247,13 +259,17 @@ type IFlowTokenData struct {
|
|||||||
Scope string
|
Scope string
|
||||||
Expire string
|
Expire string
|
||||||
APIKey string
|
APIKey string
|
||||||
|
Email string
|
||||||
}
|
}
|
||||||
|
|
||||||
// userInfoResponse represents the structure returned by the user info endpoint.
|
// userInfoResponse represents the structure returned by the user info endpoint.
|
||||||
type userInfoResponse struct {
|
type userInfoResponse struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
Data struct {
|
Data userInfoData `json:"data"`
|
||||||
APIKey string `json:"apiKey"`
|
}
|
||||||
Email string `json:"email"`
|
|
||||||
} `json:"data"`
|
type userInfoData struct {
|
||||||
|
APIKey string `json:"apiKey"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,31 +102,14 @@ func (a *IFlowAuthenticator) Login(ctx context.Context, cfg *config.Config, opts
|
|||||||
|
|
||||||
tokenStorage := authSvc.CreateTokenStorage(tokenData)
|
tokenStorage := authSvc.CreateTokenStorage(tokenData)
|
||||||
|
|
||||||
email := ""
|
email := strings.TrimSpace(tokenStorage.Email)
|
||||||
if opts.Metadata != nil {
|
|
||||||
email = opts.Metadata["email"]
|
|
||||||
if email == "" {
|
|
||||||
email = opts.Metadata["alias"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if email == "" && opts.Prompt != nil {
|
|
||||||
email, err = opts.Prompt("Please input your email address or alias for iFlow:")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
email = strings.TrimSpace(email)
|
|
||||||
if email == "" {
|
if email == "" {
|
||||||
return nil, &EmailRequiredError{Prompt: "Please provide an email address or alias for iFlow."}
|
return nil, fmt.Errorf("iflow authentication failed: missing account identifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenStorage.Email = email
|
fileName := fmt.Sprintf("iflow-%s.json", email)
|
||||||
|
|
||||||
fileName := fmt.Sprintf("iflow-%s.json", tokenStorage.Email)
|
|
||||||
metadata := map[string]any{
|
metadata := map[string]any{
|
||||||
"email": tokenStorage.Email,
|
"email": email,
|
||||||
"api_key": tokenStorage.APIKey,
|
"api_key": tokenStorage.APIKey,
|
||||||
"access_token": tokenStorage.AccessToken,
|
"access_token": tokenStorage.AccessToken,
|
||||||
"refresh_token": tokenStorage.RefreshToken,
|
"refresh_token": tokenStorage.RefreshToken,
|
||||||
|
|||||||
Reference in New Issue
Block a user