package auth import ( "encoding/json" "fmt" "io" "net/http" "time" "github.com/claudia/docs-cli/internal/api" "github.com/claudia/docs-cli/internal/config" "github.com/claudia/docs-cli/pkg/types" ) // AuthClient handles authentication type AuthClient struct { client *api.Client } // NewAuthClient creates a new auth client func NewAuthClient(c *api.Client) *AuthClient { return &AuthClient{client: c} } // Login authenticates a user and returns the token func (a *AuthClient) Login(username, password string) (*types.AuthResponse, error) { req := types.LoginRequest{ Username: username, Password: password, } resp, err := a.client.DoRequest(http.MethodPost, "/auth/login", req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { return nil, handleAuthError(resp, body) } var authResp types.Response if err := json.Unmarshal(body, &authResp); err != nil { return nil, err } return mapToAuthResponse(authResp.Data), nil } // Register registers a new user func (a *AuthClient) Register(username, password string) (*types.AuthResponse, error) { req := types.RegisterRequest{ Username: username, Password: password, } resp, err := a.client.DoRequest(http.MethodPost, "/auth/register", req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { return nil, handleAuthError(resp, body) } var authResp types.Response if err := json.Unmarshal(body, &authResp); err != nil { return nil, err } return mapToAuthResponse(authResp.Data), nil } // Me returns the current user info func (a *AuthClient) Me() (map[string]interface{}, error) { resp, err := a.client.DoRequest(http.MethodGet, "/auth/me", nil) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { return nil, handleAuthError(resp, body) } var authResp types.Response if err := json.Unmarshal(body, &authResp); err != nil { return nil, err } if m, ok := authResp.Data.(map[string]interface{}); ok { return m, nil } return nil, fmt.Errorf("unexpected response format") } // Logout clears the saved token func Logout() error { return config.ClearToken() } // Status returns the current auth status func Status() (map[string]interface{}, error) { cfg := config.GetConfig() if cfg == nil { return map[string]interface{}{ "authenticated": false, "server": "http://localhost:8000", }, nil } token := cfg.Token if token == "" { return map[string]interface{}{ "authenticated": false, "server": cfg.Server, }, nil } return map[string]interface{}{ "authenticated": true, "server": cfg.Server, }, nil } func handleAuthError(resp *http.Response, body []byte) error { var authResp types.Response if err := json.Unmarshal(body, &authResp); err != nil { return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)) } if authResp.Error != nil { return fmt.Errorf("%s: %s", authResp.Error.Code, authResp.Error.Message) } return fmt.Errorf("HTTP %d", resp.StatusCode) } func mapToAuthResponse(data interface{}) *types.AuthResponse { resp := &types.AuthResponse{} if m, ok := data.(map[string]interface{}); ok { if v, ok := m["access_token"].(string); ok { resp.Token = v } if v, ok := m["token_type"].(string); ok { resp.TokenType = v } if v, ok := m["expires_at"].(string); ok { resp.ExpiresAt, _ = time.Parse(time.RFC3339, v) } else if v, ok := m["expires_at"].(float64); ok { resp.ExpiresAt = time.Unix(int64(v), 0) } } return resp }