feat: add env-based configuration and token-in-cookie auth
Implement 12-factor configuration via environment variables and token-in-cookie authentication for Gitea API access. - internal/config/config.go: reads GITEA_URL, GITEA_TOKEN, LISTEN_ADDR, SESSION_SECRET from environment with validation - internal/auth/cookie.go: HMAC-signed HTTP-only cookie for storing Gitea API tokens (Secure, SameSite=Strict) - internal/middleware/auth.go: extracts token from cookie, injects into request context, redirects unauthenticated users to /settings - internal/middleware/logging.go: structured JSON request logging - internal/handlers/settings.go: settings page for entering/removing Gitea API token with mobile-first dark UI - cmd/server/main.go: integrated config, auth middleware, and settings Includes unit tests for config loading, cookie signing/verification, and auth middleware bypass/redirect logic. Closes leeworks-agents/gitea-mobile#2 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const testSecret = "test-secret-that-is-at-least-32-chars-long"
|
||||
|
||||
func TestSignAndVerify(t *testing.T) {
|
||||
token := "abc123-gitea-token"
|
||||
signed := sign(token, testSecret)
|
||||
|
||||
got, err := verify(signed, testSecret)
|
||||
if err != nil {
|
||||
t.Fatalf("verify failed: %v", err)
|
||||
}
|
||||
if got != token {
|
||||
t.Errorf("got %q, want %q", got, token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerify_InvalidSignature(t *testing.T) {
|
||||
token := "abc123-gitea-token"
|
||||
signed := sign(token, testSecret)
|
||||
|
||||
_, err := verify(signed, "wrong-secret-that-is-at-least-32-chars")
|
||||
if err != ErrInvalidSignature {
|
||||
t.Errorf("expected ErrInvalidSignature, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerify_MalformedCookie(t *testing.T) {
|
||||
_, err := verify("no-dot-separator", testSecret)
|
||||
if err != ErrMalformedCookie {
|
||||
t.Errorf("expected ErrMalformedCookie, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetAndGetToken(t *testing.T) {
|
||||
token := "my-gitea-api-token"
|
||||
|
||||
// Create a response recorder to capture the Set-Cookie header.
|
||||
w := httptest.NewRecorder()
|
||||
SetTokenCookie(w, token, testSecret, false)
|
||||
|
||||
// Extract the cookie from the response.
|
||||
resp := w.Result()
|
||||
cookies := resp.Cookies()
|
||||
if len(cookies) == 0 {
|
||||
t.Fatal("expected a cookie to be set")
|
||||
}
|
||||
|
||||
// Create a new request with the cookie.
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.AddCookie(cookies[0])
|
||||
|
||||
got, err := GetToken(req, testSecret)
|
||||
if err != nil {
|
||||
t.Fatalf("GetToken failed: %v", err)
|
||||
}
|
||||
if got != token {
|
||||
t.Errorf("got %q, want %q", got, token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetToken_NoCookie(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
|
||||
_, err := GetToken(req, testSecret)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for missing cookie")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearTokenCookie(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
ClearTokenCookie(w, false)
|
||||
|
||||
resp := w.Result()
|
||||
cookies := resp.Cookies()
|
||||
if len(cookies) == 0 {
|
||||
t.Fatal("expected a cookie to be set")
|
||||
}
|
||||
if cookies[0].MaxAge != -1 {
|
||||
t.Errorf("MaxAge = %d, want -1", cookies[0].MaxAge)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user