fix: correct GitLink auth to use autologin_trustie cookie

GitLink authenticates via Set-Cookie autologin_trustie, not Bearer
token or query param. Login response body contains user info directly
(no status wrapper on success). Fixed login to extract cookie from
response headers and transport to inject it on subsequent requests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Your GitHub Username 2026-03-30 21:56:00 +08:00
parent eaa348b0e2
commit 52809e110f
2 changed files with 42 additions and 19 deletions

View File

@ -6,20 +6,22 @@ import (
"fmt"
"io"
"net/http"
"strings"
"github.com/gitlink-org/gitlink-cli/internal/config"
)
type LoginResponse struct {
UserID int `json:"user_id"`
type LoginResult struct {
Username string `json:"username"`
Login string `json:"login"`
Token string `json:"token"`
Status int `json:"status"`
Message string `json:"message"`
UserID int `json:"user_id"`
// Error fields (only present on failure)
Status int `json:"status"`
Message string `json:"message"`
}
// Login authenticates with username/password and stores the token.
func Login(username, password string) (*LoginResponse, error) {
// Login authenticates with username/password and stores the autologin cookie.
func Login(username, password string) (*LoginResult, error) {
cfg, err := config.Load()
if err != nil {
return nil, fmt.Errorf("failed to load config: %w", err)
@ -31,8 +33,15 @@ func Login(username, password string) (*LoginResponse, error) {
}
bodyJSON, _ := json.Marshal(body)
url := cfg.BaseURL + "/accounts/login"
resp, err := http.Post(url, "application/json", bytes.NewReader(bodyJSON))
loginURL := cfg.BaseURL + "/accounts/login"
req, err := http.NewRequest("POST", loginURL, bytes.NewReader(bodyJSON))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "*/*")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
@ -43,20 +52,30 @@ func Login(username, password string) (*LoginResponse, error) {
return nil, fmt.Errorf("failed to read response: %w", err)
}
var result LoginResponse
var result LoginResult
if err := json.Unmarshal(data, &result); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
if result.Token == "" {
msg := result.Message
if msg == "" {
msg = "login failed, no token returned"
}
return nil, fmt.Errorf("%s", msg)
// Check for error response (has negative status)
if result.Status < 0 {
return nil, fmt.Errorf("%s", result.Message)
}
if err := StoreToken(result.Token); err != nil {
// Extract autologin_trustie cookie
autologinToken := ""
for _, cookie := range resp.Cookies() {
if strings.Contains(strings.ToLower(cookie.Name), "autologin") {
autologinToken = cookie.Value
break
}
}
if autologinToken == "" {
return nil, fmt.Errorf("login succeeded but no autologin cookie received")
}
if err := StoreToken(autologinToken); err != nil {
return nil, fmt.Errorf("failed to store token: %w", err)
}

View File

@ -4,7 +4,7 @@ import (
"net/http"
)
// Transport wraps an http.RoundTripper and injects the Bearer token header.
// Transport wraps an http.RoundTripper and injects the autologin token.
type Transport struct {
Base http.RoundTripper
}
@ -12,7 +12,11 @@ type Transport struct {
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
token, err := LoadToken()
if err == nil && token != "" {
req.Header.Set("Authorization", "Bearer "+token)
// GitLink authenticates via cookie or query param
req.AddCookie(&http.Cookie{Name: "autologin_trustie", Value: token})
q := req.URL.Query()
q.Set("token", token)
req.URL.RawQuery = q.Encode()
}
if req.Body != nil && req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "application/json")