1package remote
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "io"
9 "net/http"
10)
11
12// PasswordRequest represents the request body for POST /auth/password.
13type PasswordRequest struct {
14 Username string `json:"username"`
15 Password string `json:"password"`
16}
17
18// PasswordResponse represents the response from POST /auth/password.
19type PasswordResponse struct {
20 Token string `json:"token"`
21 ExpiresIn int `json:"expires_in"`
22 IssuedAt string `json:"issued_at"`
23}
24
25// Authenticate calls POST /auth/password and returns a token.
26// This method does NOT require an existing token since it's used to obtain one.
27func (c *Client) Authenticate(ctx context.Context, username, password string) (*PasswordResponse, error) {
28 url := c.baseURL + "/auth/password"
29
30 reqBody := PasswordRequest{
31 Username: username,
32 Password: password,
33 }
34
35 jsonBody, err := json.Marshal(reqBody)
36 if err != nil {
37 return nil, fmt.Errorf("failed to marshal request body: %w", err)
38 }
39
40 req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(jsonBody))
41 if err != nil {
42 return nil, fmt.Errorf("failed to create request: %w", err)
43 }
44
45 req.Header.Set("Content-Type", "application/json")
46 req.Header.Set("Accept", "application/json")
47
48 resp, err := c.httpClient.Do(req)
49 if err != nil {
50 return nil, fmt.Errorf("failed to connect: %w", err)
51 }
52 defer resp.Body.Close()
53
54 if resp.StatusCode >= 400 {
55 body, _ := io.ReadAll(resp.Body)
56 var errResp struct {
57 Error string `json:"error"`
58 }
59 if err := json.Unmarshal(body, &errResp); err == nil && errResp.Error != "" {
60 return nil, fmt.Errorf("%s: %s", resp.Status, errResp.Error)
61 }
62 return nil, fmt.Errorf("%s: %s", resp.Status, string(body))
63 }
64
65 var result PasswordResponse
66 if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
67 return nil, fmt.Errorf("failed to decode response: %w", err)
68 }
69
70 return &result, nil
71}
72
73// UpdateRemoteToken updates the token for a named remote.
74func UpdateRemoteToken(name, token string) error {
75 config, err := LoadRemotes("")
76 if err != nil {
77 return err
78 }
79
80 remote, ok := config.Remotes[name]
81 if !ok {
82 return fmt.Errorf("remote '%s' not found", name)
83 }
84
85 remote.Token = token
86 remote.TokenEnv = "" // Clear token_env when setting token directly
87 config.Remotes[name] = remote
88
89 return SaveRemotes("", config)
90}