API / internal /domain /services /auth_service.go
sshinmen's picture
Update HF Spaces deployment with latest changes
2e6b65c
Raw
History Blame Contribute Delete
7.83 kB
package services
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/router-for-me/CLIProxyAPI/v6/internal/domain/errors"
"github.com/router-for-me/CLIProxyAPI/v6/internal/domain/ports"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
)
// AuthService implements the ports.AuthFileService interface
type AuthService struct {
repo ports.AuthRepository
logger Logger
}
// NewAuthService creates a new AuthService
func NewAuthService(repo ports.AuthRepository, logger Logger) *AuthService {
return &AuthService{
repo: repo,
logger: logger,
}
}
// ListAuthFiles retrieves all authentication files
func (s *AuthService) ListAuthFiles(ctx context.Context) ([]*ports.AuthFile, error) {
if s.logger != nil {
s.logger.Debug(ctx, "listing auth files")
}
files, err := s.repo.List(ctx)
if err != nil {
if s.logger != nil {
s.logger.Error(ctx, "failed to list auth files", err)
}
return nil, err
}
return files, nil
}
// GetAuthFile retrieves a single authentication file by ID
func (s *AuthService) GetAuthFile(ctx context.Context, id string) (*ports.AuthFile, error) {
if id == "" {
return nil, errors.New(errors.InvalidInput, "auth file ID is empty")
}
if s.logger != nil {
s.logger.Debugf(ctx, "getting auth file: %s", id)
}
file, err := s.repo.GetByID(ctx, id)
if err != nil {
if s.logger != nil {
s.logger.Error(ctx, fmt.Sprintf("failed to get auth file: %s", id), err)
}
return nil, err
}
return file, nil
}
// GetAuthFileModels retrieves models supported by an auth file
func (s *AuthService) GetAuthFileModels(ctx context.Context, id string) ([]*ports.AuthFileModel, error) {
if id == "" {
return nil, errors.New(errors.InvalidInput, "auth file ID is empty")
}
if s.logger != nil {
s.logger.Debugf(ctx, "getting models for auth file: %s", id)
}
// Get auth file to verify it exists
file, err := s.GetAuthFile(ctx, id)
if err != nil {
return nil, err
}
// Get models from registry
reg := registry.GetGlobalRegistry()
models := reg.GetModelsForClient(file.ID)
result := make([]*ports.AuthFileModel, 0, len(models))
for _, m := range models {
model := &ports.AuthFileModel{
ID: m.ID,
}
if m.DisplayName != "" {
model.DisplayName = m.DisplayName
}
if m.Type != "" {
model.Type = m.Type
}
if m.OwnedBy != "" {
model.OwnedBy = m.OwnedBy
}
result = append(result, model)
}
return result, nil
}
// UploadAuthFile uploads a new authentication file
func (s *AuthService) UploadAuthFile(ctx context.Context, filename string, data []byte) (*ports.AuthFile, error) {
if filename == "" {
return nil, errors.New(errors.InvalidInput, "filename is empty")
}
if len(data) == 0 {
return nil, errors.New(errors.InvalidInput, "file data is empty")
}
if !strings.HasSuffix(strings.ToLower(filename), ".json") {
return nil, errors.NewValidationError("file must be .json", "filename", "expected .json extension")
}
// Validate JSON
var metadata map[string]interface{}
if err := json.Unmarshal(data, &metadata); err != nil {
return nil, errors.Wrap(errors.ValidationFailed, "invalid JSON", err)
}
if s.logger != nil {
s.logger.Debugf(ctx, "uploading auth file: %s", filename)
}
// Extract provider and email from metadata
provider, _ := metadata["type"].(string)
if provider == "" {
provider = "unknown"
}
email, _ := metadata["email"].(string)
label := provider
if email != "" {
label = email
}
file := &ports.AuthFile{
ID: filename,
FileName: filename,
Provider: provider,
Label: label,
Email: email,
Metadata: metadata,
Status: "active",
}
if err := s.repo.Save(ctx, file); err != nil {
if s.logger != nil {
s.logger.Error(ctx, fmt.Sprintf("failed to save auth file: %s", filename), err)
}
return nil, err
}
if s.logger != nil {
s.logger.Infof(ctx, "auth file uploaded: %s", filename)
}
return file, nil
}
// DownloadAuthFile retrieves the raw content of an auth file
func (s *AuthService) DownloadAuthFile(ctx context.Context, id string) ([]byte, error) {
if id == "" {
return nil, errors.New(errors.InvalidInput, "auth file ID is empty")
}
// Validate ID to prevent path traversal
if strings.Contains(id, string(os.PathSeparator)) || strings.Contains(id, "/") {
return nil, errors.New(errors.InvalidInput, "invalid auth file ID")
}
if !strings.HasSuffix(strings.ToLower(id), ".json") {
return nil, errors.NewValidationError("filename must end with .json", "id", "expected .json extension")
}
if s.logger != nil {
s.logger.Debugf(ctx, "downloading auth file: %s", id)
}
authDir := s.repo.GetAuthDir()
fullPath := filepath.Join(authDir, id)
data, err := os.ReadFile(fullPath)
if err != nil {
if os.IsNotExist(err) {
return nil, errors.NewNotFoundError("auth file", id)
}
return nil, errors.Wrap(errors.InternalError, "failed to read auth file", err)
}
return data, nil
}
// DeleteAuthFile deletes an authentication file
func (s *AuthService) DeleteAuthFile(ctx context.Context, id string) error {
if id == "" {
return errors.New(errors.InvalidInput, "auth file ID is empty")
}
if s.logger != nil {
s.logger.Debugf(ctx, "deleting auth file: %s", id)
}
if err := s.repo.Delete(ctx, id); err != nil {
if s.logger != nil {
s.logger.Error(ctx, fmt.Sprintf("failed to delete auth file: %s", id), err)
}
return err
}
if s.logger != nil {
s.logger.Infof(ctx, "auth file deleted: %s", id)
}
return nil
}
// DeleteAllAuthFiles deletes all authentication files
func (s *AuthService) DeleteAllAuthFiles(ctx context.Context) (int, error) {
if s.logger != nil {
s.logger.Debug(ctx, "deleting all auth files")
}
deleted, err := s.repo.DeleteAll(ctx)
if err != nil {
if s.logger != nil {
s.logger.Error(ctx, "failed to delete all auth files", err)
}
return 0, err
}
if s.logger != nil {
s.logger.Infof(ctx, "deleted %d auth files", deleted)
}
return deleted, nil
}
// DisableAuthFile disables an authentication file
func (s *AuthService) DisableAuthFile(ctx context.Context, id string) error {
if id == "" {
return errors.New(errors.InvalidInput, "auth file ID is empty")
}
if s.logger != nil {
s.logger.Debugf(ctx, "disabling auth file: %s", id)
}
if err := s.repo.Disable(ctx, id, "disabled via management API"); err != nil {
if s.logger != nil {
s.logger.Error(ctx, fmt.Sprintf("failed to disable auth file: %s", id), err)
}
return err
}
if s.logger != nil {
s.logger.Infof(ctx, "auth file disabled: %s", id)
}
return nil
}
// EnableAuthFile enables an authentication file
func (s *AuthService) EnableAuthFile(ctx context.Context, id string) error {
if id == "" {
return errors.New(errors.InvalidInput, "auth file ID is empty")
}
if s.logger != nil {
s.logger.Debugf(ctx, "enabling auth file: %s", id)
}
if err := s.repo.Enable(ctx, id); err != nil {
if s.logger != nil {
s.logger.Error(ctx, fmt.Sprintf("failed to enable auth file: %s", id), err)
}
return err
}
if s.logger != nil {
s.logger.Infof(ctx, "auth file enabled: %s", id)
}
return nil
}
// RefreshAuthToken refreshes the token for an auth file
func (s *AuthService) RefreshAuthToken(ctx context.Context, id string) error {
if id == "" {
return errors.New(errors.InvalidInput, "auth file ID is empty")
}
if s.logger != nil {
s.logger.Debugf(ctx, "refreshing auth token: %s", id)
}
// Get the auth file
file, err := s.GetAuthFile(ctx, id)
if err != nil {
return err
}
// Token refresh logic would be implemented here
// This is a placeholder for the actual implementation
_ = file
if s.logger != nil {
s.logger.Infof(ctx, "auth token refreshed: %s", id)
}
return nil
}
// Ensure AuthService implements the interface
var _ ports.AuthFileService = (*AuthService)(nil)