package pluginhost import ( "context" "encoding/json" "fmt" "os" "path/filepath" "runtime" "sort" "strconv" "strings" "time" coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" "github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginapi") type rpcHostAuthGetRequest struct { AuthIndex string `json:"auth_index"`} type rpcHostAuthListResponse struct { Files []pluginapi.HostAuthFileEntry `json:"files"`} type rpcHostAuthGetResponse struct { AuthIndex string `json:"auth_index"` Name string `json:"name,omitempty"` Path string `json:"path,omitempty"` JSON json.RawMessage `json:"json"`} func (h *Host) SetAuthManager(manager *coreauth.Manager) { if h == nil { return } h.mu.Lock() h.authManager = manager h.mu.Unlock()} func (h *Host) currentAuthManager() *coreauth.Manager { if h == nil { return nil } h.mu.Lock() manager := h.authManager h.mu.Unlock() return manager} func (h *Host) callHostAuthList(ctx context.Context, request []byte) ([]byte, error) { _ = ctx if len(bytesTrimSpace(request)) > 0 { var req map[string]any if errUnmarshal := json.Unmarshal(request, &req); errUnmarshal != nil { return nil, fmt.Errorf("decode host auth list request: %w", errUnmarshal) } } entries, errList := h.listAuthFiles() if errList != nil { return nil, errList } return marshalRPCResult(rpcHostAuthListResponse{Files: entries})} func (h *Host) callHostAuthGet(ctx context.Context, request []byte) ([]byte, error) { _ = ctx var req rpcHostAuthGetRequest if errUnmarshal := json.Unmarshal(request, &req); errUnmarshal != nil { return nil, fmt.Errorf("decode host auth get request: %w", errUnmarshal) } authIndex := strings.TrimSpace(req.AuthIndex) if authIndex == "" { return nil, fmt.Errorf("auth_index is required") } auth, rawJSON, errGet := h.authPhysicalJSONByIndex(authIndex) if errGet != nil { return nil, errGet } name := strings.TrimSpace(auth.FileName) if name == "" { name = strings.TrimSpace(auth.ID) } path := strings.TrimSpace(authAttribute(auth, "path")) return marshalRPCResult(rpcHostAuthGetResponse{ AuthIndex: authIndex, Name: name, Path: path, JSON: json.RawMessage(rawJSON), })} func (h *Host) callHostAuthGetRuntime(ctx context.Context, request []byte) ([]byte, error) { _ = ctx var req rpcHostAuthGetRequest if errUnmarshal := json.Unmarshal(request, &req); errUnmarshal != nil { return nil, fmt.Errorf("decode host auth get runtime request: %w", errUnmarshal) } authIndex := strings.TrimSpace(req.AuthIndex) if authIndex == "" { return nil, fmt.Errorf("auth_index is required") } auth, errGet := h.authByIndex(authIndex) if errGet != nil { return nil, errGet } entry := h.buildHostAuthFileEntry(auth) if entry == nil { return nil, fmt.Errorf("auth runtime info not found for auth_index %s", authIndex) } return marshalRPCResult(pluginapi.HostAuthGetRuntimeResponse{Auth: *entry})} func (h *Host) callHostAuthSave(ctx context.Context, request []byte) ([]byte, error) { var req pluginapi.HostAuthSaveRequest if errUnmarshal := json.Unmarshal(request, &req); errUnmarshal != nil { return nil, fmt.Errorf("decode host auth save request: %w", errUnmarshal) } name, rawJSON, errValidate := validateHostAuthSaveRequest(req) if errValidate != nil { return nil, errValidate } path, errSave := h.saveAuthFile(ctx, name, rawJSON) if errSave != nil { return nil, errSave } return marshalRPCResult(pluginapi.HostAuthSaveResponse{ Name: name, Path: path, })} func (h *Host) listAuthFiles() ([]pluginapi.HostAuthFileEntry, error) { manager := h.currentAuthManager() if manager != nil { auths := manager.List() entries := make([]pluginapi.HostAuthFileEntry, 0, len(auths)) for _, auth := range auths { if entry := h.buildHostAuthFileEntry(auth); entry != nil { entries = append(entries, *entry) } } sort.Slice(entries, func(i, j int) bool { return strings.ToLower(entries[i].Name) < strings.ToLower(entries[j].Name) }) return entries, nil } return h.listAuthFilesFromDisk()} func (h *Host) listAuthFilesFromDisk() ([]pluginapi.HostAuthFileEntry, error) { authDir := h.resolvedAuthDir() if authDir == "" { return nil, fmt.Errorf("auth directory is unavailable") } entries, errReadDir := os.ReadDir(authDir) if errReadDir != nil { return nil, fmt.Errorf("failed to read auth dir: %w", errReadDir) } files := make([]pluginapi.HostAuthFileEntry, 0) for _, entry := range entries { if entry.IsDir() { continue } name := entry.Name() if !strings.HasSuffix(strings.ToLower(name), ".json") { continue } full := filepath.Join(authDir, name) fileEntry := pluginapi.HostAuthFileEntry{ Name: name, Source: "file", Path: full, } if info, errInfo := entry.Info(); errInfo == nil { fileEntry.Size = info.Size() fileEntry.ModTime = info.ModTime() } if data, errRead := os.ReadFile(full); errRead == nil { var metadata map[string]any if errUnmarshal := json.Unmarshal(data, &metadata); errUnmarshal == nil { if provider, ok := metadata["type"].(string); ok { fileEntry.Type = strings.TrimSpace(provider) fileEntry.Provider = fileEntry.Type } if email, ok := metadata["email"].(string); ok { fileEntry.Email = strings.TrimSpace(email) } if projectID, ok := metadata["project_id"].(string); ok { fileEntry.ProjectID = strings.TrimSpace(projectID) } if rawPriority, ok := metadata["priority"]; ok { if priority, okPriority := parsePriorityValue(rawPriority); okPriority { fileEntry.Priority = priority } } if note, ok := metadata["note"].(string); ok { fileEntry.Note = strings.TrimSpace(note) } if baseURL, ok := metadata["base_url"].(string); ok { fileEntry.BaseURL = strings.TrimSpace(baseURL) } if websockets, okWebsockets := parseWebsocketsValue(metadata["websockets"]); okWebsockets { fileEntry.Websockets = websockets } } } files = append(files, fileEntry) } sort.Slice(files, func(i, j int) bool { return strings.ToLower(files[i].Name) < strings.ToLower(files[j].Name) }) return files, nil} func (h *Host) authByIndex(authIndex string) (*coreauth.Auth, error) { authIndex = strings.TrimSpace(authIndex) if authIndex == "" { return nil, fmt.Errorf("auth_index is required") } manager := h.currentAuthManager() if manager == nil { return nil, fmt.Errorf("core auth manager unavailable") } for _, auth := range manager.List() { if auth == nil { continue } auth.EnsureIndex() if auth.Index == authIndex { return auth, nil } } return nil, fmt.Errorf("auth not found for auth_index %s", authIndex)} func (h *Host) authPhysicalJSONByIndex(authIndex string) (*coreauth.Auth, []byte, error) { auth, errGet := h.authByIndex(authIndex) if errGet != nil { return nil, nil, errGet } path := strings.TrimSpace(authAttribute(auth, "path")) if path == "" { return nil, nil, fmt.Errorf("auth file path not found for auth_index %s", authIndex) } data, errRead := os.ReadFile(path) if errRead != nil { if os.IsNotExist(errRead) { return nil, nil, fmt.Errorf("auth file not found for auth_index %s", authIndex) } return nil, nil, fmt.Errorf("failed to read auth file: %w", errRead) } if len(bytesTrimSpace(data)) == 0 { return nil, nil, fmt.Errorf("auth file is empty for auth_index %s", authIndex) } var metadata map[string]any if errUnmarshal := json.Unmarshal(data, &metadata); errUnmarshal != nil { return nil, nil, fmt.Errorf("invalid auth file for auth_index %s: %w", authIndex, errUnmarshal) } return auth, data, nil} func validateHostAuthSaveRequest(req pluginapi.HostAuthSaveRequest) (string, []byte, error) { name := strings.TrimSpace(req.Name) if isUnsafeAuthFileName(name) { return "", nil, fmt.Errorf("invalid auth file name") } if !strings.HasSuffix(strings.ToLower(name), ".json") { return "", nil, fmt.Errorf("auth file name must end with .json") } rawJSON := bytesTrimSpace(req.JSON) if len(rawJSON) == 0 { return "", nil, fmt.Errorf("json is required") } var metadata map[string]any if errUnmarshal := json.Unmarshal(rawJSON, &metadata); errUnmarshal != nil { return "", nil, fmt.Errorf("invalid auth json: %w", errUnmarshal) } return filepath.Base(name), rawJSON, nil} func (h *Host) saveAuthFile(ctx context.Context, name string, data []byte) (string, error) { authDir := h.resolvedAuthDir() if authDir == "" { return "", fmt.Errorf("auth directory is unavailable") } dst := filepath.Join(authDir, filepath.Base(name)) if !filepath.IsAbs(dst) { if abs, errAbs := filepath.Abs(dst); errAbs == nil { dst = abs } } auth, errBuild := h.buildAuthFromFileData(dst, data) if errBuild != nil { return "", errBuild } if errWrite := os.WriteFile(dst, data, 0o600); errWrite != nil { return "", fmt.Errorf("failed to write auth file: %w", errWrite) } if errUpsert := h.upsertAuthRecord(ctx, auth); errUpsert != nil { return "", errUpsert } return dst, nil} func (h *Host) buildAuthFromFileData(path string, data []byte) (*coreauth.Auth, error) { if strings.TrimSpace(path) == "" { return nil, fmt.Errorf("auth path is empty") } if data == nil { var errRead error data, errRead = os.ReadFile(path) if errRead != nil { return nil, fmt.Errorf("failed to read auth file: %w", errRead) } } metadata := make(map[string]any) if errUnmarshal := json.Unmarshal(data, &metadata); errUnmarshal != nil { return nil, fmt.Errorf("invalid auth file: %w", errUnmarshal) } coreauth.NormalizeCredentialMetadata(metadata) provider, _ := metadata["type"].(string) if strings.TrimSpace(provider) == "" { provider = "unknown" } label := provider if email, ok := metadata["email"].(string); ok && strings.TrimSpace(email) != "" { label = strings.TrimSpace(email) } authID := h.authIDForPath(path) if authID == "" { authID = path } auth := &coreauth.Auth{ ID: authID, Provider: provider, FileName: filepath.Base(path), Label: label, Status: coreauth.StatusActive, Attributes: map[string]string{ "path": path, "source": path, }, Metadata: metadata, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), } if manager := h.currentAuthManager(); manager != nil { if existing, ok := manager.GetByID(authID); ok { auth.CreatedAt = existing.CreatedAt auth.LastRefreshedAt = existing.LastRefreshedAt auth.NextRetryAfter = existing.NextRetryAfter auth.Runtime = existing.Runtime } } if errWeight := coreauth.ValidateAuthWeight(auth); errWeight != nil { return nil, fmt.Errorf("invalid auth weight: %w", errWeight) } coreauth.ApplyCustomHeadersFromMetadata(auth) return auth, nil} func (h *Host) upsertAuthRecord(ctx context.Context, auth *coreauth.Auth) error { manager := h.currentAuthManager() if manager == nil || auth == nil { return nil } if existing, ok := manager.GetByID(auth.ID); ok { auth.CreatedAt = existing.CreatedAt _, errUpdate := manager.Update(ctx, auth) return errUpdate } _, errRegister := manager.Register(ctx, auth) return errRegister} func isUnsafeAuthFileName(name string) bool { if strings.TrimSpace(name) == "" { return true } if strings.ContainsAny(name, "/\\") { return true } if filepath.VolumeName(name) != "" { return true } return false} func (h *Host) buildHostAuthFileEntry(auth *coreauth.Auth) *pluginapi.HostAuthFileEntry { if auth == nil { return nil } auth.EnsureIndex() runtimeOnly := isRuntimeOnlyAuth(auth) if runtimeOnly && (auth.Disabled || auth.Status == coreauth.StatusDisabled) { return nil } path := strings.TrimSpace(authAttribute(auth, "path")) if path == "" && !runtimeOnly { return nil } name := strings.TrimSpace(auth.FileName) if name == "" { name = auth.ID } entry := &pluginapi.HostAuthFileEntry{ ID: auth.ID, AuthIndex: auth.Index, Name: name, Type: strings.TrimSpace(auth.Provider), Provider: strings.TrimSpace(auth.Provider), Label: auth.Label, Status: string(auth.Status), StatusMessage: auth.StatusMessage, Disabled: auth.Disabled, Unavailable: auth.Unavailable, RuntimeOnly: runtimeOnly, Source: "memory", Success: auth.Success, Failed: auth.Failed, RecentRequests: hostRecentRequests(auth), } if email := authEmail(auth); email != "" { entry.Email = email } if projectID := authProjectID(auth); projectID != "" { entry.ProjectID = projectID } if accountType, account := auth.AccountInfo(); accountType != "" || account != "" { entry.AccountType = accountType entry.Account = account } if !auth.CreatedAt.IsZero() { entry.CreatedAt = auth.CreatedAt } if !auth.UpdatedAt.IsZero() { entry.ModTime = auth.UpdatedAt entry.UpdatedAt = auth.UpdatedAt } if !auth.LastRefreshedAt.IsZero() { entry.LastRefresh = auth.LastRefreshedAt } if !auth.NextRetryAfter.IsZero() { entry.NextRetryAfter = auth.NextRetryAfter } if path != "" { entry.Path = path entry.Source = "file" if info, err := os.Stat(path); err == nil { entry.Size = info.Size() entry.ModTime = info.ModTime() } else if os.IsNotExist(err) { if !runtimeOnly && (auth.Disabled || auth.Status == coreauth.StatusDisabled || strings.EqualFold(strings.TrimSpace(auth.StatusMessage), "removed via management api")) { return nil } entry.Source = "memory" } } if p := strings.TrimSpace(authAttribute(auth, "priority")); p != "" { if parsed, err := strconv.Atoi(p); err == nil { entry.Priority = parsed } } else if auth.Metadata != nil { if rawPriority, ok := auth.Metadata["priority"]; ok { if priority, okPriority := parsePriorityValue(rawPriority); okPriority { entry.Priority = priority } } } if note := strings.TrimSpace(authAttribute(auth, "note")); note != "" { entry.Note = note } else if auth.Metadata != nil { if rawNote, ok := auth.Metadata["note"].(string); ok { entry.Note = strings.TrimSpace(rawNote) } } if baseURL := strings.TrimSpace(authAttribute(auth, "base_url")); baseURL != "" { entry.BaseURL = baseURL } else if auth.Metadata != nil { if rawBaseURL, ok := auth.Metadata["base_url"].(string); ok { entry.BaseURL = strings.TrimSpace(rawBaseURL) } } if websockets, ok := authWebsocketsValue(auth); ok { entry.Websockets = websockets } return entry} func (h *Host) resolvedAuthDir() string { if h == nil { return "" } h.mu.Lock() authDir := "" if h.runtimeConfig != nil { authDir = strings.TrimSpace(h.runtimeConfig.AuthDir)Lines 1–500 of 662. Load more
package pluginhost import ( "context" "encoding/json" "fmt" "os" "path/filepath" "runtime" "sort" "strconv" "strings" "time" coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" "github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginapi" ) type rpcHostAuthGetRequest struct { AuthIndex string `json:"auth_index"` } type rpcHostAuthListResponse struct { Files []pluginapi.HostAuthFileEntry `json:"files"` } type rpcHostAuthGetResponse struct { AuthIndex string `json:"auth_index"` Name string `json:"name,omitempty"` Path string `json:"path,omitempty"` JSON json.RawMessage `json:"json"` } func (h *Host) SetAuthManager(manager *coreauth.Manager) { if h == nil { return } h.mu.Lock() h.authManager = manager h.mu.Unlock() } func (h *Host) currentAuthManager() *coreauth.Manager { if h == nil { return nil } h.mu.Lock() manager := h.authManager h.mu.Unlock() return manager } func (h *Host) callHostAuthList(ctx context.Context, request []byte) ([]byte, error) { _ = ctx if len(bytesTrimSpace(request)) > 0 { var req map[string]any if errUnmarshal := json.Unmarshal(request, &req); errUnmarshal != nil { return nil, fmt.Errorf("decode host auth list request: %w", errUnmarshal) } } entries, errList := h.listAuthFiles() if errList != nil { return nil, errList } return marshalRPCResult(rpcHostAuthListResponse{Files: entries}) } func (h *Host) callHostAuthGet(ctx context.Context, request []byte) ([]byte, error) { _ = ctx var req rpcHostAuthGetRequest if errUnmarshal := json.Unmarshal(request, &req); errUnmarshal != nil { return nil, fmt.Errorf("decode host auth get request: %w", errUnmarshal) } authIndex := strings.TrimSpace(req.AuthIndex) if authIndex == "" { return nil, fmt.Errorf("auth_index is required") } auth, rawJSON, errGet := h.authPhysicalJSONByIndex(authIndex) if errGet != nil { return nil, errGet } name := strings.TrimSpace(auth.FileName) if name == "" { name = strings.TrimSpace(auth.ID) } path := strings.TrimSpace(authAttribute(auth, "path")) return marshalRPCResult(rpcHostAuthGetResponse{ AuthIndex: authIndex, Name: name, Path: path, JSON: json.RawMessage(rawJSON), }) } func (h *Host) callHostAuthGetRuntime(ctx context.Context, request []byte) ([]byte, error) { _ = ctx var req rpcHostAuthGetRequest if errUnmarshal := json.Unmarshal(request, &req); errUnmarshal != nil { return nil, fmt.Errorf("decode host auth get runtime request: %w", errUnmarshal) } authIndex := strings.TrimSpace(req.AuthIndex) if authIndex == "" { return nil, fmt.Errorf("auth_index is required") } auth, errGet := h.authByIndex(authIndex) if errGet != nil { return nil, errGet } entry := h.buildHostAuthFileEntry(auth) if entry == nil { return nil, fmt.Errorf("auth runtime info not found for auth_index %s", authIndex) } return marshalRPCResult(pluginapi.HostAuthGetRuntimeResponse{Auth: *entry}) } func (h *Host) callHostAuthSave(ctx context.Context, request []byte) ([]byte, error) { var req pluginapi.HostAuthSaveRequest if errUnmarshal := json.Unmarshal(request, &req); errUnmarshal != nil { return nil, fmt.Errorf("decode host auth save request: %w", errUnmarshal) } name, rawJSON, errValidate := validateHostAuthSaveRequest(req) if errValidate != nil { return nil, errValidate } path, errSave := h.saveAuthFile(ctx, name, rawJSON) if errSave != nil { return nil, errSave } return marshalRPCResult(pluginapi.HostAuthSaveResponse{ Name: name, Path: path, }) } func (h *Host) listAuthFiles() ([]pluginapi.HostAuthFileEntry, error) { manager := h.currentAuthManager() if manager != nil { auths := manager.List() entries := make([]pluginapi.HostAuthFileEntry, 0, len(auths)) for _, auth := range auths { if entry := h.buildHostAuthFileEntry(auth); entry != nil { entries = append(entries, *entry) } } sort.Slice(entries, func(i, j int) bool { return strings.ToLower(entries[i].Name) < strings.ToLower(entries[j].Name) }) return entries, nil } return h.listAuthFilesFromDisk() } func (h *Host) listAuthFilesFromDisk() ([]pluginapi.HostAuthFileEntry, error) { authDir := h.resolvedAuthDir() if authDir == "" { return nil, fmt.Errorf("auth directory is unavailable") } entries, errReadDir := os.ReadDir(authDir) if errReadDir != nil { return nil, fmt.Errorf("failed to read auth dir: %w", errReadDir) } files := make([]pluginapi.HostAuthFileEntry, 0) for _, entry := range entries { if entry.IsDir() { continue } name := entry.Name() if !strings.HasSuffix(strings.ToLower(name), ".json") { continue } full := filepath.Join(authDir, name) fileEntry := pluginapi.HostAuthFileEntry{ Name: name, Source: "file", Path: full, } if info, errInfo := entry.Info(); errInfo == nil { fileEntry.Size = info.Size() fileEntry.ModTime = info.ModTime() } if data, errRead := os.ReadFile(full); errRead == nil { var metadata map[string]any if errUnmarshal := json.Unmarshal(data, &metadata); errUnmarshal == nil { if provider, ok := metadata["type"].(string); ok { fileEntry.Type = strings.TrimSpace(provider) fileEntry.Provider = fileEntry.Type } if email, ok := metadata["email"].(string); ok { fileEntry.Email = strings.TrimSpace(email) } if projectID, ok := metadata["project_id"].(string); ok { fileEntry.ProjectID = strings.TrimSpace(projectID) } if rawPriority, ok := metadata["priority"]; ok { if priority, okPriority := parsePriorityValue(rawPriority); okPriority { fileEntry.Priority = priority } } if note, ok := metadata["note"].(string); ok { fileEntry.Note = strings.TrimSpace(note) } if baseURL, ok := metadata["base_url"].(string); ok { fileEntry.BaseURL = strings.TrimSpace(baseURL) } if websockets, okWebsockets := parseWebsocketsValue(metadata["websockets"]); okWebsockets { fileEntry.Websockets = websockets } } } files = append(files, fileEntry) } sort.Slice(files, func(i, j int) bool { return strings.ToLower(files[i].Name) < strings.ToLower(files[j].Name) }) return files, nil } func (h *Host) authByIndex(authIndex string) (*coreauth.Auth, error) { authIndex = strings.TrimSpace(authIndex) if authIndex == "" { return nil, fmt.Errorf("auth_index is required") } manager := h.currentAuthManager() if manager == nil { return nil, fmt.Errorf("core auth manager unavailable") } for _, auth := range manager.List() { if auth == nil { continue } auth.EnsureIndex() if auth.Index == authIndex { return auth, nil } } return nil, fmt.Errorf("auth not found for auth_index %s", authIndex) } func (h *Host) authPhysicalJSONByIndex(authIndex string) (*coreauth.Auth, []byte, error) { auth, errGet := h.authByIndex(authIndex) if errGet != nil { return nil, nil, errGet } path := strings.TrimSpace(authAttribute(auth, "path")) if path == "" { return nil, nil, fmt.Errorf("auth file path not found for auth_index %s", authIndex) } data, errRead := os.ReadFile(path) if errRead != nil { if os.IsNotExist(errRead) { return nil, nil, fmt.Errorf("auth file not found for auth_index %s", authIndex) } return nil, nil, fmt.Errorf("failed to read auth file: %w", errRead) } if len(bytesTrimSpace(data)) == 0 { return nil, nil, fmt.Errorf("auth file is empty for auth_index %s", authIndex) } var metadata map[string]any if errUnmarshal := json.Unmarshal(data, &metadata); errUnmarshal != nil { return nil, nil, fmt.Errorf("invalid auth file for auth_index %s: %w", authIndex, errUnmarshal) } return auth, data, nil } func validateHostAuthSaveRequest(req pluginapi.HostAuthSaveRequest) (string, []byte, error) { name := strings.TrimSpace(req.Name) if isUnsafeAuthFileName(name) { return "", nil, fmt.Errorf("invalid auth file name") } if !strings.HasSuffix(strings.ToLower(name), ".json") { return "", nil, fmt.Errorf("auth file name must end with .json") } rawJSON := bytesTrimSpace(req.JSON) if len(rawJSON) == 0 { return "", nil, fmt.Errorf("json is required") } var metadata map[string]any if errUnmarshal := json.Unmarshal(rawJSON, &metadata); errUnmarshal != nil { return "", nil, fmt.Errorf("invalid auth json: %w", errUnmarshal) } return filepath.Base(name), rawJSON, nil } func (h *Host) saveAuthFile(ctx context.Context, name string, data []byte) (string, error) { authDir := h.resolvedAuthDir() if authDir == "" { return "", fmt.Errorf("auth directory is unavailable") } dst := filepath.Join(authDir, filepath.Base(name)) if !filepath.IsAbs(dst) { if abs, errAbs := filepath.Abs(dst); errAbs == nil { dst = abs } } auth, errBuild := h.buildAuthFromFileData(dst, data) if errBuild != nil { return "", errBuild } if errWrite := os.WriteFile(dst, data, 0o600); errWrite != nil { return "", fmt.Errorf("failed to write auth file: %w", errWrite) } if errUpsert := h.upsertAuthRecord(ctx, auth); errUpsert != nil { return "", errUpsert } return dst, nil } func (h *Host) buildAuthFromFileData(path string, data []byte) (*coreauth.Auth, error) { if strings.TrimSpace(path) == "" { return nil, fmt.Errorf("auth path is empty") } if data == nil { var errRead error data, errRead = os.ReadFile(path) if errRead != nil { return nil, fmt.Errorf("failed to read auth file: %w", errRead) } } metadata := make(map[string]any) if errUnmarshal := json.Unmarshal(data, &metadata); errUnmarshal != nil { return nil, fmt.Errorf("invalid auth file: %w", errUnmarshal) } coreauth.NormalizeCredentialMetadata(metadata) provider, _ := metadata["type"].(string) if strings.TrimSpace(provider) == "" { provider = "unknown" } label := provider if email, ok := metadata["email"].(string); ok && strings.TrimSpace(email) != "" { label = strings.TrimSpace(email) } authID := h.authIDForPath(path) if authID == "" { authID = path } auth := &coreauth.Auth{ ID: authID, Provider: provider, FileName: filepath.Base(path), Label: label, Status: coreauth.StatusActive, Attributes: map[string]string{ "path": path, "source": path, }, Metadata: metadata, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), } if manager := h.currentAuthManager(); manager != nil { if existing, ok := manager.GetByID(authID); ok { auth.CreatedAt = existing.CreatedAt auth.LastRefreshedAt = existing.LastRefreshedAt auth.NextRetryAfter = existing.NextRetryAfter auth.Runtime = existing.Runtime } } if errWeight := coreauth.ValidateAuthWeight(auth); errWeight != nil { return nil, fmt.Errorf("invalid auth weight: %w", errWeight) } coreauth.ApplyCustomHeadersFromMetadata(auth) return auth, nil } func (h *Host) upsertAuthRecord(ctx context.Context, auth *coreauth.Auth) error { manager := h.currentAuthManager() if manager == nil || auth == nil { return nil } if existing, ok := manager.GetByID(auth.ID); ok { auth.CreatedAt = existing.CreatedAt _, errUpdate := manager.Update(ctx, auth) return errUpdate } _, errRegister := manager.Register(ctx, auth) return errRegister } func isUnsafeAuthFileName(name string) bool { if strings.TrimSpace(name) == "" { return true } if strings.ContainsAny(name, "/\\") { return true } if filepath.VolumeName(name) != "" { return true } return false } func (h *Host) buildHostAuthFileEntry(auth *coreauth.Auth) *pluginapi.HostAuthFileEntry { if auth == nil { return nil } auth.EnsureIndex() runtimeOnly := isRuntimeOnlyAuth(auth) if runtimeOnly && (auth.Disabled || auth.Status == coreauth.StatusDisabled) { return nil } path := strings.TrimSpace(authAttribute(auth, "path")) if path == "" && !runtimeOnly { return nil } name := strings.TrimSpace(auth.FileName) if name == "" { name = auth.ID } entry := &pluginapi.HostAuthFileEntry{ ID: auth.ID, AuthIndex: auth.Index, Name: name, Type: strings.TrimSpace(auth.Provider), Provider: strings.TrimSpace(auth.Provider), Label: auth.Label, Status: string(auth.Status), StatusMessage: auth.StatusMessage, Disabled: auth.Disabled, Unavailable: auth.Unavailable, RuntimeOnly: runtimeOnly, Source: "memory", Success: auth.Success, Failed: auth.Failed, RecentRequests: hostRecentRequests(auth), } if email := authEmail(auth); email != "" { entry.Email = email } if projectID := authProjectID(auth); projectID != "" { entry.ProjectID = projectID } if accountType, account := auth.AccountInfo(); accountType != "" || account != "" { entry.AccountType = accountType entry.Account = account } if !auth.CreatedAt.IsZero() { entry.CreatedAt = auth.CreatedAt } if !auth.UpdatedAt.IsZero() { entry.ModTime = auth.UpdatedAt entry.UpdatedAt = auth.UpdatedAt } if !auth.LastRefreshedAt.IsZero() { entry.LastRefresh = auth.LastRefreshedAt } if !auth.NextRetryAfter.IsZero() { entry.NextRetryAfter = auth.NextRetryAfter } if path != "" { entry.Path = path entry.Source = "file" if info, err := os.Stat(path); err == nil { entry.Size = info.Size() entry.ModTime = info.ModTime() } else if os.IsNotExist(err) { if !runtimeOnly && (auth.Disabled || auth.Status == coreauth.StatusDisabled || strings.EqualFold(strings.TrimSpace(auth.StatusMessage), "removed via management api")) { return nil } entry.Source = "memory" } } if p := strings.TrimSpace(authAttribute(auth, "priority")); p != "" { if parsed, err := strconv.Atoi(p); err == nil { entry.Priority = parsed } } else if auth.Metadata != nil { if rawPriority, ok := auth.Metadata["priority"]; ok { if priority, okPriority := parsePriorityValue(rawPriority); okPriority { entry.Priority = priority } } } if note := strings.TrimSpace(authAttribute(auth, "note")); note != "" { entry.Note = note } else if auth.Metadata != nil { if rawNote, ok := auth.Metadata["note"].(string); ok { entry.Note = strings.TrimSpace(rawNote) } } if baseURL := strings.TrimSpace(authAttribute(auth, "base_url")); baseURL != "" { entry.BaseURL = baseURL } else if auth.Metadata != nil { if rawBaseURL, ok := auth.Metadata["base_url"].(string); ok { entry.BaseURL = strings.TrimSpace(rawBaseURL) } } if websockets, ok := authWebsocketsValue(auth); ok { entry.Websockets = websockets } return entry } func (h *Host) resolvedAuthDir() string { if h == nil { return "" } h.mu.Lock() authDir := "" if h.runtimeConfig != nil { authDir = strings.TrimSpace(h.runtimeConfig.AuthDir)