TykJS.TykMiddleware

From the samples and the docs about JS middleware (Middleware Scripting Guide), I found out that returning

You must return both the request and session metadata
return apiKeyDomainCheck.ReturnData(request, session.meta_data);

in an JS middleware, this can be a problem, when it is about a request path that is ignored (extended_paths→ignored) in the API definition.
In such case, the session object is null, and we get an error in the middleware and 404 code on the response occasionally (under load).

From the GW log:

level=error msg=“Failed to run JS middleware” api_id=sample-service api_name=“Sample service API” error=“TypeError: Cannot access member ‘meta_data’ of null” mw=DynamicMiddleware org_id=ogr1origin=10.15.1.1 path=“/sample-service/v1/version”

Hi @bkomac,

Can you provide information that will help me reproduce this?
Gateway version, API definition, minimal JS code?

Can you also describe the load scenario?

I use Tyk OSS 4.1.0.
Sorry for long post. Could not formati it.
Middleware is:

var apiKeyDomainCheck = new TykJS.TykMiddleware.NewMiddleware({});

apiKeyDomainCheck.NewProcessRequest(function (request, session, spec) {

//log("** Session is: "+ JSON.stringify(session));
if (session != null && session != undefined && session.meta_data != undefined && session.meta_data.allowed_domain != undefined) {
    var allowed_domain = session.meta_data.allowed_domain

    if (allowed_domain.indexOf(request.Headers.Host) < 0) {
        log('Domain ' + request.Headers.Host + ' not allowed for key (' + session.key_id + '). Allowed domain is ' + allowed_domain + '.');
        request.ReturnOverrides.ResponseCode = 406;
        request.ReturnOverrides.ResponseError = "This API-key is not issued for this domain.";
    }
}

// You must return both the request and session metadata - session.meta_data
return apiKeyDomainCheck.ReturnData(request, session.meta_data);

});

// Log that middleware is initialised
log(“JavaScript middleware ‘apiKeyDomainCheck.js’ is initialised”);

