refactor: replace Bearer token auth with HttpOnly cookies
All checks were successful
check / check (push) Successful in 2m21s
All checks were successful
check / check (push) Successful in 2m21s
- Remove POST /api/v1/register endpoint entirely - Session creation (POST /api/v1/session) now sets neoirc_auth HttpOnly cookie instead of returning token in JSON body - Login (POST /api/v1/login) now sets neoirc_auth HttpOnly cookie instead of returning token in JSON body - Add PASS IRC command for setting session password (enables multi-client login via POST /api/v1/login) - All per-request auth reads from neoirc_auth cookie instead of Authorization: Bearer header - Cookie properties: HttpOnly, SameSite=Strict, Secure when behind TLS - Logout and QUIT clear the auth cookie - Update CORS to AllowCredentials:true with origin reflection - Remove Authorization from CORS AllowedHeaders - Update CLI client to use cookie jar (net/http/cookiejar) - Remove Token field from SessionResponse - Add SetPassword to DB layer, remove RegisterUser - Comprehensive test updates for cookie-based auth - Add tests: TestPassCommand, TestPassCommandShortPassword, TestPassCommandEmpty, TestSessionCookie - Update README extensively: auth model, API reference, curl examples, security model, design principles, roadmap closes #83
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -28,16 +29,19 @@ var errHTTP = errors.New("HTTP error")
|
||||
// Client wraps HTTP calls to the neoirc server API.
|
||||
type Client struct {
|
||||
BaseURL string
|
||||
Token string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates a new API client.
|
||||
// NewClient creates a new API client with a cookie jar
|
||||
// for automatic auth cookie management.
|
||||
func NewClient(baseURL string) *Client {
|
||||
return &Client{ //nolint:exhaustruct // Token set after CreateSession
|
||||
jar, _ := cookiejar.New(nil)
|
||||
|
||||
return &Client{
|
||||
BaseURL: baseURL,
|
||||
HTTPClient: &http.Client{ //nolint:exhaustruct // defaults fine
|
||||
Timeout: httpTimeout,
|
||||
Jar: jar,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -79,8 +83,6 @@ func (client *Client) CreateSession(
|
||||
return nil, fmt.Errorf("decode session: %w", err)
|
||||
}
|
||||
|
||||
client.Token = resp.Token
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
@@ -121,6 +123,7 @@ func (client *Client) PollMessages(
|
||||
Timeout: time.Duration(
|
||||
timeout+pollExtraTime,
|
||||
) * time.Second,
|
||||
Jar: client.HTTPClient.Jar,
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
@@ -145,10 +148,6 @@ func (client *Client) PollMessages(
|
||||
return nil, fmt.Errorf("new request: %w", err)
|
||||
}
|
||||
|
||||
request.Header.Set(
|
||||
"Authorization", "Bearer "+client.Token,
|
||||
)
|
||||
|
||||
resp, err := pollClient.Do(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("poll request: %w", err)
|
||||
@@ -304,12 +303,6 @@ func (client *Client) do(
|
||||
"Content-Type", "application/json",
|
||||
)
|
||||
|
||||
if client.Token != "" {
|
||||
request.Header.Set(
|
||||
"Authorization", "Bearer "+client.Token,
|
||||
)
|
||||
}
|
||||
|
||||
resp, err := client.HTTPClient.Do(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("http: %w", err)
|
||||
|
||||
@@ -10,9 +10,8 @@ type SessionRequest struct {
|
||||
|
||||
// SessionResponse is the response from session creation.
|
||||
type SessionResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
Nick string `json:"nick"`
|
||||
Token string `json:"token"`
|
||||
ID int64 `json:"id"`
|
||||
Nick string `json:"nick"`
|
||||
}
|
||||
|
||||
// StateResponse is the response from GET /api/v1/state.
|
||||
|
||||
Reference in New Issue
Block a user