diff --git a/apis/record_helpers.go b/apis/record_helpers.go index 96e7de31..d8e9ad75 100644 --- a/apis/record_helpers.go +++ b/apis/record_helpers.go @@ -30,9 +30,18 @@ func RequestData(c echo.Context) *models.RequestData { } result := &models.RequestData{ - Method: c.Request().Method, - Query: map[string]any{}, - Data: map[string]any{}, + Method: c.Request().Method, + Query: map[string]any{}, + Data: map[string]any{}, + Headers: map[string]string{}, + } + + // extract the first value of all headers and normalizes the keys + // ("X-Token" is converted to "x_token") + for k, v := range c.Request().Header { + if len(v) > 0 { + result.Headers[strings.ToLower(strings.ReplaceAll(k, "-", "_"))] = v[0] + } } result.AuthRecord, _ = c.Get(ContextAuthRecordKey).(*models.Record) diff --git a/apis/record_helpers_test.go b/apis/record_helpers_test.go index 7f96c2ff..765182ce 100644 --- a/apis/record_helpers_test.go +++ b/apis/record_helpers_test.go @@ -17,6 +17,7 @@ func TestRequestData(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodPost, "/?test=123", strings.NewReader(`{"test":456}`)) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + req.Header.Set("X-Token-Test", "123") rec := httptest.NewRecorder() c := e.NewContext(req, rec) @@ -38,6 +39,12 @@ func TestRequestData(t *testing.T) { t.Fatalf("Expected Method %v, got %v", http.MethodPost, result.Method) } + rawHeaders, _ := json.Marshal(result.Headers) + expectedHeaders := `{"content_type":"application/json","x_token_test":"123"}` + if v := string(rawHeaders); v != expectedHeaders { + t.Fatalf("Expected Query %v, got %v", expectedHeaders, v) + } + rawQuery, _ := json.Marshal(result.Query) expectedQuery := `{"test":"123"}` if v := string(rawQuery); v != expectedQuery { diff --git a/models/request_data.go b/models/request_data.go index 693795c3..e574c0c7 100644 --- a/models/request_data.go +++ b/models/request_data.go @@ -9,11 +9,12 @@ import ( // RequestData defines a HTTP request data struct, usually used // as part of the `@request.*` filter resolver. type RequestData struct { - Method string `json:"method"` - Query map[string]any `json:"query"` - Data map[string]any `json:"data"` - AuthRecord *Record `json:"authRecord"` - Admin *Admin `json:"admin"` + Method string `json:"method"` + Query map[string]any `json:"query"` + Data map[string]any `json:"data"` + Headers map[string]string `json:"headers"` + AuthRecord *Record `json:"authRecord"` + Admin *Admin `json:"admin"` } // HasModifierDataKeys loosely checks if the current struct has any modifier Data keys. diff --git a/resolvers/record_field_resolver.go b/resolvers/record_field_resolver.go index 220abbfe..9bd18af2 100644 --- a/resolvers/record_field_resolver.go +++ b/resolvers/record_field_resolver.go @@ -84,6 +84,7 @@ func NewRecordFieldResolver( `^\@request\.auth\.[\w\.\:]*\w+$`, `^\@request\.data\.[\w\.\:]*\w+$`, `^\@request\.query\.[\w\.\:]*\w+$`, + `^\@request\.headers\.\w+$`, `^\@collection\.\w+\.[\w\.\:]*\w+$`, }, } @@ -92,6 +93,7 @@ func NewRecordFieldResolver( if r.requestData != nil { r.staticRequestData["method"] = r.requestData.Method r.staticRequestData["query"] = r.requestData.Query + r.staticRequestData["headers"] = r.requestData.Headers r.staticRequestData["data"] = r.requestData.Data r.staticRequestData["auth"] = nil if r.requestData.AuthRecord != nil { @@ -132,6 +134,7 @@ func (r *RecordFieldResolver) UpdateQuery(query *dbx.SelectQuery) error { // project.screen.status // @request.status // @request.query.filter +// @request.headers.x_token // @request.auth.someRelation.name // @request.data.someRelation.name // @request.data.someField diff --git a/resolvers/record_field_resolver_test.go b/resolvers/record_field_resolver_test.go index 206e32c4..28d6d34c 100644 --- a/resolvers/record_field_resolver_test.go +++ b/resolvers/record_field_resolver_test.go @@ -23,6 +23,10 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) { } requestData := &models.RequestData{ + Headers: map[string]string{ + "a": "123", + "b": "456", + }, Query: map[string]any{ "a": nil, "b": 123, @@ -438,6 +442,9 @@ func TestRecordFieldResolverResolveStaticRequestDataFields(t *testing.T) { "b": 456, "c": map[string]int{"sub": 1}, }, + Headers: map[string]string{ + "d": "789", + }, AuthRecord: authRecord, } @@ -456,6 +463,10 @@ func TestRecordFieldResolverResolveStaticRequestDataFields(t *testing.T) { {"@request.query", true, ``}, {"@request.query.a", false, `123`}, {"@request.query.a.missing", false, ``}, + {"@request.headers", true, ``}, + {"@request.headers.missing", false, ``}, + {"@request.headers.d", false, `456`}, + {"@request.headers.d.sub", true, ``}, {"@request.data", true, ``}, {"@request.data.b", false, `456`}, {"@request.data.number", false, `10`}, // number field normalization diff --git a/ui/src/components/base/FilterAutocompleteInput.svelte b/ui/src/components/base/FilterAutocompleteInput.svelte index 4e8e6c0d..152979ee 100644 --- a/ui/src/components/base/FilterAutocompleteInput.svelte +++ b/ui/src/components/base/FilterAutocompleteInput.svelte @@ -256,6 +256,7 @@ result.push("@request.method"); result.push("@request.query."); result.push("@request.data."); + result.push("@request.headers."); result.push("@request.auth.id"); result.push("@request.auth.collectionId"); result.push("@request.auth.collectionName"); diff --git a/ui/src/components/collections/CollectionRulesTab.svelte b/ui/src/components/collections/CollectionRulesTab.svelte index 6fcfed3c..74ece011 100644 --- a/ui/src/components/collections/CollectionRulesTab.svelte +++ b/ui/src/components/collections/CollectionRulesTab.svelte @@ -45,7 +45,7 @@ The request fields could be accessed with the special @request filter:
@request.method
+ @request.headers.*
@request.query.*
@request.data.*
@request.auth.*