diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 261f9b54..97549aab 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,42 +1,14 @@ version: 2 updates: - - package-ecosystem: npm - directory: "/packages/dnslink-api" - schedule: - interval: monthly - - package-ecosystem: npm - directory: "/packages/handshake-api" - schedule: - interval: monthly - package-ecosystem: npm directory: "/packages/health-check" schedule: interval: monthly - - package-ecosystem: npm - directory: "/packages/website" - schedule: - interval: monthly - - package-ecosystem: docker - directory: "/docker/nginx" - schedule: - interval: monthly - package-ecosystem: docker directory: "/docker/sia" schedule: interval: monthly - - package-ecosystem: docker - directory: "/packages/dnslink-api" - schedule: - interval: monthly - - package-ecosystem: docker - directory: "/packages/handshake-api" - schedule: - interval: monthly - package-ecosystem: docker directory: "/packages/health-check" schedule: interval: monthly - - package-ecosystem: docker - directory: "/packages/website" - schedule: - interval: monthly diff --git a/.github/workflows/deploy-dashboard-v2-storybook.yml b/.github/workflows/deploy-dashboard-v2-storybook.yml deleted file mode 100644 index b4591b59..00000000 --- a/.github/workflows/deploy-dashboard-v2-storybook.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Build Storybook - packages/dashboard-v2 - -on: - push: - branches: - - master - paths: - - "packages/dashboard-v2/**" - pull_request: - paths: - - "packages/dashboard-v2/**" - -defaults: - run: - working-directory: packages/dashboard-v2 - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16.x - - run: yarn install - - run: yarn build-storybook - - name: "Deploy to Skynet" - uses: skynetlabs/deploy-to-skynet-action@v2 - with: - upload-dir: packages/dashboard-v2/storybook-build - github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml deleted file mode 100644 index 334e9718..00000000 --- a/.github/workflows/deploy-website.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Deploy website to Skynet - -on: - push: - branches: - - master - paths: - - "packages/website/**" - pull_request: - paths: - - "packages/website/**" - -defaults: - run: - working-directory: packages/website - -env: - PORTAL_DOMAIN: siasky.net - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 16.x - cache: yarn - cache-dependency-path: packages/website/yarn.lock - - - run: yarn - - run: yarn build - - - name: "Integration tests" - uses: cypress-io/github-action@v3 - env: - CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - working-directory: packages/website - install: false - record: true - start: yarn develop - wait-on: http://localhost:8000 - wait-on-timeout: 120 - config: baseUrl=http://localhost:8000 - - - name: "Deploy to Skynet" - uses: skynetlabs/deploy-to-skynet-action@v2 - with: - upload-dir: packages/website/public - github-token: ${{ secrets.GITHUB_TOKEN }} - registry-seed: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && secrets.WEBSITE_REGISTRY_SEED || '' }} diff --git a/.github/workflows/lint-dockerfiles.yml b/.github/workflows/lint-dockerfiles.yml index 7c30ab9f..3eb4248a 100644 --- a/.github/workflows/lint-dockerfiles.yml +++ b/.github/workflows/lint-dockerfiles.yml @@ -13,14 +13,10 @@ jobs: fail-fast: false matrix: dockerfile: - - docker/nginx/Dockerfile - - docker/nginx/testing/Dockerfile - docker/sia/Dockerfile - packages/dashboard-v2/Dockerfile - - packages/dnslink-api/Dockerfile - packages/handshake-api/Dockerfile - packages/health-check/Dockerfile - - packages/website/Dockerfile steps: - uses: actions/checkout@v3 - uses: hadolint/hadolint-action@v2.0.0 diff --git a/.github/workflows/lint-packages-dashboard-v2.yml b/.github/workflows/lint-packages-dashboard-v2.yml deleted file mode 100644 index a8577562..00000000 --- a/.github/workflows/lint-packages-dashboard-v2.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Lint - packages/dashboard-v2 - -on: - pull_request: - paths: - - packages/dashboard-v2/** - -defaults: - run: - working-directory: packages/dashboard-v2 - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16.x - - - run: yarn - - run: yarn prettier --check - - run: yarn lint diff --git a/.github/workflows/lint-packages-dnslink-api.yml b/.github/workflows/lint-packages-dnslink-api.yml deleted file mode 100644 index ea839f75..00000000 --- a/.github/workflows/lint-packages-dnslink-api.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Lint - packages/dnslink-api - -on: - pull_request: - paths: - - packages/dnslink-api/** - -defaults: - run: - working-directory: packages/dnslink-api - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16.x - - - run: yarn - - run: yarn prettier --check . diff --git a/.github/workflows/lint-packages-handshake-api.yml b/.github/workflows/lint-packages-handshake-api.yml deleted file mode 100644 index 3e6404c6..00000000 --- a/.github/workflows/lint-packages-handshake-api.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Lint - packages/handshake-api - -on: - pull_request: - paths: - - packages/handshake-api/** - -defaults: - run: - working-directory: packages/handshake-api - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16.x - - - run: yarn - - run: yarn prettier --check . diff --git a/.github/workflows/lint-packages-website.yml b/.github/workflows/lint-packages-website.yml deleted file mode 100644 index 795ed176..00000000 --- a/.github/workflows/lint-packages-website.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Lint - packages/website - -on: - pull_request: - paths: - - packages/website/** - -defaults: - run: - working-directory: packages/website - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16.x - - - run: yarn - - run: yarn prettier --check . diff --git a/.github/workflows/nginx-lua-unit-tests.yml b/.github/workflows/nginx-lua-unit-tests.yml deleted file mode 100644 index 5af9c101..00000000 --- a/.github/workflows/nginx-lua-unit-tests.yml +++ /dev/null @@ -1,46 +0,0 @@ -# Install and run unit tests with busted -# Docs: http://olivinelabs.com/busted/ - -name: Nginx Lua Unit Tests - -on: - push: - branches: - - master - paths: - - docker/nginx/libs/** - pull_request: - paths: - - docker/nginx/libs/** - -jobs: - test: - runs-on: ubuntu-latest - container: openresty/openresty:1.19.9.1-focal - steps: - - uses: actions/checkout@v3 - - - name: Install Dependencies - run: | - luarocks install lua-resty-http - luarocks install hasher - luarocks install busted - luarocks install luacov - luarocks install luacheck - - - name: Lint Code With Luacheck - run: luacheck docker/nginx/libs --std ngx_lua+busted - - - name: Run Tests With Busted - # ran from root repo directory; produces luacov.stats.out file - run: docker/nginx/testing/rbusted --lpath='docker/nginx/libs/?.lua;docker/nginx/libs/?/?.lua' --verbose --coverage --pattern=spec docker/nginx/libs - - - name: Generate Code Coverage Report With Luacov - # requires config file in cwd; produces luacov.report.out file - run: cp docker/nginx/testing/.luacov . && luacov && rm .luacov - - - uses: codecov/codecov-action@v3 - with: - root_dir: ${GITHUB_WORKSPACE} - files: ./luacov.report.out - flags: nginx-lua diff --git a/.gitignore b/.gitignore index 8a98ee28..78c3ccb9 100644 --- a/.gitignore +++ b/.gitignore @@ -67,10 +67,6 @@ yarn-error.log # Yarn Integrity file .yarn-integrity -# Cypress -packages/website/cypress/screenshots -packages/website/cypress/videos - # Docker data docker/data diff --git a/docker-compose.accounts.yml b/docker-compose.accounts.yml index cd40001d..a069325d 100644 --- a/docker-compose.accounts.yml +++ b/docker-compose.accounts.yml @@ -82,8 +82,9 @@ services: # ============================================================ # dashboard-v2: # build: - # context: ./packages/dashboard-v2 + # context: https://github.com/SkynetLabs/webportal-accounts-dashboard.git#main # dockerfile: Dockerfile + # image: skynetlabs/webportal-accounts-dashboard@1.0.1 # container_name: dashboard-v2 # restart: unless-stopped # logging: *default-logging diff --git a/docker-compose.yml b/docker-compose.yml index 08095730..5125e370 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -57,9 +57,9 @@ services: nginx: # uncomment "build" and comment out "image" to build from sources # build: - # context: https://github.com/SkynetLabs/skynet-webportal.git#master - # dockerfile: ./docker/nginx/Dockerfile - image: skynetlabs/nginx + # context: https://github.com/SkynetLabs/webportal-nginx.git#main + # dockerfile: Dockerfile + image: skynetlabs/webportal-nginx:0.1.1 container_name: nginx restart: unless-stopped logging: *default-logging @@ -72,10 +72,6 @@ services: - ./docker/data/nginx/skynet:/data/nginx/skynet:ro - ./docker/data/sia/apipassword:/data/sia/apipassword:ro - ./docker/data/certbot:/etc/letsencrypt - - ./docker/nginx/libs:/etc/nginx/libs - - ./docker/nginx/conf.d:/etc/nginx/conf.d - - ./docker/nginx/conf.d.templates:/etc/nginx/templates - - ./docker/nginx/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf networks: shared: ipv4_address: 10.10.10.30 @@ -91,9 +87,9 @@ services: website: # uncomment "build" and comment out "image" to build from sources # build: - # context: https://github.com/SkynetLabs/skynet-webportal.git#master - # dockerfile: ./packages/website/Dockerfile - image: skynetlabs/website + # context: https://github.com/SkynetLabs/webportal-website.git#main + # dockerfile: Dockerfile + image: skynetlabs/webportal-website:0.1.0 container_name: website restart: unless-stopped logging: *default-logging @@ -132,9 +128,9 @@ services: handshake-api: # uncomment "build" and comment out "image" to build from sources # build: - # context: https://github.com/SkynetLabs/skynet-webportal.git#master - # dockerfile: ./packages/handshake-api/Dockerfile - image: skynetlabs/handshake-api + # context: https://github.com/SkynetLabs/webportal-handshake-api.git#main + # dockerfile: Dockerfile + image: skynetlabs/webportal-handshake-api:0.1.1 container_name: handshake-api restart: unless-stopped logging: *default-logging @@ -156,9 +152,9 @@ services: dnslink-api: # uncomment "build" and comment out "image" to build from sources # build: - # context: https://github.com/SkynetLabs/skynet-webportal.git#master - # dockerfile: ./packages/dnslink-api/Dockerfile - image: skynetlabs/dnslink-api + # context: https://github.com/SkynetLabs/webportal-dnslink-api.git#main + # dockerfile: Dockerfile + image: skynetlabs/webportal-dnslink-api:0.1.1 container_name: dnslink-api restart: unless-stopped logging: *default-logging diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile deleted file mode 100644 index f35c0aff..00000000 --- a/docker/nginx/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM openresty/openresty:1.19.9.1-focal - -WORKDIR / - -RUN apt-get update && apt-get --no-install-recommends -y install bc=1.07.1-2build1 && \ - apt-get clean && rm -rf /var/lib/apt/lists/* && \ - luarocks install lua-resty-http && \ - luarocks install hasher - -# reload nginx every 6 hours (for reloading certificates) -ENV NGINX_ENTRYPOINT_RELOAD_EVERY_X_HOURS 6 - -# copy entrypoint and entrypoint scripts -COPY docker/nginx/docker-entrypoint.sh / -COPY docker/nginx/docker-entrypoint.d /docker-entrypoint.d - -ENTRYPOINT ["/docker-entrypoint.sh"] - -STOPSIGNAL SIGQUIT - -CMD ["nginx", "-g", "daemon off;"] diff --git a/docker/nginx/conf.d.templates/server.account.conf.template b/docker/nginx/conf.d.templates/server.account.conf.template deleted file mode 100644 index 8507c407..00000000 --- a/docker/nginx/conf.d.templates/server.account.conf.template +++ /dev/null @@ -1,44 +0,0 @@ -server { - server_name account.${PORTAL_DOMAIN}; # example: account.siasky.net - - include /etc/nginx/conf.d/server/server.http; -} - -server { - server_name account.${PORTAL_DOMAIN}; # example: account.siasky.net - - set_by_lua_block $skynet_portal_domain { return "${PORTAL_DOMAIN}" } - set_by_lua_block $skynet_server_domain { - -- fall back to portal domain if server domain is not defined - if "${SERVER_DOMAIN}" == "" then - return "${PORTAL_DOMAIN}" - end - return "${SERVER_DOMAIN}" - } - - include /etc/nginx/conf.d/server/server.account; -} - -server { - server_name account.${SERVER_DOMAIN}; # example: account.eu-ger-1.siasky.net - - include /etc/nginx/conf.d/server/server.http; - - set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } -} - -server { - server_name account.${SERVER_DOMAIN}; # example: account.eu-ger-1.siasky.net - - set_by_lua_block $skynet_portal_domain { - -- when accessing portal directly through server domain, portal domain should be set to server domain - -- motivation: skynet-js uses Skynet-Portal-Api header (that is set to $skynet_portal_domain) to detect current - -- portal address and it should be server domain when accessing specific server by its domain address - return "${SERVER_DOMAIN}" - } - set_by_lua_block $skynet_server_domain { return "${SERVER_DOMAIN}" } - - include /etc/nginx/conf.d/server/server.account; - - set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } -} diff --git a/docker/nginx/conf.d.templates/server.api.conf.template b/docker/nginx/conf.d.templates/server.api.conf.template deleted file mode 100644 index 5f742127..00000000 --- a/docker/nginx/conf.d.templates/server.api.conf.template +++ /dev/null @@ -1,44 +0,0 @@ -server { - server_name ${PORTAL_DOMAIN}; # example: siasky.net - - include /etc/nginx/conf.d/server/server.http; -} - -server { - server_name ${PORTAL_DOMAIN}; # example: siasky.net - - set_by_lua_block $skynet_portal_domain { return "${PORTAL_DOMAIN}" } - set_by_lua_block $skynet_server_domain { - -- fall back to portal domain if server domain is not defined - if "${SERVER_DOMAIN}" == "" then - return "${PORTAL_DOMAIN}" - end - return "${SERVER_DOMAIN}" - } - - include /etc/nginx/conf.d/server/server.api; -} - -server { - server_name ${SERVER_DOMAIN}; # example: eu-ger-1.siasky.net - - include /etc/nginx/conf.d/server/server.http; - - set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } -} - -server { - server_name ${SERVER_DOMAIN}; # example: eu-ger-1.siasky.net - - set_by_lua_block $skynet_portal_domain { - -- when accessing portal directly through server domain, portal domain should be set to server domain - -- motivation: skynet-js uses Skynet-Portal-Api header (that is set to $skynet_portal_domain) to detect current - -- portal address and it should be server domain when accessing specific server by its domain address - return "${SERVER_DOMAIN}" - } - set_by_lua_block $skynet_server_domain { return "${SERVER_DOMAIN}" } - - include /etc/nginx/conf.d/server/server.api; - - set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } -} diff --git a/docker/nginx/conf.d.templates/server.dnslink.conf.template b/docker/nginx/conf.d.templates/server.dnslink.conf.template deleted file mode 100644 index 95c623b6..00000000 --- a/docker/nginx/conf.d.templates/server.dnslink.conf.template +++ /dev/null @@ -1,25 +0,0 @@ -lua_shared_dict dnslink 10m; - -server { - listen 80 default_server; - - include /etc/nginx/conf.d/server/server.dnslink; -} - -server { - listen 443 default_server; - - ssl_certificate /etc/ssl/local-certificate.crt; - ssl_certificate_key /etc/ssl/local-certificate.key; - - set_by_lua_block $skynet_portal_domain { return "${PORTAL_DOMAIN}" } - set_by_lua_block $skynet_server_domain { - -- fall back to portal domain if server domain is not defined - if "${SERVER_DOMAIN}" == "" then - return "${PORTAL_DOMAIN}" - end - return "${SERVER_DOMAIN}" - } - - include /etc/nginx/conf.d/server/server.dnslink; -} diff --git a/docker/nginx/conf.d.templates/server.hns.conf.template b/docker/nginx/conf.d.templates/server.hns.conf.template deleted file mode 100644 index f7bb43fb..00000000 --- a/docker/nginx/conf.d.templates/server.hns.conf.template +++ /dev/null @@ -1,46 +0,0 @@ -server { - server_name *.hns.${PORTAL_DOMAIN}; # example: *.hns.siasky.net - - include /etc/nginx/conf.d/server/server.http; -} - -server { - server_name *.hns.${PORTAL_DOMAIN}; # example: *.hns.siasky.net - - set_by_lua_block $skynet_portal_domain { return "${PORTAL_DOMAIN}" } - set_by_lua_block $skynet_server_domain { - -- fall back to portal domain if server domain is not defined - if "${SERVER_DOMAIN}" == "" then - return "${PORTAL_DOMAIN}" - end - return "${SERVER_DOMAIN}" - } - - proxy_set_header Host ${PORTAL_DOMAIN}; - include /etc/nginx/conf.d/server/server.hns; -} - -server { - server_name *.hns.${SERVER_DOMAIN}; # example: *.hns.eu-ger-1.siasky.net - - include /etc/nginx/conf.d/server/server.http; - - set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } -} - -server { - server_name *.hns.${SERVER_DOMAIN}; # example: *.hns.eu-ger-1.siasky.net - - set_by_lua_block $skynet_portal_domain { - -- when accessing portal directly through server domain, portal domain should be set to server domain - -- motivation: skynet-js uses Skynet-Portal-Api header (that is set to $skynet_portal_domain) to detect current - -- portal address and it should be server domain when accessing specific server by its domain address - return "${SERVER_DOMAIN}" - } - set_by_lua_block $skynet_server_domain { return "${SERVER_DOMAIN}" } - - proxy_set_header Host ${SERVER_DOMAIN}; - include /etc/nginx/conf.d/server/server.hns; - - set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } -} diff --git a/docker/nginx/conf.d.templates/server.skylink.conf.template b/docker/nginx/conf.d.templates/server.skylink.conf.template deleted file mode 100644 index 0d337abb..00000000 --- a/docker/nginx/conf.d.templates/server.skylink.conf.template +++ /dev/null @@ -1,44 +0,0 @@ -server { - server_name *.${PORTAL_DOMAIN}; # example: *.siasky.net - - include /etc/nginx/conf.d/server/server.http; -} - -server { - server_name *.${PORTAL_DOMAIN}; # example: *.siasky.net - - set_by_lua_block $skynet_portal_domain { return "${PORTAL_DOMAIN}" } - set_by_lua_block $skynet_server_domain { - -- fall back to portal domain if server domain is not defined - if "${SERVER_DOMAIN}" == "" then - return "${PORTAL_DOMAIN}" - end - return "${SERVER_DOMAIN}" - } - - include /etc/nginx/conf.d/server/server.skylink; -} - -server { - server_name *.${SERVER_DOMAIN}; # example: *.eu-ger-1.siasky.net - - include /etc/nginx/conf.d/server/server.http; - - set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } -} - -server { - server_name *.${SERVER_DOMAIN}; # example: *.eu-ger-1.siasky.net - - set_by_lua_block $skynet_portal_domain { - -- when accessing portal directly through server domain, portal domain should be set to server domain - -- motivation: skynet-js uses Skynet-Portal-Api header (that is set to $skynet_portal_domain) to detect current - -- portal address and it should be server domain when accessing specific server by its domain address - return "${SERVER_DOMAIN}" - } - set_by_lua_block $skynet_server_domain { return "${SERVER_DOMAIN}" } - - include /etc/nginx/conf.d/server/server.skylink; - - set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } -} diff --git a/docker/nginx/conf.d/dhparam.pem b/docker/nginx/conf.d/dhparam.pem deleted file mode 100644 index 9b182b72..00000000 --- a/docker/nginx/conf.d/dhparam.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN DH PARAMETERS----- -MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz -+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a -87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 -YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi -7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD -ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== ------END DH PARAMETERS----- diff --git a/docker/nginx/conf.d/gzip.conf b/docker/nginx/conf.d/gzip.conf deleted file mode 100644 index 7d7a962c..00000000 --- a/docker/nginx/conf.d/gzip.conf +++ /dev/null @@ -1,18 +0,0 @@ -# enables gzip compression -gzip on; - -# set the gzip compression level (1-9) -gzip_comp_level 6; - -# tells proxies to cache both gzipped and regular versions of a resource -gzip_vary on; - -# informs NGINX to not compress anything smaller than the defined size -gzip_min_length 256; - -# compress data even for clients that are connecting via proxies if a response header includes -# the "expired", "no-cache", "no-store", "private", and "Authorization" parameters -gzip_proxied expired no-cache no-store private auth; - -# enables the types of files that can be compressed -gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon; diff --git a/docker/nginx/conf.d/include/cors b/docker/nginx/conf.d/include/cors deleted file mode 100644 index a98074c5..00000000 --- a/docker/nginx/conf.d/include/cors +++ /dev/null @@ -1,9 +0,0 @@ -if ($request_method = 'OPTIONS') { - include /etc/nginx/conf.d/include/cors-headers; - more_set_headers 'Access-Control-Max-Age: 1728000'; - more_set_headers 'Content-Type: text/plain; charset=utf-8'; - more_set_headers 'Content-Length: 0'; - return 204; -} - -include /etc/nginx/conf.d/include/cors-headers; diff --git a/docker/nginx/conf.d/include/cors-headers b/docker/nginx/conf.d/include/cors-headers deleted file mode 100644 index f6a303cd..00000000 --- a/docker/nginx/conf.d/include/cors-headers +++ /dev/null @@ -1,5 +0,0 @@ -more_set_headers 'Access-Control-Allow-Origin: $http_origin'; -more_set_headers 'Access-Control-Allow-Credentials: true'; -more_set_headers 'Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE'; -more_set_headers 'Access-Control-Allow-Headers: DNT,User-Agent,X-Requested-With,If-Modified-Since,If-None-Match,Cache-Control,Content-Type,Range,X-HTTP-Method-Override,upload-offset,upload-metadata,upload-length,tus-version,tus-resumable,tus-extension,tus-max-size,upload-concat,location,Skynet-API-Key'; -more_set_headers 'Access-Control-Expose-Headers: Content-Length,Content-Range,ETag,Accept-Ranges,Skynet-File-Metadata,Skynet-Skylink,Skynet-Proof,Skynet-Portal-Api,Skynet-Server-Api,upload-offset,upload-metadata,upload-length,tus-version,tus-resumable,tus-extension,tus-max-size,upload-concat,location'; diff --git a/docker/nginx/conf.d/include/generate-siapath b/docker/nginx/conf.d/include/generate-siapath deleted file mode 100644 index 51ef8d84..00000000 --- a/docker/nginx/conf.d/include/generate-siapath +++ /dev/null @@ -1,11 +0,0 @@ -# Extract 2 sets of 2 characters from $request_id and assign to $dir1, $dir2 -# respectfully. The rest of the $request_id is going to be assigned to $dir3. -# We use those variables to automatically generate a unique path for the uploaded file. -# This ensures that not all uploaded files end up in the same directory, which is something -# that causes performance issues in the renter. -# Example path result: /af/24/9bc5ec894920ccc45634dc9a8065 -if ($request_id ~* "(\w{2})(\w{2})(\w+)") { - set $dir1 $1; - set $dir2 $2; - set $dir3 $3; -} diff --git a/docker/nginx/conf.d/include/init-optional-variables b/docker/nginx/conf.d/include/init-optional-variables deleted file mode 100644 index 406dfe98..00000000 --- a/docker/nginx/conf.d/include/init-optional-variables +++ /dev/null @@ -1,15 +0,0 @@ -# optional variables initialisation - those variables are used in log_format -# but are not set on every route so we need to initialise them with empty value -# because otherwise logger with throw error - -# set only on hns routes -set $hns_domain ""; - -# set only if server has been access through SERVER_DOMAIN -set $server_alias ""; - -# expose skylink variable so we can use it in access log -set $skylink ""; - -# cached account limits (json string) - applies only if accounts are enabled -set $account_limits ""; diff --git a/docker/nginx/conf.d/include/local-network-only b/docker/nginx/conf.d/include/local-network-only deleted file mode 100644 index 84fa31f3..00000000 --- a/docker/nginx/conf.d/include/local-network-only +++ /dev/null @@ -1,3 +0,0 @@ -allow 127.0.0.1/32; # localhost -allow 10.10.10.0/24; # docker network -deny all; diff --git a/docker/nginx/conf.d/include/location-hns b/docker/nginx/conf.d/include/location-hns deleted file mode 100644 index 0ddb62d7..00000000 --- a/docker/nginx/conf.d/include/location-hns +++ /dev/null @@ -1,94 +0,0 @@ -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 -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 -rewrite_by_lua_block { - local json = require('cjson') - local httpc = require("resty.http").new() - - -- make a get request to /hnsres endpoint with the domain name from request_uri - -- 10.10.10.50 points to handshake-api service (alias not available when using resty-http) - local hnsres_res, hnsres_err = httpc:request_uri("http://10.10.10.50:3100/hnsres/" .. ngx.var.hns_domain) - - -- print error and exit with 500 or exit with response if status is not 200 - if hnsres_err or (hnsres_res and hnsres_res.status ~= ngx.HTTP_OK) then - ngx.status = (hnsres_err and ngx.HTTP_INTERNAL_SERVER_ERROR) or hnsres_res.status - ngx.header["content-type"] = "text/plain" - ngx.say(hnsres_err or hnsres_res.body) - return ngx.exit(ngx.status) - end - - -- since /hnsres endpoint response is a json, we need to decode it before we access it - -- example response: '{"skylink":"sia://XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg"}' - local hnsres_json = json.decode(hnsres_res.body) - - -- define local variable containing rest of the skylink if provided - local skylink_rest - - if hnsres_json.skylink then - -- try to match the skylink with sia:// prefix - skylink, skylink_rest = string.match(hnsres_json.skylink, "sia://([^/?]+)(.*)") - - -- in case the skylink did not match, assume that there is no sia:// prefix and try to match again - if skylink == nil then - skylink, skylink_rest = string.match(hnsres_json.skylink, "/?([^/?]+)(.*)") - end - elseif hnsres_json.registry then - local publickey = hnsres_json.registry.publickey - local datakey = hnsres_json.registry.datakey - - -- make a get request to /skynet/registry endpoint with the credentials from text record - -- 10.10.10.10 points to sia service (alias not available when using resty-http) - local registry_res, registry_err = httpc:request_uri("http://10.10.10.10:9980/skynet/registry?publickey=" .. publickey .. "&datakey=" .. datakey, { - headers = { ["User-Agent"] = "Sia-Agent" } - }) - - -- print error and exit with 500 or exit with response if status is not 200 - if registry_err or (registry_res and registry_res.status ~= ngx.HTTP_OK) then - ngx.status = (registry_err and ngx.HTTP_INTERNAL_SERVER_ERROR) or registry_res.status - ngx.header["content-type"] = "text/plain" - ngx.say(registry_err or registry_res.body) - return ngx.exit(ngx.status) - end - - -- since /skynet/registry endpoint response is a json, we need to decode it before we access it - local registry_json = json.decode(registry_res.body) - -- response will contain a hex encoded skylink, we need to decode it - local data = (registry_json.data:gsub('..', function (cc) - return string.char(tonumber(cc, 16)) - end)) - - skylink = data - end - - -- fail with a generic 404 if skylink has not been extracted from a valid /hnsres response for some reason - if not skylink then - return ngx.exit(ngx.HTTP_NOT_FOUND) - end - - ngx.var.skylink = skylink - if ngx.var.path == "/" and skylink_rest ~= nil and skylink_rest ~= "" and skylink_rest ~= "/" then - ngx.var.path = skylink_rest - end -} - -# we proxy to another nginx location rather than directly to siad because we do not want to deal with caching here -proxy_pass https://127.0.0.1/$skylink$path$is_args$args; - -# in case siad returns location header, we need to replace the skylink with the domain name -header_filter_by_lua_block { - ngx.header["Skynet-Portal-Api"] = ngx.var.scheme .. "://" .. ngx.var.skynet_portal_domain - ngx.header["Skynet-Server-Api"] = ngx.var.scheme .. "://" .. ngx.var.skynet_server_domain - - if ngx.header.location then - -- match location redirect part after the skylink - local path = string.match(ngx.header.location, "[^/?]+(.*)"); - - -- because siad will set the location header to ie. XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg/index.html - -- we need to replace the skylink with the domain_name so we are not redirected to skylink - ngx.header.location = ngx.var.hns_domain .. path - end -} diff --git a/docker/nginx/conf.d/include/location-skylink b/docker/nginx/conf.d/include/location-skylink deleted file mode 100644 index b214e3a9..00000000 --- a/docker/nginx/conf.d/include/location-skylink +++ /dev/null @@ -1,53 +0,0 @@ -include /etc/nginx/conf.d/include/cors; - -limit_conn downloads_by_ip 100; # ddos protection: max 100 downloads at a time - -# ensure that skylink that we pass around is base64 encoded (transform base32 encoded ones) -# this is important because we want only one format in cache keys and logs -set_by_lua_block $skylink { return require("skynet.skylink").parse(ngx.var.skylink) } - -# default download rate to unlimited -set $limit_rate 0; - -access_by_lua_block { - if require("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() - end - - -- check if portal is in subscription only mode - if require("skynet.account").is_access_forbidden() then - return require("skynet.account").exit_access_forbidden() - end - - -- get account limits of currently authenticated user - local limits = require("skynet.account").get_account_limits() - - -- apply download speed limit - ngx.var.limit_rate = limits.download - end -} - -limit_rate_after 512k; -limit_rate $limit_rate; - -proxy_read_timeout 600; -proxy_set_header User-Agent: Sia-Agent; - -proxy_pass http://sia:9980/skynet/skylink/$skylink$path$is_args$args; - -log_by_lua_block { - local skynet_account = require("skynet.account") - local skynet_modules = require("skynet.modules") - local skynet_scanner = require("skynet.scanner") - local skynet_tracker = require("skynet.tracker") - - if skynet_modules.is_enabled("a") then - skynet_tracker.track_download(ngx.header["Skynet-Skylink"], ngx.status, skynet_account.get_auth_headers(), ngx.var.body_bytes_sent) - end - - if skynet_modules.is_enabled("s") then - skynet_scanner.scan_skylink(ngx.header["Skynet-Skylink"]) - end -} diff --git a/docker/nginx/conf.d/include/location-skynet-registry b/docker/nginx/conf.d/include/location-skynet-registry deleted file mode 100644 index cd450be9..00000000 --- a/docker/nginx/conf.d/include/location-skynet-registry +++ /dev/null @@ -1,38 +0,0 @@ -include /etc/nginx/conf.d/include/cors; -include /etc/nginx/conf.d/include/sia-auth; - -limit_req zone=registry_access_by_ip burst=600 nodelay; -limit_req zone=registry_access_by_ip_throttled burst=200 nodelay; - -proxy_set_header User-Agent: Sia-Agent; -proxy_read_timeout 600; # siad should timeout with 404 after 5 minutes -proxy_pass http://sia:9980/skynet/registry; - -access_by_lua_block { - if require("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() - end - - -- check if portal is in subscription only mode - if require("skynet.account").is_access_forbidden() then - return require("skynet.account").exit_access_forbidden() - end - - -- get account limits of currently authenticated user - local limits = require("skynet.account").get_account_limits() - - -- apply registry rate limits (forced delay) - if limits.registry > 0 then - ngx.sleep(limits.registry / 1000) - end - end -} - -log_by_lua_block { - local skynet_account = require("skynet.account") - local skynet_tracker = require("skynet.tracker") - - skynet_tracker.track_registry(ngx.status, skynet_account.get_auth_headers(), ngx.req.get_method()) -} diff --git a/docker/nginx/conf.d/include/portal-access-check b/docker/nginx/conf.d/include/portal-access-check deleted file mode 100644 index 1559d863..00000000 --- a/docker/nginx/conf.d/include/portal-access-check +++ /dev/null @@ -1,11 +0,0 @@ -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 - - -- check if portal is in subscription only mode - if require("skynet.account").is_access_forbidden() then - return require("skynet.account").exit_access_forbidden() - end -} diff --git a/docker/nginx/conf.d/include/proxy-pass-internal b/docker/nginx/conf.d/include/proxy-pass-internal deleted file mode 100644 index c41f33bc..00000000 --- a/docker/nginx/conf.d/include/proxy-pass-internal +++ /dev/null @@ -1,10 +0,0 @@ -# ---------------------------------------------------------------- -# this file should be included on all locations that proxy_pass to -# another nginx location - internal nginx traffic -# ---------------------------------------------------------------- - -# increase the timeout on internal nginx proxy_pass locations to a -# value that is significantly higher than expected and let the end -# location handle correct timeout -proxy_read_timeout 30m; -proxy_send_timeout 30m; diff --git a/docker/nginx/conf.d/include/ratelimited b/docker/nginx/conf.d/include/ratelimited deleted file mode 100644 index 3e5b5c00..00000000 --- a/docker/nginx/conf.d/include/ratelimited +++ /dev/null @@ -1,6 +0,0 @@ -# Add a list of IPs here that should be severely rate limited on upload. -# Note that it is possible to add IP ranges as well as the full IP address. -# -# Examples: -# 192.168.0.0/24 1; -# 79.85.222.247 1; diff --git a/docker/nginx/conf.d/include/sia-auth b/docker/nginx/conf.d/include/sia-auth deleted file mode 100644 index d8ec3cf4..00000000 --- a/docker/nginx/conf.d/include/sia-auth +++ /dev/null @@ -1,4 +0,0 @@ -rewrite_by_lua_block { - -- set basic authorization header with base64 encoded apipassword - ngx.req.set_header("Authorization", require("skynet.utils").authorization_header()) -} diff --git a/docker/nginx/conf.d/include/ssl-settings b/docker/nginx/conf.d/include/ssl-settings deleted file mode 100644 index 545d372b..00000000 --- a/docker/nginx/conf.d/include/ssl-settings +++ /dev/null @@ -1,26 +0,0 @@ -# https://ssl-config.mozilla.org/#server=nginx&version=1.17.7&config=intermediate&openssl=1.1.1d&hsts=false&ocsp=false&guideline=5.6 - -ssl_certificate /etc/letsencrypt/live/skynet-portal/fullchain.pem; -ssl_certificate_key /etc/letsencrypt/live/skynet-portal/privkey.pem; - -ssl_session_timeout 1d; -ssl_session_cache shared:MozSSL:10m; # about 40000 sessions -ssl_session_tickets off; - -# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam -ssl_dhparam /etc/nginx/conf.d/dhparam.pem; - -# intermediate configuration -ssl_protocols TLSv1.2 TLSv1.3; -ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; -ssl_prefer_server_ciphers off; - -# HSTS (ngx_http_headers_module is required) (63072000 seconds) -add_header Strict-Transport-Security "max-age=63072000" always; - -# OCSP stapling -ssl_stapling on; -ssl_stapling_verify on; - -# verify chain of trust of OCSP response using Root CA and Intermediate certs -ssl_trusted_certificate /etc/letsencrypt/live/skynet-portal/chain.pem; diff --git a/docker/nginx/conf.d/server-override/example b/docker/nginx/conf.d/server-override/example deleted file mode 100644 index 50c58c6d..00000000 --- a/docker/nginx/conf.d/server-override/example +++ /dev/null @@ -1,7 +0,0 @@ -# Every file from within this directory will be included in the server block -# of the nginx configuration, at the very end. See client.conf. -# -# Example: -# location /blog { -# root /var/www/blog; -# } diff --git a/docker/nginx/conf.d/server/server.account b/docker/nginx/conf.d/server/server.account deleted file mode 100644 index 9d444296..00000000 --- a/docker/nginx/conf.d/server/server.account +++ /dev/null @@ -1,62 +0,0 @@ -listen 443 ssl http2; - -include /etc/nginx/conf.d/include/ssl-settings; -include /etc/nginx/conf.d/include/init-optional-variables; - -# Uncomment to launch new Dashboard under /v2 path -# location /v2 { -# proxy_pass http://dashboard-v2:9000; -# } - -location / { - proxy_pass http://dashboard:3000; -} - -location /health { - proxy_pass http://accounts:3000; -} - -location /stripe/webhook { - proxy_pass http://accounts:3000; -} - -location /api/stripe/billing { - proxy_pass http://dashboard:3000; -} - -location /api/stripe/checkout { - proxy_pass http://dashboard:3000; -} - -location /api { - rewrite /api/(.*) /$1 break; - proxy_pass http://accounts:3000; -} - -location /api/register { - include /etc/nginx/conf.d/include/cors; - - rewrite /api/(.*) /$1 break; - proxy_pass http://accounts:3000; -} - -location /api/user/pubkey/register { - include /etc/nginx/conf.d/include/cors; - - rewrite /api/(.*) /$1 break; - proxy_pass http://accounts:3000; -} - -location /api/login { - include /etc/nginx/conf.d/include/cors; - - rewrite /api/(.*) /$1 break; - proxy_pass http://accounts:3000; -} - -location /api/logout { - include /etc/nginx/conf.d/include/cors; - - rewrite /api/(.*) /$1 break; - proxy_pass http://accounts:3000; -} diff --git a/docker/nginx/conf.d/server/server.api b/docker/nginx/conf.d/server/server.api deleted file mode 100644 index 48e7a638..00000000 --- a/docker/nginx/conf.d/server/server.api +++ /dev/null @@ -1,495 +0,0 @@ -listen 443 ssl http2; - -include /etc/nginx/conf.d/include/ssl-settings; -include /etc/nginx/conf.d/include/init-optional-variables; - -# ddos protection: closing slow connections -client_body_timeout 1h; -client_header_timeout 1h; -send_timeout 1h; - -proxy_connect_timeout 1h; -proxy_read_timeout 1h; -proxy_send_timeout 1h; - -# Increase the body buffer size, to ensure the internal POSTs can always -# parse the full POST contents into memory. -client_body_buffer_size 128k; -client_max_body_size 128k; - -# legacy endpoint rewrite -rewrite ^/portals /skynet/portals permanent; -rewrite ^/stats /skynet/stats permanent; -rewrite ^/skynet/blacklist /skynet/blocklist permanent; -rewrite ^/docs(?:/(.*))?$ https://sdk.skynetlabs.com/$1 permanent; - -location / { - include /etc/nginx/conf.d/include/cors; - - proxy_pass http://website:9000; -} - -location /skynet/blocklist { - include /etc/nginx/conf.d/include/cors; - - add_header X-Proxy-Cache $upstream_cache_status; - - proxy_cache skynet; - proxy_cache_valid any 1m; # cache blocklist for 1 minute - proxy_set_header User-Agent: Sia-Agent; - proxy_pass http://sia:9980/skynet/blocklist; -} - -location /skynet/portal/blocklist { - include /etc/nginx/conf.d/include/cors; - - add_header X-Proxy-Cache $upstream_cache_status; - - proxy_cache skynet; - proxy_cache_valid 200 204 15m; # cache portal blocklist for 15 minutes - - # 10.10.10.110 points to blocker service - proxy_pass http://10.10.10.110:4000/blocklist; -} - -location /skynet/portals { - include /etc/nginx/conf.d/include/cors; - - add_header X-Proxy-Cache $upstream_cache_status; - - proxy_cache skynet; - proxy_cache_valid any 1m; # cache portals for 1 minute - proxy_set_header User-Agent: Sia-Agent; - proxy_pass http://sia:9980/skynet/portals; -} - -location /skynet/stats { - include /etc/nginx/conf.d/include/cors; - - add_header X-Proxy-Cache $upstream_cache_status; - - proxy_cache skynet; - proxy_cache_valid any 1m; # cache stats for 1 minute - proxy_set_header User-Agent: Sia-Agent; - proxy_read_timeout 5m; # extend the read timeout - proxy_pass http://sia:9980/skynet/stats; -} - -# Define path for server load endpoint -location /serverload { - # Define root directory in the nginx container to load file from - root /usr/local/share; - - # including this because of peer pressure from the other routes - include /etc/nginx/conf.d/include/cors; - - # tell nginx to expect json - default_type 'application/json'; - - # Allow for /serverload to load /serverload.json file - try_files $uri $uri.json =404; -} - -location /skynet/health { - include /etc/nginx/conf.d/include/cors; - - add_header X-Proxy-Cache $upstream_cache_status; - - proxy_cache skynet; - proxy_cache_key $request_uri; # use whole request uri (uri + args) as cache key - proxy_cache_valid any 1m; # cache responses for 1 minute - proxy_set_header User-Agent: Sia-Agent; - proxy_read_timeout 5m; # extend the read timeout - proxy_pass http://sia:9980; -} - -location /health-check { - include /etc/nginx/conf.d/include/cors; - - access_log off; # do not log traffic to health-check endpoint - - proxy_pass http://10.10.10.60:3100; # hardcoded ip because health-check waits for nginx -} - -location /abuse { - return 308 /0404guluqu38oaqapku91ed11kbhkge55smh9lhjukmlrj37lfpm8no/; -} - -location /abuse/report { - include /etc/nginx/conf.d/include/cors; - - # 10.10.10.110 points to blocker service - proxy_pass http://10.10.10.110:4000/powblock; -} - -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 - # example: /hns/something/foo/bar matches: - # > hns_domain: something - # > path: /foo/bar/ - set_by_lua_block $hns_domain { return string.match(ngx.var.uri, "/hns/([^/?]+)") } - set_by_lua_block $path { return string.match(ngx.var.uri, "/hns/[^/?]+(.*)") } - - proxy_set_header Host $host; - include /etc/nginx/conf.d/include/location-hns; -} - -location /hnsres { - include /etc/nginx/conf.d/include/cors; - include /etc/nginx/conf.d/include/portal-access-check; - - proxy_pass http://handshake-api:3100; -} - -location /skynet/registry { - include /etc/nginx/conf.d/include/location-skynet-registry; -} - -location /skynet/restore { - include /etc/nginx/conf.d/include/cors; - include /etc/nginx/conf.d/include/sia-auth; - include /etc/nginx/conf.d/include/portal-access-check; - - client_max_body_size 5M; - - # increase request timeouts - proxy_read_timeout 600; - proxy_send_timeout 600; - - proxy_request_buffering off; # stream uploaded files through the proxy as it comes in - proxy_set_header Expect $http_expect; - proxy_set_header User-Agent: Sia-Agent; - - # proxy this call to siad endpoint (make sure the ip is correct) - proxy_pass http://sia:9980; -} - -location /skynet/registry/subscription { - include /etc/nginx/conf.d/include/cors; - - # default to unlimited bandwidth and no delay - set $bandwidthlimit "0"; - set $notificationdelay "0"; - - rewrite_by_lua_block { - local skynet_account = require("skynet.account") - - 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 - } - - proxy_set_header User-Agent: Sia-Agent; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "Upgrade"; - - proxy_pass http://sia:9980/skynet/registry/subscription?bandwidthlimit=$bandwidthlimit¬ificationdelay=$notificationdelay; -} - -location /skynet/skyfile { - include /etc/nginx/conf.d/include/cors; - include /etc/nginx/conf.d/include/sia-auth; - 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_throttled; - - limit_conn upload_conn 5; - limit_conn upload_conn_rl 1; - - client_max_body_size 5000M; # make sure to limit the size of upload to a sane value - - # increase request timeouts - proxy_read_timeout 600; - proxy_send_timeout 600; - - proxy_request_buffering off; # stream uploaded files through the proxy as it comes in - proxy_set_header Expect $http_expect; - proxy_set_header User-Agent: Sia-Agent; - - # 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; - - log_by_lua_block { - local skynet_account = require("skynet.account") - local skynet_modules = require("skynet.modules") - local skynet_scanner = require("skynet.scanner") - local skynet_tracker = require("skynet.tracker") - - if skynet_modules.is_enabled("a") then - skynet_tracker.track_upload( - ngx.header["Skynet-Skylink"], - ngx.status, - skynet_account.get_auth_headers(), - ngx.var.remote_addr - ) - end - - if skynet_modules.is_enabled("s") then - skynet_scanner.scan_skylink(ngx.header["Skynet-Skylink"]) - end - } -} - -# endpoint implementing resumable file uploads open protocol https://tus.io -location /skynet/tus { - include /etc/nginx/conf.d/include/cors-headers; # include cors headers but do not overwrite OPTIONS response - - limit_req zone=uploads_by_ip burst=10 nodelay; - limit_req zone=uploads_by_ip_throttled; - - limit_conn upload_conn 5; - limit_conn upload_conn_rl 1; - - # Do not limit body size in nginx, skyd will reject early on too large upload - client_max_body_size 0; - - # Those timeouts need to be elevated since skyd can stall reading - # data for a while when overloaded which would terminate connection - client_body_timeout 1h; - proxy_send_timeout 1h; - - # Add X-Forwarded-* headers - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Proto $scheme; - - # rewrite proxy request to use correct host uri from env variable (required to return correct location header) - proxy_redirect $scheme://$host $scheme://$skynet_server_domain; - - # proxy /skynet/tus requests to siad endpoint with all arguments - proxy_pass http://sia:9980; - - access_by_lua_block { - local skynet_account = require("skynet.account") - - 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 upload size limits - ngx.req.set_header("SkynetMaxUploadSize", limits.maxUploadSize) - end - } - - # extract skylink from base64 encoded upload metadata and assign to a proper header - header_filter_by_lua_block { - ngx.header["Skynet-Portal-Api"] = ngx.var.scheme .. "://" .. ngx.var.skynet_portal_domain - ngx.header["Skynet-Server-Api"] = ngx.var.scheme .. "://" .. ngx.var.skynet_server_domain - - if ngx.header["Upload-Metadata"] then - local encodedSkylink = string.match(ngx.header["Upload-Metadata"], "Skylink ([^,?]+)") - - if encodedSkylink then - ngx.header["Skynet-Skylink"] = ngx.decode_base64(encodedSkylink) - end - end - } - - log_by_lua_block { - local skynet_account = require("skynet.account") - local skynet_modules = require("skynet.modules") - local skynet_scanner = require("skynet.scanner") - local skynet_tracker = require("skynet.tracker") - - if skynet_modules.is_enabled("a") then - skynet_tracker.track_upload( - ngx.header["Skynet-Skylink"], - ngx.status, - skynet_account.get_auth_headers(), - ngx.var.remote_addr - ) - end - - if skynet_modules.is_enabled("s") then - skynet_scanner.scan_skylink(ngx.header["Skynet-Skylink"]) - end - } -} - -location /skynet/pin { - include /etc/nginx/conf.d/include/cors; - include /etc/nginx/conf.d/include/sia-auth; - 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_throttled; - - limit_conn upload_conn 5; - limit_conn upload_conn_rl 1; - - proxy_set_header User-Agent: Sia-Agent; - proxy_pass http://sia:9980$uri?siapath=$dir1/$dir2/$dir3&$args; - - log_by_lua_block { - local skynet_account = require("skynet.account") - local skynet_modules = require("skynet.modules") - local skynet_scanner = require("skynet.scanner") - local skynet_tracker = require("skynet.tracker") - - if skynet_modules.is_enabled("a") then - skynet_tracker.track_upload( - ngx.header["Skynet-Skylink"], - ngx.status, - skynet_account.get_auth_headers(), - ngx.var.remote_addr - ) - end - - if skynet_modules.is_enabled("s") then - skynet_scanner.scan_skylink(ngx.header["Skynet-Skylink"]) - end - } -} - -location /skynet/metadata { - include /etc/nginx/conf.d/include/cors; - include /etc/nginx/conf.d/include/portal-access-check; - - header_filter_by_lua_block { - ngx.header["Skynet-Portal-Api"] = ngx.var.scheme .. "://" .. ngx.var.skynet_portal_domain - ngx.header["Skynet-Server-Api"] = ngx.var.scheme .. "://" .. ngx.var.skynet_server_domain - } - - proxy_set_header User-Agent: Sia-Agent; - proxy_pass http://sia:9980; -} - -location /skynet/resolve { - include /etc/nginx/conf.d/include/cors; - include /etc/nginx/conf.d/include/portal-access-check; - - header_filter_by_lua_block { - ngx.header["Skynet-Portal-Api"] = ngx.var.scheme .. "://" .. ngx.var.skynet_portal_domain - ngx.header["Skynet-Server-Api"] = ngx.var.scheme .. "://" .. ngx.var.skynet_server_domain - } - - proxy_set_header User-Agent: Sia-Agent; - proxy_pass http://sia:9980; -} - -location ~ "^/(([a-zA-Z0-9-_]{46}|[a-z0-9]{55})(/.*)?)$" { - set $skylink $2; - set $path $3; - - include /etc/nginx/conf.d/include/location-skylink; -} - -location ~ "^/file/(([a-zA-Z0-9-_]{46}|[a-z0-9]{55})(/.*)?)$" { - set $skylink $2; - set $path $3; - set $args attachment=true&$args; - #set $is_args ?; - - include /etc/nginx/conf.d/include/location-skylink; -} - -location /skynet/trustless/basesector { - include /etc/nginx/conf.d/include/cors; - - limit_conn downloads_by_ip 100; # ddos protection: max 100 downloads at a time - - # default download rate to unlimited - set $limit_rate 0; - - access_by_lua_block { - local skynet_account = require("skynet.account") - - 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 download speed limit - ngx.var.limit_rate = limits.download - end - } - - limit_rate_after 512k; - limit_rate $limit_rate; - - proxy_set_header User-Agent: Sia-Agent; - proxy_pass http://sia:9980; - - log_by_lua_block { - local skynet_account = require("skynet.account") - local skynet_modules = require("skynet.modules") - local skynet_scanner = require("skynet.scanner") - local skynet_tracker = require("skynet.tracker") - - if skynet_modules.is_enabled("a") then - skynet_tracker.track_download(ngx.header["Skynet-Skylink"], ngx.status, skynet_account.get_auth_headers(), ngx.var.body_bytes_sent) - end - - if skynet_modules.is_enabled("s") then - skynet_scanner.scan_skylink(ngx.header["Skynet-Skylink"]) - end - } -} - -location /__internal/do/not/use/accounts { - include /etc/nginx/conf.d/include/cors; - - charset utf-8; - charset_types application/json; - default_type application/json; - - content_by_lua_block { - local json = require('cjson') - local skynet_account = require("skynet.account") - - local accounts_enabled = skynet_account.accounts_enabled() - local is_auth_required = skynet_account.is_auth_required() - local is_subscription_required = skynet_account.is_subscription_required() - local is_authenticated = skynet_account.is_authenticated() - local has_subscription = skynet_account.has_subscription() - - ngx.say(json.encode{ - enabled = accounts_enabled, - auth_required = is_auth_required, - subscription_required = is_subscription_required, - authenticated = is_authenticated, - subscription = has_subscription, - }) - return ngx.exit(ngx.HTTP_OK) - } -} - -include /etc/nginx/conf.d/server-override/*; diff --git a/docker/nginx/conf.d/server/server.dnslink b/docker/nginx/conf.d/server/server.dnslink deleted file mode 100644 index 139196ef..00000000 --- a/docker/nginx/conf.d/server/server.dnslink +++ /dev/null @@ -1,55 +0,0 @@ -include /etc/nginx/conf.d/include/init-optional-variables; - -location / { - set $skylink ""; - set $path $uri; - - rewrite_by_lua_block { - local cjson = require("cjson") - local cache = ngx.shared.dnslink - local cache_value = cache:get(ngx.var.host) - - if cache_value == nil then - local httpc = require("resty.http").new() - - -- 10.10.10.55 points to dnslink-api service (alias not available when using resty-http) - local res, err = httpc:request_uri("http://10.10.10.55:3100/dnslink/" .. ngx.var.host) - - if err or (res and res.status ~= ngx.HTTP_OK) then - -- check whether we can fallback to regular skylink request - local match_skylink = ngx.re.match(ngx.var.uri, "^/([a-zA-Z0-9-_]{46}|[a-z0-9]{55})(/.*)?") - - if match_skylink then - ngx.var.skylink = match_skylink[1] - ngx.var.path = match_skylink[2] or "/" - else - ngx.status = (err and ngx.HTTP_INTERNAL_SERVER_ERROR) or res.status - ngx.header["content-type"] = "text/plain" - ngx.say(err or res.body) - ngx.exit(ngx.status) - end - else - local resolved = cjson.decode(res.body) - - ngx.var.skylink = resolved.skylink - if resolved.sponsor then - ngx.req.set_header("Skynet-Api-Key", resolved.sponsor) - end - - local cache_ttl = 300 -- 5 minutes cache expire time - cache:set(ngx.var.host, res.body, cache_ttl) - end - else - local resolved = cjson.decode(cache_value) - - ngx.var.skylink = resolved.skylink - if resolved.sponsor then - ngx.req.set_header("Skynet-Api-Key", resolved.sponsor) - end - end - - ngx.var.skylink = require("skynet.skylink").parse(ngx.var.skylink) - } - - include /etc/nginx/conf.d/include/location-skylink; -} diff --git a/docker/nginx/conf.d/server/server.hns b/docker/nginx/conf.d/server/server.hns deleted file mode 100644 index 9e68dc0b..00000000 --- a/docker/nginx/conf.d/server/server.hns +++ /dev/null @@ -1,11 +0,0 @@ -listen 443 ssl http2; - -include /etc/nginx/conf.d/include/ssl-settings; -include /etc/nginx/conf.d/include/init-optional-variables; - -location / { - set_by_lua_block $hns_domain { return string.match(ngx.var.host, "[^%.]+") } - set $path $uri; - - include /etc/nginx/conf.d/include/location-hns; -} diff --git a/docker/nginx/conf.d/server/server.http b/docker/nginx/conf.d/server/server.http deleted file mode 100644 index 22ec6f30..00000000 --- a/docker/nginx/conf.d/server/server.http +++ /dev/null @@ -1,7 +0,0 @@ -listen 80; - -include /etc/nginx/conf.d/include/init-optional-variables; - -location / { - return 301 https://$host$request_uri; -} diff --git a/docker/nginx/conf.d/server/server.skylink b/docker/nginx/conf.d/server/server.skylink deleted file mode 100644 index 7f628989..00000000 --- a/docker/nginx/conf.d/server/server.skylink +++ /dev/null @@ -1,16 +0,0 @@ -listen 443 ssl http2; - -include /etc/nginx/conf.d/include/ssl-settings; -include /etc/nginx/conf.d/include/init-optional-variables; - -location / { - set_by_lua_block $skylink { return string.match(ngx.var.host, "%w+") } - set_by_lua_block $path { - -- strip ngx.var.request_uri from query params - this is basically the same as ngx.var.uri but - -- do not use ngx.var.uri because it will already be unescaped and we need to use escaped path - -- examples: escaped uri "/b%20r56+7" and unescaped uri "/b r56 7" - return string.gsub(ngx.var.request_uri, "?.*", "") - } - - include /etc/nginx/conf.d/include/location-skylink; -} diff --git a/docker/nginx/docker-entrypoint.d/20-envsubst-on-templates.sh b/docker/nginx/docker-entrypoint.d/20-envsubst-on-templates.sh deleted file mode 100755 index be9c5f8e..00000000 --- a/docker/nginx/docker-entrypoint.d/20-envsubst-on-templates.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/sh - -# https://github.com/nginxinc/docker-nginx/blob/master/entrypoint/20-envsubst-on-templates.sh -# https://github.com/nginxinc/docker-nginx/blob/master/LICENSE - -# Copyright (C) 2011-2016 Nginx, Inc. -# All rights reserved. - -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. - -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -# SUCH DAMAGE. - -set -e - -ME=$(basename $0) - -auto_envsubst() { - local template_dir="${NGINX_ENVSUBST_TEMPLATE_DIR:-/etc/nginx/templates}" - local suffix="${NGINX_ENVSUBST_TEMPLATE_SUFFIX:-.template}" - local output_dir="${NGINX_ENVSUBST_OUTPUT_DIR:-/etc/nginx/conf.d}" - - local template defined_envs relative_path output_path subdir - defined_envs=$(printf '${%s} ' $(env | cut -d= -f1)) - [ -d "$template_dir" ] || return 0 - if [ ! -w "$output_dir" ]; then - echo >&3 "$ME: ERROR: $template_dir exists, but $output_dir is not writable" - return 0 - fi - find "$template_dir" -follow -type f -name "*$suffix" -print | while read -r template; do - relative_path="${template#$template_dir/}" - output_path="$output_dir/${relative_path%$suffix}" - subdir=$(dirname "$relative_path") - # create a subdirectory where the template file exists - mkdir -p "$output_dir/$subdir" - echo >&3 "$ME: Running envsubst on $template to $output_path" - envsubst "$defined_envs" < "$template" > "$output_path" - done -} - -auto_envsubst - -exit 0 diff --git a/docker/nginx/docker-entrypoint.d/40-reload-every-x-hours.sh b/docker/nginx/docker-entrypoint.d/40-reload-every-x-hours.sh deleted file mode 100755 index cdd7d17e..00000000 --- a/docker/nginx/docker-entrypoint.d/40-reload-every-x-hours.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -# source: https://github.com/nginxinc/docker-nginx/pull/509 - -set -e - -ME=$(basename $0) - -[ "${NGINX_ENTRYPOINT_RELOAD_EVERY_X_HOURS:-}" ] || exit 0 -if [ $(echo "$NGINX_ENTRYPOINT_RELOAD_EVERY_X_HOURS > 0" | bc) = 0 ]; then - echo >&3 "$ME: Error. Provide integer or floating point number greater that 0. See 'man sleep'." - exit 1 -fi - -start_background_reload() { - echo >&3 "$ME: Reloading Nginx every $NGINX_ENTRYPOINT_RELOAD_EVERY_X_HOURS hour(s)" - while :; do sleep ${NGINX_ENTRYPOINT_RELOAD_EVERY_X_HOURS}h; echo >&3 "$ME: Reloading Nginx ..." && nginx -s reload; done & -} - -start_background_reload diff --git a/docker/nginx/docker-entrypoint.d/50-generate-local-certificate.sh b/docker/nginx/docker-entrypoint.d/50-generate-local-certificate.sh deleted file mode 100755 index f5ecada1..00000000 --- a/docker/nginx/docker-entrypoint.d/50-generate-local-certificate.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -# Generate locally signed ssl certificate to be used on routes -# that do not require certificate issued by trusted CA - -set -e - -ME=$(basename $0) - -generate_local_certificate() { - echo >&3 "$ME: Generating locally signed ssl certificate" - openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \ - -subj '/CN=local-certificate' \ - -keyout /etc/ssl/local-certificate.key \ - -out /etc/ssl/local-certificate.crt -} - -generate_local_certificate diff --git a/docker/nginx/docker-entrypoint.sh b/docker/nginx/docker-entrypoint.sh deleted file mode 100755 index 3bc26ca9..00000000 --- a/docker/nginx/docker-entrypoint.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/sh -# vim:sw=4:ts=4:et - -# https://github.com/nginxinc/docker-nginx/blob/master/entrypoint/docker-entrypoint.sh -# https://github.com/nginxinc/docker-nginx/blob/master/LICENSE - -# Copyright (C) 2011-2016 Nginx, Inc. -# All rights reserved. - -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. - -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -# SUCH DAMAGE. - -set -e - -if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then - exec 3>&1 -else - exec 3>/dev/null -fi - -# Really dirty backwards compatible workaround for single server portals: -# ======================================================================= -# in the past we used to require single server portals not to include -# server domain env variable because it messed up with caddy; we switched -# to certbot and that is not the case any more but we also switched to -# using built in envsubst instead of custom mustache script for nginx -# templating and now when server domain is not defined, it messes up whole -# nginx config and nginx will not start; this workaround assigns portal -# domain as a server domain if server domain is not defined -if [ -z "${SERVER_DOMAIN}" ]; then - export SERVER_DOMAIN=${PORTAL_DOMAIN} -fi - -if [ "$1" = "nginx" -o "$1" = "nginx-debug" ]; then - if /usr/bin/find "/docker-entrypoint.d/" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then - echo >&3 "$0: /docker-entrypoint.d/ is not empty, will attempt to perform configuration" - - echo >&3 "$0: Looking for shell scripts in /docker-entrypoint.d/" - find "/docker-entrypoint.d/" -follow -type f -print | sort -V | while read -r f; do - case "$f" in - *.sh) - if [ -x "$f" ]; then - echo >&3 "$0: Launching $f"; - "$f" - else - # warn on shell scripts without exec bit - echo >&3 "$0: Ignoring $f, not executable"; - fi - ;; - *) echo >&3 "$0: Ignoring $f";; - esac - done - - echo >&3 "$0: Configuration complete; ready for start up" - else - echo >&3 "$0: No files found in /docker-entrypoint.d/, skipping configuration" - fi -fi - -exec "$@" diff --git a/docker/nginx/libs/basexx.lua b/docker/nginx/libs/basexx.lua deleted file mode 100644 index b53c7337..00000000 --- a/docker/nginx/libs/basexx.lua +++ /dev/null @@ -1,301 +0,0 @@ --- source: https://github.com/aiq/basexx --- license: MIT --- modified: exposed from_basexx and to_basexx generic functions - --------------------------------------------------------------------------------- --- util functions --------------------------------------------------------------------------------- - -local function divide_string( str, max ) - local result = {} - - local start = 1 - for i = 1, #str do - if i % max == 0 then - table.insert( result, str:sub( start, i ) ) - start = i + 1 - elseif i == #str then - table.insert( result, str:sub( start, i ) ) - end - end - - return result -end - -local function number_to_bit( num, length ) - local bits = {} - - while num > 0 do - local rest = math.floor( math.fmod( num, 2 ) ) - table.insert( bits, rest ) - num = ( num - rest ) / 2 - end - - while #bits < length do - table.insert( bits, "0" ) - end - - return string.reverse( table.concat( bits ) ) -end - -local function ignore_set( str, set ) - if set then - str = str:gsub( "["..set.."]", "" ) - end - return str -end - -local function pure_from_bit( str ) - return ( str:gsub( '........', function ( cc ) - return string.char( tonumber( cc, 2 ) ) - end ) ) -end - -local function unexpected_char_error( str, pos ) - local c = string.sub( str, pos, pos ) - return string.format( "unexpected character at position %d: '%s'", pos, c ) -end - --------------------------------------------------------------------------------- - -local basexx = {} - --------------------------------------------------------------------------------- --- base2(bitfield) decode and encode function --------------------------------------------------------------------------------- - -local bitMap = { o = "0", i = "1", l = "1" } - -function basexx.from_bit( str, ignore ) - str = ignore_set( str, ignore ) - str = string.lower( str ) - str = str:gsub( '[ilo]', function( c ) return bitMap[ c ] end ) - local pos = string.find( str, "[^01]" ) - if pos then return nil, unexpected_char_error( str, pos ) end - - return pure_from_bit( str ) -end - -function basexx.to_bit( str ) - return ( str:gsub( '.', function ( c ) - local byte = string.byte( c ) - local bits = {} - for _ = 1,8 do - table.insert( bits, byte % 2 ) - byte = math.floor( byte / 2 ) - end - return table.concat( bits ):reverse() - end ) ) -end - --------------------------------------------------------------------------------- --- base16(hex) decode and encode function --------------------------------------------------------------------------------- - -function basexx.from_hex( str, ignore ) - str = ignore_set( str, ignore ) - local pos = string.find( str, "[^%x]" ) - if pos then return nil, unexpected_char_error( str, pos ) end - - return ( str:gsub( '..', function ( cc ) - return string.char( tonumber( cc, 16 ) ) - end ) ) -end - -function basexx.to_hex( str ) - return ( str:gsub( '.', function ( c ) - return string.format('%02X', string.byte( c ) ) - end ) ) -end - --------------------------------------------------------------------------------- --- generic function to decode and encode base32/base64 --------------------------------------------------------------------------------- - -function basexx.from_basexx( str, alphabet, bits ) - local result = {} - for i = 1, #str do - local c = string.sub( str, i, i ) - if c ~= '=' then - local index = string.find( alphabet, c, 1, true ) - if not index then - return nil, unexpected_char_error( str, i ) - end - table.insert( result, number_to_bit( index - 1, bits ) ) - end - end - - local value = table.concat( result ) - local pad = #value % 8 - return pure_from_bit( string.sub( value, 1, #value - pad ) ) -end - -function basexx.to_basexx( str, alphabet, bits, pad ) - local bitString = basexx.to_bit( str ) - - local chunks = divide_string( bitString, bits ) - local result = {} - for _,value in ipairs( chunks ) do - if ( #value < bits ) then - value = value .. string.rep( '0', bits - #value ) - end - local pos = tonumber( value, 2 ) + 1 - table.insert( result, alphabet:sub( pos, pos ) ) - end - - table.insert( result, pad ) - return table.concat( result ) -end - --------------------------------------------------------------------------------- --- rfc 3548: http://www.rfc-editor.org/rfc/rfc3548.txt --------------------------------------------------------------------------------- - -local base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" -local base32PadMap = { "", "======", "====", "===", "=" } - -function basexx.from_base32( str, ignore ) - str = ignore_set( str, ignore ) - return basexx.from_basexx( string.upper( str ), base32Alphabet, 5 ) -end - -function basexx.to_base32( str ) - return basexx.to_basexx( str, base32Alphabet, 5, base32PadMap[ #str % 5 + 1 ] ) -end - --------------------------------------------------------------------------------- --- crockford: http://www.crockford.com/wrmg/base32.html --------------------------------------------------------------------------------- - -local crockfordAlphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" -local crockfordMap = { O = "0", I = "1", L = "1" } - -function basexx.from_crockford( str, ignore ) - str = ignore_set( str, ignore ) - str = string.upper( str ) - str = str:gsub( '[ILOU]', function( c ) return crockfordMap[ c ] end ) - return basexx.from_basexx( str, crockfordAlphabet, 5 ) -end - -function basexx.to_crockford( str ) - return basexx.to_basexx( str, crockfordAlphabet, 5, "" ) -end - --------------------------------------------------------------------------------- --- base64 decode and encode function --------------------------------------------------------------------------------- - -local base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".. - "abcdefghijklmnopqrstuvwxyz".. - "0123456789+/" -local base64PadMap = { "", "==", "=" } - -function basexx.from_base64( str, ignore ) - str = ignore_set( str, ignore ) - return basexx.from_basexx( str, base64Alphabet, 6 ) -end - -function basexx.to_base64( str ) - return basexx.to_basexx( str, base64Alphabet, 6, base64PadMap[ #str % 3 + 1 ] ) -end - --------------------------------------------------------------------------------- --- URL safe base64 decode and encode function --------------------------------------------------------------------------------- - -local url64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".. - "abcdefghijklmnopqrstuvwxyz".. - "0123456789-_" - -function basexx.from_url64( str, ignore ) - str = ignore_set( str, ignore ) - return basexx.from_basexx( str, url64Alphabet, 6 ) -end - -function basexx.to_url64( str ) - return basexx.to_basexx( str, url64Alphabet, 6, "" ) -end - --------------------------------------------------------------------------------- --- --------------------------------------------------------------------------------- - -local function length_error( len, d ) - return string.format( "invalid length: %d - must be a multiple of %d", len, d ) -end - -local z85Decoder = { 0x00, 0x44, 0x00, 0x54, 0x53, 0x52, 0x48, 0x00, - 0x4B, 0x4C, 0x46, 0x41, 0x00, 0x3F, 0x3E, 0x45, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x40, 0x00, 0x49, 0x42, 0x4A, 0x47, - 0x51, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, - 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, - 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, - 0x3B, 0x3C, 0x3D, 0x4D, 0x00, 0x4E, 0x43, 0x00, - 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, - 0x21, 0x22, 0x23, 0x4F, 0x00, 0x50, 0x00, 0x00 } - -function basexx.from_z85( str, ignore ) - str = ignore_set( str, ignore ) - if ( #str % 5 ) ~= 0 then - return nil, length_error( #str, 5 ) - end - - local result = {} - - local value = 0 - for i = 1, #str do - local index = string.byte( str, i ) - 31 - if index < 1 or index >= #z85Decoder then - return nil, unexpected_char_error( str, i ) - end - value = ( value * 85 ) + z85Decoder[ index ] - if ( i % 5 ) == 0 then - local divisor = 256 * 256 * 256 - while divisor ~= 0 do - local b = math.floor( value / divisor ) % 256 - table.insert( result, string.char( b ) ) - divisor = math.floor( divisor / 256 ) - end - value = 0 - end - end - - return table.concat( result ) -end - -local z85Encoder = "0123456789".. - "abcdefghijklmnopqrstuvwxyz".. - "ABCDEFGHIJKLMNOPQRSTUVWXYZ".. - ".-:+=^!/*?&<>()[]{}@%$#" - -function basexx.to_z85( str ) - if ( #str % 4 ) ~= 0 then - return nil, length_error( #str, 4 ) - end - - local result = {} - - local value = 0 - for i = 1, #str do - local b = string.byte( str, i ) - value = ( value * 256 ) + b - if ( i % 4 ) == 0 then - local divisor = 85 * 85 * 85 * 85 - while divisor ~= 0 do - local index = ( math.floor( value / divisor ) % 85 ) + 1 - table.insert( result, z85Encoder:sub( index, index ) ) - divisor = math.floor( divisor / 85 ) - end - value = 0 - end - end - - return table.concat( result ) -end - --------------------------------------------------------------------------------- - -return basexx diff --git a/docker/nginx/libs/skynet/account.lua b/docker/nginx/libs/skynet/account.lua deleted file mode 100644 index 709d8130..00000000 --- a/docker/nginx/libs/skynet/account.lua +++ /dev/null @@ -1,153 +0,0 @@ -local _M = {} - --- constant tier ids -local tier_id_anonymous = 0 -local tier_id_free = 1 - --- fallback - remember to keep those updated -local anon_limits = { - ["tierID"] = tier_id_anonymous, - ["tierName"] = "anonymous", - ["upload"] = 655360, - ["download"] = 655360, - ["maxUploadSize"] = 1073741824, - ["registry"] = 250 -} - --- 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 utils = require("utils") - local request_headers = ngx.req.get_headers() - local headers = {} - - -- try to extract skynet-jwt cookie from cookie header - local skynet_jwt_cookie = utils.extract_cookie(request_headers["Cookie"], "skynet[-]jwt") - - -- if skynet-jwt cookie is present, pass it as is - if skynet_jwt_cookie then - headers["Cookie"] = skynet_jwt_cookie - 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 - ngx.header["content-type"] = "text/plain" - ngx.say(message or "Portal operator restricted access to authenticated users only") - return ngx.exit(ngx.status) -end - --- handle request exit when access to portal should be restricted to subscription users only -function _M.exit_access_forbidden(message) - ngx.status = ngx.HTTP_FORBIDDEN - ngx.header["content-type"] = "text/plain" - ngx.say(message or "Portal operator restricted access to users with active subscription only") - return ngx.exit(ngx.status) -end - -function _M.accounts_enabled() - local skynet_modules = require("skynet.modules") - - return skynet_modules.is_enabled("a") -end - -function _M.get_account_limits() - local cjson = require('cjson') - local utils = require('utils') - local auth_headers = _M.get_auth_headers() - - -- simple case of anonymous request - none of available auth headers exist - if utils.is_table_empty(auth_headers) then - return anon_limits - end - - if ngx.var.account_limits == "" then - local httpc = require("resty.http").new() - local uri = "http://10.10.10.70:3000/user/limits" - - -- include skylink if it is available in the context of request - -- todo: this should not rely on skylink variable to be defined - if ngx.var.skylink ~= nil and ngx.var.skylink ~= "" then - uri = uri .. "/" .. ngx.var.skylink - end - - -- 10.10.10.70 points to accounts service (alias not available when using resty-http) - local res, err = httpc:request_uri(uri .. "?unit=byte", { - headers = auth_headers, - }) - - -- fail gracefully in case /user/limits failed - if err or (res and res.status ~= ngx.HTTP_OK) then - local error_response = err or ("[HTTP " .. res.status .. "] " .. res.body) - ngx.log(ngx.ERR, "Failed accounts service request /user/limits?unit=byte: ", error_response) - 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() - if not _M.accounts_enabled() then return false end - - local limits = _M.get_account_limits() - - return limits.tierID > tier_id_anonymous -end - --- detect whether current user has active subscription -function _M.has_subscription() - local limits = _M.get_account_limits() - - return limits.tierID > tier_id_free -end - -function _M.is_auth_required() - -- authentication is required if mode is set to "authenticated" - -- or "subscription" (require active subscription to a premium plan) - return os.getenv("ACCOUNTS_LIMIT_ACCESS") == "authenticated" or _M.is_subscription_required() -end - -function _M.is_subscription_required() - return os.getenv("ACCOUNTS_LIMIT_ACCESS") == "subscription" -end - -local is_access_always_allowed = function () - -- options requests do not attach cookies - should always be available - -- requests should not be limited based on accounts if accounts are not enabled - return ngx.req.get_method() == "OPTIONS" or not _M.accounts_enabled() -end - --- check whether access is restricted if portal requires authorization -function _M.is_access_unauthorized() - if is_access_always_allowed() then return false end - - -- check if authentication is required and request is not authenticated - return _M.is_auth_required() and not _M.is_authenticated() -end - --- check whether user is authenticated but does not have access to given resources -function _M.is_access_forbidden() - if is_access_always_allowed() then return false end - - -- check if active subscription is required and request is from user without it - return _M.is_subscription_required() and not _M.has_subscription() -end - -return _M diff --git a/docker/nginx/libs/skynet/modules.lua b/docker/nginx/libs/skynet/modules.lua deleted file mode 100644 index 607e6d8e..00000000 --- a/docker/nginx/libs/skynet/modules.lua +++ /dev/null @@ -1,23 +0,0 @@ -local _M = {} - -local utils = require("utils") - -function _M.is_enabled(module_abbr) - if type(module_abbr) ~= "string" or module_abbr:len() ~= 1 then - error("Module abbreviation '" .. tostring(module_abbr) .. "' should be exactly one character long string") - end - - local enabled_modules = utils.getenv("PORTAL_MODULES") - - if not enabled_modules then - return false - end - - return enabled_modules:find(module_abbr) ~= nil -end - -function _M.is_disabled(module_abbr) - return not _M.is_enabled(module_abbr) -end - -return _M diff --git a/docker/nginx/libs/skynet/modules.spec.lua b/docker/nginx/libs/skynet/modules.spec.lua deleted file mode 100644 index 0eaaf081..00000000 --- a/docker/nginx/libs/skynet/modules.spec.lua +++ /dev/null @@ -1,95 +0,0 @@ --- luacheck: ignore os - -local skynet_modules = require("skynet.modules") - -describe("is_enabled", function() - before_each(function() - stub(os, "getenv") - end) - - after_each(function() - os.getenv:revert() - end) - - it("should return false if PORTAL_MODULES are not defined", function() - os.getenv.on_call_with("PORTAL_MODULES").returns(nil) - - assert.is_false(skynet_modules.is_enabled("a")) - end) - - it("should return false if PORTAL_MODULES are empty", function() - os.getenv.on_call_with("PORTAL_MODULES").returns("") - - assert.is_false(skynet_modules.is_enabled("a")) - end) - - it("should return false if module is not enabled", function() - os.getenv.on_call_with("PORTAL_MODULES").returns("qwerty") - - assert.is_false(skynet_modules.is_enabled("a")) - end) - - it("should return true if module is enabled", function() - os.getenv.on_call_with("PORTAL_MODULES").returns("asdfg") - - assert.is_true(skynet_modules.is_enabled("a")) - end) - - it("should throw an error for empty module", function() - assert.has_error(function() - skynet_modules.is_enabled() - end, "Module abbreviation 'nil' should be exactly one character long string") - end) - - it("should throw an error for too long module", function() - assert.has_error(function() - skynet_modules.is_enabled("gandalf") - end, "Module abbreviation 'gandalf' should be exactly one character long string") - end) -end) - -describe("is_disabled", function() - before_each(function() - stub(os, "getenv") - end) - - after_each(function() - os.getenv:revert() - end) - - it("should return true if PORTAL_MODULES are not defined", function() - os.getenv.on_call_with("PORTAL_MODULES").returns(nil) - - assert.is_true(skynet_modules.is_disabled("a")) - end) - - it("should return true if PORTAL_MODULES are empty", function() - os.getenv.on_call_with("PORTAL_MODULES").returns("") - - assert.is_true(skynet_modules.is_disabled("a")) - end) - - it("should return true if module is not enabled", function() - os.getenv.on_call_with("PORTAL_MODULES").returns("qwerty") - - assert.is_true(skynet_modules.is_disabled("a")) - end) - - it("should return false if module is enabled", function() - os.getenv.on_call_with("PORTAL_MODULES").returns("asdfg") - - assert.is_false(skynet_modules.is_disabled("a")) - end) - - it("should throw an error for empty module", function() - assert.has_error(function() - skynet_modules.is_disabled() - end, "Module abbreviation 'nil' should be exactly one character long string") - end) - - it("should throw an error for too long module", function() - assert.has_error(function() - skynet_modules.is_disabled("gandalf") - end, "Module abbreviation 'gandalf' should be exactly one character long string") - end) -end) diff --git a/docker/nginx/libs/skynet/scanner.lua b/docker/nginx/libs/skynet/scanner.lua deleted file mode 100644 index 445f1ae9..00000000 --- a/docker/nginx/libs/skynet/scanner.lua +++ /dev/null @@ -1,26 +0,0 @@ -local _M = {} - -function _M.scan_skylink_timer(premature, skylink) - if premature then return end - - local httpc = require("resty.http").new() - - -- 10.10.10.101 points to malware-scanner service (alias not available when using resty-http) - local res, err = httpc:request_uri("http://10.10.10.101:4000/scan/" .. skylink, { - method = "POST", - }) - - if err or (res and res.status ~= ngx.HTTP_OK) then - local error_response = err or ("[HTTP " .. res.status .. "] " .. res.body) - ngx.log(ngx.ERR, "Failed malware-scanner request /scan/" .. skylink .. ": ", error_response) - end -end - -function _M.scan_skylink(skylink) - if not skylink then return end - - local ok, err = ngx.timer.at(0, _M.scan_skylink_timer, skylink) - if not ok then ngx.log(ngx.ERR, "Failed to create timer: ", err) end -end - -return _M diff --git a/docker/nginx/libs/skynet/scanner.spec.lua b/docker/nginx/libs/skynet/scanner.spec.lua deleted file mode 100644 index 533ef44c..00000000 --- a/docker/nginx/libs/skynet/scanner.spec.lua +++ /dev/null @@ -1,119 +0,0 @@ --- luacheck: ignore ngx - -local skynet_scanner = require("skynet.scanner") -local skylink = "AQBG8n_sgEM_nlEp3G0w3vLjmdvSZ46ln8ZXHn-eObZNjA" - -describe("scan_skylink", function() - before_each(function() - stub(ngx.timer, "at") - end) - - after_each(function() - ngx.timer.at:revert() - end) - - it("should schedule a timer when skylink is provided", function() - ngx.timer.at.invokes(function() return true, nil end) - - skynet_scanner.scan_skylink(skylink) - - assert.stub(ngx.timer.at).was_called_with(0, skynet_scanner.scan_skylink_timer, skylink) - end) - - it("should log an error if timer failed to create", function() - stub(ngx, "log") - - ngx.timer.at.invokes(function() return false, "such a failure" end) - - skynet_scanner.scan_skylink(skylink) - - assert.stub(ngx.timer.at).was_called_with(0, skynet_scanner.scan_skylink_timer, skylink) - assert.stub(ngx.log).was_called_with(ngx.ERR, "Failed to create timer: ", "such a failure") - - ngx.log:revert() - end) - - it("should not schedule a timer if skylink is not provided", function() - skynet_scanner.scan_skylink() - - assert.stub(ngx.timer.at).was_not_called() - end) -end) - -describe("scan_skylink_timer", function() - before_each(function() - stub(ngx, "log") - end) - - after_each(function() - local resty_http = require("resty.http") - - ngx.log:revert() - resty_http.new:revert() - end) - - it("should exit early on premature", function() - local resty_http = require("resty.http") - local request_uri = spy.new() - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_scanner.scan_skylink_timer(true, skylink) - - assert.stub(request_uri).was_not_called() - assert.stub(ngx.log).was_not_called() - end) - - it("should make a post request with skylink to scanner service", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return { status = 200 } -- return 200 success - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_scanner.scan_skylink_timer(false, skylink) - - local uri = "http://10.10.10.101:4000/scan/" .. skylink - assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST" }) - assert.stub(ngx.log).was_not_called() - end) - - it("should log message on scanner request failure with response code", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return { status = 404, body = "baz" } -- return 404 failure - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_scanner.scan_skylink_timer(false, skylink) - - local uri = "http://10.10.10.101:4000/scan/" .. skylink - assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST" }) - assert.stub(ngx.log).was_called_with( - ngx.ERR, - "Failed malware-scanner request /scan/AQBG8n_sgEM_nlEp3G0w3vLjmdvSZ46ln8ZXHn-eObZNjA: ", - "[HTTP 404] baz" - ) - end) - - it("should log message on scanner request error", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return nil, "foo != bar" -- return error - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_scanner.scan_skylink_timer(false, skylink) - - local uri = "http://10.10.10.101:4000/scan/" .. skylink - assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST" }) - assert.stub(ngx.log).was_called_with( - ngx.ERR, - "Failed malware-scanner request /scan/AQBG8n_sgEM_nlEp3G0w3vLjmdvSZ46ln8ZXHn-eObZNjA: ", - "foo != bar" - ) - end) -end) diff --git a/docker/nginx/libs/skynet/skylink.lua b/docker/nginx/libs/skynet/skylink.lua deleted file mode 100644 index 86d1c4bc..00000000 --- a/docker/nginx/libs/skynet/skylink.lua +++ /dev/null @@ -1,40 +0,0 @@ -local _M = {} - -local basexx = require("basexx") -local hasher = require("hasher") - --- parse any skylink and return base64 version -function _M.parse(skylink) - if string.len(skylink) == 55 then - local decoded = basexx.from_basexx(string.upper(skylink), "0123456789ABCDEFGHIJKLMNOPQRSTUV", 5) - - return basexx.to_url64(decoded) - end - - return skylink -end - --- hash skylink into 32 bytes hash used in blocklist -function _M.hash(skylink) - -- ensure that the skylink is base64 encoded - local base64Skylink = _M.parse(skylink) - - -- decode skylink from base64 encoding - local rawSkylink = basexx.from_url64(base64Skylink) - - -- drop first two bytes and leave just merkle root - local rawMerkleRoot = string.sub(rawSkylink, 3) - - -- parse with blake2b with key length of 32 - local blake2bHashed = hasher.blake2b(rawMerkleRoot, 32) - - -- hex encode the blake hash - local hexHashed = basexx.to_hex(blake2bHashed) - - -- lowercase the hex encoded hash - local lowerHexHashed = string.lower(hexHashed) - - return lowerHexHashed -end - -return _M diff --git a/docker/nginx/libs/skynet/skylink.spec.lua b/docker/nginx/libs/skynet/skylink.spec.lua deleted file mode 100644 index 9977d7c8..00000000 --- a/docker/nginx/libs/skynet/skylink.spec.lua +++ /dev/null @@ -1,23 +0,0 @@ -local skynet_skylink = require("skynet.skylink") - -describe("parse", function() - local base32 = "0404dsjvti046fsua4ktor9grrpe76erq9jot9cvopbhsvsu76r4r30" - local base64 = "AQBG8n_sgEM_nlEp3G0w3vLjmdvSZ46ln8ZXHn-eObZNjA" - - it("should return unchanged base64 skylink", function() - assert.is.same(skynet_skylink.parse(base64), base64) - end) - - it("should transform base32 skylink into base64", function() - assert.is.same(skynet_skylink.parse(base32), base64) - end) -end) - -describe("hash", function() - local base64 = "EADi4QZWt87sSDCSjVTcmyI5tE_YAsuC90BcCi_jEmG5NA" - local hash = "6cfb9996ad74e5614bbb8e7228e72f1c1bc14dd9ce8a83b3ccabdb6d8d70f330" - - it("should hash skylink", function() - assert.is.same(hash, skynet_skylink.hash(base64)) - end) -end) diff --git a/docker/nginx/libs/skynet/tracker.lua b/docker/nginx/libs/skynet/tracker.lua deleted file mode 100644 index 37413215..00000000 --- a/docker/nginx/libs/skynet/tracker.lua +++ /dev/null @@ -1,99 +0,0 @@ -local _M = {} - -local utils = require("utils") - -function _M.track_download_timer(premature, skylink, status, auth_headers, body_bytes_sent) - if premature then return end - - local httpc = require("resty.http").new() - local query = table.concat({ "status=" .. status, "bytes=" .. body_bytes_sent }, "&") - - -- 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 = auth_headers, - }) - - if err or (res and res.status ~= 204) then - local error_response = err or ("[HTTP " .. res.status .. "] " .. res.body) - ngx.log(ngx.ERR, "Failed accounts service request /track/download/" .. skylink .. ": ", error_response) - end -end - -function _M.track_download(skylink, status_code, auth_headers, body_bytes_sent) - local has_auth_headers = not utils.is_table_empty(auth_headers) - local status_success = status_code >= 200 and status_code <= 299 - - if skylink and status_success and has_auth_headers then - local ok, err = ngx.timer.at(0, _M.track_download_timer, skylink, status_code, auth_headers, body_bytes_sent) - if not ok then ngx.log(ngx.ERR, "Failed to create timer: ", err) end - end -end - -function _M.track_upload_timer(premature, skylink, auth_headers, uploader_ip) - if premature then return end - - local httpc = require("resty.http").new() - - -- set correct content type header and include auth headers - local headers = { - ["Content-Type"] = "application/x-www-form-urlencoded", - } - for key, value in pairs(auth_headers) do - headers[key] = value - end - - -- 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 = headers, - body = "ip=" .. uploader_ip, - }) - - if err or (res and res.status ~= 204) then - local error_response = err or ("[HTTP " .. res.status .. "] " .. res.body) - ngx.log(ngx.ERR, "Failed accounts service request /track/upload/" .. skylink .. ": ", error_response) - end -end - -function _M.track_upload(skylink, status_code, auth_headers, uploader_ip) - local status_success = status_code >= 200 and status_code <= 299 - - if skylink and status_success then - local ok, err = ngx.timer.at(0, _M.track_upload_timer, skylink, auth_headers, uploader_ip) - if not ok then ngx.log(ngx.ERR, "Failed to create timer: ", err) end - end -end - -function _M.track_registry_timer(premature, auth_headers, request_method) - if premature then return end - - local httpc = require("resty.http").new() - - -- based on request method we assign a registry action string used - -- in track endpoint namely "read" for GET and "write" for POST - local registry_action = request_method == "GET" and "read" or "write" - - -- 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 = auth_headers, - }) - - if err or (res and res.status ~= 204) then - local error_response = err or ("[HTTP " .. res.status .. "] " .. res.body) - ngx.log(ngx.ERR, "Failed accounts service request /track/registry/" .. registry_action .. ": ", error_response) - end -end - -function _M.track_registry(status_code, auth_headers, request_method) - local has_auth_headers = not utils.is_table_empty(auth_headers) - local tracked_status = status_code == 200 or status_code == 404 - - if tracked_status and has_auth_headers then - local ok, err = ngx.timer.at(0, _M.track_registry_timer, auth_headers, request_method) - if not ok then ngx.log(ngx.ERR, "Failed to create timer: ", err) end - end -end - -return _M diff --git a/docker/nginx/libs/skynet/tracker.spec.lua b/docker/nginx/libs/skynet/tracker.spec.lua deleted file mode 100644 index 98d587d8..00000000 --- a/docker/nginx/libs/skynet/tracker.spec.lua +++ /dev/null @@ -1,584 +0,0 @@ --- luacheck: ignore ngx - -local skynet_tracker = require("skynet.tracker") - -local valid_skylink = "AQBG8n_sgEM_nlEp3G0w3vLjmdvSZ46ln8ZXHn-eObZNjA" -local valid_status_code = 200 -local valid_auth_headers = { ["Skynet-Api-Key"] = "foo" } -local valid_ip = "12.34.56.78" - -describe("track_download", function() - local valid_body_bytes_sent = 12345 - - before_each(function() - stub(ngx.timer, "at") - end) - - after_each(function() - ngx.timer.at:revert() - end) - - it("should schedule a timer when conditions are met", function() - ngx.timer.at.invokes(function() return true, nil end) - - skynet_tracker.track_download(valid_skylink, valid_status_code, valid_auth_headers, valid_body_bytes_sent) - - assert.stub(ngx.timer.at).was_called_with( - 0, - skynet_tracker.track_download_timer, - valid_skylink, - valid_status_code, - valid_auth_headers, - valid_body_bytes_sent - ) - end) - - it("should not schedule a timer if skylink is empty", function() - ngx.timer.at.invokes(function() return true, nil end) - - skynet_tracker.track_download(nil, valid_status_code, valid_auth_headers, valid_body_bytes_sent) - - assert.stub(ngx.timer.at).was_not_called() - end) - - it("should not schedule a timer if status code is not in 2XX range", function() - ngx.timer.at.invokes(function() return true, nil end) - - -- couple of example of 4XX and 5XX codes - skynet_tracker.track_download(valid_skylink, 401, valid_auth_headers, valid_body_bytes_sent) - skynet_tracker.track_download(valid_skylink, 403, valid_auth_headers, valid_body_bytes_sent) - skynet_tracker.track_download(valid_skylink, 490, valid_auth_headers, valid_body_bytes_sent) - skynet_tracker.track_download(valid_skylink, 500, valid_auth_headers, valid_body_bytes_sent) - skynet_tracker.track_download(valid_skylink, 502, valid_auth_headers, valid_body_bytes_sent) - - assert.stub(ngx.timer.at).was_not_called() - end) - - it("should not schedule a timer if auth headers are empty", function() - ngx.timer.at.invokes(function() return true, nil end) - - skynet_tracker.track_download(valid_skylink, valid_status_code, {}, valid_body_bytes_sent) - - assert.stub(ngx.timer.at).was_not_called() - end) - - it("should log an error if timer failed to create", function() - stub(ngx, "log") - ngx.timer.at.invokes(function() return false, "such a failure" end) - - skynet_tracker.track_download(valid_skylink, valid_status_code, valid_auth_headers, valid_body_bytes_sent) - - assert.stub(ngx.timer.at).was_called_with( - 0, - skynet_tracker.track_download_timer, - valid_skylink, - valid_status_code, - valid_auth_headers, - valid_body_bytes_sent - ) - assert.stub(ngx.log).was_called_with(ngx.ERR, "Failed to create timer: ", "such a failure") - - ngx.log:revert() - end) - - describe("track_download_timer", function() - before_each(function() - stub(ngx, "log") - end) - - after_each(function() - local resty_http = require("resty.http") - - ngx.log:revert() - resty_http.new:revert() - end) - - it("should exit early on premature", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return { status = 200 } -- return 200 success - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_tracker.track_download_timer( - true, - valid_skylink, - valid_status_code, - valid_auth_headers, - valid_body_bytes_sent - ) - - assert.stub(request_uri).was_not_called() - assert.stub(ngx.log).was_not_called() - end) - - it("should make a post request to tracker service", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return { status = 204 } -- return 204 success - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_tracker.track_download_timer( - false, - valid_skylink, - valid_status_code, - valid_auth_headers, - valid_body_bytes_sent - ) - - local uri_params = "status=" .. valid_status_code .. "&bytes=" .. valid_body_bytes_sent - local uri = "http://10.10.10.70:3000/track/download/" .. valid_skylink .. "?" .. uri_params - assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers }) - assert.stub(ngx.log).was_not_called() - end) - - it("should log message on tracker request failure with response code", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return { status = 404, body = "baz" } -- return 404 failure - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_tracker.track_download_timer( - false, - valid_skylink, - valid_status_code, - valid_auth_headers, - valid_body_bytes_sent - ) - - local uri_params = "status=" .. valid_status_code .. "&bytes=" .. valid_body_bytes_sent - local uri = "http://10.10.10.70:3000/track/download/" .. valid_skylink .. "?" .. uri_params - assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers }) - assert.stub(ngx.log).was_called_with( - ngx.ERR, - "Failed accounts service request /track/download/" .. valid_skylink .. ": ", - "[HTTP 404] baz" - ) - end) - - it("should log message on tracker request error", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return nil, "foo != bar" -- return error - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_tracker.track_download_timer( - false, - valid_skylink, - valid_status_code, - valid_auth_headers, - valid_body_bytes_sent - ) - - local uri_params = "status=" .. valid_status_code .. "&bytes=" .. valid_body_bytes_sent - local uri = "http://10.10.10.70:3000/track/download/" .. valid_skylink .. "?" .. uri_params - assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers }) - assert.stub(ngx.log).was_called_with( - ngx.ERR, - "Failed accounts service request /track/download/" .. valid_skylink .. ": ", - "foo != bar" - ) - end) - end) -end) - -describe("track_upload", function() - before_each(function() - stub(ngx.timer, "at") - end) - - after_each(function() - ngx.timer.at:revert() - end) - - it("should schedule a timer when conditions are met", function() - ngx.timer.at.invokes(function() return true, nil end) - - skynet_tracker.track_upload(valid_skylink, valid_status_code, valid_auth_headers, valid_ip) - - assert.stub(ngx.timer.at).was_called_with( - 0, - skynet_tracker.track_upload_timer, - valid_skylink, - valid_auth_headers, - valid_ip - ) - end) - - it("should not schedule a timer if skylink is empty", function() - ngx.timer.at.invokes(function() return true, nil end) - - skynet_tracker.track_upload(nil, valid_status_code, valid_auth_headers, valid_ip) - - assert.stub(ngx.timer.at).was_not_called() - end) - - it("should not schedule a timer if status code is not in 2XX range", function() - ngx.timer.at.invokes(function() return true, nil end) - - -- couple of example of 4XX and 5XX codes - skynet_tracker.track_upload(valid_skylink, 401, valid_auth_headers, valid_ip) - skynet_tracker.track_upload(valid_skylink, 403, valid_auth_headers, valid_ip) - skynet_tracker.track_upload(valid_skylink, 490, valid_auth_headers, valid_ip) - skynet_tracker.track_upload(valid_skylink, 500, valid_auth_headers, valid_ip) - skynet_tracker.track_upload(valid_skylink, 502, valid_auth_headers, valid_ip) - - assert.stub(ngx.timer.at).was_not_called() - end) - - it("should schedule a timer if auth headers are empty", function() - ngx.timer.at.invokes(function() return true, nil end) - - skynet_tracker.track_upload(valid_skylink, valid_status_code, {}, valid_ip) - - assert.stub(ngx.timer.at).was_called_with( - 0, - skynet_tracker.track_upload_timer, - valid_skylink, - {}, - valid_ip - ) - end) - - it("should log an error if timer failed to create", function() - stub(ngx, "log") - ngx.timer.at.invokes(function() return false, "such a failure" end) - - skynet_tracker.track_upload(valid_skylink, valid_status_code, valid_auth_headers, valid_ip) - - assert.stub(ngx.timer.at).was_called_with( - 0, - skynet_tracker.track_upload_timer, - valid_skylink, - valid_auth_headers, - valid_ip - ) - assert.stub(ngx.log).was_called_with(ngx.ERR, "Failed to create timer: ", "such a failure") - - ngx.log:revert() - end) - - describe("track_upload_timer", function() - before_each(function() - stub(ngx, "log") - end) - - after_each(function() - local resty_http = require("resty.http") - - ngx.log:revert() - resty_http.new:revert() - end) - - it("should exit early on premature", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return { status = 200 } -- return 200 success - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_tracker.track_upload_timer( - true, - valid_skylink, - valid_auth_headers, - valid_ip - ) - - assert.stub(request_uri).was_not_called() - assert.stub(ngx.log).was_not_called() - end) - - it("should make a post request to tracker service", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return { status = 204 } -- return 204 success - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_tracker.track_upload_timer( - false, - valid_skylink, - valid_auth_headers, - valid_ip - ) - - local uri = "http://10.10.10.70:3000/track/upload/" .. valid_skylink - assert.stub(request_uri).was_called_with(httpc, uri, { - method = "POST", - headers = { - ["Content-Type"] = "application/x-www-form-urlencoded", - ["Skynet-Api-Key"] = "foo", - }, - body = "ip=" .. valid_ip - }) - assert.stub(ngx.log).was_not_called() - end) - - it("should log message on tracker request failure with response code", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return { status = 404, body = "baz" } -- return 404 failure - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_tracker.track_upload_timer( - false, - valid_skylink, - valid_auth_headers, - valid_ip - ) - - local uri = "http://10.10.10.70:3000/track/upload/" .. valid_skylink - assert.stub(request_uri).was_called_with(httpc, uri, { - method = "POST", - headers = { - ["Content-Type"] = "application/x-www-form-urlencoded", - ["Skynet-Api-Key"] = "foo", - }, - body = "ip=" .. valid_ip - }) - assert.stub(ngx.log).was_called_with( - ngx.ERR, - "Failed accounts service request /track/upload/" .. valid_skylink .. ": ", - "[HTTP 404] baz" - ) - end) - - it("should log message on tracker request error", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return nil, "foo != bar" -- return error - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_tracker.track_upload_timer( - false, - valid_skylink, - valid_auth_headers, - valid_ip - ) - - local uri = "http://10.10.10.70:3000/track/upload/" .. valid_skylink - assert.stub(request_uri).was_called_with(httpc, uri, { - method = "POST", - headers = { - ["Content-Type"] = "application/x-www-form-urlencoded", - ["Skynet-Api-Key"] = "foo", - }, - body = "ip=" .. valid_ip - }) - assert.stub(ngx.log).was_called_with( - ngx.ERR, - "Failed accounts service request /track/upload/" .. valid_skylink .. ": ", - "foo != bar" - ) - end) - end) -end) - -describe("track_registry", function() - local status_code_ok = 200 - local status_code_not_found = 404 - local request_method_write = "POST" - local request_method_read = "GET" - - before_each(function() - stub(ngx.timer, "at") - end) - - after_each(function() - ngx.timer.at:revert() - end) - - it("should schedule a timer when status code was 200", function() - ngx.timer.at.invokes(function() return true, nil end) - - skynet_tracker.track_registry(status_code_ok, valid_auth_headers, request_method_write) - - assert.stub(ngx.timer.at).was_called_with( - 0, - skynet_tracker.track_registry_timer, - valid_auth_headers, - request_method_write - ) - end) - - it("should schedule a timer when status code was 404", function() - ngx.timer.at.invokes(function() return true, nil end) - - skynet_tracker.track_registry(status_code_not_found, valid_auth_headers, request_method_write) - - assert.stub(ngx.timer.at).was_called_with( - 0, - skynet_tracker.track_registry_timer, - valid_auth_headers, - request_method_write - ) - end) - - it("should not schedule a timer if status code is not in 200 or 404", function() - ngx.timer.at.invokes(function() return true, nil end) - - -- couple of example of invalid 2XX, 4XX and 5XX codes - skynet_tracker.track_registry(204, valid_auth_headers, request_method_write) - skynet_tracker.track_registry(206, valid_auth_headers, request_method_write) - skynet_tracker.track_registry(401, valid_auth_headers, request_method_write) - skynet_tracker.track_registry(403, valid_auth_headers, request_method_write) - skynet_tracker.track_registry(490, valid_auth_headers, request_method_write) - skynet_tracker.track_registry(500, valid_auth_headers, request_method_write) - skynet_tracker.track_registry(502, valid_auth_headers, request_method_write) - - assert.stub(ngx.timer.at).was_not_called() - end) - - it("should not schedule a timer if auth headers are empty", function() - ngx.timer.at.invokes(function() return true, nil end) - - skynet_tracker.track_registry(status_code_ok, {}, request_method_write) - - assert.stub(ngx.timer.at).was_not_called() - end) - - it("should log an error if timer failed to create", function() - stub(ngx, "log") - ngx.timer.at.invokes(function() return false, "such a failure" end) - - skynet_tracker.track_registry(status_code_ok, valid_auth_headers, request_method_write) - - assert.stub(ngx.timer.at).was_called_with( - 0, - skynet_tracker.track_registry_timer, - valid_auth_headers, - request_method_write - ) - assert.stub(ngx.log).was_called_with(ngx.ERR, "Failed to create timer: ", "such a failure") - - ngx.log:revert() - end) - - describe("track_registry_timer", function() - before_each(function() - stub(ngx, "log") - end) - - after_each(function() - local resty_http = require("resty.http") - - ngx.log:revert() - resty_http.new:revert() - end) - - it("should exit early on premature", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return { status = 200 } -- return 200 success - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_tracker.track_registry_timer( - true, - valid_auth_headers, - request_method_write - ) - - assert.stub(request_uri).was_not_called() - assert.stub(ngx.log).was_not_called() - end) - - it("should make a post request to registry write tracker service", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return { status = 204 } -- return 204 success - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_tracker.track_registry_timer( - false, - valid_auth_headers, - request_method_write - ) - - local uri = "http://10.10.10.70:3000/track/registry/write" - assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers }) - assert.stub(ngx.log).was_not_called() - end) - - it("should make a post request to registry read tracker service", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return { status = 204 } -- return 204 success - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_tracker.track_registry_timer( - false, - valid_auth_headers, - request_method_read - ) - - local uri = "http://10.10.10.70:3000/track/registry/read" - assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers }) - assert.stub(ngx.log).was_not_called() - end) - - it("should log message on tracker request failure with response code", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return { status = 404, body = "baz" } -- return 404 failure - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_tracker.track_registry_timer( - false, - valid_auth_headers, - request_method_write - ) - - local uri = "http://10.10.10.70:3000/track/registry/write" - assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers }) - assert.stub(ngx.log).was_called_with( - ngx.ERR, - "Failed accounts service request /track/registry/write: ", - "[HTTP 404] baz" - ) - end) - - it("should log message on tracker request error", function() - local resty_http = require("resty.http") - local request_uri = spy.new(function() - return nil, "foo != bar" -- return error - end) - local httpc = mock({ request_uri = request_uri }) - stub(resty_http, "new").returns(httpc) - - skynet_tracker.track_registry_timer( - false, - valid_auth_headers, - request_method_write - ) - - local uri = "http://10.10.10.70:3000/track/registry/write" - assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers }) - assert.stub(ngx.log).was_called_with( - ngx.ERR, - "Failed accounts service request /track/registry/write: ", - "foo != bar" - ) - end) - end) -end) \ No newline at end of file diff --git a/docker/nginx/libs/skynet/utils.lua b/docker/nginx/libs/skynet/utils.lua deleted file mode 100644 index 05755f7b..00000000 --- a/docker/nginx/libs/skynet/utils.lua +++ /dev/null @@ -1,30 +0,0 @@ -local _M = {} - -local ngx_base64 = require("ngx.base64") -local utils = require("utils") - -function _M.authorization_header() - -- read api password from env variable - local apipassword = utils.getenv("SIA_API_PASSWORD") - -- if api password is not available as env variable, read it from disk - if not apipassword then - -- open apipassword file for reading (b flag is required for some reason) - -- (file /etc/.sia/apipassword has to be mounted from the host system) - local apipassword_file = io.open("/data/sia/apipassword", "rb") - -- make sure to throw a meaningful error if apipassword file does not exist - if not apipassword_file then - error("Error reading /data/sia/apipassword file") - end - -- read apipassword file contents and trim newline (important) - apipassword = apipassword_file:read("*all"):gsub("%s+", "") - -- make sure to close file after reading the password - apipassword_file.close() - end - -- encode the user:password authorization string - -- (in our case user is empty so it is just :password) - local content = ngx_base64.encode_base64url(":" .. apipassword) - -- set authorization header with proper base64 encoded string - return "Basic " .. content -end - -return _M diff --git a/docker/nginx/libs/skynet/utils.spec.lua b/docker/nginx/libs/skynet/utils.spec.lua deleted file mode 100644 index 171be4fa..00000000 --- a/docker/nginx/libs/skynet/utils.spec.lua +++ /dev/null @@ -1,65 +0,0 @@ --- luacheck: ignore io - -local utils = require('utils') -local skynet_utils = require('skynet.utils') - -describe("authorization_header", function() - local apipassword = "ddd0c1430fbf2708" - local expected_header = "Basic OmRkZDBjMTQzMGZiZjI3MDg" - - it("reads SIA_API_PASSWORD from env variable and returns a header", function() - -- stub getenv on SIA_API_PASSWORD - stub(utils, "getenv") - utils.getenv.on_call_with("SIA_API_PASSWORD").returns(apipassword) - - local header = skynet_utils.authorization_header() - - assert.is_equal(header, expected_header) - - -- revert stub to original function - utils.getenv:revert() - end) - - it("uses /data/sia/apipassword file if SIA_API_PASSWORD env var is missing", function() - -- stub /data/sia/apipassword file - stub(io, "open") - io.open.on_call_with("/data/sia/apipassword", "rb").returns(mock({ - read = spy.new(function() return apipassword end), - close = spy.new() - })) - - local header = skynet_utils.authorization_header() - - assert.is_equal(header, expected_header) - - -- revert stub to original function - io.open:revert() - end) - - it("should choose env variable over file if both are available", function() - -- stub getenv on SIA_API_PASSWORD - stub(utils, "getenv") - utils.getenv.on_call_with("SIA_API_PASSWORD").returns(apipassword) - - -- stub /data/sia/apipassword file - stub(io, "open") - io.open.on_call_with("/data/sia/apipassword", "rb").returns(mock({ - read = spy.new(function() return "foooooooooooooo" end), - close = spy.new() - })) - - local header = skynet_utils.authorization_header() - - assert.is_equal(header, "Basic OmRkZDBjMTQzMGZiZjI3MDg") - - -- revert stubs to original function - utils.getenv:revert() - io.open:revert() - end) - - it("should error out if neither env variable is available nor file exists", function() - assert.has_error(function() - skynet_utils.authorization_header() - end, "Error reading /data/sia/apipassword file") - end) -end) diff --git a/docker/nginx/libs/utils.lua b/docker/nginx/libs/utils.lua deleted file mode 100644 index 8b77d802..00000000 --- a/docker/nginx/libs/utils.lua +++ /dev/null @@ -1,83 +0,0 @@ -local _M = {} - --- utility function for checking if table is empty -function _M.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 - --- extract full cookie name and value by its name from cookie string --- note: name matcher argument is a pattern so you will need to escape --- any special characters, read more https://www.lua.org/pil/20.2.html -function _M.extract_cookie(cookie_string, name_matcher) - -- nil cookie string safeguard - if cookie_string == nil then - return nil - end - - local start, stop = string.find(cookie_string, name_matcher .. "=[^;]+") - - if start then - return string.sub(cookie_string, start, stop) - end - - return nil -end - --- extract just the cookie value by its name from cookie string --- note: name matcher argument is a pattern so you will need to escape --- any special characters, read more https://www.lua.org/pil/20.2.html -function _M.extract_cookie_value(cookie_string, name_matcher) - local cookie = _M.extract_cookie(cookie_string, name_matcher) - - if cookie == nil then - return nil - end - - local value_start = string.find(cookie, "=") + 1 - - return string.sub(cookie, value_start) -end - --- utility function that builds on os.getenv to get environment variable value --- * will always return nil for both unset and empty env vars --- * parse "boolean": "true" and "1" as true, "false" and "0" as false, throws for others --- * parse "integer": any numerical string gets converted, otherwise returns nil -function _M.getenv(name, parse) - local value = os.getenv(name) - - -- treat empty string value as nil to simplify comparisons - if value == nil or value == "" then - return nil - end - - -- do not parse the value - if parse == nil then - return value - end - - -- try to parse as boolean - if parse == "boolean" then - if string.lower(value) == "true" or value == "1" then - return true - end - - if string.lower(value) == "false" or value == "0" then - return false - end - - error("utils.getenv: Parsing value '" .. tostring(value) .. "' to boolean is not supported") - end - - -- try to parse as integer - if parse == "integer" then - return tonumber(value) - end - - error("utils.getenv: Parsing to '" .. parse .. "' is not supported") -end - -return _M diff --git a/docker/nginx/libs/utils.spec.lua b/docker/nginx/libs/utils.spec.lua deleted file mode 100644 index 71ef086c..00000000 --- a/docker/nginx/libs/utils.spec.lua +++ /dev/null @@ -1,215 +0,0 @@ --- luacheck: ignore os - -local utils = require('utils') - -describe("is_table_empty", function() - it("should return true for empty table", function() - assert.is_true(utils.is_table_empty({})) - end) - - it("should return false for not empty table", function() - assert.is_false(utils.is_table_empty({ ["foo"] = "bar" })) - end) -end) - -describe("extract_cookie", function() - local cookie_string = "aaa=bbb; skynet-jwt=MTY0NzUyr8jD-ytiWtspm0tGabKfooxeIDuWcXhJ3lnY0eEw==; xxx=yyy" - - it("should return nil if cookie string is nil", function() - local cookie = utils.extract_cookie_value(nil, "aaa") - - assert.is_nil(cookie) - end) - - it("should return nil if cookie name is not found", function() - local cookie = utils.extract_cookie(cookie_string, "foo") - - assert.is_nil(cookie) - end) - - it("should return cookie if cookie_string starts with that cookie name", function() - local cookie = utils.extract_cookie(cookie_string, "aaa") - - assert.are.equals(cookie, "aaa=bbb") - end) - - it("should return cookie if cookie_string ends with that cookie name", function() - local cookie = utils.extract_cookie(cookie_string, "xxx") - - assert.are.equals(cookie, "xxx=yyy") - end) - - it("should return cookie with custom matcher", function() - local cookie = utils.extract_cookie(cookie_string, "skynet[-]jwt") - - assert.are.equals(cookie, "skynet-jwt=MTY0NzUyr8jD-ytiWtspm0tGabKfooxeIDuWcXhJ3lnY0eEw==") - end) -end) - -describe("extract_cookie_value", function() - local cookie_string = "aaa=bbb; skynet-jwt=MTY0NzUyr8jD-ytiWtspm0tGabKfooxeIDuWcXhJ3lnY0eEw==; xxx=yyy" - - it("should return nil if cookie string is nil", function() - local value = utils.extract_cookie_value(nil, "aaa") - - assert.is_nil(value) - end) - - it("should return nil if cookie name is not found", function() - local value = utils.extract_cookie_value(cookie_string, "foo") - - assert.is_nil(value) - end) - - it("should return value if cookie_string starts with that cookie name", function() - local value = utils.extract_cookie_value(cookie_string, "aaa") - - assert.are.equals(value, "bbb") - end) - - it("should return cookie if cookie_string ends with that cookie name", function() - local value = utils.extract_cookie_value(cookie_string, "xxx") - - assert.are.equals(value, "yyy") - end) - - it("should return cookie with custom matcher", function() - local value = utils.extract_cookie_value(cookie_string, "skynet[-]jwt") - - assert.are.equals(value, "MTY0NzUyr8jD-ytiWtspm0tGabKfooxeIDuWcXhJ3lnY0eEw==") - end) -end) - -describe("getenv", function() - before_each(function() - stub(os, "getenv") - end) - - after_each(function() - os.getenv:revert() - end) - - it("should return nil for not existing env var", function() - os.getenv.on_call_with("foo").returns(nil) - - assert.is_nil(utils.getenv("foo")) - end) - - it("should return nil for env var that is an empty string", function() - os.getenv.on_call_with("foo").returns("") - - assert.is_nil(utils.getenv("foo")) - end) - - it("should return the value as is when it is non empty string", function() - os.getenv.on_call_with("foo").returns("bar") - - assert.is_equal(utils.getenv("foo"), "bar") - end) - - describe("parse", function() - it("should throw on not supported parser", function() - os.getenv.on_call_with("foo").returns("test") - - assert.has_error(function() - utils.getenv("foo", "starwars") - end, "utils.getenv: Parsing to 'starwars' is not supported") - end) - - describe("as boolean", function() - it("should return nil for not existing env var", function() - os.getenv.on_call_with("foo").returns(nil) - - assert.is_nil(utils.getenv("foo", "boolean")) - end) - - it("should return nil for env var that is an empty string", function() - os.getenv.on_call_with("foo").returns("") - - assert.is_nil(utils.getenv("foo", "boolean")) - end) - - it("should parse 'true' string as true", function() - os.getenv.on_call_with("foo").returns("true") - - assert.is_true(utils.getenv("foo", "boolean")) - end) - - it("should parse 'True' string as true", function() - os.getenv.on_call_with("foo").returns("True") - - assert.is_true(utils.getenv("foo", "boolean")) - end) - - it("should parse '1' string as true", function() - os.getenv.on_call_with("foo").returns("1") - - assert.is_true(utils.getenv("foo", "boolean")) - end) - - it("should parse 'false' string as false", function() - os.getenv.on_call_with("foo").returns("false") - - assert.is_false(utils.getenv("foo", "boolean")) - end) - - it("should parse 'False' string as false", function() - os.getenv.on_call_with("foo").returns("False") - - assert.is_false(utils.getenv("foo", "boolean")) - end) - - it("should parse '0' string as false", function() - os.getenv.on_call_with("foo").returns("0") - - assert.is_false(utils.getenv("foo", "boolean")) - end) - - it("should throw an error for not supported string", function() - os.getenv.on_call_with("foo").returns("mandalorian") - - assert.has_error(function() - utils.getenv("foo", "boolean") - end, "utils.getenv: Parsing value 'mandalorian' to boolean is not supported") - end) - end) - - describe("as integer", function() - it("should return nil for not existing env var", function() - os.getenv.on_call_with("foo").returns(nil) - - assert.is_nil(utils.getenv("foo", "integer")) - end) - - it("should return nil for env var that is an empty string", function() - os.getenv.on_call_with("foo").returns("") - - assert.is_nil(utils.getenv("foo", "integer")) - end) - - it("should parse '0' string as 0", function() - os.getenv.on_call_with("foo").returns("0") - - assert.equals(utils.getenv("foo", "integer"), 0) - end) - - it("should parse '1' string as 1", function() - os.getenv.on_call_with("foo").returns("1") - - assert.equals(utils.getenv("foo", "integer"), 1) - end) - - it("should parse '-1' string as -1", function() - os.getenv.on_call_with("foo").returns("-1") - - assert.equals(utils.getenv("foo", "integer"), -1) - end) - - it("should return nil for non numerical string", function() - os.getenv.on_call_with("foo").returns("test") - - assert.is_nil(utils.getenv("foo", "integer")) - end) - end) - end) -end) diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf deleted file mode 100644 index de55d276..00000000 --- a/docker/nginx/nginx.conf +++ /dev/null @@ -1,125 +0,0 @@ -# nginx.conf -- docker-openresty -# -# This file is installed to: -# `/usr/local/openresty/nginx/conf/nginx.conf` -# and is the file loaded by nginx at startup, -# unless the user specifies otherwise. -# -# It tracks the upstream OpenResty's `nginx.conf`, but removes the `server` -# section and adds this directive: -# `include /etc/nginx/conf.d/*.conf;` -# -# The `docker-openresty` file `nginx.vh.default.conf` is copied to -# `/etc/nginx/conf.d/default.conf`. It contains the `server section -# of the upstream `nginx.conf`. -# -# See https://github.com/openresty/docker-openresty/blob/master/README.md#nginx-config-files -# - -user root; -worker_processes auto; - -# Enables the use of JIT for regular expressions to speed-up their processing. -pcre_jit on; - -#error_log logs/error.log; -#error_log logs/error.log notice; -#error_log logs/error.log info; - -#pid logs/nginx.pid; - -# declare env variables to use it in config -env PORTAL_DOMAIN; -env SERVER_DOMAIN; -env PORTAL_MODULES; -env ACCOUNTS_LIMIT_ACCESS; -env SIA_API_PASSWORD; - -events { - worker_connections 8192; -} - -http { - include mime.types; - default_type application/octet-stream; - - lua_package_path "/etc/nginx/libs/?.lua;;"; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" $upstream_response_time ' - '$upstream_bytes_sent $upstream_bytes_received ' - '"$upstream_http_content_type" "$upstream_cache_status" ' - '"$server_alias" "$sent_http_skynet_skylink" ' - '$upstream_connect_time $upstream_header_time ' - '$request_time "$hns_domain" "$skylink" $upstream_http_skynet_cache_ratio'; - - access_log logs/access.log main; - - # See Move default writable paths to a dedicated directory (#119) - # https://github.com/openresty/docker-openresty/issues/119 - client_body_temp_path /var/run/openresty/nginx-client-body; - proxy_temp_path /var/run/openresty/nginx-proxy; - fastcgi_temp_path /var/run/openresty/nginx-fastcgi; - uwsgi_temp_path /var/run/openresty/nginx-uwsgi; - scgi_temp_path /var/run/openresty/nginx-scgi; - - sendfile on; - #tcp_nopush on; - - #keepalive_timeout 0; - keepalive_timeout 65; - - # globally enable http 1.1 on all proxied requests - # http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_http_version - proxy_http_version 1.1; - - # proxy cache definition - proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=skynet:10m max_size=50g min_free=100g inactive=48h use_temp_path=off; - - # this runs before forking out nginx worker processes - init_by_lua_block { - require "cjson" - require "resty.http" - require "skynet.skylink" - require "skynet.utils" - } - - # include skynet-portal-api and skynet-server-api header on every request - header_filter_by_lua_block { - ngx.header["Skynet-Portal-Api"] = ngx.var.scheme .. "://" .. ngx.var.skynet_portal_domain - ngx.header["Skynet-Server-Api"] = ngx.var.scheme .. "://" .. ngx.var.skynet_server_domain - } - - # ratelimit specified IPs - geo $limit { - default 0; - include /etc/nginx/conf.d/include/ratelimited; - } - - map $limit $limit_key { - 0 ""; - 1 $binary_remote_addr; - } - - limit_req_zone $binary_remote_addr zone=uploads_by_ip:10m rate=10r/s; - limit_req_zone $limit_key zone=uploads_by_ip_throttled:10m rate=10r/m; - - limit_req_zone $binary_remote_addr zone=registry_access_by_ip:10m rate=60r/m; - limit_req_zone $limit_key zone=registry_access_by_ip_throttled:10m rate=20r/m; - - limit_conn_zone $binary_remote_addr zone=upload_conn:10m; - limit_conn_zone $limit_key zone=upload_conn_rl:10m; - - limit_conn_zone $binary_remote_addr zone=downloads_by_ip:10m; - - limit_req_status 429; - limit_conn_status 429; - - # Add X-Forwarded-* headers - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Proto $scheme; - - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/conf.extra.d/*.conf; -} diff --git a/docker/nginx/testing/.luacov b/docker/nginx/testing/.luacov deleted file mode 100644 index 2c55f3da..00000000 --- a/docker/nginx/testing/.luacov +++ /dev/null @@ -1,6 +0,0 @@ -exclude = { - "/usr/local/openresty", -- internal openresty libraries - "rbusted", -- busted executable - "basexx", -- external library https://github.com/aiq/basexx -} -includeuntestedfiles = true diff --git a/docker/nginx/testing/Dockerfile b/docker/nginx/testing/Dockerfile deleted file mode 100644 index ed7740b6..00000000 --- a/docker/nginx/testing/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM openresty/openresty:1.19.9.1-focal - -WORKDIR /etc/nginx - -RUN luarocks install lua-resty-http && \ - luarocks install hasher && \ - luarocks install busted - -COPY rbusted /etc/nginx/ - -CMD ["/etc/nginx/rbusted", "--verbose", "--pattern=spec", "/usr/local/openresty/site/lualib"] diff --git a/docker/nginx/testing/README.md b/docker/nginx/testing/README.md deleted file mode 100644 index f40e8d95..00000000 --- a/docker/nginx/testing/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Running tests locally - -`docker run -v $(pwd)/docker/nginx/libs:/usr/local/openresty/site/lualib --rm -it $(docker build -q docker/nginx/testing)` diff --git a/docker/nginx/testing/rbusted b/docker/nginx/testing/rbusted deleted file mode 100755 index 94149350..00000000 --- a/docker/nginx/testing/rbusted +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env resty - -setmetatable(_G, nil) - -pcall(require, "luarocks.loader") - --- Busted command-line runner -require "busted.runner"({ standalone = false }) diff --git a/packages/dashboard-v2/.eslintignore b/packages/dashboard-v2/.eslintignore deleted file mode 100644 index 65ea287b..00000000 --- a/packages/dashboard-v2/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules/ -.cache/ -public/ -storybook-build/ diff --git a/packages/dashboard-v2/.eslintrc.js b/packages/dashboard-v2/.eslintrc.js deleted file mode 100644 index 51d8f9b5..00000000 --- a/packages/dashboard-v2/.eslintrc.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - globals: { - __PATH_PREFIX__: true, - }, - extends: ["react-app", "plugin:storybook/recommended"], -}; diff --git a/packages/dashboard-v2/.gitignore b/packages/dashboard-v2/.gitignore deleted file mode 100644 index 65ea287b..00000000 --- a/packages/dashboard-v2/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules/ -.cache/ -public/ -storybook-build/ diff --git a/packages/dashboard-v2/.prettierignore b/packages/dashboard-v2/.prettierignore deleted file mode 100644 index 65ea287b..00000000 --- a/packages/dashboard-v2/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules/ -.cache/ -public/ -storybook-build/ diff --git a/packages/dashboard-v2/.prettierrc.json b/packages/dashboard-v2/.prettierrc.json deleted file mode 100644 index 963354f2..00000000 --- a/packages/dashboard-v2/.prettierrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "printWidth": 120 -} diff --git a/packages/dashboard-v2/.storybook/main.js b/packages/dashboard-v2/.storybook/main.js deleted file mode 100644 index 8a1198a4..00000000 --- a/packages/dashboard-v2/.storybook/main.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"], - addons: [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "storybook-addon-gatsby", - { - name: "@storybook/addon-postcss", - options: { - postcssLoaderOptions: { - implementation: require("postcss"), - }, - }, - }, - ], - core: { - builder: "webpack5", - }, -}; diff --git a/packages/dashboard-v2/.storybook/preview.js b/packages/dashboard-v2/.storybook/preview.js deleted file mode 100644 index de9fb8cb..00000000 --- a/packages/dashboard-v2/.storybook/preview.js +++ /dev/null @@ -1,20 +0,0 @@ -import "tailwindcss/tailwind.css"; -import "@fontsource/sora/300.css"; // light -import "@fontsource/sora/400.css"; // normal -import "@fontsource/sora/500.css"; // medium -import "@fontsource/sora/600.css"; // semibold -import "@fontsource/source-sans-pro/400.css"; // normal -import "@fontsource/source-sans-pro/600.css"; // semibold - -import "../src/styles/global.css"; - -export const parameters = { - actions: { argTypesRegex: "^on[A-Z].*" }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, - layout: "fullscreen", -}; diff --git a/packages/dashboard-v2/Dockerfile b/packages/dashboard-v2/Dockerfile deleted file mode 100644 index 86fe89bc..00000000 --- a/packages/dashboard-v2/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM node:16.14.2-alpine - -WORKDIR /usr/app - -COPY package.json \ - yarn.lock \ - ./ - -RUN yarn --frozen-lockfile - -COPY static ./static -COPY src ./src -COPY gatsby*.js \ - postcss.config.js \ - tailwind.config.js \ - ./ - -CMD ["sh", "-c", "yarn build && yarn serve --host 0.0.0.0 -p 9000"] diff --git a/packages/dashboard-v2/README.md b/packages/dashboard-v2/README.md deleted file mode 100644 index e8924bc3..00000000 --- a/packages/dashboard-v2/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Skynet Account Dashboard - -Code behind [account.skynetpro.net](https://account.skynetpro.net/) - -## Development - -This is a Gatsby application. To run it locally, all you need is: - -- `yarn install` -- `yarn start` - -## Accessing remote APIs - -To have a fully functioning local environment, you'll need to make the browser believe you're actually on the same domain as a working API (i.e. a remote dev or production server) -- otherwise the browser will block the session cookie. -To do the trick, configure proper environment variables in the `.env.development` file. -This file allows to easily control which domain name you want to use locally and which API you'd like to access. - -Example: - -```env -GATSBY_PORTAL_DOMAIN=skynetfree.net # Use skynetfree.net APIs -GATSBY_HOST=local.skynetfree.net # Address of your local build -``` - -> It's recommended to keep the 2LD the same, so any cookies dispatched by the API work without issues. - -With the file configured, run `yarn develop:secure` -- it will run `gatsby develop` with `--https -p=443` options. -If you're on macOS, you may need to `sudo` the command to successfully bind to port `443` (https). - -Gatsby will automatically add a proper entry to your `/etc/hosts` file and clean it up when process exits. diff --git a/packages/dashboard-v2/gatsby-browser.js b/packages/dashboard-v2/gatsby-browser.js deleted file mode 100644 index 927fd206..00000000 --- a/packages/dashboard-v2/gatsby-browser.js +++ /dev/null @@ -1,32 +0,0 @@ -import * as React from "react"; -import { SWRConfig } from "swr"; -import { Elements } from "@stripe/react-stripe-js"; -import { loadStripe } from "@stripe/stripe-js"; -import "@fontsource/sora/300.css"; // light -import "@fontsource/sora/400.css"; // normal -import "@fontsource/sora/500.css"; // medium -import "@fontsource/sora/600.css"; // semibold -import "@fontsource/source-sans-pro/400.css"; // normal -import "@fontsource/source-sans-pro/600.css"; // semibold -import "./src/styles/global.css"; -import swrConfig from "./src/lib/swrConfig"; -import { MODAL_ROOT_ID } from "./src/components/Modal"; -import { PortalSettingsProvider } from "./src/contexts/portal-settings"; - -const stripePromise = loadStripe(process.env.GATSBY_STRIPE_PUBLISHABLE_KEY); - -export function wrapPageElement({ element, props }) { - const Layout = element.type.Layout ?? React.Fragment; - return ( - - - - - {element} -
- - - - - ); -} diff --git a/packages/dashboard-v2/gatsby-config.js b/packages/dashboard-v2/gatsby-config.js deleted file mode 100644 index d087fa65..00000000 --- a/packages/dashboard-v2/gatsby-config.js +++ /dev/null @@ -1,57 +0,0 @@ -require("dotenv").config({ - path: `.env.${process.env.NODE_ENV}`, -}); - -const { createProxyMiddleware } = require("http-proxy-middleware"); - -const { GATSBY_PORTAL_DOMAIN } = process.env; - -module.exports = { - siteMetadata: { - title: `Account Dashboard`, - siteUrl: `https://account.${GATSBY_PORTAL_DOMAIN}`, - }, - pathPrefix: "/v2", - trailingSlash: "never", - plugins: [ - "gatsby-plugin-image", - "gatsby-plugin-provide-react", - "gatsby-plugin-react-helmet", - "gatsby-plugin-sharp", - "gatsby-transformer-sharp", - "gatsby-plugin-styled-components", - "gatsby-plugin-postcss", - { - resolve: "gatsby-source-filesystem", - options: { - name: "images", - path: "./static/images/", - }, - __key: "images", - }, - ], - developMiddleware: (app) => { - // Proxy Accounts service API requests: - app.use( - "/api/", - createProxyMiddleware({ - target: `https://account.${GATSBY_PORTAL_DOMAIN}`, - secure: false, // Do not reject self-signed certificates. - changeOrigin: true, - }) - ); - - // Proxy /skynet requests (e.g. uploads) - app.use( - ["/skynet", "/__internal/"], - createProxyMiddleware({ - target: `https://${GATSBY_PORTAL_DOMAIN}`, - secure: false, // Do not reject self-signed certificates. - changeOrigin: true, - pathRewrite: { - "^/skynet": "", - }, - }) - ); - }, -}; diff --git a/packages/dashboard-v2/gatsby-ssr.js b/packages/dashboard-v2/gatsby-ssr.js deleted file mode 100644 index 927fd206..00000000 --- a/packages/dashboard-v2/gatsby-ssr.js +++ /dev/null @@ -1,32 +0,0 @@ -import * as React from "react"; -import { SWRConfig } from "swr"; -import { Elements } from "@stripe/react-stripe-js"; -import { loadStripe } from "@stripe/stripe-js"; -import "@fontsource/sora/300.css"; // light -import "@fontsource/sora/400.css"; // normal -import "@fontsource/sora/500.css"; // medium -import "@fontsource/sora/600.css"; // semibold -import "@fontsource/source-sans-pro/400.css"; // normal -import "@fontsource/source-sans-pro/600.css"; // semibold -import "./src/styles/global.css"; -import swrConfig from "./src/lib/swrConfig"; -import { MODAL_ROOT_ID } from "./src/components/Modal"; -import { PortalSettingsProvider } from "./src/contexts/portal-settings"; - -const stripePromise = loadStripe(process.env.GATSBY_STRIPE_PUBLISHABLE_KEY); - -export function wrapPageElement({ element, props }) { - const Layout = element.type.Layout ?? React.Fragment; - return ( - - - - - {element} -
- - - - - ); -} diff --git a/packages/dashboard-v2/package.json b/packages/dashboard-v2/package.json deleted file mode 100644 index 53edfa12..00000000 --- a/packages/dashboard-v2/package.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "name": "accounts-dashboard", - "version": "1.0.0", - "private": true, - "description": "Accounts Dashboard", - "author": "Skynet Labs", - "keywords": [ - "gatsby" - ], - "scripts": { - "develop": "gatsby develop", - "develop:secure": "dotenv -e .env.development -- gatsby develop --https -p=443", - "start": "gatsby develop", - "build": "gatsby build --prefix-paths", - "serve": "gatsby serve --prefix-paths", - "clean": "gatsby clean", - "lint": "eslint .", - "prettier": "prettier .", - "storybook": "start-storybook -p 6006", - "build-storybook": "build-storybook -o storybook-build" - }, - "dependencies": { - "@fontsource/sora": "^4.5.3", - "@fontsource/source-sans-pro": "^4.5.3", - "@stripe/react-stripe-js": "^1.7.1", - "@stripe/stripe-js": "^1.27.0", - "classnames": "^2.3.1", - "copy-text-to-clipboard": "^3.0.1", - "dayjs": "^1.10.8", - "formik": "^2.2.9", - "gatsby": "^4.6.2", - "gatsby-plugin-postcss": "^5.7.0", - "http-status-codes": "^2.2.0", - "ky": "^0.30.0", - "nanoid": "^3.3.1", - "path-browserify": "^1.0.1", - "postcss": "^8.4.6", - "react": "^17.0.1", - "react-dom": "^17.0.1", - "react-dropzone": "^12.0.4", - "react-helmet": "^6.1.0", - "react-use": "^17.3.2", - "skynet-js": "4.0.27-beta", - "swr": "^1.2.2", - "tailwindcss": "^3.0.23", - "yup": "^0.32.11" - }, - "devDependencies": { - "@babel/core": "^7.17.4", - "@storybook/addon-actions": "^6.4.19", - "@storybook/addon-essentials": "^6.4.19", - "@storybook/addon-interactions": "^6.4.19", - "@storybook/addon-links": "^6.4.19", - "@storybook/addon-postcss": "^2.0.0", - "@storybook/builder-webpack5": "^6.4.19", - "@storybook/manager-webpack5": "^6.4.19", - "@storybook/react": "^6.4.19", - "@storybook/testing-library": "^0.0.9", - "autoprefixer": "^10.4.2", - "babel-eslint": "^10.1.0", - "babel-loader": "^8.2.3", - "babel-plugin-preval": "^5.1.0", - "babel-plugin-styled-components": "^2.0.2", - "dotenv": "^16.0.0", - "dotenv-cli": "^5.1.0", - "eslint": "^8.9.0", - "eslint-config-react-app": "^7.0.0", - "eslint-plugin-storybook": "^0.5.6", - "gatsby-plugin-alias-imports": "^1.0.5", - "gatsby-plugin-image": "^2.6.0", - "gatsby-plugin-preval": "^1.0.0", - "gatsby-plugin-provide-react": "^1.0.2", - "gatsby-plugin-react-helmet": "^5.6.0", - "gatsby-plugin-sharp": "^4.6.0", - "gatsby-plugin-styled-components": "^5.8.0", - "gatsby-source-filesystem": "^4.6.0", - "gatsby-transformer-sharp": "^4.6.0", - "http-proxy-middleware": "^1.3.1", - "prettier": "2.5.1", - "react-is": "^17.0.2", - "storybook-addon-gatsby": "^0.0.5", - "styled-components": "^5.3.3" - } -} diff --git a/packages/dashboard-v2/postcss.config.js b/packages/dashboard-v2/postcss.config.js deleted file mode 100644 index 3b35b010..00000000 --- a/packages/dashboard-v2/postcss.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - plugins: [require("tailwindcss/nesting"), require("tailwindcss"), require("autoprefixer")], -}; diff --git a/packages/dashboard-v2/src/components/APIKeyList/APIKey.js b/packages/dashboard-v2/src/components/APIKeyList/APIKey.js deleted file mode 100644 index b90a539e..00000000 --- a/packages/dashboard-v2/src/components/APIKeyList/APIKey.js +++ /dev/null @@ -1,157 +0,0 @@ -import dayjs from "dayjs"; -import cn from "classnames"; -import { useCallback, useState } from "react"; - -import { Alert } from "../Alert"; -import { Button } from "../Button"; -import { AddSkylinkToSponsorKeyForm } from "../forms/AddSkylinkToSponsorKeyForm"; -import { CogIcon, TrashIcon } from "../Icons"; -import { Modal } from "../Modal"; - -import { useAPIKeyEdit } from "./useAPIKeyEdit"; -import { useAPIKeyRemoval } from "./useAPIKeyRemoval"; - -export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => { - const { id, name, createdAt, skylinks } = apiKey; - const isSponsorKey = apiKey.public === "true"; - const [error, setError] = useState(null); - - const onSkylinkListEdited = useCallback(() => { - setError(null); - onEdited(); - }, [onEdited]); - - const onSkylinkListEditFailure = (errorMessage) => setError(errorMessage); - - const { - removalError, - removalInitiated, - prompt: promptRemoval, - abort: abortRemoval, - confirm: confirmRemoval, - } = useAPIKeyRemoval({ - key: apiKey, - onSuccess: onRemoved, - onFailure: onRemovalError, - }); - - const { - editInitiated, - prompt: promptEdit, - abort: abortEdit, - addSkylink, - removeSkylink, - } = useAPIKeyEdit({ - key: apiKey, - onSkylinkListUpdate: onSkylinkListEdited, - onSkylinkListUpdateFailure: onSkylinkListEditFailure, - }); - - const closeEditModal = useCallback(() => { - setError(null); - abortEdit(); - }, [abortEdit]); - - const skylinksNumber = skylinks?.length ?? 0; - const isNotConfigured = isSponsorKey && skylinksNumber === 0; - const skylinksPhrasePrefix = skylinksNumber === 0 ? "No" : skylinksNumber; - const skylinksPhrase = `${skylinksPhrasePrefix} ${skylinksNumber === 1 ? "skylink" : "skylinks"} sponsored`; - - return ( -
  • - - - {name || "unnamed key"} - {isSponsorKey && ( - - )} - - - - {dayjs(createdAt).format("MMM DD, YYYY")} - - {isSponsorKey && ( - - )} - - - - {removalInitiated && ( - -

    Delete API key

    -
    -

    Are you sure you want to delete the following API key?

    -

    {name || id}

    -
    - {removalError && {removalError}} - -
    - - -
    -
    - )} - {editInitiated && ( - -

    Sponsored skylinks

    - {skylinks?.length > 0 ? ( -
      - {skylinks.map((skylink) => ( -
    • - - {skylink} - - -
    • - ))} -
    - ) : ( - No skylinks here yet. You can add the first one below 🙃 - )} - -
    - {error && {error}} - -
    -
    - -
    -
    - )} -
  • - ); -}; diff --git a/packages/dashboard-v2/src/components/APIKeyList/APIKeyList.js b/packages/dashboard-v2/src/components/APIKeyList/APIKeyList.js deleted file mode 100644 index 3d3e504d..00000000 --- a/packages/dashboard-v2/src/components/APIKeyList/APIKeyList.js +++ /dev/null @@ -1,14 +0,0 @@ -import { APIKey } from "./APIKey"; - -export const APIKeyList = ({ keys, reloadKeys, title }) => { - return ( - <> -
    {title}
    -
      - {keys.map((key) => ( - - ))} -
    - - ); -}; diff --git a/packages/dashboard-v2/src/components/APIKeyList/index.js b/packages/dashboard-v2/src/components/APIKeyList/index.js deleted file mode 100644 index 8ade7744..00000000 --- a/packages/dashboard-v2/src/components/APIKeyList/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from "./APIKeyList"; diff --git a/packages/dashboard-v2/src/components/APIKeyList/useAPIKeyEdit.js b/packages/dashboard-v2/src/components/APIKeyList/useAPIKeyEdit.js deleted file mode 100644 index a821ca02..00000000 --- a/packages/dashboard-v2/src/components/APIKeyList/useAPIKeyEdit.js +++ /dev/null @@ -1,43 +0,0 @@ -import { useCallback, useState } from "react"; -import accountsService from "../../services/accountsService"; - -export const useAPIKeyEdit = ({ key, onSkylinkListUpdate, onSkylinkListUpdateFailure }) => { - const [editInitiated, setEditInitiated] = useState(false); - - const prompt = () => setEditInitiated(true); - const abort = () => setEditInitiated(false); - const updateSkylinkList = useCallback( - async (action, skylink) => { - try { - await accountsService.patch(`user/apikeys/${key.id}`, { - json: { - [action]: [skylink], - }, - }); - onSkylinkListUpdate(); - - return true; - } catch (err) { - if (err.response) { - const { message } = await err.response.json(); - onSkylinkListUpdateFailure(message); - } else { - onSkylinkListUpdateFailure("Unknown error occured, please try again."); - } - - return false; - } - }, - [onSkylinkListUpdate, onSkylinkListUpdateFailure, key] - ); - const addSkylink = (skylink) => updateSkylinkList("add", skylink); - const removeSkylink = (skylink) => updateSkylinkList("remove", skylink); - - return { - editInitiated, - prompt, - abort, - addSkylink, - removeSkylink, - }; -}; diff --git a/packages/dashboard-v2/src/components/APIKeyList/useAPIKeyRemoval.js b/packages/dashboard-v2/src/components/APIKeyList/useAPIKeyRemoval.js deleted file mode 100644 index b9c53bd9..00000000 --- a/packages/dashboard-v2/src/components/APIKeyList/useAPIKeyRemoval.js +++ /dev/null @@ -1,41 +0,0 @@ -import { useCallback, useState } from "react"; -import accountsService from "../../services/accountsService"; - -export const useAPIKeyRemoval = ({ key, onSuccess }) => { - const [removalInitiated, setRemovalInitiated] = useState(false); - const [removalError, setRemovalError] = useState(null); - - const prompt = () => { - setRemovalError(null); - setRemovalInitiated(true); - }; - const abort = () => setRemovalInitiated(false); - - const confirm = useCallback(async () => { - setRemovalError(null); - try { - await accountsService.delete(`user/apikeys/${key.id}`); - setRemovalInitiated(false); - onSuccess(); - } catch (err) { - let message = "There was an error processing your request. Please try again later."; - - if (err.response) { - const response = await err.response.json(); - if (response.message) { - message = response.message; - } - } - - setRemovalError(message); - } - }, [onSuccess, key]); - - return { - removalInitiated, - removalError, - prompt, - abort, - confirm, - }; -}; diff --git a/packages/dashboard-v2/src/components/Alert/Alert.js b/packages/dashboard-v2/src/components/Alert/Alert.js deleted file mode 100644 index 4db72620..00000000 --- a/packages/dashboard-v2/src/components/Alert/Alert.js +++ /dev/null @@ -1,10 +0,0 @@ -import styled from "styled-components"; -import cn from "classnames"; - -export const Alert = styled.div.attrs(({ $variant }) => ({ - className: cn("px-3 py-2 sm:px-6 sm:py-4 rounded border", { - "bg-blue-100 border-blue-200 text-palette-400": $variant === "info", - "bg-red-100 border-red-200 text-error": $variant === "error", - "bg-green-100 border-green-200 text-palette-400": $variant === "success", - }), -}))``; diff --git a/packages/dashboard-v2/src/components/Alert/index.js b/packages/dashboard-v2/src/components/Alert/index.js deleted file mode 100644 index b8e17a03..00000000 --- a/packages/dashboard-v2/src/components/Alert/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from "./Alert"; diff --git a/packages/dashboard-v2/src/components/AvatarUploader/AvatarUploader.js b/packages/dashboard-v2/src/components/AvatarUploader/AvatarUploader.js deleted file mode 100644 index f97ca2d5..00000000 --- a/packages/dashboard-v2/src/components/AvatarUploader/AvatarUploader.js +++ /dev/null @@ -1,36 +0,0 @@ -import { useEffect, useState } from "react"; - -import { useUser } from "../../contexts/user"; -// import { SimpleUploadIcon } from "../Icons"; - -import avatarPlaceholder from "../../../static/images/avatar-placeholder.svg"; - -export const AvatarUploader = (props) => { - const { user } = useUser(); - const [imageUrl, setImageUrl] = useState(avatarPlaceholder); - - useEffect(() => { - setImageUrl(user.avatarUrl ?? avatarPlaceholder); - }, [user]); - - return ( -
    -
    - -
    - {/* TODO: uncomment when avatar uploads work -
    - -
    - */} -
    - ); -}; diff --git a/packages/dashboard-v2/src/components/AvatarUploader/index.js b/packages/dashboard-v2/src/components/AvatarUploader/index.js deleted file mode 100644 index 74358cdc..00000000 --- a/packages/dashboard-v2/src/components/AvatarUploader/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from "./AvatarUploader"; diff --git a/packages/dashboard-v2/src/components/Button/Button.js b/packages/dashboard-v2/src/components/Button/Button.js deleted file mode 100644 index 2a49244e..00000000 --- a/packages/dashboard-v2/src/components/Button/Button.js +++ /dev/null @@ -1,47 +0,0 @@ -import cn from "classnames"; -import PropTypes from "prop-types"; -import styled from "styled-components"; - -/** - * Primary UI component for user interaction - */ -export const Button = styled.button.attrs(({ as: polymorphicAs, disabled, $primary, type }) => { - // We want to default to type=button in most cases, but sometimes we use this component - // as a polymorphic one (i.e. for links), and then we should avoid setting `type` property, - // as it breaks styling in Safari. - const typeAttr = polymorphicAs && polymorphicAs !== "button" ? undefined : type; - - return { - type: typeAttr, - className: cn( - "px-6 py-2.5 inline-block rounded-full font-sans uppercase text-xs tracking-wide transition-[opacity_filter]", - { - "bg-primary text-palette-600": $primary, - "bg-white border-2 border-black text-palette-600": !$primary, - "cursor-not-allowed opacity-60": disabled, - "hover:brightness-90": !disabled, - } - ), - }; -})``; - -Button.propTypes = { - /** - * Is this the principal call to action on the page? - */ - $primary: PropTypes.bool, - /** - * Prevent interaction on the button - */ - disabled: PropTypes.bool, - /** - * Type of button (button / submit) - */ - type: PropTypes.oneOf(["button", "submit"]), -}; - -Button.defaultProps = { - $primary: false, - disabled: false, - type: "button", -}; diff --git a/packages/dashboard-v2/src/components/Button/Button.stories.js b/packages/dashboard-v2/src/components/Button/Button.stories.js deleted file mode 100644 index 74f2ca90..00000000 --- a/packages/dashboard-v2/src/components/Button/Button.stories.js +++ /dev/null @@ -1,38 +0,0 @@ -import { Button } from "./Button"; - -// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export -export default { - title: "SkynetLibrary/Button", - component: Button, - // More on argTypes: https://storybook.js.org/docs/react/api/argtypes - argTypes: { - backgroundColor: { control: "color" }, - }, -}; - -// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args -const Template = (args) => - - Copied to clipboard - -
    - ); -}; diff --git a/packages/dashboard-v2/src/components/CurrentPlan/CurrentPlan.js b/packages/dashboard-v2/src/components/CurrentPlan/CurrentPlan.js deleted file mode 100644 index f9bc101a..00000000 --- a/packages/dashboard-v2/src/components/CurrentPlan/CurrentPlan.js +++ /dev/null @@ -1,52 +0,0 @@ -import dayjs from "dayjs"; -import relativeTime from "dayjs/plugin/relativeTime"; - -import { useUser } from "../../contexts/user"; -import useActivePlan from "../../hooks/useActivePlan"; -import humanBytes from "../../lib/humanBytes"; -import { ContainerLoadingIndicator } from "../LoadingIndicator"; - -import LatestPayment from "./LatestPayment"; -import SuggestedPlan from "./SuggestedPlan"; - -dayjs.extend(relativeTime); - -const CurrentPlan = () => { - const { user, error: userError } = useUser(); - const { plans, activePlan, error: plansError } = useActivePlan(user); - - if (!user || !activePlan) { - return ; - } - - if (userError || plansError) { - return ( -
    -

    An error occurred while loading this data.

    -

    We'll retry automatically.

    -
    - ); - } - - return ( -
    -

    {activePlan.name}

    -
    - {activePlan.price === 0 && activePlan.limits && ( -

    {humanBytes(activePlan.limits.storageLimit)} without paying a dime! 🎉

    - )} - {activePlan.price !== 0 && - (user.subscriptionCancelAtPeriodEnd ? ( -

    Your subscription expires {dayjs(user.subscribedUntil).fromNow()}

    - ) : ( -

    {dayjs(user.subscribedUntil).fromNow(true)} until the next payment

    - ))} - - {user.subscriptionStatus && } - -
    -
    - ); -}; - -export default CurrentPlan; diff --git a/packages/dashboard-v2/src/components/CurrentPlan/LatestPayment.js b/packages/dashboard-v2/src/components/CurrentPlan/LatestPayment.js deleted file mode 100644 index 8ca2ab9e..00000000 --- a/packages/dashboard-v2/src/components/CurrentPlan/LatestPayment.js +++ /dev/null @@ -1,18 +0,0 @@ -import dayjs from "dayjs"; - -// TODO: this is not an accurate information, we need this data from the backend -const LatestPayment = ({ user }) => ( -
    -
    - Latest payment - - {dayjs(user.subscribedUntil).subtract(1, "month").format("MM/DD/YYYY")} - -
    -
    - Success -
    -
    -); - -export default LatestPayment; diff --git a/packages/dashboard-v2/src/components/CurrentPlan/SuggestedPlan.js b/packages/dashboard-v2/src/components/CurrentPlan/SuggestedPlan.js deleted file mode 100644 index ed59c382..00000000 --- a/packages/dashboard-v2/src/components/CurrentPlan/SuggestedPlan.js +++ /dev/null @@ -1,24 +0,0 @@ -import { Link } from "gatsby"; -import { useMemo } from "react"; - -import { Button } from "../Button"; - -const SuggestedPlan = ({ plans, activePlan }) => { - const nextPlan = useMemo(() => plans.find(({ tier }) => tier > activePlan.tier), [plans, activePlan]); - - if (!nextPlan) { - return null; - } - - return ( -
    -

    Discover {nextPlan.name}

    -

    {nextPlan.description}

    - -
    - ); -}; - -export default SuggestedPlan; diff --git a/packages/dashboard-v2/src/components/CurrentPlan/index.js b/packages/dashboard-v2/src/components/CurrentPlan/index.js deleted file mode 100644 index 20390eab..00000000 --- a/packages/dashboard-v2/src/components/CurrentPlan/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import CurrentPlan from "./CurrentPlan"; - -export default CurrentPlan; diff --git a/packages/dashboard-v2/src/components/CurrentUsage/CurrentUsage.js b/packages/dashboard-v2/src/components/CurrentUsage/CurrentUsage.js deleted file mode 100644 index cac40771..00000000 --- a/packages/dashboard-v2/src/components/CurrentUsage/CurrentUsage.js +++ /dev/null @@ -1,116 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import { Link } from "gatsby"; -import cn from "classnames"; -import useSWR from "swr"; - -import { useUser } from "../../contexts/user"; -import useActivePlan from "../../hooks/useActivePlan"; -import { ContainerLoadingIndicator } from "../LoadingIndicator"; - -import { GraphBar } from "./GraphBar"; -import { UsageGraph } from "./UsageGraph"; -import humanBytes from "../../lib/humanBytes"; - -const useUsageData = () => { - const { user } = useUser(); - const { activePlan, error } = useActivePlan(user); - const { data: stats, error: statsError } = useSWR("user/stats"); - - const [loading, setLoading] = useState(true); - const [usage, setUsage] = useState({}); - - const hasError = error || statsError; - const hasData = activePlan && stats; - - useEffect(() => { - if (hasData || hasError) { - setLoading(false); - } - - if (hasData && !hasError) { - setUsage({ - filesUsed: stats?.numUploads, - filesLimit: activePlan?.limits?.maxNumberUploads, - storageUsed: stats?.totalUploadsSize, - storageLimit: activePlan?.limits?.storageLimit, - }); - } - }, [hasData, hasError, stats, activePlan]); - - return { - error: error || statsError, - loading, - usage, - }; -}; - -const size = (bytes) => { - const text = humanBytes(bytes ?? 0, { precision: 0 }); - const [value, unit] = text.split(" "); - - return { - text, - value, - unit, - }; -}; - -const ErrorMessage = () => ( -
    -

    We were not able to fetch the current usage data.

    -

    We'll try again automatically.

    -
    -); - -export default function CurrentUsage() { - const { activePlan, plans } = useActivePlan(); - const { usage, error, loading } = useUsageData(); - const nextPlan = useMemo(() => plans.find(({ tier }) => tier > activePlan?.tier), [plans, activePlan]); - const storageUsage = size(usage.storageUsed); - const storageLimit = size(usage.storageLimit); - const filesUsedLabel = useMemo(() => ({ value: usage.filesUsed, unit: "files" }), [usage.filesUsed]); - - if (loading) { - return ; - } - - if (error) { - return ; - } - - return ( - <> -

    - {storageUsage.text} of {storageLimit.text} -

    -

    - {usage.filesUsed} of {usage.filesLimit} files -

    -
    -
    - Storage - {storageLimit.text} -
    - - - - -
    - Files - - - UPGRADE - {" "} - {usage.filesLimit} - -
    -
    - - ); -} diff --git a/packages/dashboard-v2/src/components/CurrentUsage/GraphBar.js b/packages/dashboard-v2/src/components/CurrentUsage/GraphBar.js deleted file mode 100644 index fd9a015e..00000000 --- a/packages/dashboard-v2/src/components/CurrentUsage/GraphBar.js +++ /dev/null @@ -1,37 +0,0 @@ -import styled from "styled-components"; - -const Bar = styled.div.attrs({ - className: `relative flex justify-end h-4 bg-primary rounded-l rounded-r-lg`, -})` - min-width: 1rem; - width: ${({ $percentage }) => $percentage}%; -`; - -const BarTip = styled.span.attrs({ - className: "relative w-4 h-4 border-2 rounded-full bg-white border-primary", -})``; - -const BarLabel = styled.span.attrs({ - className: "usage-label bg-white rounded border-2 border-palette-200 px-3 whitespace-nowrap absolute shadow", -})` - ${({ $percentage }) => ` - left: max(0%, ${$percentage}%); - top: -0.5rem; - transform: translateX(-${$percentage}%); - `} -`; - -export const GraphBar = ({ value, limit, label, className }) => { - const percentage = typeof limit !== "number" || limit === 0 ? 0 : (value / limit) * 100; - - return ( -
    - - - - - {label.value} {label.unit} - -
    - ); -}; diff --git a/packages/dashboard-v2/src/components/CurrentUsage/UsageGraph.js b/packages/dashboard-v2/src/components/CurrentUsage/UsageGraph.js deleted file mode 100644 index de4e7e46..00000000 --- a/packages/dashboard-v2/src/components/CurrentUsage/UsageGraph.js +++ /dev/null @@ -1,11 +0,0 @@ -import styled from "styled-components"; - -import usageGraphBg from "../../../static/images/usage-graph-bg.svg"; - -export const UsageGraph = styled.div.attrs({ - className: "w-full my-3 grid grid-flow-row grid-rows-2", -})` - height: 146px; - background: url(${usageGraphBg}) no-repeat; - background-size: cover; -`; diff --git a/packages/dashboard-v2/src/components/CurrentUsage/index.js b/packages/dashboard-v2/src/components/CurrentUsage/index.js deleted file mode 100644 index 802aa4e1..00000000 --- a/packages/dashboard-v2/src/components/CurrentUsage/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import CurrentUsage from "./CurrentUsage"; - -export default CurrentUsage; diff --git a/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenu.js b/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenu.js deleted file mode 100644 index 1bf6f001..00000000 --- a/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenu.js +++ /dev/null @@ -1,63 +0,0 @@ -import { useRef, useState } from "react"; -import { useClickAway } from "react-use"; -import PropTypes from "prop-types"; -import styled, { css, keyframes } from "styled-components"; - -import { ChevronDownIcon } from "../Icons"; - -const dropDown = keyframes` - 0% { - transform: rotateX(-90deg); - } - 100% { - transform: rotateX(0deg); - } -`; - -const Container = styled.div.attrs({ className: `relative inline-flex` })``; - -const Trigger = styled.button.attrs({ - className: "flex items-center", -})``; - -const TriggerIcon = styled(ChevronDownIcon).attrs({ - className: "transition-transform text-primary", -})` - transform: ${({ open }) => (open ? "rotateX(180deg)" : "none")}; -`; - -const Flyout = styled.div.attrs(({ open }) => ({ - className: `absolute top-full right-0 p-0 z-10 - border rounded border-palette-100 - bg-white shadow-md shadow-palette-200/50 - ${open ? "visible" : "invisible"}`, -}))` - transform-origin: top center; - animation: ${({ open }) => - open - ? css` - ${dropDown} .15s ease-in-out forwards; - ` - : "none"}; -`; - -export const DropdownMenu = ({ title, children }) => { - const [open, setOpen] = useState(false); - const menuRef = useRef(); - - useClickAway(menuRef, () => setOpen(false)); - - return ( - - setOpen((open) => !open)}> - {title} - - {children} - - ); -}; - -DropdownMenu.propTypes = { - title: PropTypes.string.isRequired, - children: PropTypes.node.isRequired, -}; diff --git a/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenu.stories.js b/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenu.stories.js deleted file mode 100644 index 09e5a712..00000000 --- a/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenu.stories.js +++ /dev/null @@ -1,25 +0,0 @@ -import { Panel } from "../Panel"; -import { DropdownMenu, DropdownMenuLink } from "."; -import { CogIcon, LockClosedIcon } from "../Icons"; - -export default { - title: "SkynetLibrary/DropdownMenu", - component: DropdownMenu, - subcomponents: { - DropdownMenuLink, - }, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export const NavigationDropdown = () => ( - - - - -); diff --git a/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenuLink.js b/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenuLink.js deleted file mode 100644 index 426501c4..00000000 --- a/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenuLink.js +++ /dev/null @@ -1,22 +0,0 @@ -import styled from "styled-components"; -import PropTypes from "prop-types"; - -const DropdownLink = styled.a.attrs({ - className: `m-0 border-t border-palette-200/50 h-[60px] - whitespace-nowrap transition-colors - hover:bg-palette-100/50 flex items-center - pr-8 pl-6 py-4 gap-4 first:border-0`, -})``; - -export const DropdownMenuLink = ({ active, icon: Icon, label, ...props }) => ( - - {Icon ? : null} - {label} - -); - -DropdownMenuLink.propTypes = { - label: PropTypes.oneOfType([PropTypes.node, PropTypes.string]).isRequired, - active: PropTypes.bool, - icon: PropTypes.func, -}; diff --git a/packages/dashboard-v2/src/components/DropdownMenu/index.js b/packages/dashboard-v2/src/components/DropdownMenu/index.js deleted file mode 100644 index db833f26..00000000 --- a/packages/dashboard-v2/src/components/DropdownMenu/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./DropdownMenu"; -export * from "./DropdownMenuLink"; diff --git a/packages/dashboard-v2/src/components/FileList/FileList.js b/packages/dashboard-v2/src/components/FileList/FileList.js deleted file mode 100644 index ad6087ed..00000000 --- a/packages/dashboard-v2/src/components/FileList/FileList.js +++ /dev/null @@ -1,57 +0,0 @@ -import { useState } from "react"; -import useSWR from "swr"; -import { useMedia } from "react-use"; - -import theme from "../../lib/theme"; - -import { ContainerLoadingIndicator } from "../LoadingIndicator"; - -import FileTable from "./FileTable"; -import useFormattedFilesData from "./useFormattedFilesData"; -import { MobileFileList } from "./MobileFileList"; -import { Pagination } from "./Pagination"; - -const PAGE_SIZE = 10; - -const FileList = ({ type }) => { - const isMediumScreenOrLarger = useMedia(`(min-width: ${theme.screens.md})`); - const [offset, setOffset] = useState(0); - const baseUrl = `user/${type}?pageSize=${PAGE_SIZE}`; - const { - data, - error, - mutate: refreshList, - } = useSWR(`${baseUrl}&offset=${offset}`, { - revalidateOnMount: true, - }); - const items = useFormattedFilesData(data?.items || []); - const count = data?.count || 0; - - // Next page preloading - const hasMoreRecords = data ? data.offset + data.pageSize < data.count : false; - const nextPageOffset = hasMoreRecords ? data.offset + data.pageSize : offset; - useSWR(`${baseUrl}&offset=${nextPageOffset}`); - - if (!items.length) { - return ( -
    - {!data && !error && } - {!data && error &&

    An error occurred while loading this data.

    } - {data &&

    No {type} found.

    } -
    - ); - } - - return ( - <> - {isMediumScreenOrLarger ? ( - - ) : ( - - )} - - - ); -}; - -export default FileList; diff --git a/packages/dashboard-v2/src/components/FileList/FileTable.js b/packages/dashboard-v2/src/components/FileList/FileTable.js deleted file mode 100644 index 88477648..00000000 --- a/packages/dashboard-v2/src/components/FileList/FileTable.js +++ /dev/null @@ -1,79 +0,0 @@ -import { CogIcon, ShareIcon } from "../Icons"; -import { PopoverMenu } from "../PopoverMenu/PopoverMenu"; -import { Table, TableBody, TableCell, TableHead, TableHeadCell, TableRow } from "../Table"; -import { CopyButton } from "../CopyButton"; -import { useSkylinkOptions } from "./useSkylinkOptions"; -import { useSkylinkSharing } from "./useSkylinkSharing"; - -const SkylinkOptionsMenu = ({ skylink, onUpdated }) => { - const { inProgress, options } = useSkylinkOptions({ skylink, onUpdated }); - - return ( - - - - ); -}; - -const SkylinkSharingMenu = ({ skylink }) => { - const { options } = useSkylinkSharing(skylink); - - return ( - - - - ); -}; - -export default function FileTable({ items, onUpdated }) { - return ( -
    - - - - Name - Type - - Size - - Uploaded - Skylink - Activity - - - - {items.map((item) => { - const { id, name, type, size, date, skylink } = item; - - return ( - - {name} - {type} - - {size} - - {date} - -
    - - {skylink} -
    -
    - -
    - - -
    -
    -
    - ); - })} -
    -
    -
    - ); -} diff --git a/packages/dashboard-v2/src/components/FileList/MobileFileList.js b/packages/dashboard-v2/src/components/FileList/MobileFileList.js deleted file mode 100644 index bd11aa10..00000000 --- a/packages/dashboard-v2/src/components/FileList/MobileFileList.js +++ /dev/null @@ -1,84 +0,0 @@ -import { useState } from "react"; -import cn from "classnames"; - -import { ChevronDownIcon } from "../Icons"; -import { useSkylinkSharing } from "./useSkylinkSharing"; -import { ContainerLoadingIndicator } from "../LoadingIndicator"; -import { useSkylinkOptions } from "./useSkylinkOptions"; - -const SharingMenu = ({ skylink }) => { - const { options } = useSkylinkSharing(skylink); - - return ( -
    - {options.map(({ label, callback }, index) => ( - - ))} -
    - ); -}; - -const OptionsMenu = ({ skylink, onUpdated }) => { - const { inProgress, options } = useSkylinkOptions({ skylink, onUpdated }); - - return ( -
    -
    - {options.map(({ label, callback }, index) => ( - - ))} -
    - {inProgress && ( - - )} -
    - ); -}; - -const ListItem = ({ item, onUpdated }) => { - const { name, type, size, date, skylink } = item; - const [open, setOpen] = useState(false); - - const toggle = () => setOpen((open) => !open); - - return ( -
    -
    -
    -
    {name}
    -
    - {type} - {size} - {date} -
    -
    - -
    -
    - - -
    -
    - ); -}; - -export const MobileFileList = ({ items, onUpdated }) => { - return ( -
    - {items.map((item) => ( - - ))} -
    - ); -}; diff --git a/packages/dashboard-v2/src/components/FileList/Pagination.js b/packages/dashboard-v2/src/components/FileList/Pagination.js deleted file mode 100644 index 248c03a3..00000000 --- a/packages/dashboard-v2/src/components/FileList/Pagination.js +++ /dev/null @@ -1,32 +0,0 @@ -import { Button } from "../Button"; - -export const Pagination = ({ count, offset, setOffset, pageSize }) => { - const start = count ? offset + 1 : 0; - const end = offset + pageSize > count ? count : offset + pageSize; - - const showPaginationButtons = offset > 0 || count > end; - - return ( - - ); -}; diff --git a/packages/dashboard-v2/src/components/FileList/index.js b/packages/dashboard-v2/src/components/FileList/index.js deleted file mode 100644 index 93296508..00000000 --- a/packages/dashboard-v2/src/components/FileList/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from "./FileList"; diff --git a/packages/dashboard-v2/src/components/FileList/useFormattedFilesData.js b/packages/dashboard-v2/src/components/FileList/useFormattedFilesData.js deleted file mode 100644 index 10639458..00000000 --- a/packages/dashboard-v2/src/components/FileList/useFormattedFilesData.js +++ /dev/null @@ -1,27 +0,0 @@ -import { useMemo } from "react"; -import dayjs from "dayjs"; -import { DATE_FORMAT } from "../../lib/config"; -import humanBytes from "../../lib/humanBytes"; - -const parseFileName = (fileName) => { - const lastDotIndex = Math.max(0, fileName.lastIndexOf(".")) || Infinity; - - return [fileName.substr(0, lastDotIndex), fileName.substr(lastDotIndex)]; -}; - -const formatItem = ({ size, name: rawFileName, uploadedOn, downloadedOn, ...rest }) => { - const [name, type] = parseFileName(rawFileName); - const date = dayjs(uploadedOn || downloadedOn).format(DATE_FORMAT); - - return { - ...rest, - date, - size: humanBytes(size, { precision: 2 }), - type, - name, - }; -}; - -const useFormattedFilesData = (items) => useMemo(() => items.map(formatItem), [items]); - -export default useFormattedFilesData; diff --git a/packages/dashboard-v2/src/components/FileList/useSkylinkOptions.js b/packages/dashboard-v2/src/components/FileList/useSkylinkOptions.js deleted file mode 100644 index ad116cd4..00000000 --- a/packages/dashboard-v2/src/components/FileList/useSkylinkOptions.js +++ /dev/null @@ -1,35 +0,0 @@ -import { useMemo, useState } from "react"; - -import accountsService from "../../services/accountsService"; -import skynetClient from "../../services/skynetClient"; - -export const useSkylinkOptions = ({ skylink, onUpdated }) => { - const [inProgress, setInProgress] = useState(false); - - const options = useMemo( - () => [ - { - label: "Preview", - callback: async () => window.open(await skynetClient.getSkylinkUrl(skylink)), - }, - { - label: "Download", - callback: () => skynetClient.downloadFile(skylink), - }, - { - label: "Unpin", - callback: async () => { - setInProgress(true); - await accountsService.delete(`user/uploads/${skylink}`); - await onUpdated(); // No need to setInProgress(false), since at this point this hook should already be unmounted - }, - }, - ], - [skylink, onUpdated] - ); - - return { - inProgress, - options, - }; -}; diff --git a/packages/dashboard-v2/src/components/FileList/useSkylinkSharing.js b/packages/dashboard-v2/src/components/FileList/useSkylinkSharing.js deleted file mode 100644 index 0b09302b..00000000 --- a/packages/dashboard-v2/src/components/FileList/useSkylinkSharing.js +++ /dev/null @@ -1,40 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import copy from "copy-text-to-clipboard"; - -import skynetClient from "../../services/skynetClient"; - -const COPY_LINK_LABEL = "Copy link"; - -export const useSkylinkSharing = (skylink) => { - const [copied, setCopied] = useState(false); - const [copyLabel, setCopyLabel] = useState(COPY_LINK_LABEL); - - useEffect(() => { - if (copied) { - setCopyLabel("Copied!"); - - const timeout = setTimeout(() => setCopied(false), 1500); - - return () => clearTimeout(timeout); - } else { - setCopyLabel(COPY_LINK_LABEL); - } - }, [copied]); - - const options = useMemo( - () => [ - { - label: copyLabel, - callback: async () => { - setCopied(true); - copy(await skynetClient.getSkylinkUrl(skylink)); - }, - }, - ], - [skylink, copyLabel] - ); - - return { - options, - }; -}; diff --git a/packages/dashboard-v2/src/components/Footer/Footer.js b/packages/dashboard-v2/src/components/Footer/Footer.js deleted file mode 100644 index 8096337a..00000000 --- a/packages/dashboard-v2/src/components/Footer/Footer.js +++ /dev/null @@ -1,19 +0,0 @@ -import * as React from "react"; -import styled from "styled-components"; - -import { PageContainer } from "../PageContainer"; - -const FooterLink = styled.a.attrs({ - className: "text-palette-400 underline decoration-dotted decoration-offset-4 decoration-1", - rel: "noreferrer", - target: "_blank", -})``; - -export const Footer = () => ( - -

    - Made by Skynet Labs. Open-sourced{" "} - on Github. -

    -
    -); diff --git a/packages/dashboard-v2/src/components/Footer/index.js b/packages/dashboard-v2/src/components/Footer/index.js deleted file mode 100644 index 5fca53f7..00000000 --- a/packages/dashboard-v2/src/components/Footer/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from "./Footer"; diff --git a/packages/dashboard-v2/src/components/Form/TextField.js b/packages/dashboard-v2/src/components/Form/TextField.js deleted file mode 100644 index 6ae35021..00000000 --- a/packages/dashboard-v2/src/components/Form/TextField.js +++ /dev/null @@ -1,56 +0,0 @@ -import PropTypes from "prop-types"; -import cn from "classnames"; -import { Field } from "formik"; - -export const TextField = ({ id, label, name, error, touched, className, ...props }) => { - return ( -
    - {label && ( - - )} - - {touched && error && ( -
    - {error} -
    - )} -
    - ); -}; - -/** Besides noted properties, it accepts all props accepted by: - * - a regular element - * - Formik's component - */ -TextField.propTypes = { - /** - * ID for the field. Used to couple