refactor: replace Bearer token auth with HttpOnly cookies (#84)
Všechny kontroly byly úspěšné
check / check (push) Successful in 2m34s

## Summary

Major auth refactor replacing Bearer token authentication with HttpOnly cookie-based auth, removing the registration endpoint, and adding the PASS IRC command for password management.

## Changes

### Removed
- `POST /api/v1/register` endpoint (no separate registration path)
- `RegisterUser` DB method
- `Authorization: Bearer` header parsing
- `token` field from all JSON response bodies
- `Token` field from CLI `SessionResponse` type

### Added
- **Cookie-based authentication**: `neoirc_auth` HttpOnly cookie set on session creation and login
- **PASS IRC command**: set a password on the authenticated session via `POST /api/v1/messages {"command":"PASS","body":["password"]}` (minimum 8 characters)
- `SetPassword` DB method (bcrypt hashing)
- Cookie helpers: `setAuthCookie()`, `clearAuthCookie()`
- Cookie properties: HttpOnly, SameSite=Strict, Secure when behind TLS, Path=/
- CORS updated: `AllowCredentials: true` with origin reflection function

### Auth Flow
1. `POST /api/v1/session {"nick":"alice"}` → sets `neoirc_auth` cookie, returns `{"id":1,"nick":"alice"}`
2. (Optional) `POST /api/v1/messages {"command":"PASS","body":["s3cret"]}` → sets password for multi-client
3. Another client: `POST /api/v1/login {"nick":"alice","password":"s3cret"}` → sets `neoirc_auth` cookie
4. Logout and QUIT clear the cookie

### Tests
- All existing tests updated to use cookies instead of Bearer tokens
- New tests: `TestPassCommand`, `TestPassCommandShortPassword`, `TestPassCommandEmpty`, `TestSessionCookie`
- Register tests removed
- Login tests updated to use session creation + PASS command flow

### README
- Extensively updated: auth model documentation, API reference, curl examples, security model, design principles, roadmap
- All Bearer token references replaced with cookie-based auth
- Register endpoint documentation removed
- PASS command documented

### CI
- `docker build .` passes (format check, lint, all tests, build)

closes #83

Co-authored-by: clawbot <clawbot@eeqj.de>
Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de>
Reviewed-on: #84
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
This commit was merged in pull request #84.
Tento commit je obsažen v:
2026-03-20 23:54:23 +01:00
odevzdal Jeffrey Paul
rodič db3d23c224
revize 5f3c0633f6
11 změnil soubory, kde provedl 657 přidání a 917 odebrání

Zobrazit soubor

@@ -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)