@@ -1533,7 +1533,8 @@ func (st *SignatureTest) Close() {
15331533
15341534// fakeNetConn simulates an http.Request.Body buffer that will be consumed
15351535// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1536−// https://github.com/18F/hmacauth/pull/4
1536+//
1537+// https://github.com/18F/hmacauth/pull/4
15371538type fakeNetConn struct {
15381539 reqBody string
15391540}
@@ -2421,6 +2422,116 @@ func Test_buildRoutesAllowlist(t *testing.T) {
24212422 }
24222423}
24232424
2425+func TestApiRoutes(t *testing.T) {
2426+
2427+ ajaxAPIServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2428+ w.WriteHeader(200)
2429+ _, err := w.Write([]byte("AJAX API Request"))
2430+ if err != nil {
2431+ t.Fatal(err)
2432+ }
2433+ }))
2434+ t.Cleanup(ajaxAPIServer.Close)
2435+
2436+ apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2437+ w.WriteHeader(200)
2438+ _, err := w.Write([]byte("API Request"))
2439+ if err != nil {
2440+ t.Fatal(err)
2441+ }
2442+ }))
2443+ t.Cleanup(apiServer.Close)
2444+
2445+ uiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2446+ w.WriteHeader(200)
2447+ _, err := w.Write([]byte("API Request"))
2448+ if err != nil {
2449+ t.Fatal(err)
2450+ }
2451+ }))
2452+ t.Cleanup(uiServer.Close)
2453+
2454+ opts := baseTestOptions()
2455+ opts.UpstreamServers = options.UpstreamConfig{
2456+ Upstreams: []options.Upstream{
2457+ {
2458+ ID: apiServer.URL,
2459+ Path: "/api",
2460+ URI: apiServer.URL,
2461+ },
2462+ {
2463+ ID: ajaxAPIServer.URL,
2464+ Path: "/ajaxapi",
2465+ URI: ajaxAPIServer.URL,
2466+ },
2467+ {
2468+ ID: uiServer.URL,
2469+ Path: "/ui",
2470+ URI: uiServer.URL,
2471+ },
2472+ },
2473+ }
2474+ opts.APIRoutes = []string{
2475+ "^/api",
2476+ }
2477+ opts.SkipProviderButton = true
2478+ err := validation.Validate(opts)
2479+ assert.NoError(t, err)
2480+ proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2481+ if err != nil {
2482+ t.Fatal(err)
2483+ }
2484+
2485+ testCases := []struct {
2486+ name string
2487+ contentType string
2488+ url string
2489+ shouldRedirect bool
2490+ }{
2491+ {
2492+ name: "AJAX request matching API regex",
2493+ contentType: "application/json",
2494+ url: "/api/v1/UserInfo",
2495+ shouldRedirect: false,
2496+ },
2497+ {
2498+ name: "AJAX request not matching API regex",
2499+ contentType: "application/json",
2500+ url: "/ajaxapi/v1/UserInfo",
2501+ shouldRedirect: false,
2502+ },
2503+ {
2504+ name: "Other Request matching API regex",
2505+ contentType: "application/grpcwebtext",
2506+ url: "/api/v1/UserInfo",
2507+ shouldRedirect: false,
2508+ },
2509+ {
2510+ name: "UI request",
2511+ contentType: "html",
2512+ url: "/ui/index.html",
2513+ shouldRedirect: true,
2514+ },
2515+ }
2516+
2517+ for _, tc := range testCases {
2518+ t.Run(tc.name, func(t *testing.T) {
2519+ req, err := http.NewRequest("GET", tc.url, nil)
2520+ req.Header.Set("Accept", tc.contentType)
2521+ assert.NoError(t, err)
2522+
2523+ rw := httptest.NewRecorder()
2524+ proxy.ServeHTTP(rw, req)
2525+
2526+ if tc.shouldRedirect {
2527+ assert.Equal(t, 302, rw.Code)
2528+ } else {
2529+ assert.Equal(t, 401, rw.Code)
2530+ }
2531+ })
2532+ }
2533+}
2534+
24242535func TestAllowedRequest(t *testing.T) {
24252536 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24262537 w.WriteHeader(200)