Api definition:
{
“name”: “Sample service API”,
“expiration”: “”,
“slug”: “sample-service”,
“listen_port”: 0,
“protocol”: “”,
“enable_proxy_protocol”: false,
“api_id”: “sample-service”,
“org_id”: “petrol”,
“use_keyless”: false,
“use_oauth2”: false,
“use_openid”: false,
“openid_options”: {
“providers”: null,
“segregate_by_client”: false
},
“oauth_meta”: {
“allowed_access_types”: null,
“allowed_authorize_types”: null,
“auth_login_redirect”: “”
},
“auth”: {
“name”: “”,
“use_param”: false,
“param_name”: “”,
“use_cookie”: false,
“cookie_name”: “”,
“disable_header”: false,
“auth_header_name”: “”,
“use_certificate”: false,
“validate_signature”: false,
“signature”: {
“algorithm”: “”,
“header”: “”,
“use_param”: false,
“param_name”: “”,
“secret”: “”,
“allowed_clock_skew”: 0,
“error_code”: 0,
“error_message”: “”
}
},
“auth_configs”: {
“authToken”: {
“name”: “”,
“use_param”: false,
“param_name”: “api_key”,
“use_cookie”: false,
“cookie_name”: “”,
“disable_header”: false,
“auth_header_name”: “Authorization”,
“use_certificate”: false,
“validate_signature”: false,
“signature”: {
“algorithm”: “”,
“header”: “”,
“use_param”: false,
“param_name”: “”,
“secret”: “”,
“allowed_clock_skew”: 0,
“error_code”: 0,
“error_message”: “”
}
}
},
“use_basic_auth”: false,
“basic_auth”: {
“disable_caching”: false,
“cache_ttl”: 0,
“extract_from_body”: false,
“body_user_regexp”: “”,
“body_password_regexp”: “”
},
“use_mutual_tls_auth”: false,
“client_certificates”: null,
“upstream_certificates”: null,
“upstream_certificates_disabled”: false,
“pinned_public_keys”: null,
“certificate_pinning_disabled”: false,
“enable_jwt”: false,
“use_standard_auth”: false,
“use_go_plugin_auth”: false,
“enable_coprocess_auth”: false,
“jwt_signing_method”: “”,
“jwt_source”: “”,
“jwt_identity_base_field”: “”,
“jwt_client_base_field”: “”,
“jwt_policy_field_name”: “”,
“jwt_default_policies”: null,
“jwt_issued_at_validation_skew”: 0,
“jwt_expires_at_validation_skew”: 0,
“jwt_not_before_validation_skew”: 0,
“jwt_skip_kid”: false,
“scopes”: {
“jwt”: {
“scope_claim_name”: “”,
“scope_to_policy”: null
},
“oidc”: {
“scope_claim_name”: “”,
“scope_to_policy”: null
}
},
“jwt_scope_to_policy_mapping”: null,
“jwt_scope_claim_name”: “”,
“notifications”: {
“shared_secret”: “”,
“oauth_on_keychange_url”: “”
},
“enable_signature_checking”: false,
“hmac_allowed_clock_skew”: 0,
“hmac_allowed_algorithms”: null,
“request_signing”: {
“is_enabled”: false,
“secret”: “”,
“key_id”: “”,
“algorithm”: “”,
“header_list”: null,
“certificate_id”: “”,
“signature_header”: “”
},
“base_identity_provided_by”: “”,
“definition”: {
“enabled”: false,
“name”: “”,
“default”: “”,
“location”: “header”,
“key”: “x-api-version”,
“strip_path”: false,
“strip_versioning_data”: false,
“versions”: null
},
“version_data”: {
“not_versioned”: true,
“default_version”: “”,
“versions”: {
“Default”: {
“name”: “Default”,
“expires”: “”,
“paths”: {
“ignored”: null,
“white_list”: null,
“black_list”: null
},
“use_extended_paths”: true,
“extended_paths”: {
“ignored”: [
{
“disabled”: false,
“path”: “/v1/health”,
“method”: “”,
“ignore_case”: false
},
{
“disabled”: false,
“path”: “/v1/version”,
“method”: “”,
“ignore_case”: false
}
]
},
“global_headers”: null,
“global_headers_remove”: null,
“global_response_headers”: null,
“global_response_headers_remove”: null,
“ignore_endpoint_case”: false,
“global_size_limit”: 0,
“override_target”: “”
}
}
},
“uptime_tests”: {
“check_list”: null,
“config”: {
“expire_utime_after”: 0,
“service_discovery”: {
“use_discovery_service”: false,
“query_endpoint”: “”,
“use_nested_query”: false,
“parent_data_path”: “”,
“data_path”: “”,
“port_data_path”: “”,
“target_path”: “”,
“use_target_list”: false,
“cache_timeout”: 0,
“endpoint_returns_list”: false
},
“recheck_wait”: 0
}
},
“proxy”: {
“preserve_host_header”: false,
“listen_path”: “/sample-service/”,
“target_url”: “http://pws-test.petrol.si/sample-service/”,
“disable_strip_slash”: false,
“strip_listen_path”: true,
“enable_load_balancing”: false,
“target_list”: null,
“check_host_against_uptime_tests”: false,
“service_discovery”: {
“use_discovery_service”: false,
“query_endpoint”: “”,
“use_nested_query”: false,
“parent_data_path”: “”,
“data_path”: “”,
“port_data_path”: “”,
“target_path”: “”,
“use_target_list”: false,
“cache_timeout”: 0,
“endpoint_returns_list”: false
},
“transport”: {
“ssl_insecure_skip_verify”: false,
“ssl_ciphers”: null,
“ssl_min_version”: 0,
“ssl_max_version”: 0,
“ssl_force_common_name_check”: false,
“proxy_url”: “”
}
},
“disable_rate_limit”: false,
“disable_quota”: false,
“custom_middleware”: {
“pre”: null,
“post”: [],
“post_key_auth”: null,
“auth_check”: {
“name”: “”,
“path”: “”,
“require_session”: false,
“raw_body_only”: false
},
“response”: null,
“driver”: “”,
“id_extractor”: {
“extract_from”: “”,
“extract_with”: “”,
“extractor_config”: null
}
},
“custom_middleware_bundle”: “”,
“cache_options”: {
“cache_timeout”: 0,
“enable_cache”: false,
“cache_all_safe_requests”: false,
“cache_response_codes”: null,
“enable_upstream_cache_control”: false,
“cache_control_ttl_header”: “”,
“cache_by_headers”: null
},
“session_lifetime”: 0,
“active”: true,
“internal”: false,
“auth_provider”: {
“name”: “”,
“storage_engine”: “”,
“meta”: null
},
“session_provider”: {
“name”: “”,
“storage_engine”: “”,
“meta”: null
},
“event_handlers”: {
“events”: null
},
“enable_batch_request_support”: false,
“enable_ip_whitelisting”: false,
“allowed_ips”: null,
“enable_ip_blacklisting”: false,
“blacklisted_ips”: null,
“dont_set_quota_on_create”: false,
“expire_analytics_after”: 0,
“response_processors”: null,
“CORS”: {
“enable”: true,
“allowed_origins”: [
“https://.petrol.si",
"http://
.petrol.si”
],
“allowed_methods”: [
“GET”,
“POST”,
“PUT”
],
“allowed_headers”: [],
“exposed_headers”: [],
“allow_credentials”: true,
“max_age”: 24,
“options_passthrough”: false,
“debug”: false
},
“domain”: “apiti.petrol.si”,
“domain_disabled”: false,
“certificates”: null,
“do_not_track”: false,
“enable_context_vars”: false,
“config_data”: null,
“tag_headers”: [
“x-forwarded-host”,
“x-forwarded-for”,
“x-real-ip”
],
“global_rate_limit”: {
“rate”: 0,
“per”: 0
},
“strip_auth_data”: false,
“enable_detailed_recording”: false,
“graphql”: {
“enabled”: false,
“execution_mode”: “”,
“version”: “”,
“schema”: “”,
“type_field_configurations”: null,
“playground”: {
“enabled”: false,
“path”: “”
},
“engine”: {
“field_configs”: null,
“data_sources”: null
},
“proxy”: {
“auth_headers”: null
},
“subgraph”: {
“sdl”: “”
},
“supergraph”: {
“subgraphs”: null,
“merged_sdl”: “”,
“global_headers”: null,
“disable_query_batching”: false
}
},
“analytics_plugin”: {
“enable”: false,
“plugin_path”: “”,
“func_name”: “”
},
“tags_disabled”: false,
“tags”: [
“internal”,
“external”,
“version:2.48.0-SNAPSHOT-1204”
],
“is_oas”: false
}

