- Add portal_auth.go: Portal user session auth with HMAC-signed cookies
- Add /api/portal/session/{login,logout,state} endpoints
- Update nginx config template: cookie-to-header trusted proxy pattern
- Update frontend: sync CRM session on login/logout
- Add TRUSTED_SUBJECT_DEPLOY_GUIDE.md with remote43 deployment steps
- Update EXECUTION_BOARD.md: mark trusted-subject blocking issue as resolved
This implements the secure chain:
Browser → Portal → nginx (cookie→header) → CRM (verify proxy secret)
Required remote43 actions:
1. Generate 64-char hex secret
2. Update .env.crm with TRUSTED_* config
3. Update nginx with cookie map and header injection
4. Restart services
Fixes EXECUTION_BOARD.md 2026-06-08 blocking issue
56 lines
1.8 KiB
Go
56 lines
1.8 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"sub2api-cn-relay-manager/internal/metrics"
|
|
)
|
|
|
|
func TestUserKeyCreateResolveHostErrorRecordsMetric(t *testing.T) {
|
|
t.Parallel()
|
|
store := openAppTestStore(t)
|
|
defer closeAppTestStore(t, store)
|
|
|
|
handler := NewAPIHandler("t", ActionSet{
|
|
UserKeyHandler: buildUserKeyHandler(appTestDSN(t, store), testUserKeyAuthConfig()),
|
|
})
|
|
|
|
req := makeCreateRequest(t, http.MethodPost, "/api/keys", makeCreateBody("missing-group", "portal key", []string{"gpt-5.4"}))
|
|
applyTrustedProxyAuthHeaders(req, "portal-user")
|
|
resp := httptestRecorder(handler, req)
|
|
if resp.code != http.StatusInternalServerError {
|
|
t.Fatalf("status code = %d, want 500 body=%s", resp.code, resp.Body().String())
|
|
}
|
|
|
|
metricsReq := httptest.NewRequest(http.MethodGet, "/metrics", nil)
|
|
metricsResp := httptest.NewRecorder()
|
|
metrics.Handler().ServeHTTP(metricsResp, metricsReq)
|
|
body := metricsResp.Body.String()
|
|
if !strings.Contains(body, `user_key_operations_total{operation="create",result="resolve_host_error"}`) {
|
|
t.Fatalf("metrics body missing create resolve_host_error metric: %s", body)
|
|
}
|
|
}
|
|
|
|
func TestUserKeyDeleteGetKeyErrorRecordsMetric(t *testing.T) {
|
|
t.Parallel()
|
|
store := openAppTestStore(t)
|
|
defer closeAppTestStore(t, store)
|
|
|
|
handler := buildUserKeyHandler(appTestDSN(t, store))
|
|
if err := handler.deleteFn(context.Background(), "key_missing", "portal-user"); err == nil {
|
|
t.Fatal("expected deleteFn to fail for missing key")
|
|
}
|
|
|
|
metricsReq := httptest.NewRequest(http.MethodGet, "/metrics", nil)
|
|
metricsResp := httptest.NewRecorder()
|
|
metrics.Handler().ServeHTTP(metricsResp, metricsReq)
|
|
body := metricsResp.Body.String()
|
|
if !strings.Contains(body, `user_key_operations_total{operation="delete",result="get_key_error"}`) {
|
|
t.Fatalf("metrics body missing delete get_key_error metric: %s", body)
|
|
}
|
|
}
|