- Auth commands: login, logout, status - Document commands: create, list, get, update, delete - Project commands: list, get - Folder commands: list, create - Tag commands: list, create, add - Search command - Reasoning save command - JSON output format with --output text option - Viper-based configuration management - Cobra CLI framework
170 lines
3.9 KiB
Go
170 lines
3.9 KiB
Go
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
|
|
}
|