Njs: http filter/access optional response

Created on 10 Dec 2020  路  6Comments  路  Source: nginx/njs

I have a service which acts as both a rate limiter and access filter.
The service responds with a 200 status and a body like

  { "limited":false, "allow":true }

I have the following example

http {
  access_log /dev/stdout;
  js_import gateway.js;

  server {
    server_name   localhost;
    listen        8000;
    listen        [::]:8000;
    # potential filter here <-

    location /foo/ {
      # potential filter here <-
      proxy_set_header Special "imspecial";
      proxy_pass http://backend:9000/;
    }
}

I would like to add some sort of auth filter at the server level, or at each location block.
Is it possible to invoke a function like

function my_filter(request) {
   request.subrequest("/check-access", {
      body: JSON.stringify(body),
      method: "POST",
    })
    .then((reply) => {
      var body = JSON.parse(reply.responseBody);
      if (body["limited"]===true) {
         request.return(429, JSON.stringify({"foo": "special response body here etc"}));
      }

      if (body["allow"]===false) {
         request.return(401, JSON.stringify({"foo": "special response body here etc"}));
      }

      // do nothing, continue on to the proxy pass
    })
    // imagine error handling here for subrequest
}

It seems I cannot; both call js_content and proxy_pass right after in the same location block.

Ive tried all sorts of combinations to the point I cannot remember each of them, any help would be greatly appreciated.

answered question

Most helpful comment

@tylerhjones

Hi!

It seems I cannot; both call js_content and proxy_pass right after in the same location block.

looks like it can be achieved by hacking a auth_request:

    location = /auth {
        js_content test.auth;
    }

    location @access_denied {
        js_content test.access_denied;
    }

    location /secure {
        auth_request /auth;
        auth_request_set $x_auth_status "$x_auth_status";
        auth_request_set $x_auth_response "$x_auth_response";
        error_page 403 = @access_denied;

        proxy_pass http://127.0.0.1:10000/content;
    }

    location /content {
        return 200 "Welcome";
    }
function auth(r) {
    var x = (Math.random() * 1000) & 2;
    switch (x) {
    case 0:
        r.return(204);
        return;
    case 1:
        r.variables['x_auth_status'] = 429;
        r.variables['x_auth_response'] = `Rate limit, ${new Date()}`;
        break;
    case 2:
        r.variables['x_auth_status'] = 401;
        r.variables['x_auth_response'] = `Auth required, ${new Date()}`;
        break;
    }
    r.return(403);
}

function access_denied(r) {
    var status = +r.variables['x_auth_status'];
    var response = r.variables['x_auth_response'];
    r.return(status, response);
}

All 6 comments

@tylerhjones

Hi!

It seems I cannot; both call js_content and proxy_pass right after in the same location block.

looks like it can be achieved by hacking a auth_request:

    location = /auth {
        js_content test.auth;
    }

    location @access_denied {
        js_content test.access_denied;
    }

    location /secure {
        auth_request /auth;
        auth_request_set $x_auth_status "$x_auth_status";
        auth_request_set $x_auth_response "$x_auth_response";
        error_page 403 = @access_denied;

        proxy_pass http://127.0.0.1:10000/content;
    }

    location /content {
        return 200 "Welcome";
    }
function auth(r) {
    var x = (Math.random() * 1000) & 2;
    switch (x) {
    case 0:
        r.return(204);
        return;
    case 1:
        r.variables['x_auth_status'] = 429;
        r.variables['x_auth_response'] = `Rate limit, ${new Date()}`;
        break;
    case 2:
        r.variables['x_auth_status'] = 401;
        r.variables['x_auth_response'] = `Auth required, ${new Date()}`;
        break;
    }
    r.return(403);
}

function access_denied(r) {
    var status = +r.variables['x_auth_status'];
    var response = r.variables['x_auth_response'];
    r.return(status, response);
}

@tylerhjones we are planning to add js body filter in the near term future.

@drsm Thank you so much for this example.

Is x_auth_status special in some way?
When I attempt to set extra values
e.g.

       r.variables['try-after'] = "1234"; // <- new value;
        r.variables['x_auth_status'] = 429;
        r.variables['x_auth_response'] = `Rate limit, ${new Date()}`;

I get an error

[error] 29#29: *1 js: Error: variable not found

馃う馃徎 nvm, forgot to define the variable first auth_request_set $try_after "$try_after";

resolved

Was this page helpful?
0 / 5 - 0 ratings

Related issues

xeioex picture xeioex  路  3Comments

an0ma1ia picture an0ma1ia  路  4Comments

drsm picture drsm  路  4Comments

drsm picture drsm  路  3Comments

pavelsevcik picture pavelsevcik  路  4Comments