1package middleware
2
3import (
4 "bytes"
5 "encoding/json"
6 "net/http"
7 "net/http/httptest"
8 "testing"
9
10 "github.com/bnema/zerowrap"
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13)
14
15func TestRequestLogger_RedactsSensitiveReferers(t *testing.T) {
16 tests := []struct {
17 name string
18 referer string
19 wantReferer string
20 }{
21 {
22 name: "query params",
23 referer: "https://idp.example.com/callback?code=secret-code&state=ok&token=secret-token",
24 wantReferer: "https://idp.example.com/callback?code=[REDACTED]&state=ok&token=[REDACTED]",
25 },
26 {
27 name: "fragment removed",
28 referer: "https://idp.example.com/callback?state=ok#access_token=secret-token",
29 wantReferer: "https://idp.example.com/callback?state=ok",
30 },
31 {
32 name: "userinfo removed",
33 referer: "https://user:secret-password@idp.example.com/callback?code=secret-code",
34 wantReferer: "https://idp.example.com/callback?code=[REDACTED]",
35 },
36 {
37 name: "malformed query key redacted conservatively",
38 referer: "https://idp.example.com/callback?token%ZZ=secret-token&state=ok",
39 wantReferer: "https://idp.example.com/callback?token%ZZ=[REDACTED]&state=ok",
40 },
41 }
42
43 for _, tt := range tests {
44 t.Run(tt.name, func(t *testing.T) {
45 var logs bytes.Buffer
46 log := zerowrap.New(zerowrap.Config{Level: "info", Format: "json", Output: &logs})
47
48 handler := RequestLogger(log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
49 w.WriteHeader(http.StatusNoContent)
50 }))
51
52 req := httptest.NewRequest(http.MethodGet, "/target?token=direct-secret", nil)
53 req.Header.Set("Referer", tt.referer)
54 rw := httptest.NewRecorder()
55
56 handler.ServeHTTP(rw, req)
57
58 logOutput := logs.String()
59 require.NotEmpty(t, logOutput)
60 var entry map[string]any
61 require.NoError(t, json.Unmarshal(logs.Bytes(), &entry))
62 assert.Equal(t, "token=[REDACTED]", entry["query"])
63 assert.Equal(t, tt.wantReferer, entry["referer"])
64 assert.NotContains(t, logOutput, "direct-secret")
65 assert.NotContains(t, logOutput, "secret-code")
66 assert.NotContains(t, logOutput, "secret-token")
67 assert.NotContains(t, logOutput, "secret-password")
68 })
69 }
70}
71
72func TestPanicRecovery_RethrowsReverseProxyAbort(t *testing.T) {
73 log := testLogger()
74
75 handler := PanicRecovery(log)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
76 panic(http.ErrAbortHandler)
77 }))
78
79 req := httptest.NewRequest(http.MethodGet, "/stream", nil)
80 rw := httptest.NewRecorder()
81
82 assert.PanicsWithValue(t, http.ErrAbortHandler, func() {
83 handler.ServeHTTP(rw, req)
84 })
85 assert.NotEqual(t, http.StatusInternalServerError, rw.Code)
86}