diff --git a/docker/nginx/conf.d/include/track-download b/docker/nginx/conf.d/include/track-download index 0bd74dad..7c637fe3 100644 --- a/docker/nginx/conf.d/include/track-download +++ b/docker/nginx/conf.d/include/track-download @@ -1,8 +1,10 @@ # register the download in accounts service (cookies should contain jwt) log_by_lua_block { - -- this block runs only when accounts are enabled - if require("skynet.account").accounts_enabled() then - local function track(premature, skylink, status, body_bytes_sent, jwt) + local skynet_account = require("skynet.account") + + -- tracking runs only when request comes from authenticated user + if skynet_account.is_authenticated() then + local function track(premature, skylink, status, body_bytes_sent, auth_headers) if premature then return end local httpc = require("resty.http").new() @@ -11,11 +13,7 @@ log_by_lua_block { -- 10.10.10.70 points to accounts service (alias not available when using resty-http) local res, err = httpc:request_uri("http://10.10.10.70:3000/track/download/" .. skylink .. "?" .. query, { method = "POST", - headers = { - ["Cookie"] = "skynet-jwt=" .. jwt, - ["Authorization"] = ngx.header["Authorization"], - ["Skynet-Api-Key"] = ngx.header["Skynet-Api-Key"], - }, + headers = auth_headers, }) if err or (res and res.status ~= ngx.HTTP_NO_CONTENT) then @@ -23,8 +21,8 @@ log_by_lua_block { end end - if ngx.header["Skynet-Skylink"] and ngx.var.skynet_jwt ~= "" and ngx.status >= ngx.HTTP_OK and ngx.status < ngx.HTTP_SPECIAL_RESPONSE then - local ok, err = ngx.timer.at(0, track, ngx.header["Skynet-Skylink"], ngx.status, ngx.var.body_bytes_sent, ngx.var.skynet_jwt) + if ngx.header["Skynet-Skylink"] and ngx.status >= ngx.HTTP_OK and ngx.status < ngx.HTTP_SPECIAL_RESPONSE then + local ok, err = ngx.timer.at(0, track, ngx.header["Skynet-Skylink"], ngx.status, ngx.var.body_bytes_sent, skynet_account.get_auth_headers()) if err then ngx.log(ngx.ERR, "Failed to create timer: ", err) end end end diff --git a/docker/nginx/conf.d/include/track-registry b/docker/nginx/conf.d/include/track-registry index ac981466..0344b6c6 100644 --- a/docker/nginx/conf.d/include/track-registry +++ b/docker/nginx/conf.d/include/track-registry @@ -1,8 +1,10 @@ # register the registry access in accounts service (cookies should contain jwt) log_by_lua_block { - -- this block runs only when accounts are enabled - if require("skynet.account").accounts_enabled() then - local function track(premature, request_method, jwt) + local skynet_account = require("skynet.account") + + -- tracking runs only when request comes from authenticated user + if skynet_account.is_authenticated() then + local function track(premature, request_method, auth_headers) if premature then return end local httpc = require("resty.http").new() @@ -14,11 +16,7 @@ log_by_lua_block { -- 10.10.10.70 points to accounts service (alias not available when using resty-http) local res, err = httpc:request_uri("http://10.10.10.70:3000/track/registry/" .. registry_action, { method = "POST", - headers = { - ["Cookie"] = "skynet-jwt=" .. jwt, - ["Authorization"] = ngx.header["Authorization"], - ["Skynet-Api-Key"] = ngx.header["Skynet-Api-Key"], - }, + headers = auth_headers, }) if err or (res and res.status ~= ngx.HTTP_NO_CONTENT) then @@ -26,8 +24,8 @@ log_by_lua_block { end end - if ngx.var.skynet_jwt ~= "" and (ngx.status == ngx.HTTP_OK or ngx.status == ngx.HTTP_NOT_FOUND) then - local ok, err = ngx.timer.at(0, track, ngx.req.get_method(), ngx.var.skynet_jwt) + if ngx.status == ngx.HTTP_OK or ngx.status == ngx.HTTP_NOT_FOUND then + local ok, err = ngx.timer.at(0, track, ngx.req.get_method(), skynet_account.get_auth_headers()) if err then ngx.log(ngx.ERR, "Failed to create timer: ", err) end end end diff --git a/docker/nginx/conf.d/include/track-upload b/docker/nginx/conf.d/include/track-upload index 53795d55..21df6d83 100644 --- a/docker/nginx/conf.d/include/track-upload +++ b/docker/nginx/conf.d/include/track-upload @@ -1,8 +1,10 @@ # register the upload in accounts service (cookies should contain jwt) log_by_lua_block { - -- this block runs only when accounts are enabled - if require("skynet.account").accounts_enabled() then - local function track(premature, skylink, jwt) + local skynet_account = require("skynet.account") + + -- tracking runs only when request comes from authenticated user + if skynet_account.is_authenticated() then + local function track(premature, skylink, auth_headers) if premature then return end local httpc = require("resty.http").new() @@ -10,11 +12,7 @@ log_by_lua_block { -- 10.10.10.70 points to accounts service (alias not available when using resty-http) local res, err = httpc:request_uri("http://10.10.10.70:3000/track/upload/" .. skylink, { method = "POST", - headers = { - ["Cookie"] = "skynet-jwt=" .. jwt, - ["Authorization"] = ngx.header["Authorization"], - ["Skynet-Api-Key"] = ngx.header["Skynet-Api-Key"], - }, + headers = auth_headers, }) if err or (res and res.status ~= ngx.HTTP_NO_CONTENT) then @@ -23,8 +21,8 @@ log_by_lua_block { end -- report all skylinks (header empty if request failed) but only if jwt is preset (user is authenticated) - if ngx.header["Skynet-Skylink"] and ngx.var.skynet_jwt ~= "" then - local ok, err = ngx.timer.at(0, track, ngx.header["Skynet-Skylink"], ngx.var.skynet_jwt) + if ngx.header["Skynet-Skylink"] then + local ok, err = ngx.timer.at(0, track, ngx.header["Skynet-Skylink"], skynet_account.get_auth_headers()) if err then ngx.log(ngx.ERR, "Failed to create timer: ", err) end end end diff --git a/docker/nginx/conf.d/server/server.api b/docker/nginx/conf.d/server/server.api index fc6f7034..24e8509d 100644 --- a/docker/nginx/conf.d/server/server.api +++ b/docker/nginx/conf.d/server/server.api @@ -172,28 +172,25 @@ location /skynet/registry/subscription { set $notificationdelay "0"; rewrite_by_lua_block { - -- this block runs only when accounts are enabled - if os.getenv("PORTAL_MODULES"):match("a") then - local httpc = require("resty.http").new() + local skynet_account = require("skynet.account") - -- fetch account limits and set download bandwidth and registry delays accordingly - local res, err = httpc:request_uri("http://10.10.10.70:3000/user/limits", { - headers = { - ["Cookie"] = "skynet-jwt=" .. ngx.var.skynet_jwt, - ["Authorization"] = ngx.header["Authorization"], - ["Skynet-Api-Key"] = ngx.header["Skynet-Api-Key"], - } - }) - - -- fail gracefully in case /user/limits failed - if err or (res and res.status ~= ngx.HTTP_OK) then - ngx.log(ngx.ERR, "Failed accounts service request /user/limits: ", err or ("[HTTP " .. res.status .. "] " .. res.body)) - elseif res and res.status == ngx.HTTP_OK then - local json = require('cjson') - local limits = json.decode(res.body) - ngx.var.bandwidthlimit = limits.download - ngx.var.notificationdelay = limits.registry + if skynet_account.accounts_enabled() then + -- check if portal is in authenticated only mode + if skynet_account.is_access_unauthorized() then + return skynet_account.exit_access_unauthorized() end + + -- check if portal is in subscription only mode + if skynet_account.is_access_forbidden() then + return skynet_account.exit_access_forbidden() + end + + -- get account limits of currently authenticated user + local limits = skynet_account.get_account_limits() + + -- apply bandwidth limit and notification delay + ngx.var.bandwidthlimit = limits.download + ngx.var.notificationdelay = limits.registry end } @@ -261,19 +258,21 @@ location /skynet/tus { proxy_pass http://sia:9980; access_by_lua_block { - if require("skynet.account").accounts_enabled() then + local skynet_account = require("skynet.account") + + if skynet_account.accounts_enabled() then -- check if portal is in authenticated only mode - if require("skynet.account").is_access_unauthorized() then - return require("skynet.account").exit_access_unauthorized() + if skynet_account.is_access_unauthorized() then + return skynet_account.exit_access_unauthorized() end -- check if portal is in subscription only mode - if require("skynet.account").is_access_forbidden() then - return require("skynet.account").exit_access_forbidden() + if skynet_account.is_access_forbidden() then + return skynet_account.exit_access_forbidden() end -- get account limits of currently authenticated user - local limits = require("skynet.account").get_account_limits() + local limits = skynet_account.get_account_limits() -- apply upload size limits ngx.req.set_header("SkynetMaxUploadSize", limits.maxUploadSize) @@ -365,19 +364,21 @@ location /skynet/trustless/basesector { set $limit_rate 0; access_by_lua_block { - if require("skynet.account").accounts_enabled() then + local skynet_account = require("skynet.account") + + if skynet_account.accounts_enabled() then -- check if portal is in authenticated only mode - if require("skynet.account").is_access_unauthorized() then - return require("skynet.account").exit_access_unauthorized() + if skynet_account.is_access_unauthorized() then + return skynet_account.exit_access_unauthorized() end -- check if portal is in subscription only mode - if require("skynet.account").is_access_forbidden() then - return require("skynet.account").exit_access_forbidden() + if skynet_account.is_access_forbidden() then + return skynet_account.exit_access_forbidden() end -- get account limits of currently authenticated user - local limits = require("skynet.account").get_account_limits() + local limits = skynet_account.get_account_limits() -- apply download speed limit ngx.var.limit_rate = limits.download diff --git a/docker/nginx/libs/skynet/account.lua b/docker/nginx/libs/skynet/account.lua index 5319f665..72a2c9ea 100644 --- a/docker/nginx/libs/skynet/account.lua +++ b/docker/nginx/libs/skynet/account.lua @@ -14,6 +14,39 @@ local anon_limits = { ["registry"] = 250 } +-- utility function for checking if table is empty +function is_table_empty(check) + -- bind next to local variable to achieve ultimate efficiency + -- https://stackoverflow.com/a/1252776 + local next = next + + return next(check) == nil +end + +-- get all non empty authentication headers from request, we want to return +-- all of them and let accounts service deal with validation and prioritisation +function _M.get_auth_headers() + local request_headers = ngx.req.get_headers() + local headers = {} + + -- if skynet_jwt is set, include it as a cookie + if ngx.var.skynet_jwt ~= "" then + headers["Cookie"] = "skynet-jwt=" .. ngx.var.skynet_jwt + end + + -- if authorization header is set, pass it as is + if request_headers["Authorization"] then + headers["Authorization"] = request_headers["Authorization"] + end + + -- if skynet api key header is set, pass it as is + if request_headers["Skynet-Api-Key"] then + headers["Skynet-Api-Key"] = request_headers["Skynet-Api-Key"] + end + + return headers +end + -- handle request exit when access to portal should be restricted to authenticated users only function _M.exit_access_unauthorized(message) ngx.status = ngx.HTTP_UNAUTHORIZED @@ -36,8 +69,10 @@ end function _M.get_account_limits() local cjson = require('cjson') + local auth_headers = _M.get_auth_headers() - if ngx.var.skynet_jwt == "" then + -- simple case of anonymous request - none of available auth headers exist + if is_table_empty(auth_headers) then return anon_limits end @@ -46,11 +81,7 @@ function _M.get_account_limits() -- 10.10.10.70 points to accounts service (alias not available when using resty-http) local res, err = httpc:request_uri("http://10.10.10.70:3000/user/limits", { - headers = { - ["Cookie"] = "skynet-jwt=" .. ngx.var.skynet_jwt, - ["Authorization"] = ngx.header["Authorization"], - ["Skynet-Api-Key"] = ngx.header["Skynet-Api-Key"], - } + headers = auth_headers, }) -- fail gracefully in case /user/limits failed