ngx_http_complex_value_t fetch_proxy_cv;
+ ngx_str_t access;
ngx_str_t content;
ngx_str_t header_filter;
ngx_str_t body_filter;
ngx_chain_t *in);
ngx_js_periodic_t *periodic;
+
+ unsigned in_progress:1;
};
} ngx_http_js_entry_t;
+static ngx_int_t ngx_http_js_access_handler(ngx_http_request_t *r);
+static void ngx_http_js_access_write_event_handler(ngx_http_request_t *r);
static ngx_int_t ngx_http_js_content_handler(ngx_http_request_t *r);
static void ngx_http_js_content_event_handler(ngx_http_request_t *r);
static void ngx_http_js_content_write_event_handler(ngx_http_request_t *r);
njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
static njs_int_t ngx_http_js_ext_return(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
+static njs_int_t ngx_http_js_ext_decline(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
static njs_int_t ngx_http_js_ext_internal_redirect(njs_vm_t *vm,
njs_value_t *args, njs_uint_t nargs, njs_index_t unused,
njs_value_t *retval);
JSValueConst this_val, int type);
static JSValue ngx_http_qjs_ext_return(JSContext *cx, JSValueConst this_val,
int argc, JSValueConst *argv);
+static JSValue ngx_http_qjs_ext_decline(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
static JSValue ngx_http_qjs_ext_send(JSContext *cx, JSValueConst this_val,
int argc, JSValueConst *argv);
static JSValue ngx_http_qjs_ext_send_buffer(JSContext *cx,
void *conf);
static char *ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *ngx_http_js_var(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_js_access(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
static char *ngx_http_js_content(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_http_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd,
0,
NULL },
+ { ngx_string("js_access"),
+ NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
+ ngx_http_js_access,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
{ ngx_string("js_content"),
NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
ngx_http_js_content,
}
},
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("decline"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = ngx_http_js_ext_decline,
+ }
+ },
+
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("send"),
JS_CGETSET_MAGIC_DEF("responseText", ngx_http_qjs_ext_response_body, NULL,
NGX_JS_STRING),
JS_CFUNC_DEF("return", 2, ngx_http_qjs_ext_return),
+ JS_CFUNC_DEF("decline", 0, ngx_http_qjs_ext_decline),
JS_CFUNC_DEF("send", 1, ngx_http_qjs_ext_send),
JS_CFUNC_DEF("sendBuffer", 2, ngx_http_qjs_ext_send_buffer),
JS_CFUNC_DEF("sendHeader", 0, ngx_http_qjs_ext_send_header),
#endif
+static ngx_int_t
+ngx_http_js_access_handler(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_js_ctx_t *ctx;
+ ngx_http_js_loc_conf_t *jlcf;
+
+ jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module);
+
+ if (jlcf->access.len == 0) {
+ return NGX_DECLINED;
+ }
+
+ if (r != r->main) {
+ return NGX_DECLINED;
+ }
+
+ rc = ngx_http_js_init_vm(r, ngx_http_js_request_proto_id);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ if (ctx->in_progress) {
+ if (ngx_js_ctx_pending(ctx)) {
+ return NGX_AGAIN;
+ }
+
+ ctx->in_progress = 0;
+
+ if (ctx->rejected_promises != NULL
+ && ctx->rejected_promises->items > 0)
+ {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ return ctx->status;
+ }
+
+ ctx->status = NGX_OK;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http js access handler \"%V\"", &jlcf->access);
+
+ rc = ctx->engine->call((ngx_js_ctx_t *) ctx, &jlcf->access, &ctx->args[0],
+ 1);
+
+ if (rc == NGX_ERROR) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_js_ctx_pending(ctx)) {
+ ctx->in_progress = 1;
+ r->write_event_handler = ngx_http_js_access_write_event_handler;
+ return NGX_AGAIN;
+ }
+
+ return ctx->status;
+}
+
+
+static void
+ngx_http_js_access_write_event_handler(ngx_http_request_t *r)
+{
+ ngx_http_js_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http js access write event handler");
+
+ if (!ngx_js_ctx_pending(ctx)) {
+ ngx_http_core_run_phases(r);
+ return;
+ }
+}
+
+
static ngx_int_t
ngx_http_js_content_handler(ngx_http_request_t *r)
{
}
+static njs_int_t
+ngx_http_js_ext_decline(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+ njs_index_t unused, njs_value_t *retval)
+{
+ ngx_http_js_ctx_t *ctx;
+ ngx_http_request_t *r;
+
+ r = njs_vm_external(vm, ngx_http_js_request_proto_id,
+ njs_argument(args, 0));
+ if (r == NULL) {
+ njs_vm_error(vm, "\"this\" is not an external");
+ return NJS_ERROR;
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ ctx->status = NGX_DECLINED;
+
+ njs_value_undefined_set(retval);
+
+ return NJS_OK;
+}
+
+
static njs_int_t
ngx_http_js_ext_internal_redirect(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused, njs_value_t *retval)
}
+static JSValue
+ngx_http_qjs_ext_decline(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ ngx_http_js_ctx_t *ctx;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ ctx->status = NGX_DECLINED;
+
+ return JS_UNDEFINED;
+}
+
+
static JSValue
ngx_http_qjs_ext_status_get(JSContext *cx, JSValueConst this_val)
{
static ngx_int_t
ngx_http_js_init(ngx_conf_t *cf)
{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_js_header_filter;
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_js_body_filter;
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_js_access_handler;
+
return NGX_OK;
}
}
+static char *
+ngx_http_js_access(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_js_loc_conf_t *jlcf = conf;
+
+ ngx_str_t *value;
+
+ if (jlcf->access.data) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+ jlcf->access = value[1];
+
+ return NGX_CONF_OK;
+}
+
+
static char *
ngx_http_js_content(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_js_loc_conf_t *prev = parent;
ngx_http_js_loc_conf_t *conf = child;
+ ngx_conf_merge_str_value(conf->access, prev->access, "");
ngx_conf_merge_str_value(conf->content, prev->content, "");
ngx_conf_merge_str_value(conf->header_filter, prev->header_filter, "");
ngx_conf_merge_str_value(conf->body_filter, prev->body_filter, "");
return NGX_CONF_ERROR;
}
+ if (conf->access.len != 0) {
+ if (conf->imports == NGX_CONF_UNSET_PTR) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no imports defined for \"js_access\" \"%V\", "
+ "use \"js_import\" directive", &conf->access);
+ return NGX_CONF_ERROR;
+ }
+ }
+
if (conf->content.len != 0) {
if (conf->imports == NGX_CONF_UNSET_PTR) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
--- /dev/null
+#!/usr/bin/perl
+
+# (C) Dmitry Volyntsev
+# (C) F5, Inc.
+
+# Tests for http njs module, js_access directive.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+use Socket qw/ CRLF /;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http rewrite proxy/)
+ ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ js_import test.js;
+
+ js_var $foo;
+ js_var $upstream;
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location /var {
+ js_content test.content;
+ }
+
+ location /deny {
+ js_access test.deny;
+ js_content test.content;
+ }
+
+ location /exception {
+ js_access test.exception;
+ js_content test.content;
+ }
+
+ location /noop {
+ js_access test.noop;
+ js_content test.content;
+ }
+
+ location /decline {
+ js_access test.decline;
+ js_content test.content;
+ }
+
+ location /override {
+ js_access test.override;
+ js_content test.content;
+ }
+
+ location /content_only {
+ js_content test.content_only;
+ }
+
+ location /async_timeout {
+ js_access test.async_timeout;
+ js_content test.content;
+ }
+
+ location /async_deny {
+ js_access test.async_deny;
+ js_content test.content;
+ }
+
+ location /async_exception {
+ js_access test.async_exception;
+ js_content test.content;
+ }
+
+ location /sr_skip {
+ js_content test.sr_skip;
+ }
+
+ location /sub {
+ js_access test.deny;
+ js_content test.content;
+ }
+
+ location /sr {
+ js_access test.sr;
+ js_content test.content;
+ }
+
+ location /fetch {
+ js_access test.fetch;
+ js_content test.content;
+ }
+
+ location /route {
+ js_access test.route;
+ proxy_pass http://$upstream;
+ }
+
+ location /auth_check {
+ js_content test.auth_check;
+ }
+
+ location /redirect {
+ js_access test.redirect;
+ js_content test.content;
+ }
+
+ location /redirect_async {
+ js_access test.redirect_async;
+ js_content test.content;
+ }
+
+ location /callback {
+ js_content test.content;
+ }
+ }
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name noaccess;
+
+ location /no_access {
+ js_content test.content_only;
+ }
+ }
+
+ server {
+ listen 127.0.0.1:8081;
+
+ location / {
+ return 200 "backend1";
+ }
+ }
+
+ server {
+ listen 127.0.0.1:8082;
+
+ location / {
+ return 200 "backend2";
+ }
+ }
+}
+
+EOF
+
+my $p0 = port(8080);
+my $p1 = port(8081);
+my $p2 = port(8082);
+
+$t->write_file('test.js', <<EOF);
+ function content(r) {
+ r.return(200, `var:\${r.variables.foo}`);
+ }
+
+ function deny(r) {
+ r.return(403);
+ }
+
+ function exception(r) {
+ throw new Error("access_error");
+ }
+
+ function noop(r) {
+ /* does nothing, should continue to content handler */
+ }
+
+ function decline(r) {
+ r.decline();
+ }
+
+ function override(r) {
+ r.variables.foo = 'overridden';
+ }
+
+ function content_only(r) {
+ r.return(200, 'content_only');
+ }
+
+ async function async_timeout(r) {
+ await new Promise(resolve => setTimeout(resolve, 5));
+ r.variables.foo = 'timeout_ok';
+ }
+
+ async function async_deny(r) {
+ await new Promise(resolve => setTimeout(resolve, 5));
+ r.return(403);
+ }
+
+ async function async_exception(r) {
+ await new Promise(resolve => setTimeout(resolve, 5));
+ throw new Error("async_access_error");
+ }
+
+ async function sr_skip(r) {
+ let reply = await r.subrequest('/sub');
+ r.return(reply.status, reply.responseText);
+ }
+
+ async function fetch(r) {
+ let resp = await ngx.fetch(
+ \`http://127.0.0.1:$p0/auth_check?token=\${r.variables.arg_token}\`);
+
+ if (resp.status != 200) {
+ r.return(resp.status);
+ return;
+ }
+
+ r.variables.foo = await resp.text();
+ }
+
+ async function sr(r) {
+ let reply = await r.subrequest('/auth_check?token='
+ + r.variables.arg_token);
+ if (reply.status != 200) {
+ r.return(reply.status);
+ return;
+ }
+
+ r.variables.foo = reply.responseText;
+ }
+
+ function route(r) {
+ let dest = r.variables.arg_dest;
+ r.variables.upstream = (dest === 'one')
+ ? '127.0.0.1:$p1' : '127.0.0.1:$p2';
+ }
+
+ function auth_check(r) {
+ let token = r.variables.arg_token;
+
+ if (token === 'valid') {
+ r.return(200, 'authenticated');
+ } else {
+ r.return(403);
+ }
+ }
+
+ function redirect(r) {
+ r.return(302, 'http://127.0.0.1:$p0/callback');
+ }
+
+ async function redirect_async(r) {
+ await new Promise(resolve => setTimeout(resolve, 5));
+ r.return(302, 'http://127.0.0.1:$p0/callback');
+ }
+
+ export default { content, deny, exception, noop, override,
+ decline, content_only, async_timeout, async_deny,
+ async_exception, sr_skip, sr, fetch, route,
+ auth_check, redirect, redirect_async };
+
+EOF
+
+$t->write_file_expand('duplicate.conf', bad_conf(
+ http => 'js_import test.js;',
+ location => 'js_access test.noop; js_access test.noop;'));
+
+$t->write_file_expand('no_import.conf', bad_conf(
+ location => 'js_access test.noop;'));
+
+$t->try_run('no js_access')->plan(26);
+
+###############################################################################
+
+like(http_get('/deny'), qr/403 Forbidden/,
+ 'js_access sync r.return(403) rejects');
+like(http_post('/deny'), qr/403 Forbidden/,
+ 'js_access deny with request body');
+like(http_get('/exception'), qr/500 Internal Server Error/,
+ 'js_access sync exception returns 500');
+like(http_get('/noop'), qr/var:/,
+ 'js_access noop continues to content');
+like(http_get('/decline'), qr/var:/,
+ 'js_access decline continues to content');
+like(http_get('/override'), qr/var:overridden/,
+ 'js_access override in location');
+like(http_get('/content_only'), qr/content_only/,
+ 'js_content without js_access');
+like(http("GET /no_access HTTP/1.0" . CRLF .
+ "Host: noaccess" . CRLF . CRLF),
+ qr/content_only/,
+ 'js_access not inherited in sibling server');
+like(http_get('/async_timeout'), qr/var:timeout_ok/,
+ 'async js_access with setTimeout');
+like(http_get('/async_deny'), qr/403 Forbidden/,
+ 'async js_access r.return(403) rejects');
+like(http_get('/async_exception'), qr/500 Internal Server Error/,
+ 'async js_access exception returns 500');
+like(http_get('/sr_skip'), qr/var:/,
+ 'js_access skipped for subrequests');
+like(http_get('/sr?token=valid'), qr/var:authenticated/,
+ 'subrequest access allow');
+like(http_get('/sr?token=invalid'), qr/403 Forbidden/,
+ 'subrequest access deny');
+like(http_get('/fetch?token=valid'), qr/var:authenticated/,
+ 'fetch access allow');
+like(http_get('/fetch?token=invalid'), qr/403 Forbidden/,
+ 'fetch access deny');
+like(http_get('/route?dest=one'), qr/backend1/,
+ 'variable routing to backend1');
+like(http_get('/route?dest=two'), qr/backend2/,
+ 'variable routing to backend2');
+like(http_get('/redirect'), qr/302 Moved/,
+ 'js_access sync redirect');
+like(http_get('/redirect'), qr!Location: http://127.0.0.1:$p0/callback!,
+ 'js_access sync redirect Location header');
+like(http_get('/redirect_async'), qr/302 Moved/,
+ 'js_access async redirect');
+like(http_get('/redirect_async'), qr!Location: http://127.0.0.1:$p0/callback!,
+ 'js_access async redirect Location header');
+
+my ($rc, $out) = nginx_test_conf($t, 'duplicate.conf');
+
+isnt($rc, 0, 'duplicate js_access fails');
+like($out, qr/"js_access" directive is duplicate/,
+ 'duplicate js_access error');
+
+($rc, $out) = nginx_test_conf($t, 'no_import.conf');
+
+isnt($rc, 0, 'js_access without js_import fails');
+like($out, qr/no imports defined for "js_access" "test\.noop"/,
+ 'js_access without js_import error');
+
+###############################################################################
+
+sub http_post {
+ my ($url, %extra) = @_;
+
+ my $p = "POST $url HTTP/1.0" . CRLF .
+ "Host: localhost" . CRLF .
+ "Content-Length: 8" . CRLF .
+ CRLF .
+ "REQ-BODY";
+
+ return http($p, %extra);
+}
+
+sub nginx_test_conf {
+ my ($t, $conf) = @_;
+ my $testdir = $t->testdir();
+ my $cmd = "$Test::Nginx::NGINX -p $testdir/ -c $conf -t "
+ . "-e error.log 2>&1";
+
+ my $out = `$cmd`;
+
+ return ($? >> 8, $out);
+}
+
+sub bad_conf {
+ my %args = @_;
+ my $http = $args{http} // '';
+ my $loc = $args{location} // '';
+
+ return <<"EOF";
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ $http
+
+ server {
+ listen 127.0.0.1:8080;
+
+ location / {
+ $loc
+ }
+ }
+}
+
+EOF
+}
--- /dev/null
+#!/usr/bin/perl
+
+# (C) Dmitry Volyntsev
+# (C) F5, Inc.
+
+# Tests for http njs module, js_access directive with satisfy.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http rewrite access/)
+ ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ js_import test.js;
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location /all_decline_allow {
+ satisfy all;
+ allow all;
+ js_access test.decline;
+ js_content test.content;
+ }
+
+ location /all_decline_deny {
+ satisfy all;
+ deny all;
+ js_access test.decline;
+ js_content test.content;
+ }
+
+ location /any_allow_deny {
+ satisfy any;
+ deny all;
+ js_access test.allow;
+ js_content test.content;
+ }
+
+ location /any_deny_allow {
+ satisfy any;
+ allow all;
+ js_access test.deny;
+ js_content test.content;
+ }
+
+ location /any_both_deny {
+ satisfy any;
+ deny all;
+ js_access test.deny;
+ js_content test.content;
+ }
+
+ location /any_decline_deny {
+ satisfy any;
+ deny all;
+ js_access test.decline;
+ js_content test.content;
+ }
+
+ location /any_decline_allow {
+ satisfy any;
+ allow all;
+ js_access test.decline;
+ js_content test.content;
+ }
+
+ location /any_async_allow_deny {
+ satisfy any;
+ deny all;
+ js_access test.async_allow;
+ js_content test.content;
+ }
+
+ location /any_async_decline_deny {
+ satisfy any;
+ deny all;
+ js_access test.async_decline;
+ js_content test.content;
+ }
+ }
+}
+
+EOF
+
+$t->write_file('test.js', <<'EOF');
+ function allow(r) {
+ /* default: normal return yields NGX_OK */
+ }
+
+ function deny(r) {
+ r.return(403);
+ }
+
+ function decline(r) {
+ r.decline();
+ }
+
+ async function async_allow(r) {
+ await new Promise(resolve => setTimeout(resolve, 5));
+ }
+
+ async function async_decline(r) {
+ await new Promise(resolve => setTimeout(resolve, 5));
+ r.decline();
+ }
+
+ function content(r) {
+ r.return(200, 'PASSED');
+ }
+
+ export default { allow, deny, decline, async_allow, async_decline,
+ content };
+EOF
+
+$t->try_run('no js_access')->plan(9);
+
+###############################################################################
+
+# satisfy all + decline: ip decides
+like(http_get('/all_decline_allow'), qr/PASSED/,
+ 'satisfy all: js declines + ip allows');
+like(http_get('/all_decline_deny'), qr/403 Forbidden/,
+ 'satisfy all: js declines + ip denies');
+
+# satisfy any: js allows overrides ip deny
+like(http_get('/any_allow_deny'), qr/PASSED/,
+ 'satisfy any: js allows + ip denies');
+
+# satisfy any: ip allows overrides js deny
+like(http_get('/any_deny_allow'), qr/PASSED/,
+ 'satisfy any: js denies + ip allows');
+
+# satisfy any: both deny
+like(http_get('/any_both_deny'), qr/403 Forbidden/,
+ 'satisfy any: both deny');
+
+# satisfy any + decline: js has no opinion, ip decides
+like(http_get('/any_decline_deny'), qr/403 Forbidden/,
+ 'satisfy any: js declines + ip denies');
+like(http_get('/any_decline_allow'), qr/PASSED/,
+ 'satisfy any: js declines + ip allows');
+
+# async variants
+like(http_get('/any_async_allow_deny'), qr/PASSED/,
+ 'satisfy any: async js allows + ip denies');
+like(http_get('/any_async_decline_deny'), qr/403 Forbidden/,
+ 'satisfy any: async js declines + ip denies');
+
+###############################################################################
* @param body Respose body.
*/
return(status: number, body?: NjsStringOrBuffer): void;
+ /**
+ * Signals that the handler has no opinion about whether access
+ * should be allowed or denied. Useful with the ``satisfy any``
+ * directive: without this call the handler implicitly allows
+ * access (returns NGX_OK to the access phase checker).
+ */
+ decline(): void;
/**
* Sends a part of the response body to the client.
*/