github.com/Tyktechnologies/tyk@v2.9.5+incompatible/gateway/mw_js_plugin_test.go (about)

     1  package gateway
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"net/url"
    10  	"regexp"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/stretchr/testify/assert"
    16  
    17  	"github.com/sirupsen/logrus"
    18  	prefixed "github.com/x-cray/logrus-prefixed-formatter"
    19  
    20  	"github.com/TykTechnologies/tyk/apidef"
    21  	"github.com/TykTechnologies/tyk/config"
    22  	logger "github.com/TykTechnologies/tyk/log"
    23  	"github.com/TykTechnologies/tyk/test"
    24  )
    25  
    26  func TestJSVMLogs(t *testing.T) {
    27  	var buf bytes.Buffer
    28  	log := logrus.New()
    29  	log.Out = &buf
    30  	log.Formatter = new(prefixed.TextFormatter)
    31  
    32  	jsvm := JSVM{}
    33  	jsvm.Init(nil, logrus.NewEntry(log))
    34  
    35  	jsvm.RawLog = logrus.New()
    36  	jsvm.RawLog.Out = &buf
    37  	jsvm.RawLog.Formatter = new(logger.RawFormatter)
    38  
    39  	const in = `
    40  log("foo")
    41  log('{"x": "y"}')
    42  rawlog("foo")
    43  rawlog('{"x": "y"}')
    44  `
    45  
    46  	want := []string{
    47  		`time=TIME level=info msg=foo prefix=jsvm type=log-msg`,
    48  		`time=TIME level=info msg="{"x": "y"}" prefix=jsvm type=log-msg`,
    49  		`foo`,
    50  		`{"x": "y"}`,
    51  	}
    52  	if _, err := jsvm.VM.Run(in); err != nil {
    53  		t.Fatalf("failed to run js: %v", err)
    54  	}
    55  	got := strings.Split(strings.Trim(buf.String(), "\n"), "\n")
    56  	i := 0
    57  	timeRe := regexp.MustCompile(`time="[^"]*"`)
    58  	for _, line := range got {
    59  		if i >= len(want) {
    60  			t.Logf("too many lines")
    61  			t.Fail()
    62  			break
    63  		}
    64  		s := timeRe.ReplaceAllString(line, "time=TIME")
    65  		if s != line && !strings.Contains(s, "type=log-msg") {
    66  			continue // log line from elsewhere (async)
    67  		}
    68  		if s != want[i] {
    69  			t.Logf("%s != %s", s, want[i])
    70  			t.Fail()
    71  		}
    72  		i++
    73  	}
    74  }
    75  
    76  func TestJSVMBody(t *testing.T) {
    77  	dynMid := &DynamicMiddleware{
    78  		BaseMiddleware: BaseMiddleware{
    79  			Spec: &APISpec{APIDefinition: &apidef.APIDefinition{}},
    80  		},
    81  		MiddlewareClassName: "leakMid",
    82  		Pre:                 true,
    83  	}
    84  	body := "foô \uffff \u0000 \xff bàr"
    85  	req := httptest.NewRequest("GET", "/foo", strings.NewReader(body))
    86  	jsvm := JSVM{}
    87  	jsvm.Init(nil, logrus.NewEntry(log))
    88  
    89  	const js = `
    90  var leakMid = new TykJS.TykMiddleware.NewMiddleware({})
    91  
    92  leakMid.NewProcessRequest(function(request, session) {
    93  	request.Body += " appended"
    94  	return leakMid.ReturnData(request, session.meta_data)
    95  });`
    96  	if _, err := jsvm.VM.Run(js); err != nil {
    97  		t.Fatalf("failed to set up js plugin: %v", err)
    98  	}
    99  	dynMid.Spec.JSVM = jsvm
   100  	dynMid.ProcessRequest(nil, req, nil)
   101  
   102  	bs, err := ioutil.ReadAll(req.Body)
   103  	if err != nil {
   104  		t.Fatalf("failed to read final body: %v", err)
   105  	}
   106  	want := body + " appended"
   107  	if got := string(bs); want != got {
   108  		t.Fatalf("JS plugin broke non-UTF8 body %q into %q",
   109  			want, got)
   110  	}
   111  }
   112  
   113  func TestJSVMProcessTimeout(t *testing.T) {
   114  	dynMid := &DynamicMiddleware{
   115  		BaseMiddleware: BaseMiddleware{
   116  			Spec: &APISpec{APIDefinition: &apidef.APIDefinition{}},
   117  		},
   118  		MiddlewareClassName: "leakMid",
   119  		Pre:                 true,
   120  	}
   121  	req := httptest.NewRequest("GET", "/foo", strings.NewReader("body"))
   122  	jsvm := JSVM{}
   123  	jsvm.Init(nil, logrus.NewEntry(log))
   124  	jsvm.Timeout = time.Millisecond
   125  
   126  	// this js plugin just loops forever, keeping Otto at 100% CPU
   127  	// usage and running forever.
   128  	const js = `
   129  var leakMid = new TykJS.TykMiddleware.NewMiddleware({})
   130  
   131  leakMid.NewProcessRequest(function(request, session) {
   132  	while (true) {
   133  	}
   134  	return leakMid.ReturnData(request, session.meta_data)
   135  });`
   136  	if _, err := jsvm.VM.Run(js); err != nil {
   137  		t.Fatalf("failed to set up js plugin: %v", err)
   138  	}
   139  	dynMid.Spec.JSVM = jsvm
   140  
   141  	done := make(chan bool)
   142  	go func() {
   143  		dynMid.ProcessRequest(nil, req, nil)
   144  		done <- true
   145  	}()
   146  	select {
   147  	case <-done:
   148  	case <-time.After(time.Second):
   149  		t.Fatal("js vm wasn't killed after its timeout")
   150  	}
   151  }
   152  
   153  func TestJSVMConfigData(t *testing.T) {
   154  	spec := &APISpec{APIDefinition: &apidef.APIDefinition{}}
   155  	spec.ConfigData = map[string]interface{}{
   156  		"foo": "bar",
   157  	}
   158  	const js = `
   159  var testJSVMData = new TykJS.TykMiddleware.NewMiddleware({})
   160  
   161  testJSVMData.NewProcessRequest(function(request, session, spec) {
   162  	request.SetHeaders["data-foo"] = spec.config_data.foo
   163  	return testJSVMData.ReturnData(request, {})
   164  });`
   165  	dynMid := &DynamicMiddleware{
   166  		BaseMiddleware:      BaseMiddleware{Spec: spec, Proxy: nil},
   167  		MiddlewareClassName: "testJSVMData",
   168  		Pre:                 true,
   169  	}
   170  	jsvm := JSVM{}
   171  	jsvm.Init(nil, logrus.NewEntry(log))
   172  	if _, err := jsvm.VM.Run(js); err != nil {
   173  		t.Fatalf("failed to set up js plugin: %v", err)
   174  	}
   175  	dynMid.Spec.JSVM = jsvm
   176  
   177  	r := TestReq(t, "GET", "/v1/test-data", nil)
   178  	dynMid.ProcessRequest(nil, r, nil)
   179  	if want, got := "bar", r.Header.Get("data-foo"); want != got {
   180  		t.Fatalf("wanted header to be %q, got %q", want, got)
   181  	}
   182  }
   183  
   184  func TestJSVMUserCore(t *testing.T) {
   185  	spec := &APISpec{APIDefinition: &apidef.APIDefinition{}}
   186  	const js = `
   187  var testJSVMCore = new TykJS.TykMiddleware.NewMiddleware({})
   188  
   189  testJSVMCore.NewProcessRequest(function(request, session, config) {
   190  	request.SetHeaders["global"] = globalVar
   191  	return testJSVMCore.ReturnData(request, {})
   192  });`
   193  	dynMid := &DynamicMiddleware{
   194  		BaseMiddleware:      BaseMiddleware{Spec: spec, Proxy: nil},
   195  		MiddlewareClassName: "testJSVMCore",
   196  		Pre:                 true,
   197  	}
   198  	tfile, err := ioutil.TempFile("", "tykjs")
   199  	if err != nil {
   200  		t.Fatal(err)
   201  	}
   202  	if _, err := io.WriteString(tfile, `var globalVar = "globalValue"`); err != nil {
   203  		t.Fatal(err)
   204  	}
   205  	globalConf := config.Global()
   206  	old := globalConf.TykJSPath
   207  	globalConf.TykJSPath = tfile.Name()
   208  	config.SetGlobal(globalConf)
   209  	defer func() {
   210  		globalConf.TykJSPath = old
   211  		config.SetGlobal(globalConf)
   212  	}()
   213  	jsvm := JSVM{}
   214  	jsvm.Init(nil, logrus.NewEntry(log))
   215  	if _, err := jsvm.VM.Run(js); err != nil {
   216  		t.Fatalf("failed to set up js plugin: %v", err)
   217  	}
   218  	dynMid.Spec.JSVM = jsvm
   219  
   220  	r := TestReq(t, "GET", "/foo", nil)
   221  	dynMid.ProcessRequest(nil, r, nil)
   222  
   223  	if want, got := "globalValue", r.Header.Get("global"); want != got {
   224  		t.Fatalf("wanted header to be %q, got %q", want, got)
   225  	}
   226  }
   227  
   228  func TestJSVMRequestScheme(t *testing.T) {
   229  	dynMid := &DynamicMiddleware{
   230  		BaseMiddleware: BaseMiddleware{
   231  			Spec: &APISpec{APIDefinition: &apidef.APIDefinition{}},
   232  		},
   233  		MiddlewareClassName: "leakMid",
   234  		Pre:                 true,
   235  	}
   236  	req := httptest.NewRequest("GET", "/foo", nil)
   237  	jsvm := JSVM{}
   238  	jsvm.Init(nil, logrus.NewEntry(log))
   239  
   240  	const js = `
   241  var leakMid = new TykJS.TykMiddleware.NewMiddleware({})
   242  leakMid.NewProcessRequest(function(request, session) {
   243  	var test = request.Scheme += " appended"
   244  	var responseObject = {
   245          Body: test,
   246          Code: 200
   247      }
   248  	return leakMid.ReturnData(responseObject, session.meta_data)
   249  });`
   250  	if _, err := jsvm.VM.Run(js); err != nil {
   251  		t.Fatalf("failed to set up js plugin: %v", err)
   252  	}
   253  	dynMid.Spec.JSVM = jsvm
   254  	dynMid.ProcessRequest(nil, req, nil)
   255  
   256  	bs, err := ioutil.ReadAll(req.Body)
   257  	if err != nil {
   258  		t.Fatalf("failed to read final body: %v", err)
   259  	}
   260  	want := "http" + " appended"
   261  	if got := string(bs); want != got {
   262  		t.Fatalf("JS plugin broke non-UTF8 body %q into %q",
   263  			want, got)
   264  	}
   265  }
   266  
   267  func TestTykMakeHTTPRequest(t *testing.T) {
   268  	ts := StartTest()
   269  	defer ts.Close()
   270  
   271  	bundle := RegisterBundle("jsvm_make_http_request", map[string]string{
   272  		"manifest.json": `
   273  		{
   274  		    "file_list": [],
   275  		    "custom_middleware": {
   276  		        "driver": "otto",
   277  		        "pre": [{
   278  		            "name": "testTykMakeHTTPRequest",
   279  		            "path": "middleware.js"
   280  		        }]
   281  		    }
   282  		}
   283  	`,
   284  		"middleware.js": `
   285  	var testTykMakeHTTPRequest = new TykJS.TykMiddleware.NewMiddleware({})
   286  
   287  	testTykMakeHTTPRequest.NewProcessRequest(function(request, session, spec) {
   288  		var newRequest = {
   289  			"Method": "GET",
   290  			"Headers": {"Accept": "application/json"},
   291  			"Domain": spec.config_data.base_url,
   292  			"Resource": "/api/get?param1=dummy"
   293  		}
   294  
   295  		var resp = TykMakeHttpRequest(JSON.stringify(newRequest));
   296  		var usableResponse = JSON.parse(resp);
   297  
   298  		if(usableResponse.Code > 400) {
   299  			request.ReturnOverrides.ResponseCode = usableResponse.code
   300  			request.ReturnOverrides.ResponseError = "error"
   301  		}
   302  
   303  		request.Body = usableResponse.Body
   304  
   305  		return testTykMakeHTTPRequest.ReturnData(request, {})
   306  	});
   307  	`})
   308  
   309  	t.Run("Existing endpoint", func(t *testing.T) {
   310  		BuildAndLoadAPI(func(spec *APISpec) {
   311  			spec.Proxy.ListenPath = "/sample"
   312  			spec.ConfigData = map[string]interface{}{
   313  				"base_url": ts.URL,
   314  			}
   315  			spec.CustomMiddlewareBundle = bundle
   316  		}, func(spec *APISpec) {
   317  			spec.Proxy.ListenPath = "/api"
   318  		})
   319  
   320  		ts.Run(t, test.TestCase{Path: "/sample", Code: 200})
   321  	})
   322  
   323  	t.Run("Nonexistent endpoint", func(t *testing.T) {
   324  		BuildAndLoadAPI(func(spec *APISpec) {
   325  			spec.Proxy.ListenPath = "/sample"
   326  			spec.ConfigData = map[string]interface{}{
   327  				"base_url": ts.URL,
   328  			}
   329  			spec.CustomMiddlewareBundle = bundle
   330  		})
   331  
   332  		ts.Run(t, test.TestCase{Path: "/sample", Code: 404})
   333  	})
   334  
   335  	t.Run("Endpoint with query", func(t *testing.T) {
   336  		BuildAndLoadAPI(func(spec *APISpec) {
   337  			spec.Proxy.ListenPath = "/sample"
   338  			spec.ConfigData = map[string]interface{}{
   339  				"base_url": ts.URL,
   340  			}
   341  			spec.CustomMiddlewareBundle = bundle
   342  		}, func(spec *APISpec) {
   343  			spec.Proxy.ListenPath = "/api"
   344  		})
   345  
   346  		ts.Run(t, test.TestCase{Path: "/sample", BodyMatch: `/api/get\?param1=dummy`, Code: 200})
   347  	})
   348  
   349  	t.Run("Endpoint with skip cleaning", func(t *testing.T) {
   350  		ts.Close()
   351  		globalConf := config.Global()
   352  		globalConf.HttpServerOptions.SkipURLCleaning = true
   353  		globalConf.HttpServerOptions.OverrideDefaults = true
   354  		config.SetGlobal(globalConf)
   355  
   356  		prevSkipClean := defaultTestConfig.HttpServerOptions.OverrideDefaults &&
   357  			defaultTestConfig.HttpServerOptions.SkipURLCleaning
   358  		testServerRouter.SkipClean(true)
   359  		defer testServerRouter.SkipClean(prevSkipClean)
   360  
   361  		ts := StartTest()
   362  		defer ts.Close()
   363  		defer ResetTestConfig()
   364  
   365  		BuildAndLoadAPI(func(spec *APISpec) {
   366  			spec.Proxy.ListenPath = "/sample"
   367  			spec.ConfigData = map[string]interface{}{
   368  				"base_url": ts.URL,
   369  			}
   370  			spec.CustomMiddlewareBundle = bundle
   371  		}, func(spec *APISpec) {
   372  			spec.Proxy.ListenPath = "/api"
   373  		})
   374  
   375  		ts.Run(t, test.TestCase{Path: "/sample/99999-XXXX+%2F%2F+dog+9+fff%C3%A9o+party", BodyMatch: `URI":"/sample/99999-XXXX\+%2F%2F\+dog\+9\+fff%C3%A9o\+party"`, Code: 200})
   376  	})
   377  }
   378  
   379  func TestJSVMBase64(t *testing.T) {
   380  	jsvm := JSVM{}
   381  	jsvm.Init(nil, logrus.NewEntry(log))
   382  
   383  	inputString := "teststring"
   384  	inputB64 := "dGVzdHN0cmluZw=="
   385  	jwtPayload := "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ"
   386  	decodedJwtPayload := `{"sub":"1234567890","name":"John Doe","iat":1516239022}`
   387  
   388  	t.Run("b64dec with simple string input", func(t *testing.T) {
   389  		v, err := jsvm.VM.Run(`b64dec("` + inputB64 + `")`)
   390  		if err != nil {
   391  			t.Fatalf("b64dec call failed: %s", err.Error())
   392  		}
   393  		if s := v.String(); s != inputString {
   394  			t.Fatalf("wanted '%s', got '%s'", inputString, s)
   395  		}
   396  	})
   397  
   398  	t.Run("b64dec with a JWT payload", func(t *testing.T) {
   399  		v, err := jsvm.VM.Run(`b64dec("` + jwtPayload + `")`)
   400  		if err != nil {
   401  			t.Fatalf("b64dec call failed: %s", err.Error())
   402  		}
   403  		if s := v.String(); s != decodedJwtPayload {
   404  			t.Fatalf("wanted '%s', got '%s'", decodedJwtPayload, s)
   405  		}
   406  	})
   407  
   408  	t.Run("b64enc with simple string input", func(t *testing.T) {
   409  		v, err := jsvm.VM.Run(`b64enc("` + inputString + `")`)
   410  		if err != nil {
   411  			t.Fatalf("b64enc call failed: %s", err.Error())
   412  		}
   413  		if s := v.String(); s != inputB64 {
   414  			t.Fatalf("wanted '%s', got '%s'", inputB64, s)
   415  		}
   416  	})
   417  
   418  	t.Run("rawb64dec with simple string input", func(t *testing.T) {
   419  		v, err := jsvm.VM.Run(`rawb64dec("` + jwtPayload + `")`)
   420  		if err != nil {
   421  			t.Fatalf("rawb64dec call failed: %s", err.Error())
   422  		}
   423  		if s := v.String(); s != decodedJwtPayload {
   424  			t.Fatalf("wanted '%s', got '%s'", decodedJwtPayload, s)
   425  		}
   426  	})
   427  
   428  	t.Run("rawb64enc with simple string input", func(t *testing.T) {
   429  		jsvm.VM.Set("input", decodedJwtPayload)
   430  		v, err := jsvm.VM.Run(`rawb64enc(input)`)
   431  		if err != nil {
   432  			t.Fatalf("rawb64enc call failed: %s", err.Error())
   433  		}
   434  		if s := v.String(); s != jwtPayload {
   435  			t.Fatalf("wanted '%s', got '%s'", jwtPayload, s)
   436  		}
   437  	})
   438  }
   439  
   440  func TestJSVMStagesRequest(t *testing.T) {
   441  	ts := StartTest()
   442  	defer ts.Close()
   443  
   444  	pre := `var pre = new TykJS.TykMiddleware.NewMiddleware({});
   445  
   446  pre.NewProcessRequest(function(request, session) {
   447      // You can log to Tyk console output by calloing the built-in log() function:
   448      log("Running sample  PRE PROCESSOR JSVM middleware")
   449      
   450      // Set headers in an outbound request
   451      request.SetHeaders["Pre"] = "foobar";
   452      // Add or delete request parmeters, these are encoded for the request as needed.
   453      request.AddParams["pre"] = "foobar";
   454      
   455      // You MUST return both the request and session metadata    
   456      return pre.ReturnData(request, {"pre": "foobar"});
   457  });`
   458  
   459  	post := `var post = new TykJS.TykMiddleware.NewMiddleware({});
   460  
   461  post.NewProcessRequest(function(request, session) {
   462      // You can log to Tyk console output by calloing the built-in log() function:
   463      log("Running sample  POST PROCESSOR JSVM middleware")
   464      
   465      // Set headers in an outbound request
   466      request.SetHeaders["Post"] = "foobar";
   467      // Add or delete request parmeters, these are encoded for the request as needed.
   468      request.AddParams["post"] = "foobar";
   469      
   470      // You MUST return both the request and session metadata    
   471      return post.ReturnData(request, {"post": "foobar"});
   472  });`
   473  
   474  	t.Run("Bundles", func(t *testing.T) {
   475  		bundle := RegisterBundle("jsvm_stages", map[string]string{
   476  			"manifest.json": `
   477  		{
   478  		    "file_list": [],
   479  		    "custom_middleware": {
   480  		        "driver": "otto",
   481  		        "pre": [{
   482  		            "name": "pre",
   483  		            "path": "pre.js"
   484  		        }],
   485  				"post": [{
   486  		            "name": "post",
   487  		            "path": "post.js"
   488  		        }]
   489  		    }
   490  		}
   491  	`,
   492  			"pre.js":  pre,
   493  			"post.js": post,
   494  		})
   495  
   496  		BuildAndLoadAPI(func(spec *APISpec) {
   497  			spec.Proxy.ListenPath = "/test"
   498  			spec.CustomMiddlewareBundle = bundle
   499  		})
   500  
   501  		ts.Run(t, []test.TestCase{
   502  			{Path: "/test", Code: 200, BodyMatch: `"Pre":"foobar"`},
   503  			{Path: "/test", Code: 200, BodyMatch: `"Post":"foobar"`},
   504  		}...)
   505  	})
   506  
   507  	t.Run("Files", func(t *testing.T) {
   508  		// Object names are forced to be "pre" and "post"
   509  		RegisterJSFileMiddleware("jsvm_file_test", map[string]string{
   510  			"pre/pre.js":   pre,
   511  			"post/post.js": post,
   512  		})
   513  
   514  		BuildAndLoadAPI(func(spec *APISpec) {
   515  			spec.APIID = "jsvm_file_test"
   516  			spec.Proxy.ListenPath = "/test"
   517  		})
   518  
   519  		ts.Run(t, []test.TestCase{
   520  			{Path: "/test", Code: 200, BodyMatch: `"Pre":"foobar"`},
   521  			{Path: "/test", Code: 200, BodyMatch: `"Post":"foobar"`},
   522  		}...)
   523  	})
   524  
   525  	t.Run("API definition", func(t *testing.T) {
   526  		// Write to non APIID folder
   527  		RegisterJSFileMiddleware("jsvm_api", map[string]string{
   528  			"pre.js":  pre,
   529  			"post.js": post,
   530  		})
   531  
   532  		BuildAndLoadAPI(func(spec *APISpec) {
   533  			spec.Proxy.ListenPath = "/test"
   534  			spec.CustomMiddleware = apidef.MiddlewareSection{
   535  				Pre: []apidef.MiddlewareDefinition{{
   536  					Name: "pre",
   537  					Path: config.Global().MiddlewarePath + "/jsvm_api/pre.js",
   538  				}},
   539  				Post: []apidef.MiddlewareDefinition{{
   540  					Name: "post",
   541  					Path: config.Global().MiddlewarePath + "/jsvm_api/post.js",
   542  				}},
   543  			}
   544  		})
   545  
   546  		ts.Run(t, []test.TestCase{
   547  			{Path: "/test", Code: 200, BodyMatch: `"Pre":"foobar"`},
   548  			{Path: "/test", Code: 200, BodyMatch: `"Post":"foobar"`},
   549  		}...)
   550  	})
   551  }
   552  
   553  func TestMiniRequestObject_ReconstructParams(t *testing.T) {
   554  	const exampleURL = "http://example.com/get?b=1&c=2&a=3"
   555  	r, _ := http.NewRequest(http.MethodGet, exampleURL, nil)
   556  	mr := MiniRequestObject{}
   557  
   558  	t.Run("Don't touch queries if no change on params", func(t *testing.T) {
   559  		mr.ReconstructParams(r)
   560  		assert.Equal(t, exampleURL, r.URL.String())
   561  	})
   562  
   563  	t.Run("Update params", func(t *testing.T) {
   564  		mr.AddParams = map[string]string{
   565  			"d": "4",
   566  		}
   567  		mr.DeleteParams = append(mr.DeleteHeaders, "b")
   568  		mr.ReconstructParams(r)
   569  
   570  		assert.Equal(t, url.Values{
   571  			"a": []string{"3"},
   572  			"c": []string{"2"},
   573  			"d": []string{"4"},
   574  		}, r.URL.Query())
   575  	})
   576  }
   577  
   578  func TestJSVM_Auth(t *testing.T) {
   579  	ts := StartTest()
   580  	defer ts.Close()
   581  
   582  	bundle := RegisterBundle("custom_auth", map[string]string{
   583  		"manifest.json": `{
   584  			"file_list": [
   585  				"testmw.js"
   586  			],
   587  			"custom_middleware": {
   588  				"pre": null,
   589  				"post": null,
   590  				"post_key_auth": null,
   591  				"auth_check": {
   592  					"name": "ottoAuthExample",
   593  					"path": "testmw.js",
   594  					"require_session": false
   595  				},
   596  				"response": null,
   597  				"driver": "otto",
   598  				"id_extractor": {
   599  					"extract_from": "",
   600  					"extract_with": "",
   601  					"extractor_config": null
   602  				}
   603  			},
   604  			"checksum": "65694908d609b14df0e280c1a95a8ca4",
   605  			"signature": ""
   606  		}`,
   607  		"testmw.js": `log("====> JS Auth initialising");
   608  
   609  		var ottoAuthExample = new TykJS.TykMiddleware.NewMiddleware({});
   610  		
   611  		ottoAuthExample.NewProcessRequest(function(request, session) {
   612  			log("----> Running ottoAuthExample JSVM Auth Middleware")
   613  		
   614  			var thisToken = request.Headers["Authorization"];
   615  		
   616  			if (thisToken == undefined) {
   617  				// no token at all?
   618  				request.ReturnOverrides.ResponseCode = 401
   619  				request.ReturnOverrides.ResponseError = 'Header missing (JS middleware)'
   620  				return ottoAuthExample.ReturnData(request, {});
   621  			}
   622  		
   623  			if (thisToken != "foobar") {
   624  				request.ReturnOverrides.ResponseCode = 401
   625  				request.ReturnOverrides.ResponseError = 'Not authorized (JS middleware)'
   626  				return ottoAuthExample.ReturnData(request, {});
   627  			}
   628  		
   629  			log("auth is ok")
   630  		
   631  			var thisSession = {
   632  				"allowance": 100,
   633  				"rate": 100,
   634  				"per": 1,
   635  				"quota_max": -1,
   636  				"quota_renews": 1906121006,
   637  				"expires": 1906121006,
   638  				"access_rights": {}
   639  			};
   640  		
   641  			return ottoAuthExample.ReturnAuthData(request, thisSession);
   642  		});
   643  		
   644  		// Ensure init with a post-declaration log message
   645  		log("====> JS Auth initialised");
   646  		`,
   647  	})
   648  	BuildAndLoadAPI(func(spec *APISpec) {
   649  		spec.Proxy.ListenPath = "/sample"
   650  		spec.ConfigData = map[string]interface{}{
   651  			"base_url": ts.URL,
   652  		}
   653  		spec.CustomMiddlewareBundle = bundle
   654  		spec.EnableCoProcessAuth = true
   655  		spec.UseKeylessAccess = false
   656  	})
   657  	ts.Run(t,
   658  		test.TestCase{Path: "/sample", Code: http.StatusUnauthorized, BodyMatchFunc: func(b []byte) bool {
   659  			return strings.Contains(string(b), "Header missing (JS middleware)")
   660  		}},
   661  		test.TestCase{Path: "/sample", Code: http.StatusUnauthorized, BodyMatchFunc: func(b []byte) bool {
   662  			return strings.Contains(string(b), "Not authorized (JS middleware)")
   663  		},
   664  			Headers: map[string]string{"Authorization": "foo"},
   665  		},
   666  		test.TestCase{Path: "/sample", Code: http.StatusOK, Headers: map[string]string{
   667  			"Authorization": "foobar",
   668  		}},
   669  	)
   670  }