It turned out that the 404 code was coming from the second Tyk GW (second docker container on the same host). The requests are load balanced between two GWs (exactly the same configuration), and the second GW was receiving a strange error:

time=“Sep 27 10:26:05” level=debug msg=“Running: apiKeyDomainCheck” api_id=sample-service api_name=“Sample service API” mw=DynamicMiddleware org_id=petrol origin=10.15.129.94 path=“/sample-service/v1/version”
2022/09/27 10:26:05 http: panic serving 10.15.145.204:49484: runtime error: invalid memory address or nil pointer dereference
goroutine 9398 [running]:
net/http.(*conn).serve.func1(0xc0039bc000)
/usr/local/go/src/net/http/server.go:1801 +0x147
panic(0x229d700, 0x34bdad0)
/usr/local/go/src/runtime/panic.go:975 +0x47a
github.com/TykTechnologies/tyk/gateway.(*DynamicMiddleware).ProcessRequest(0xc0021d7100, 0x282db40, 0xc002ef47e0, 0xc003b99900, 0x0, 0x0, 0x0, 0x0, 0x0)

Hey @bkomac,

Yeah, I was just about to mention I never got a 404, even though I could reproduce this in the logs.

level=error msg=“Failed to run JS middleware” api_id=sample-service api_name=“Sample service API” error=“TypeError: Cannot access member ‘meta_data’ of null” mw=DynamicMiddleware org_id=ogr1origin=10.15.1.1 path=“/sample-service/v1/version”

Meanwhile, I believe the message is expected since you’re combining it with (extended_paths→ignored) which means that the path will not be processed for authentication data (1).

Will have to see about this strange error…

Thank you. Maybe the 404 is not related to this error, because it is hard to align logs with analytics data.
The thing is that we have a lot of /health endpoints which we want to be ignored, so we are getting a lot of errors like the one above.
I tried to return meta_data as {}, but I don’t know what that can cause to the rest of the request lifecycle in Tyk processing.