introduce portals for authenticated users only
This commit is contained in:
parent
b7a7d078ea
commit
c04fd5067d
|
@ -1,7 +1,6 @@
|
||||||
version: "3.7"
|
version: "3.7"
|
||||||
|
|
||||||
x-logging:
|
x-logging: &default-logging
|
||||||
&default-logging
|
|
||||||
driver: json-file
|
driver: json-file
|
||||||
options:
|
options:
|
||||||
max-size: "10m"
|
max-size: "10m"
|
||||||
|
@ -11,6 +10,7 @@ services:
|
||||||
nginx:
|
nginx:
|
||||||
environment:
|
environment:
|
||||||
- ACCOUNTS_ENABLED=true
|
- ACCOUNTS_ENABLED=true
|
||||||
|
- ACCOUNTS_AUTH_REQUIRED=${ACCOUNTS_AUTH_REQUIRED:-true} # default to authenticated access only
|
||||||
depends_on:
|
depends_on:
|
||||||
- accounts
|
- accounts
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,6 @@ services:
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
volumes:
|
volumes:
|
||||||
- ./docker/nginx/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro
|
|
||||||
- ./docker/data/nginx/cache:/data/nginx/cache
|
- ./docker/data/nginx/cache:/data/nginx/cache
|
||||||
- ./docker/data/nginx/blocker:/data/nginx/blocker
|
- ./docker/data/nginx/blocker:/data/nginx/blocker
|
||||||
- ./docker/data/nginx/logs:/usr/local/openresty/nginx/logs
|
- ./docker/data/nginx/logs:/usr/local/openresty/nginx/logs
|
||||||
|
|
|
@ -11,6 +11,7 @@ COPY mo ./
|
||||||
COPY libs /etc/nginx/libs
|
COPY libs /etc/nginx/libs
|
||||||
COPY conf.d /etc/nginx/conf.d
|
COPY conf.d /etc/nginx/conf.d
|
||||||
COPY conf.d.templates /etc/nginx/conf.d.templates
|
COPY conf.d.templates /etc/nginx/conf.d.templates
|
||||||
|
COPY nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
|
||||||
|
|
||||||
CMD [ "bash", "-c", \
|
CMD [ "bash", "-c", \
|
||||||
"./mo < /etc/nginx/conf.d.templates/server.account.conf > /etc/nginx/conf.d/server.account.conf ; \
|
"./mo < /etc/nginx/conf.d.templates/server.account.conf > /etc/nginx/conf.d/server.account.conf ; \
|
||||||
|
|
|
@ -3,10 +3,16 @@
|
||||||
# because otherwise logger with throw error
|
# because otherwise logger with throw error
|
||||||
|
|
||||||
# set only on hns routes
|
# set only on hns routes
|
||||||
set $hns_domain '';
|
set $hns_domain "";
|
||||||
|
|
||||||
# set only if server has been access through SERVER_DOMAIN
|
# set only if server has been access through SERVER_DOMAIN
|
||||||
set $server_alias '';
|
set $server_alias "";
|
||||||
|
|
||||||
# expose skylink variable so we can use it in access log
|
# expose skylink variable so we can use it in access log
|
||||||
set $skylink '';
|
set $skylink "";
|
||||||
|
|
||||||
|
# cached account limits (json string) - applies only if accounts are enabled
|
||||||
|
set $account_limits "";
|
||||||
|
|
||||||
|
# set this internal flag to true if current request should not be limited in any way
|
||||||
|
set $internal_no_limits "false";
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
include /etc/nginx/conf.d/include/proxy-buffer;
|
include /etc/nginx/conf.d/include/proxy-buffer;
|
||||||
include /etc/nginx/conf.d/include/proxy-pass-internal;
|
include /etc/nginx/conf.d/include/proxy-pass-internal;
|
||||||
|
include /etc/nginx/conf.d/include/portal-access-check;
|
||||||
|
|
||||||
# variable definititions - we need to define a variable to be able to access it in lua by ngx.var.something
|
# variable definititions - we need to define a variable to be able to access it in lua by ngx.var.something
|
||||||
set $skylink ''; # placeholder for the raw 46 bit skylink
|
set $skylink ''; # placeholder for the raw 46 bit skylink
|
||||||
|
|
||||||
# resolve handshake domain by requesting to /hnsres endpoint and assign correct values to $skylink and $rest
|
# resolve handshake domain by requesting to /hnsres endpoint and assign correct values to $skylink and $rest
|
||||||
access_by_lua_block {
|
rewrite_by_lua_block {
|
||||||
local json = require('cjson')
|
local json = require('cjson')
|
||||||
local httpc = require("resty.http").new()
|
local httpc = require("resty.http").new()
|
||||||
|
|
||||||
|
|
|
@ -53,10 +53,7 @@ access_by_lua_block {
|
||||||
|
|
||||||
-- check if skylink v1 is present on blocklist (compare hashes)
|
-- check if skylink v1 is present on blocklist (compare hashes)
|
||||||
if require("skynet.blocklist").is_blocked(ngx.var.skylink_v1) then
|
if require("skynet.blocklist").is_blocked(ngx.var.skylink_v1) then
|
||||||
ngx.status = ngx.HTTP_ILLEGAL
|
return require("skynet.blocklist").exit_illegal()
|
||||||
ngx.header["content-type"] = "text/plain"
|
|
||||||
ngx.say("Unavailable For Legal Reasons")
|
|
||||||
return ngx.exit(ngx.status)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- if skylink is found on nocache list then set internal nocache variable
|
-- if skylink is found on nocache list then set internal nocache variable
|
||||||
|
@ -65,21 +62,16 @@ access_by_lua_block {
|
||||||
ngx.var.nocache = "1"
|
ngx.var.nocache = "1"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- this block runs only when accounts are enabled
|
if require("skynet.account").accounts_enabled() then
|
||||||
if not os.getenv("PORTAL_MODULES"):match("a") then return end
|
-- check if portal is in authenticated only mode
|
||||||
|
if require("skynet.account").is_access_unauthorized() then
|
||||||
|
return require("skynet.account").exit_access_unauthorized()
|
||||||
|
end
|
||||||
|
|
||||||
-- 10.10.10.70 points to accounts service (alias not available when using resty-http)
|
-- get account limits of currently authenticated user
|
||||||
local res, err = httpc:request_uri("http://10.10.10.70:3000/user/limits", {
|
local limits = require("skynet.account").get_account_limits()
|
||||||
headers = { ["Cookie"] = "skynet-jwt=" .. ngx.var.skynet_jwt }
|
|
||||||
})
|
-- apply download speed limit
|
||||||
|
|
||||||
-- 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))
|
|
||||||
ngx.var.limit_rate = 2621440 -- (20 * 1024 * 1024 / 8) conservative fallback to 20 mbps in case accounts failed to return limits
|
|
||||||
elseif res and res.status == ngx.HTTP_OK then
|
|
||||||
local json = require('cjson')
|
|
||||||
local limits = json.decode(res.body)
|
|
||||||
ngx.var.limit_rate = limits.download
|
ngx.var.limit_rate = limits.download
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
@ -97,7 +89,8 @@ header_filter_by_lua_block {
|
||||||
end
|
end
|
||||||
|
|
||||||
-- add skylink to nocache list if it exceeds 1GB (1e+9 bytes) threshold
|
-- add skylink to nocache list if it exceeds 1GB (1e+9 bytes) threshold
|
||||||
if tonumber(ngx.header["Content-Length"]) > 1e+9 then
|
-- (content length can be nil for already cached files - we can ignore them)
|
||||||
|
if ngx.header["Content-Length"] and tonumber(ngx.header["Content-Length"]) > 1e+9 then
|
||||||
ngx.shared.nocache:set(ngx.var.skylink_v1, ngx.header["Content-Length"])
|
ngx.shared.nocache:set(ngx.var.skylink_v1, ngx.header["Content-Length"])
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,22 +10,16 @@ proxy_read_timeout 600; # siad should timeout with 404 after 5 minutes
|
||||||
proxy_pass http://sia:9980/skynet/registry;
|
proxy_pass http://sia:9980/skynet/registry;
|
||||||
|
|
||||||
access_by_lua_block {
|
access_by_lua_block {
|
||||||
-- this block runs only when accounts are enabled
|
if require("skynet.account").accounts_enabled() then
|
||||||
if not os.getenv("PORTAL_MODULES"):match("a") then return end
|
-- check if portal is in authenticated only mode
|
||||||
|
if require("skynet.account").is_access_unauthorized() then
|
||||||
|
return require("skynet.account").exit_access_unauthorized()
|
||||||
|
end
|
||||||
|
|
||||||
local httpc = require("resty.http").new()
|
-- get account limits of currently authenticated user
|
||||||
|
local limits = require("skynet.account").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", {
|
-- apply registry rate limits (forced delay)
|
||||||
headers = { ["Cookie"] = "skynet-jwt=" .. ngx.var.skynet_jwt }
|
|
||||||
})
|
|
||||||
|
|
||||||
-- 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)
|
|
||||||
if limits.registry > 0 then
|
if limits.registry > 0 then
|
||||||
ngx.sleep(limits.registry / 1000)
|
ngx.sleep(limits.registry / 1000)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
access_by_lua_block {
|
||||||
|
-- check portal access rules and exit if access is restricted
|
||||||
|
if require("skynet.account").is_access_unauthorized() then
|
||||||
|
return require("skynet.account").exit_access_unauthorized()
|
||||||
|
end
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
# register the download in accounts service (cookies should contain jwt)
|
# register the download in accounts service (cookies should contain jwt)
|
||||||
log_by_lua_block {
|
log_by_lua_block {
|
||||||
-- this block runs only when accounts are enabled
|
-- this block runs only when accounts are enabled
|
||||||
if os.getenv("PORTAL_MODULES"):match("a") then
|
if require("skynet.account").accounts_enabled() then
|
||||||
local function track(premature, skylink, status, body_bytes_sent, jwt)
|
local function track(premature, skylink, status, body_bytes_sent, jwt)
|
||||||
if premature then return end
|
if premature then return end
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# register the registry access in accounts service (cookies should contain jwt)
|
# register the registry access in accounts service (cookies should contain jwt)
|
||||||
log_by_lua_block {
|
log_by_lua_block {
|
||||||
-- this block runs only when accounts are enabled
|
-- this block runs only when accounts are enabled
|
||||||
if os.getenv("PORTAL_MODULES"):match("a") then
|
if require("skynet.account").accounts_enabled() then
|
||||||
local function track(premature, request_method, jwt)
|
local function track(premature, request_method, jwt)
|
||||||
if premature then return end
|
if premature then return end
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# register the upload in accounts service (cookies should contain jwt)
|
# register the upload in accounts service (cookies should contain jwt)
|
||||||
log_by_lua_block {
|
log_by_lua_block {
|
||||||
-- this block runs only when accounts are enabled
|
-- this block runs only when accounts are enabled
|
||||||
if os.getenv("PORTAL_MODULES"):match("a") then
|
if require("skynet.account").accounts_enabled() then
|
||||||
local function track(premature, skylink, jwt)
|
local function track(premature, skylink, jwt)
|
||||||
if premature then return end
|
if premature then return end
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ location / {
|
||||||
|
|
||||||
set $skylink "0404dsjvti046fsua4ktor9grrpe76erq9jot9cvopbhsvsu76r4r30";
|
set $skylink "0404dsjvti046fsua4ktor9grrpe76erq9jot9cvopbhsvsu76r4r30";
|
||||||
set $path $uri;
|
set $path $uri;
|
||||||
|
set $internal_no_limits "true";
|
||||||
|
|
||||||
include /etc/nginx/conf.d/include/location-skylink;
|
include /etc/nginx/conf.d/include/location-skylink;
|
||||||
|
|
||||||
|
@ -117,6 +118,8 @@ location /abuse/report {
|
||||||
}
|
}
|
||||||
|
|
||||||
location /hns {
|
location /hns {
|
||||||
|
include /etc/nginx/conf.d/include/cors;
|
||||||
|
|
||||||
# match the request_uri and extract the hns domain and anything that is passed in the uri after it
|
# match the request_uri and extract the hns domain and anything that is passed in the uri after it
|
||||||
# example: /hns/something/foo/bar matches:
|
# example: /hns/something/foo/bar matches:
|
||||||
# > hns_domain: something
|
# > hns_domain: something
|
||||||
|
@ -130,6 +133,7 @@ location /hns {
|
||||||
|
|
||||||
location /hnsres {
|
location /hnsres {
|
||||||
include /etc/nginx/conf.d/include/cors;
|
include /etc/nginx/conf.d/include/cors;
|
||||||
|
include /etc/nginx/conf.d/include/portal-access-check;
|
||||||
|
|
||||||
proxy_pass http://handshake-api:3100;
|
proxy_pass http://handshake-api:3100;
|
||||||
}
|
}
|
||||||
|
@ -141,6 +145,7 @@ location /skynet/registry {
|
||||||
location /skynet/restore {
|
location /skynet/restore {
|
||||||
include /etc/nginx/conf.d/include/cors;
|
include /etc/nginx/conf.d/include/cors;
|
||||||
include /etc/nginx/conf.d/include/sia-auth;
|
include /etc/nginx/conf.d/include/sia-auth;
|
||||||
|
include /etc/nginx/conf.d/include/portal-access-check;
|
||||||
|
|
||||||
client_max_body_size 5M;
|
client_max_body_size 5M;
|
||||||
|
|
||||||
|
@ -197,6 +202,7 @@ location /skynet/skyfile {
|
||||||
include /etc/nginx/conf.d/include/sia-auth;
|
include /etc/nginx/conf.d/include/sia-auth;
|
||||||
include /etc/nginx/conf.d/include/track-upload;
|
include /etc/nginx/conf.d/include/track-upload;
|
||||||
include /etc/nginx/conf.d/include/generate-siapath;
|
include /etc/nginx/conf.d/include/generate-siapath;
|
||||||
|
include /etc/nginx/conf.d/include/portal-access-check;
|
||||||
|
|
||||||
limit_req zone=uploads_by_ip burst=10 nodelay;
|
limit_req zone=uploads_by_ip burst=10 nodelay;
|
||||||
limit_req zone=uploads_by_ip_throttled;
|
limit_req zone=uploads_by_ip_throttled;
|
||||||
|
@ -214,19 +220,6 @@ location /skynet/skyfile {
|
||||||
proxy_set_header Expect $http_expect;
|
proxy_set_header Expect $http_expect;
|
||||||
proxy_set_header User-Agent: Sia-Agent;
|
proxy_set_header User-Agent: Sia-Agent;
|
||||||
|
|
||||||
# access_by_lua_block {
|
|
||||||
# -- this block runs only when accounts are enabled
|
|
||||||
# if not os.getenv("PORTAL_MODULES"):match("a") then return end
|
|
||||||
|
|
||||||
# ngx.var.upload_limit_rate = 5 * 1024 * 1024
|
|
||||||
# local res = ngx.location.capture("/accounts/user", { copy_all_vars = true })
|
|
||||||
# if res.status == ngx.HTTP_OK then
|
|
||||||
# local json = require('cjson')
|
|
||||||
# local user = json.decode(res.body)
|
|
||||||
# ngx.var.upload_limit_rate = ngx.var.upload_limit_rate * (user.tier + 1)
|
|
||||||
# end
|
|
||||||
# }
|
|
||||||
|
|
||||||
# proxy this call to siad endpoint (make sure the ip is correct)
|
# proxy this call to siad endpoint (make sure the ip is correct)
|
||||||
proxy_pass http://sia:9980/skynet/skyfile/$dir1/$dir2/$dir3$is_args$args;
|
proxy_pass http://sia:9980/skynet/skyfile/$dir1/$dir2/$dir3$is_args$args;
|
||||||
}
|
}
|
||||||
|
@ -261,27 +254,17 @@ location /skynet/tus {
|
||||||
# proxy /skynet/tus requests to siad endpoint with all arguments
|
# proxy /skynet/tus requests to siad endpoint with all arguments
|
||||||
proxy_pass http://sia:9980;
|
proxy_pass http://sia:9980;
|
||||||
|
|
||||||
# set max upload size dynamically based on account limits
|
access_by_lua_block {
|
||||||
rewrite_by_lua_block {
|
if require("skynet.account").accounts_enabled() then
|
||||||
-- set default limit value to 5 GB
|
-- check if portal is in authenticated only mode
|
||||||
ngx.req.set_header("SkynetMaxUploadSize", 5368709120)
|
if require("skynet.account").is_access_unauthorized() then
|
||||||
|
return require("skynet.account").exit_access_unauthorized()
|
||||||
-- this block runs only when accounts are enabled
|
end
|
||||||
if not os.getenv("PORTAL_MODULES"):match("a") then return end
|
|
||||||
|
-- get account limits of currently authenticated user
|
||||||
local httpc = require("resty.http").new()
|
local limits = require("skynet.account").get_account_limits()
|
||||||
|
|
||||||
-- fetch account limits and set max upload size accordingly
|
-- apply upload size limits
|
||||||
local res, err = httpc:request_uri("http://10.10.10.70:3000/user/limits", {
|
|
||||||
headers = { ["Cookie"] = "skynet-jwt=" .. ngx.var.skynet_jwt }
|
|
||||||
})
|
|
||||||
|
|
||||||
-- 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.req.set_header("SkynetMaxUploadSize", limits.maxUploadSize)
|
ngx.req.set_header("SkynetMaxUploadSize", limits.maxUploadSize)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
@ -306,6 +289,7 @@ location /skynet/pin {
|
||||||
include /etc/nginx/conf.d/include/sia-auth;
|
include /etc/nginx/conf.d/include/sia-auth;
|
||||||
include /etc/nginx/conf.d/include/track-upload;
|
include /etc/nginx/conf.d/include/track-upload;
|
||||||
include /etc/nginx/conf.d/include/generate-siapath;
|
include /etc/nginx/conf.d/include/generate-siapath;
|
||||||
|
include /etc/nginx/conf.d/include/portal-access-check;
|
||||||
|
|
||||||
limit_req zone=uploads_by_ip burst=10 nodelay;
|
limit_req zone=uploads_by_ip burst=10 nodelay;
|
||||||
limit_req zone=uploads_by_ip_throttled;
|
limit_req zone=uploads_by_ip_throttled;
|
||||||
|
@ -319,6 +303,7 @@ location /skynet/pin {
|
||||||
|
|
||||||
location /skynet/metadata {
|
location /skynet/metadata {
|
||||||
include /etc/nginx/conf.d/include/cors;
|
include /etc/nginx/conf.d/include/cors;
|
||||||
|
include /etc/nginx/conf.d/include/portal-access-check;
|
||||||
|
|
||||||
header_filter_by_lua_block {
|
header_filter_by_lua_block {
|
||||||
ngx.header["Skynet-Portal-Api"] = os.getenv("SKYNET_PORTAL_API")
|
ngx.header["Skynet-Portal-Api"] = os.getenv("SKYNET_PORTAL_API")
|
||||||
|
@ -331,6 +316,7 @@ location /skynet/metadata {
|
||||||
|
|
||||||
location /skynet/resolve {
|
location /skynet/resolve {
|
||||||
include /etc/nginx/conf.d/include/cors;
|
include /etc/nginx/conf.d/include/cors;
|
||||||
|
include /etc/nginx/conf.d/include/portal-access-check;
|
||||||
|
|
||||||
header_filter_by_lua_block {
|
header_filter_by_lua_block {
|
||||||
ngx.header["Skynet-Portal-Api"] = os.getenv("SKYNET_PORTAL_API")
|
ngx.header["Skynet-Portal-Api"] = os.getenv("SKYNET_PORTAL_API")
|
||||||
|
@ -357,7 +343,7 @@ location ~ "^/file/(([a-zA-Z0-9-_]{46}|[a-z0-9]{55})(/.*)?)$" {
|
||||||
include /etc/nginx/conf.d/include/location-skylink;
|
include /etc/nginx/conf.d/include/location-skylink;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /__internal/do/not/use/authenticated {
|
location /__internal/do/not/use/accounts {
|
||||||
include /etc/nginx/conf.d/include/cors;
|
include /etc/nginx/conf.d/include/cors;
|
||||||
|
|
||||||
charset utf-8;
|
charset utf-8;
|
||||||
|
@ -366,29 +352,16 @@ location /__internal/do/not/use/authenticated {
|
||||||
|
|
||||||
content_by_lua_block {
|
content_by_lua_block {
|
||||||
local json = require('cjson')
|
local json = require('cjson')
|
||||||
|
local accounts_enabled = require("skynet.account").accounts_enabled()
|
||||||
|
local is_auth_required = require("skynet.account").is_auth_required()
|
||||||
|
local is_authenticated = accounts_enabled and require("skynet.account").is_authenticated()
|
||||||
|
|
||||||
-- this block runs only when accounts are enabled
|
ngx.say(json.encode{
|
||||||
if not os.getenv("PORTAL_MODULES"):match("a") then
|
enabled = accounts_enabled,
|
||||||
ngx.say(json.encode{authenticated = false})
|
auth_required = is_auth_required,
|
||||||
return ngx.exit(ngx.HTTP_OK)
|
authenticated = is_authenticated,
|
||||||
end
|
|
||||||
|
|
||||||
local httpc = require("resty.http").new()
|
|
||||||
|
|
||||||
-- 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", {
|
|
||||||
headers = { ["Cookie"] = "skynet-jwt=" .. ngx.var.skynet_jwt }
|
|
||||||
})
|
})
|
||||||
|
return ngx.exit(ngx.HTTP_OK)
|
||||||
-- endpoint /user should return HTTP_OK for authenticated and HTTP_UNAUTHORIZED for not authenticated
|
|
||||||
if res and (res.status == ngx.HTTP_OK or res.status == ngx.HTTP_UNAUTHORIZED) then
|
|
||||||
ngx.say(json.encode{authenticated = res.status == ngx.HTTP_OK})
|
|
||||||
return ngx.exit(ngx.HTTP_OK)
|
|
||||||
else
|
|
||||||
ngx.log(ngx.ERR, "Failed accounts service request /user: ", err or ("[HTTP " .. res.status .. "] " .. res.body))
|
|
||||||
ngx.say(json.encode{authenticated = false})
|
|
||||||
return ngx.exit(ngx.HTTP_OK)
|
|
||||||
end
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
local _M = {}
|
||||||
|
|
||||||
|
-- fallback - remember to keep those updated
|
||||||
|
local anon_limits = { ["tierName"] = "anonymous", ["upload"] = 655360, ["download"] = 655360, ["maxUploadSize"] = 1073741824, ["registry"] = 250 }
|
||||||
|
|
||||||
|
-- no limits applied
|
||||||
|
local no_limits = { ["tierName"] = "internal", ["upload"] = 0, ["download"] = 0, ["maxUploadSize"] = 0, ["registry"] = 0 }
|
||||||
|
|
||||||
|
-- handle request exit when access to portal should be restricted
|
||||||
|
-- currently handles only HTTP_UNAUTHORIZED but can be extended in future
|
||||||
|
function _M.exit_access_unauthorized(message)
|
||||||
|
ngx.status = ngx.HTTP_UNAUTHORIZED
|
||||||
|
ngx.header["content-type"] = "text/plain"
|
||||||
|
ngx.say(message or "Portal operator restricted access to authenticated users only")
|
||||||
|
return ngx.exit(ngx.status)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.accounts_enabled()
|
||||||
|
return os.getenv("PORTAL_MODULES"):match("a") ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.get_account_limits()
|
||||||
|
local cjson = require('cjson')
|
||||||
|
|
||||||
|
if ngx.var.internal_no_limits == "true" then
|
||||||
|
return no_limits
|
||||||
|
end
|
||||||
|
|
||||||
|
if ngx.var.skynet_jwt == "" then
|
||||||
|
return anon_limits
|
||||||
|
end
|
||||||
|
|
||||||
|
if ngx.var.account_limits == "" then
|
||||||
|
local httpc = require("resty.http").new()
|
||||||
|
|
||||||
|
-- 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 }
|
||||||
|
})
|
||||||
|
|
||||||
|
-- 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))
|
||||||
|
ngx.var.account_limits = cjson.encode(anon_limits)
|
||||||
|
elseif res and res.status == ngx.HTTP_OK then
|
||||||
|
ngx.var.account_limits = res.body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return cjson.decode(ngx.var.account_limits)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- detect whether current user is authenticated
|
||||||
|
function _M.is_authenticated()
|
||||||
|
local limits = _M.get_account_limits()
|
||||||
|
|
||||||
|
return limits.tierName ~= anon_limits.tierName
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.is_auth_required()
|
||||||
|
return os.getenv("ACCOUNTS_AUTH_REQUIRED") == "true"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check whether access to portal should be restricted
|
||||||
|
-- based on the configurable environment variable
|
||||||
|
function _M.is_access_unauthorized()
|
||||||
|
return _M.accounts_enabled() and _M.is_auth_required() and not _M.is_authenticated()
|
||||||
|
end
|
||||||
|
|
||||||
|
return _M
|
|
@ -52,4 +52,12 @@ function _M.is_blocked(skylink)
|
||||||
return ngx.shared.blocklist:get_stale(hash) == true
|
return ngx.shared.blocklist:get_stale(hash) == true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- exit with 416 illegal content status code
|
||||||
|
function _M.exit_illegal()
|
||||||
|
ngx.status = ngx.HTTP_ILLEGAL
|
||||||
|
ngx.header["content-type"] = "text/plain"
|
||||||
|
ngx.say("Unavailable For Legal Reasons")
|
||||||
|
return ngx.exit(ngx.status)
|
||||||
|
end
|
||||||
|
|
||||||
return _M
|
return _M
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
local skynet_skylink = require("skynet/skylink")
|
local skynet_skylink = require("skynet.skylink")
|
||||||
|
|
||||||
describe("parse", function()
|
describe("parse", function()
|
||||||
local base32 = "0404dsjvti046fsua4ktor9grrpe76erq9jot9cvopbhsvsu76r4r30"
|
local base32 = "0404dsjvti046fsua4ktor9grrpe76erq9jot9cvopbhsvsu76r4r30"
|
||||||
|
|
|
@ -29,6 +29,7 @@ worker_processes auto;
|
||||||
env SKYNET_PORTAL_API;
|
env SKYNET_PORTAL_API;
|
||||||
env SKYNET_SERVER_API;
|
env SKYNET_SERVER_API;
|
||||||
env PORTAL_MODULES;
|
env PORTAL_MODULES;
|
||||||
|
env ACCOUNTS_AUTH_REQUIRED;
|
||||||
|
|
||||||
events {
|
events {
|
||||||
worker_connections 8192;
|
worker_connections 8192;
|
||||||
|
|
|
@ -78,7 +78,7 @@
|
||||||
"build": "gatsby build",
|
"build": "gatsby build",
|
||||||
"develop": "cross-env GATSBY_API_URL=https://siasky.net gatsby develop",
|
"develop": "cross-env GATSBY_API_URL=https://siasky.net gatsby develop",
|
||||||
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
|
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
|
||||||
"start": "yarn develop",
|
"start": "gatsby develop",
|
||||||
"serve": "gatsby serve",
|
"serve": "gatsby serve",
|
||||||
"clean": "gatsby clean",
|
"clean": "gatsby clean",
|
||||||
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1",
|
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1",
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as React from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import Link from "../Link";
|
import Link from "../Link";
|
||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
import useAuthenticatedStatus from "../../services/useAuthenticatedStatus";
|
import useAccounts from "../../services/useAccounts";
|
||||||
import { LogoWhiteText, LogoBlackText, MenuMobile, MenuMobileClose, DiscordSmall } from "../Icons";
|
import { LogoWhiteText, LogoBlackText, MenuMobile, MenuMobileClose, DiscordSmall } from "../Icons";
|
||||||
import { useWindowSize, useWindowScroll } from "react-use";
|
import { useWindowSize, useWindowScroll } from "react-use";
|
||||||
|
|
||||||
|
@ -24,8 +24,7 @@ const Navigation = ({ mode, uri }) => {
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
const isWindowTop = useWindowTop();
|
const isWindowTop = useWindowTop();
|
||||||
const { data: authenticationStatus } = useAuthenticatedStatus();
|
const { data: accounts } = useAccounts();
|
||||||
const authenticated = authenticationStatus?.authenticated ?? false;
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
@ -40,6 +39,8 @@ const Navigation = ({ mode, uri }) => {
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
const mobileMenuOffset = navRef.current ? navRef.current.offsetTop : 0;
|
const mobileMenuOffset = navRef.current ? navRef.current.offsetTop : 0;
|
||||||
|
const showLoginNavigation = accounts?.enabled && !accounts?.authenticated;
|
||||||
|
const showAccountNavigation = accounts?.enabled && accounts?.authenticated;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
|
@ -85,19 +86,19 @@ const Navigation = ({ mode, uri }) => {
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{!authenticated && (
|
{showLoginNavigation && (
|
||||||
<Link href="https://account.siasky.net" className="button-link-primary">
|
<>
|
||||||
Log in
|
<Link href="https://account.siasky.net" className="button-link-primary">
|
||||||
</Link>
|
Log in
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link href="https://account.siasky.net/auth/registration" className="button-primary">
|
||||||
|
Sign up
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!authenticated && (
|
{showAccountNavigation && (
|
||||||
<Link href="https://account.siasky.net/auth/registration" className="button-primary">
|
|
||||||
Sign up
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{authenticated && (
|
|
||||||
<Link href="https://account.siasky.net" className="button-primary">
|
<Link href="https://account.siasky.net" className="button-primary">
|
||||||
My account
|
My account
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -139,19 +140,19 @@ const Navigation = ({ mode, uri }) => {
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-12 pb-8 border-t border-palette-500">
|
<div className="pt-12 pb-8 border-t border-palette-500">
|
||||||
<div className="flex items-center justify-center px-4 space-x-6">
|
<div className="flex items-center justify-center px-4 space-x-6">
|
||||||
{!authenticated && (
|
{showLoginNavigation && (
|
||||||
<Link href="https://account.siasky.net" className="button-secondary-light">
|
<>
|
||||||
Log in
|
<Link href="https://account.siasky.net" className="button-secondary-light">
|
||||||
</Link>
|
Log in
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link href="https://account.siasky.net/auth/registration" className="button-primary">
|
||||||
|
Sign up
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!authenticated && (
|
{showAccountNavigation && (
|
||||||
<Link href="https://account.siasky.net/auth/registration" className="button-primary">
|
|
||||||
Sign up
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{authenticated && (
|
|
||||||
<Link href="https://account.siasky.net" className="button-primary">
|
<Link href="https://account.siasky.net" className="button-primary">
|
||||||
My account
|
My account
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import classNames from "classnames";
|
||||||
import path from "path-browserify";
|
import path from "path-browserify";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import useAuthenticatedStatus from "../../services/useAuthenticatedStatus";
|
import useAccounts from "../../services/useAccounts";
|
||||||
import Link from "../Link";
|
import Link from "../Link";
|
||||||
import UploaderElement from "./UploaderElement";
|
import UploaderElement from "./UploaderElement";
|
||||||
|
|
||||||
|
@ -29,12 +29,22 @@ const RegistrationLink = () => (
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const LogInLink = () => (
|
||||||
|
<Link
|
||||||
|
href="https://account.siasky.net/auth/login"
|
||||||
|
className="uppercase underline-primary hover:text-primary transition-colors duration-200"
|
||||||
|
>
|
||||||
|
Log in
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
|
||||||
const Uploader = () => {
|
const Uploader = () => {
|
||||||
const [mode, setMode] = React.useState("file");
|
const [mode, setMode] = React.useState("file");
|
||||||
const [uploads, setUploads] = React.useState([]);
|
const [uploads, setUploads] = React.useState([]);
|
||||||
|
|
||||||
const { data: authenticationStatus } = useAuthenticatedStatus();
|
const { data: accounts } = useAccounts();
|
||||||
const authenticated = authenticationStatus?.authenticated ?? false;
|
const showAccountFeatures = accounts?.enabled && !accounts?.auth_required && !accounts?.authenticated;
|
||||||
|
const disabledComponent = accounts?.enabled && accounts?.auth_required && !accounts?.authenticated;
|
||||||
|
|
||||||
const onUploadStateChange = React.useCallback((id, state) => {
|
const onUploadStateChange = React.useCallback((id, state) => {
|
||||||
setUploads((uploads) => {
|
setUploads((uploads) => {
|
||||||
|
@ -80,7 +90,7 @@ const Uploader = () => {
|
||||||
}, [inputElement, mode]);
|
}, [inputElement, mode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={classnames("relative", { "p-8": disabledComponent })}>
|
||||||
<div className="max-w-content mx-auto rounded-lg shadow bg-white z-0 relative">
|
<div className="max-w-content mx-auto rounded-lg shadow bg-white z-0 relative">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<button
|
<button
|
||||||
|
@ -115,6 +125,7 @@ const Uploader = () => {
|
||||||
"drop-active": isDragActive,
|
"drop-active": isDragActive,
|
||||||
})}
|
})}
|
||||||
{...getRootProps()}
|
{...getRootProps()}
|
||||||
|
disabled={true}
|
||||||
>
|
>
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
<div
|
<div
|
||||||
|
@ -131,11 +142,13 @@ const Uploader = () => {
|
||||||
{mode === "directory" && <span>Drop any folder with an index.html file to deploy to Skynet</span>}
|
{mode === "directory" && <span>Drop any folder with an index.html file to deploy to Skynet</span>}
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute left-1/2 -bottom-4 desktop:-bottom-8">
|
{!disabledComponent && (
|
||||||
<div className="relative -left-1/2 transform transition-transform hover:rotate-180" role="button">
|
<div className="absolute left-1/2 -bottom-4 desktop:-bottom-8">
|
||||||
<Add />
|
<div className="relative -left-1/2 transform transition-transform hover:rotate-180" role="button">
|
||||||
|
<Add />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{uploads.length > 0 && (
|
{uploads.length > 0 && (
|
||||||
|
@ -144,7 +157,7 @@ const Uploader = () => {
|
||||||
<UploaderElement key={upload.id} onUploadStateChange={onUploadStateChange} upload={upload} />
|
<UploaderElement key={upload.id} onUploadStateChange={onUploadStateChange} upload={upload} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{!authenticated && (
|
{showAccountFeatures && (
|
||||||
<div className="z-0 relative flex flex-col items-center space-y-1 pt-8">
|
<div className="z-0 relative flex flex-col items-center space-y-1 pt-8">
|
||||||
<Info />
|
<Info />
|
||||||
|
|
||||||
|
@ -166,7 +179,7 @@ const Uploader = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{uploads.length === 0 && !authenticated && (
|
{uploads.length === 0 && showAccountFeatures && (
|
||||||
<div className="z-0 relative flex flex-col items-center space-y-1 mt-10">
|
<div className="z-0 relative flex flex-col items-center space-y-1 mt-10">
|
||||||
<Unlock />
|
<Unlock />
|
||||||
<p className="text-sm font-light text-palette-600">
|
<p className="text-sm font-light text-palette-600">
|
||||||
|
@ -174,6 +187,18 @@ const Uploader = () => {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{disabledComponent && (
|
||||||
|
<div className="absolute inset-0 bg-palette-500 bg-opacity-90 rounded-lg">
|
||||||
|
<div class="flex h-full">
|
||||||
|
<div class="m-auto">
|
||||||
|
<h4 className="font-light text-palette-100 text-lg mt-2 text-center">
|
||||||
|
<LogInLink /> or <RegistrationLink /> for free
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
const prefix = process.env.GATSBY_API_URL ?? "";
|
||||||
|
const fetcher = (url) => fetch(`${prefix}${url}`).then((response) => response.json());
|
||||||
|
|
||||||
|
export default function useAccounts() {
|
||||||
|
return useSWR("/__internal/do/not/use/accounts", fetcher);
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
import useSWR from "swr";
|
|
||||||
|
|
||||||
const fetcher = (url) => fetch(url).then((response) => response.json());
|
|
||||||
|
|
||||||
export default function useAuthenticatedStatus() {
|
|
||||||
return useSWR("/__internal/do/not/use/authenticated", fetcher);
|
|
||||||
}
|
|
Reference in New Issue