docs: flesh out Phase 2 auth task specs
Detail Authenticator interface, APIKeyAuthenticator built-in, HubConfig integration (nil = backward compat), Client auth fields, OnAuthFailure callback, and full test matrix with integration tests. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
53d8a15544
commit
534bbe571f
1 changed files with 29 additions and 3 deletions
32
TODO.md
32
TODO.md
|
|
@ -20,9 +20,35 @@ Dispatched from core/go orchestration. Pick up tasks in order.
|
|||
|
||||
## Phase 2: Auth
|
||||
|
||||
- [ ] Add token-based authentication on WebSocket upgrade handshake
|
||||
- [ ] Validate JWT or API key before promoting HTTP connection to WebSocket
|
||||
- [ ] Reject unauthenticated connections with appropriate HTTP status
|
||||
Token-based authentication on WebSocket upgrade handshake. Pure Go, no JWT library dependency — consumers bring their own validation logic via an interface.
|
||||
|
||||
### 2.1 Authenticator Interface
|
||||
|
||||
- [ ] **Create `auth.go`** — Define the auth abstraction:
|
||||
- `type AuthResult struct { Valid bool; UserID string; Claims map[string]any; Error error }` — result of authentication
|
||||
- `type Authenticator interface { Authenticate(r *http.Request) AuthResult }` — validates the HTTP request during upgrade. Implementations can check headers (`Authorization: Bearer <token>`), query params (`?token=xxx`), or cookies.
|
||||
- `type AuthenticatorFunc func(r *http.Request) AuthResult` — adapter for using functions as Authenticators (implements the interface)
|
||||
- `type APIKeyAuthenticator struct { Keys map[string]string }` — built-in authenticator that validates `Authorization: Bearer <key>` against a static key→userID map. Provided as a convenience; consumers can use their own JWT-based authenticator.
|
||||
- `func NewAPIKeyAuth(keys map[string]string) *APIKeyAuthenticator` — constructor
|
||||
|
||||
### 2.2 Wire Into Hub
|
||||
|
||||
- [ ] **Add `Authenticator` to `HubConfig`** — Optional field. When nil, all connections are accepted (backward compatible). When set, `Handler()` calls `Authenticate(r)` before upgrading.
|
||||
- [ ] **Update `Handler()`** — If `h.config.Authenticator != nil`, call `Authenticate(r)`. If `!result.Valid`, respond with `http.StatusUnauthorized` (or `http.StatusForbidden` if `result.Error` indicates a different status) and return without upgrading. If valid, store `result.UserID` and `result.Claims` on the `Client` struct.
|
||||
- [ ] **Add auth fields to `Client`** — `UserID string` and `Claims map[string]any` fields. Set during authenticated upgrade. Empty for unauthenticated hubs (nil authenticator).
|
||||
- [ ] **Expose `OnAuthFailure` callback** — Optional `OnAuthFailure func(r *http.Request, result AuthResult)` on `HubConfig` for logging/metrics on rejected connections.
|
||||
|
||||
### 2.3 Tests
|
||||
|
||||
- [ ] **Unit tests** — (a) APIKeyAuthenticator valid key, (b) invalid key, (c) missing header, (d) malformed header ("Bearer" without token, wrong scheme), (e) AuthenticatorFunc adapter, (f) nil Authenticator (backward compat — all connections accepted)
|
||||
- [ ] **Integration tests** — Using httptest + gorilla/websocket Dial:
|
||||
- (a) Authenticated connect with valid API key → upgrade succeeds, client.UserID set
|
||||
- (b) Rejected connect with invalid key → HTTP 401, no WebSocket upgrade
|
||||
- (c) Rejected connect with no auth header → HTTP 401
|
||||
- (d) Nil authenticator → all connections accepted (existing behaviour preserved)
|
||||
- (e) OnAuthFailure callback fires on rejection
|
||||
- (f) Multiple clients with different API keys → each gets correct UserID
|
||||
- [ ] **Existing tests still pass** — No authenticator set = backward compatible
|
||||
|
||||
## Phase 3: Scaling
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue