diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml deleted file mode 100644 index af04248..0000000 --- a/.github/workflows/dev.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: "Test" - -on: - push: - branches: - - "dev" - -jobs: - push-to-registry: - name: "Build and push Docker image to Docker Hub" - runs-on: "ubuntu-latest" - steps: - - name: "Check out the repo" - uses: actions/checkout@v3 - - - name: "Log in to Docker Hub" - uses: "docker/login-action@v2" - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PASSWORD }} - - - name: "Build and push Docker image" - uses: "docker/build-push-action@v3" - with: - context: . - push: true - tags: clowzed/sero:dev-unstable diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d1eb30..faf4f63 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -80,6 +80,7 @@ jobs: needs: - "get-tag" - "upload-assets" + - "publish-api" runs-on: "ubuntu-latest" steps: - name: "Check out the repo" @@ -104,53 +105,3 @@ jobs: push: true tags: clowzed/sero:v${{ needs.get-tag.outputs.pkg-version }} labels: ${{ steps.meta.outputs.labels }} - upload-openapi-client: - needs: - - "push-to-registry" - name: "Build and upload openapi python client" - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Install Node.js - uses: actions/setup-node@v3 - with: - node-version: "16" - - - name: Install OpenAPI Generator CLI - run: | - npm install @openapitools/openapi-generator-cli -g - - name: Extract version from OpenAPI spec - id: extract_version - run: | - VERSION=$(jq -r '.info.version' openapi.json) - echo "VERSION=$VERSION" >> $GITHUB_ENV - - - name: Generate Python client from OpenAPI spec - run: | - openapi-generator-cli generate -i openapi.json -g python -o seroapi --additional-properties=packageName=seroapi,packageVersion=${{ env.VERSION }} - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: "3.x" - - - name: Install Poetry - run: | - curl -sSL https://install.python-poetry.org | python3 - - - - name: Configure Poetry - run: | - poetry config virtualenvs.in-project true - - - name: Install dependencies - run: | - cd seroapi - poetry install - - - name: Publish to Test PyPI - run: | - cd seroapi - poetry publish -u __token__ -p ${{ secrets.PYPI_PASSWORD }} --build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2a2e902..5556be5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,6 +46,24 @@ jobs: steps: - uses: actions/checkout@v3 + - name: "Get version from Cargo.toml" + id: "get-cargo-version" + shell: "bash" + run: | + echo PKG_VERSION=$(awk -F ' = ' '$1 ~ /version/ { gsub(/["]/, "", $2); printf("%s",$2) }' Cargo.toml) >> $GITHUB_OUTPUT + - name: Get version from openapi.json + id: get-openapi-version + run: | + echo OAPI_VERSION=$(jq -r '.info.version' openapi.json) >> $GITHUB_OUTPUT + + - name: Compare versions + run: | + if [ "${{ steps.get-cargo-version.outputs.PKG_VERSION }}" != "${{ steps.get-openapi-version.outputs.OAPI_VERSION }}" ]; then + echo "Version mismatch between cargo.toml and generated OpenAPI JSON." + exit 1 + else + echo "Version matches between cargo.toml and generated OpenAPI JSON." + fi - name: Run tests (with database service) run: cargo test --verbose -- --test-threads=1 @@ -59,14 +77,14 @@ jobs: - uses: "actions-rs/toolchain@v1" with: profile: "minimal" - toolchain: "nightly" + toolchain: "stable" override: true - run: "rustup component add rustfmt" - uses: "actions-rs/cargo@v1" with: - command: "fmt" + command: "+nightly fmt" args: "--all -- --check" clippy: diff --git a/.gitignore b/.gitignore index 99dc2ec..3337113 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,3 @@ sites-uploads logs test_upload_files .env -openapi.json diff --git a/Cargo.toml b/Cargo.toml index 80b0906..283caab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sero" -version = "0.2.8" +version = "0.2.0" edition = "2021" authors = ["clowzed "] description = "Muiltidomain static site hosting" @@ -11,19 +11,19 @@ license = "MIT" [dependencies] envy = "0.4.2" sea-orm = { version = "0.12.3", features = [ - "sqlx-postgres", - "runtime-tokio-rustls", - "macros", + "sqlx-postgres", + "runtime-tokio-rustls", + "macros", ] } tokio = { version = "1.32.0", features = ["full"] } tokio-postgres = "0.7.10" tracing = { version = "0.1.37", features = ["async-await"] } tracing-subscriber = { version = "0.3.17", features = [ - "env-filter", - "fmt", - "ansi", - "std", - "json", + "env-filter", + "fmt", + "ansi", + "std", + "json", ] } entity = { path = "entity" } migration = { path = "migration" } @@ -41,9 +41,9 @@ mime = "0.3.17" mime_guess = "2.0.4" argon2 = { version = "0.5.3", features = ["std"] } utoipa = { version = "4.2.0", features = [ - "axum_extras", - "chrono", - "preserve_order", + "axum_extras", + "chrono", + "preserve_order", ] } dotenvy = "0.15.7" toml = "0.8.8" @@ -52,16 +52,16 @@ utoipa-rapidoc = { version = "4.0.0", features = ["axum"] } utoipa-redoc = { version = "4.0.0", features = ["axum"] } utoipa-swagger-ui = { version = "7.1.0", features = ["axum"] } axum = { version = "0.7.4", features = [ - "macros", - "tracing", - "json", - "multipart", + "macros", + "tracing", + "json", + "multipart", ] } axum_typed_multipart = "0.11.0" tower-http = { git = "https://github.com/tower-rs/tower-http.git", features = [ - "cors", - "trace", - "timeout", + "cors", + "trace", + "timeout", ] } tower = { version = "0.4.13", features = ["util"] } hyper = "0.14.28" @@ -77,11 +77,5 @@ members = [".", "entity", "migration"] [dev-dependencies] + axum-test = "15.2.0" - - -[profile.release] -lto = true -strip = true -opt-level = 3 -codegen-units = 1 diff --git a/assets/zips/correct-1.zip b/assets/zips/correct-1.zip index 7842288..6c82e27 100644 Binary files a/assets/zips/correct-1.zip and b/assets/zips/correct-1.zip differ diff --git a/assets/zips/correct-2.zip b/assets/zips/correct-2.zip index f30aa57..939621c 100644 Binary files a/assets/zips/correct-2.zip and b/assets/zips/correct-2.zip differ diff --git a/assets/zips/correct-3.zip b/assets/zips/correct-3.zip index 2641e25..0e9c53c 100644 Binary files a/assets/zips/correct-3.zip and b/assets/zips/correct-3.zip differ diff --git a/assets/zips/correct-with-404.html.zip b/assets/zips/correct-with-404.html.zip index daec77b..9b17c0b 100644 Binary files a/assets/zips/correct-with-404.html.zip and b/assets/zips/correct-with-404.html.zip differ diff --git a/assets/zips/correct-with-503.html.zip b/assets/zips/correct-with-503.html.zip index a6cf06f..5a66f25 100644 Binary files a/assets/zips/correct-with-503.html.zip and b/assets/zips/correct-with-503.html.zip differ diff --git a/assets/zips/correct-without-404.html.zip b/assets/zips/correct-without-404.html.zip index 7842288..8c9eb8b 100644 Binary files a/assets/zips/correct-without-404.html.zip and b/assets/zips/correct-without-404.html.zip differ diff --git a/assets/zips/correct-without-503.html.zip b/assets/zips/correct-without-503.html.zip index cfc3724..65e4f40 100644 Binary files a/assets/zips/correct-without-503.html.zip and b/assets/zips/correct-without-503.html.zip differ diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index d5091aa..0000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,69 +0,0 @@ -version: "3" - -services: - database: - image: postgres:16 - user: postgres - environment: - - POSTGRES_USER=postgres - - POSTGRES_DB=sero - - POSTGRES_PASSWORD=1234 - - healthcheck: - test: ["CMD-SHELL", "pg_isready"] - interval: 5s - timeout: 5s - retries: 5 - volumes: - - pgdata:/var/lib/postgresql/data - - - proxy: - image: nginx:alpine3.18-slim - environment: - - DOLLAR=$ - - SERVER_PORT=8080 - - SERVER=server - # Edit this - - DOMAIN= - - ZONE= - # End of edit - volumes: - - ./nginx-templates:/etc/nginx/templates - ports: - - 443:443 - - 80:80 - links: - - server - depends_on: - - server - - server: - image: clowzed/sero:dev-unstable - build: . - depends_on: - database: - condition: service_healthy - volumes: - - server-files:/app/sites-uploads - ports: - - 8080:8080 - environment: - - DATABASE_URL=postgresql://postgres:1234@database/sero - - PORT=8080 - # You can edit this section - # Empty means no limits - - MAX_USERS=1 - - MAX_SITES_PER_USER=100 - - MAX_BODY_LIMIT_SIZE=10000000 # 10mb - - RUST_LOG=none,sero=trace - - JWT_SECRET=mysuperstrongjwtscret - # end of section - - JWT_TTL_SECONDS=120 - - SQLX_LOGGING=true - - UPLOAD_FOLDER=./sites-uploads - - -volumes: - server-files: - pgdata: diff --git a/docker-compose.yml b/docker-compose.yml index 8c1be93..f7727d8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,7 @@ services: database: image: postgres:16 user: postgres + restart: always environment: - POSTGRES_USER=postgres - POSTGRES_DB=sero @@ -20,6 +21,7 @@ services: proxy: image: nginx:alpine3.18-slim + restart: always environment: - DOLLAR=$ - SERVER_PORT=8080 @@ -35,17 +37,20 @@ services: - 80:80 links: - server - depends_on: - - server + profiles: + - donotstart server: - image: clowzed/sero:v0.2.7 + image: clowzed/sero + restart: always build: . depends_on: database: condition: service_healthy volumes: - server-files:/app/sites-uploads + ports: + - 8080:8080 environment: - DATABASE_URL=postgresql://postgres:1234@database/sero - PORT=8080 @@ -55,8 +60,8 @@ services: - MAX_SITES_PER_USER=100 - MAX_BODY_LIMIT_SIZE=10000000 # 10mb - RUST_LOG=none,sero=trace - - JWT_SECRET=mysuperstrongjwtscret # end of section + - JWT_SECRET=mysuperstrongjwtscret - JWT_TTL_SECONDS=120 - SQLX_LOGGING=true - UPLOAD_FOLDER=./sites-uploads diff --git a/nginx-templates/default.conf.template b/nginx-templates/default.conf.template index bf3719b..d0f5380 100644 --- a/nginx-templates/default.conf.template +++ b/nginx-templates/default.conf.template @@ -1,22 +1,18 @@ -map $http_host $subdomain { - ~^(?[a-zA-Z0-9-]+)\.${DOMAIN}\.${ZONE}${DOLLAR} $subdomain; -} - server { + + + # We check if $subdomain is not empty and if the X-subdomain header is not present in the request + # ($http_x_subdomain = ""). + # If both conditions are met, we set the X-subdomain header using proxy_set_header. + # Otherwise, we do nothing, and the existing X-subdomain header (if any) will be preserved. + listen 80; - server_name ~^(?[a-zA-Z0-9-]+)\.${DOMAIN}\.${ZONE}${DOLLAR}; + server_name ~^(?\w*)\.${DOMAIN}.${ZONE}${DOLLAR}; location / { - proxy_set_header X-Subdomain $subdomain; - proxy_pass http://${SERVER}:${SERVER_PORT}; - } -} - -server { - listen 80; - server_name ${DOMAIN}.${ZONE}; - - location / { - proxy_pass http://${SERVER}:${SERVER_PORT}; + if ($subdomain != "" && $http_x_subdomain = "") { + proxy_set_header x-subdomain $subdomain; + } + proxy_pass http://${SERVER}:${SERVER_PORT}/; } } diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..f76b9d4 --- /dev/null +++ b/openapi.json @@ -0,0 +1,1219 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "sero", + "description": "Muiltidomain static site hosting", + "contact": { + "name": "clowzed", + "email": "clowzed.work@gmail.com" + }, + "license": { + "name": "MIT" + }, + "version": "0.2.0" + }, + "paths": { + "/api/auth/login": { + "post": { + "tags": [ + "Account management" + ], + "summary": "Login user and receive JWT token.", + "description": "This endpoint allows users to login to sero server. The TTL for token is set by\nthe owner of the server by `JWT_TTL` env.", + "operationId": "Login", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "User was successfully authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginResponse" + } + } + } + }, + "400": { + "description": "Bad request or bad credentials. See details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "404": { + "description": "Login was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "500": { + "description": "Some error occurred on the server.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + } + } + } + }, + "/api/auth/registration": { + "post": { + "tags": [ + "Account management" + ], + "summary": "Register new user for sero server.", + "description": "This endpoint creates new user for sero server. The amount of users is checked\nby [RegistrationGuard]. The amount of allowed users is determined by `MAX_USERS` env.", + "operationId": "Registration", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegistrationRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "User was successfully registered.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegistrationResponse" + } + } + } + }, + "400": { + "description": "Bad request or bad credentials. See details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "409": { + "description": "Login has already been registered.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "500": { + "description": "Some error occurred on the server.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + } + } + } + }, + "/api/origin": { + "get": { + "tags": [ + "Origins Management and Dynamic Access Control" + ], + "summary": "List all origins for specified subdomain for dynamic CORS (Cross-Origin Resource Sharing) management.", + "description": "This endpoint allows users to list all origins that are permitted to access resources\non their specified subdomains. The action is authenticated using a JWT, and the subdomain must\nbe owned by the user making the request. This will be checked by the server.", + "operationId": "Get all origins", + "parameters": [ + { + "name": "x-subdomain", + "in": "header", + "description": "'x-subdomain' header represents the name of the subdomain on which the action is to be performed.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Origins were successfully retrieved for subdomain.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListOriginsResponse" + } + } + } + }, + "400": { + "description": "The 'x-subdomain' header is missing or contains invalid characters.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "401": { + "description": "Unauthorized: The JWT in the header is invalid or expired.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "403": { + "description": "Forbidden: The subdomain is owned by another user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "404": { + "description": "Not Found: The login or subdomain was not found. See details for more information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "500": { + "description": "Internal Server Error: An error occurred on the server.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + } + }, + "security": [ + { + "Bearer-JWT": [] + } + ] + }, + "post": { + "tags": [ + "Origins Management and Dynamic Access Control" + ], + "summary": "Adds a new origin to a specified subdomain for dynamic CORS (Cross-Origin Resource Sharing) management.", + "description": "This endpoint allows users to add origins that are permitted to access resources\non their specified subdomains. The action is authenticated using a JWT, and the subdomain must\nbe owned by the user making the request. This will be checked by the server.", + "operationId": "Create origin", + "parameters": [ + { + "name": "x-subdomain", + "in": "header", + "description": "'x-subdomain' header represents the name of the subdomain on which the action is to be performed.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddOriginRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "The origin was successfully added.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddOriginResponse" + } + } + } + }, + "400": { + "description": "The 'x-subdomain' header is missing or contains invalid characters.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "401": { + "description": "Unauthorized: The JWT in the header is invalid or expired.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "403": { + "description": "Forbidden: The subdomain is owned by another user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "404": { + "description": "Not Found: The login or subdomain was not found. See details for more information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "500": { + "description": "Internal Server Error: An error occurred on the server.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + } + }, + "security": [ + { + "Bearer-JWT": [] + } + ] + }, + "delete": { + "tags": [ + "Origins Management and Dynamic Access Control" + ], + "summary": "Delete all origins for specified subdomain for dynamic CORS (Cross-Origin Resource Sharing) management.", + "description": "This endpoint allows users to delete all origins that are permitted to access resources\non their specified subdomains. The action is authenticated using a JWT, and the subdomain must\nbe owned by the user making the request. This will be checked by the server.", + "operationId": "Delete all origins", + "parameters": [ + { + "name": "x-subdomain", + "in": "header", + "description": "'x-subdomain' header represents the name of the subdomain on which the action is to be performed.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Origins were successfully deleted for subdomain." + }, + "400": { + "description": "The 'x-subdomain' header is missing or contains invalid characters.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "401": { + "description": "Unauthorized: The JWT in the header is invalid or expired.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "403": { + "description": "Forbidden: The subdomain is owned by another user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "404": { + "description": "Not Found: The login or subdomain was not found. See details for more information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "500": { + "description": "Internal Server Error: An error occurred on the server.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + } + }, + "security": [ + { + "Bearer-JWT": [] + } + ] + } + }, + "/api/origin/{id}": { + "get": { + "tags": [ + "Origins Management and Dynamic Access Control" + ], + "summary": "Get specified origin [by id] for specified subdomain for dynamic CORS (Cross-Origin Resource Sharing) management.", + "description": "This endpoint allows users to get specified origin by id that is permitted to access resources\non specified subdomain. The action is authenticated using a JWT, and the subdomain must\nbe owned by the user making the request. This will be checked by the server.", + "operationId": "Get origin by id", + "parameters": [ + { + "name": "x-subdomain", + "in": "header", + "description": "'x-subdomain' header represents the name of the subdomain on which the action is to be performed.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "description": "Id of the origin to retrieve", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Origin was successfully retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetOriginResponse" + } + } + } + }, + "400": { + "description": "The 'x-subdomain' header is missing or contains invalid characters.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "401": { + "description": "Unauthorized: The JWT in the header is invalid or expired.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "403": { + "description": "Forbidden: The subdomain is owned by another user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "404": { + "description": "Not Found: The login or subdomain or origin was not found. See details for more information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "500": { + "description": "Internal Server Error: An error occurred on the server.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + } + }, + "security": [ + { + "Bearer-JWT": [] + } + ] + }, + "delete": { + "tags": [ + "Origins Management and Dynamic Access Control" + ], + "summary": "Delete origin by id for specified subdomain for dynamic CORS (Cross-Origin Resource Sharing) management.", + "description": "This endpoint allows users to delete origin by id that is permitted to access resources\non their specified subdomains. The action is authenticated using a JWT, and the subdomain must\nbe owned by the user making the request. This will be checked by the server.", + "operationId": "Delete origin by id", + "parameters": [ + { + "name": "x-subdomain", + "in": "header", + "description": "'x-subdomain' header represents the name of the subdomain on which the action is to be performed.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "description": "Id of the origin to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "204": { + "description": "Origin was successfully deleted for subdomain." + }, + "400": { + "description": "The 'x-subdomain' header is missing or contains invalid characters.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "401": { + "description": "Unauthorized: The JWT in the header is invalid or expired.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "403": { + "description": "Forbidden: The origin is owned by another user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "404": { + "description": "Not Found: The login or subdomain or origin was not found. See details for more information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "500": { + "description": "Internal Server Error: An error occurred on the server.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + } + }, + "security": [ + { + "Bearer-JWT": [] + } + ] + } + }, + "/api/site": { + "get": { + "tags": [ + "Actions" + ], + "summary": "Download site of the specified subdomain.", + "description": "Returns a zip file which was uploaded by user (last)", + "operationId": "Download site", + "parameters": [ + { + "name": "x-subdomain", + "in": "header", + "description": "x-subdomain header represents name of subdomain to call action on", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Site was successfully downloaded", + "content": { + "application/octet-stream": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "The 'x-subdomain' header is missing or contains invalid characters.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "401": { + "description": "Unauthorized: The JWT in the header is invalid or expired.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "403": { + "description": "Forbidden: The subdomain is owned by another user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "404": { + "description": "Not Found: The login or subdomain was not found. See details for more information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "500": { + "description": "Internal Server Error: An error occurred on the server.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + } + }, + "security": [ + { + "Bearer-JWT": [] + } + ] + }, + "post": { + "tags": [ + "Actions" + ], + "summary": "Uploads site for a specified subdomain.", + "description": "Warning: Old files will be removed after successful upload.\nThe cleanup task is configured with `CLEAN_OBSOLETE_INTERVAL` env\nIf upload fails then old files will be preserved.\nIf upload fails on th stage of extracting zips then\nnew subdomain will be associated with user\n\nUpload guard checks amount of uploads available for user.\nThe guard is configured with `MAX_SITES_PER_USER` env.", + "operationId": "Upload site", + "parameters": [ + { + "name": "x-subdomain", + "in": "header", + "description": "x-subdomain header represents name of subdomain to call action on", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/UploadData" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Site was successfully uploaded" + }, + "400": { + "description": "The 'x-subdomain' header is missing or contains invalid characters.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "401": { + "description": "Unauthorized: The JWT in the header is invalid or expired.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "403": { + "description": "Forbidden: The subdomain is owned by another user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "404": { + "description": "Not Found: The login or subdomain was not found. See details for more information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "500": { + "description": "Internal Server Error: An error occurred on the server.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + } + }, + "security": [ + { + "Bearer-JWT": [] + } + ] + }, + "delete": { + "tags": [ + "Actions" + ], + "summary": "Removes a specific site identified by the `x-subdomain` header.", + "description": "This endpoint allows authenticated users to remove a site associated with the specified subdomain.\nThe subdomain to be removed is specified in the `x-subdomain` header.", + "operationId": "Teardown site", + "parameters": [ + { + "name": "x-subdomain", + "in": "header", + "description": "x-subdomain header represents name of subdomain to call action on", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Site was successfully removed." + }, + "400": { + "description": "The 'x-subdomain' header is missing or contains invalid characters.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "401": { + "description": "Unauthorized: The JWT in the header is invalid or expired.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "403": { + "description": "Forbidden: The subdomain is owned by another user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "404": { + "description": "Not Found: The login or subdomain was not found. See details for more information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "500": { + "description": "Internal Server Error: An error occurred on the server.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + } + }, + "security": [ + { + "Bearer-JWT": [] + } + ] + } + }, + "/api/site/disable": { + "patch": { + "tags": [ + "Actions" + ], + "summary": "Disables a specific site identified by the `x-subdomain` header.", + "description": "This endpoint allows authenticated users to disable a site associated with the specified subdomain.", + "operationId": "Disable site", + "parameters": [ + { + "name": "x-subdomain", + "in": "header", + "description": "x-subdomain header represents name of subdomain to call action on", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Site was successfully disabled." + }, + "400": { + "description": "The 'x-subdomain' header is missing or contains invalid characters.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "401": { + "description": "Unauthorized: The JWT in the header is invalid or expired.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "403": { + "description": "Forbidden: The subdomain is owned by another user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "404": { + "description": "Not Found: The login or subdomain was not found. See details for more information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "500": { + "description": "Internal Server Error: An error occurred on the server.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + } + }, + "security": [ + { + "Bearer-JWT": [] + } + ] + } + }, + "/api/site/enable": { + "patch": { + "tags": [ + "Actions" + ], + "summary": "Enables a specific site identified by the `x-subdomain` header.", + "description": "This endpoint allows authenticated users to enable a site associated with the specified subdomain.", + "operationId": "Enable site", + "parameters": [ + { + "name": "x-subdomain", + "in": "header", + "description": "x-subdomain header represents name of subdomain to call action on", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Site was successfully enabled" + }, + "400": { + "description": "The 'x-subdomain' header is missing or contains invalid characters.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "401": { + "description": "Unauthorized: The JWT in the header is invalid or expired.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "403": { + "description": "Forbidden: The subdomain is owned by another user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "404": { + "description": "Not Found: The login or subdomain was not found. See details for more information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + }, + "500": { + "description": "Internal Server Error: An error occurred on the server.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Details" + } + } + } + } + }, + "security": [ + { + "Bearer-JWT": [] + } + ] + } + } + }, + "components": { + "schemas": { + "AddOriginRequest": { + "type": "object", + "required": [ + "origin" + ], + "properties": { + "origin": { + "type": "string", + "description": "Origin to be added" + } + }, + "example": { + "origin": "https://example.com/" + } + }, + "AddOriginResponse": { + "type": "object", + "required": [ + "id", + "origin" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64", + "description": "Automatically generated id for new origin\nThis can be used for further management" + }, + "origin": { + "type": "string", + "description": "This duplicates origin from response payload\nto match REST specification" + } + }, + "example": { + "id": "42", + "origin": "https://example.com/" + } + }, + "Details": { + "type": "object", + "description": "This struct is a response of server in bad situation\nThat can be INTERNAL SERVER ERROR or BAD REQUEST\nYou can find all information in reason field", + "required": [ + "reason" + ], + "properties": { + "reason": { + "type": "string", + "description": "This field will contain error information" + } + } + }, + "GetOriginResponse": { + "type": "object", + "required": [ + "origin" + ], + "properties": { + "origin": { + "$ref": "#/components/schemas/OriginModel" + } + }, + "example": { + "origin": { + "id": 42, + "subdomain_id": 1, + "value": "https://example.com" + } + } + }, + "ListOriginsResponse": { + "type": "object", + "required": [ + "origins" + ], + "properties": { + "origins": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OriginModel" + }, + "description": "List of retrieved origins" + } + }, + "example": { + "origins": [ + { + "id": 42, + "subdomain_id": 1, + "value": "https://example.com" + } + ] + } + }, + "LoginRequest": { + "type": "object", + "required": [ + "login", + "password" + ], + "properties": { + "login": { + "type": "string", + "description": "The username used for authentication.\nIt must adhere to the following criteria:\n- It can contain letters (a-z), numbers (0-9), and periods (.).\n- It cannot contain any of the following characters: & = ' - + , < >\n- It cannot have multiple periods (.) consecutively.\n- Minimum length of 5 characters.\n- Maximum length of 40 characters.", + "maxLength": 40, + "minLength": 5 + }, + "password": { + "type": "string", + "description": "The password used for authentication.\nIt must meet the following requirements:\n- Minimum length of 12 characters.\n- Maximum length of 40 characters.\n- A combination of letters, numbers, and symbols.", + "maxLength": 40, + "minLength": 12 + } + } + }, + "LoginResponse": { + "type": "object", + "description": "The JWT token generated for authentication purposes.", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "string", + "description": "Token in JWT format" + } + }, + "example": { + "token": "ferwfwerfwer.fwerfwerfwerfwer.fwerfewfr" + } + }, + "OriginModel": { + "type": "object", + "required": [ + "id", + "subdomain_id", + "value" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "subdomain_id": { + "type": "integer", + "format": "int64" + }, + "value": { + "type": "string" + } + } + }, + "RegistrationRequest": { + "type": "object", + "required": [ + "login", + "password" + ], + "properties": { + "login": { + "type": "string", + "description": "The username used for authentication.\nIt must adhere to the following criteria:\n- It can contain letters (a-z), numbers (0-9), and periods (.).\n- It cannot contain any of the following characters: & = ' - + , < >\n- It cannot have multiple periods (.) consecutively.\n- Minimum length of 5 characters.\n- Maximum length of 40 characters.", + "maxLength": 40, + "minLength": 5 + }, + "password": { + "type": "string", + "description": "The password used for authentication.\nIt must meet the following requirements:\n- Minimum length of 12 characters.\n- Maximum length of 40 characters.\n- A combination of letters, numbers, and symbols.", + "maxLength": 40, + "minLength": 12 + } + } + }, + "RegistrationResponse": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64", + "description": "Auto generated id of a registered user" + } + }, + "example": { + "id": 1293983717 + } + }, + "UploadData": { + "type": "object", + "required": [ + "archive" + ], + "properties": { + "archive": { + "type": "string", + "format": "binary" + } + } + } + }, + "securitySchemes": { + "Bearer-JWT": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + } + } +} diff --git a/readme.md b/readme.md index 744c7de..9c4ed94 100644 --- a/readme.md +++ b/readme.md @@ -21,23 +21,31 @@

+# Warning + +> [!CAUTION] +> +> **_This project was in a huge rewrite and upload tool and docs are not updated! +> THis will be fixed very soon._** + ## 📖 Table Of Contents -- [📖 Table Of Contents](#-table-of-contents) -- [Docs](#docs) -- [🔧 Tools](#-tools) -- [❓ About The Project](#-about-the-project) -- [🚀 Features](#-features) -- [🔌 Built With](#-built-with) -- [📍 Roadmap](#-roadmap) -- [🧑‍🤝‍🧑 Contributing](#-contributing) - - [Creating A Pull Request](#creating-a-pull-request) -- [License](#license) -- [Authors](#authors) +- [Warning!](#warning) + - [📖 Table Of Contents](#-table-of-contents) + - [Docs](#docs) + - [🔧 Tools](#-tools) + - [❓ About The Project](#-about-the-project) + - [🚀 Features](#-features) + - [🔌 Built With](#-built-with) + - [📍 Roadmap](#-roadmap) + - [🧑‍🤝‍🧑 Contributing](#-contributing) + - [Creating A Pull Request](#creating-a-pull-request) + - [License](#license) + - [Authors](#authors) ## Docs -Read [docs here]("clowzed.github.io/sero-docs/") for fast installation. +Read [docs here]("http://sero-docs.clowzed.ru") for fast installation. ## 🔧 Tools @@ -62,7 +70,7 @@ One key feature that it is self-hosted. This gives users more flexibility and co - Custom 503.html `new` `(on disabled site)` - Clean urls - Dynamic CORS Management -- `[WIP]` SSE +- `[WIP]` Server events with websocket ## 🔌 Built With diff --git a/src/api/auth/login/error.rs b/src/api/auth/login/error.rs index e638c8a..28fb312 100644 --- a/src/api/auth/login/error.rs +++ b/src/api/auth/login/error.rs @@ -27,7 +27,7 @@ impl IntoResponse for LoginError { fn into_response(self) -> Response { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); + (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/auth/login/request.rs b/src/api/auth/login/request.rs index ea7e6da..3e8064e 100644 --- a/src/api/auth/login/request.rs +++ b/src/api/auth/login/request.rs @@ -1,15 +1,25 @@ -use serde::{Deserialize, Serialize}; use std::fmt::{self, Debug}; + +use serde::{Deserialize, Serialize}; use utoipa::{schema, ToSchema}; -use validator::Validate; +use validator::{Validate, ValidationError}; #[derive(Deserialize, Serialize, Validate, ToSchema)] pub struct LoginRequest { /// The username used for authentication. /// It must adhere to the following criteria: + /// - It can contain letters (a-z), numbers (0-9), and periods (.). + /// - It cannot contain any of the following characters: & = ' - + , < > + /// - It cannot have multiple periods (.) consecutively. /// - Minimum length of 5 characters. /// - Maximum length of 40 characters. - #[validate(length(min = 5, max = 40))] + #[validate( + length(min = 5, max = 40), + custom( + function = validate_login, + message = "Login can contain letters (a-z), numbers (0-9), and periods (.), + and cannot contain any of the following characters: & = ' + , < > or multiple periods (.)" + ))] #[schema(min_length = 5, max_length = 40)] pub login: String, @@ -17,11 +27,53 @@ pub struct LoginRequest { /// It must meet the following requirements: /// - Minimum length of 12 characters. /// - Maximum length of 40 characters. - #[validate(length(min = 12, max = 40))] + /// - A combination of letters, numbers, and symbols. + #[validate( + length(min = 12, max = 40), + custom(function = validate_password, + message = "Minimum length of 12 characters and maximum length of 40 characters and a combination of + letters, numbers, and symbols.") + )] #[schema(min_length = 12, max_length = 40)] pub password: String, } +fn validate_login(login: &str) -> Result<(), ValidationError> { + let invalid_chars = "&='+,<>"; + let invalid_double_period = ".."; + + if login.chars().any(|c| invalid_chars.contains(c)) || login.contains(invalid_double_period) { + return Err(ValidationError::new( + "Rules for login: Login can contain letters (a-z), numbers (0-9), and periods (.), + and cannot contain any of the following characters: & = ' + , < > or multiple periods (.)", + )); + } + + Ok(()) +} + +fn validate_password(password: &str) -> Result<(), ValidationError> { + let mut has_digit = false; + let mut has_special_char = false; + + for c in password.chars() { + if !has_digit && c.is_ascii_digit() { + has_digit = true; + } else if c.is_ascii_punctuation() || c.is_ascii_whitespace() { + has_special_char = true; + } + + if has_digit && has_special_char { + return Ok(()); + } + } + + Err(ValidationError::new( + "Rules for password: Minimum length of 12 characters and maximum length of 40 characters and a combination of + letters, numbers, and symbols.", + )) +} + impl Debug for LoginRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("LoginRequest") diff --git a/src/api/auth/registration/error.rs b/src/api/auth/registration/error.rs index f7d5ad9..4518139 100644 --- a/src/api/auth/registration/error.rs +++ b/src/api/auth/registration/error.rs @@ -32,7 +32,6 @@ impl IntoResponse for RegistrationError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/auth/registration/request.rs b/src/api/auth/registration/request.rs index e63ea53..a5ed8f2 100644 --- a/src/api/auth/registration/request.rs +++ b/src/api/auth/registration/request.rs @@ -1,15 +1,25 @@ -use serde::{Deserialize, Serialize}; use std::fmt::{self, Debug}; + +use serde::{Deserialize, Serialize}; use utoipa::{schema, ToSchema}; -use validator::Validate; +use validator::{Validate, ValidationError}; #[derive(Deserialize, Serialize, Validate, ToSchema)] pub struct RegistrationRequest { /// The username used for authentication. /// It must adhere to the following criteria: + /// - It can contain letters (a-z), numbers (0-9), and periods (.). + /// - It cannot contain any of the following characters: & = ' - + , < > + /// - It cannot have multiple periods (.) consecutively. /// - Minimum length of 5 characters. /// - Maximum length of 40 characters. - #[validate(length(min = 5, max = 40))] + #[validate( + length(min = 5, max = 40), + custom( + function = validate_login, + message = "Login can contain letters (a-z), numbers (0-9), and periods (.), + and cannot contain any of the following characters: & = ' + , < > or multiple periods (.)" + ))] #[schema(min_length = 5, max_length = 40)] pub login: String, @@ -17,11 +27,53 @@ pub struct RegistrationRequest { /// It must meet the following requirements: /// - Minimum length of 12 characters. /// - Maximum length of 40 characters. - #[validate(length(min = 12, max = 40))] + /// - A combination of letters, numbers, and symbols. + #[validate( + length(min = 12, max = 40), + custom(function = validate_password, + message = "Minimum length of 12 characters and maximum length of 40 characters and a combination of + letters, numbers, and symbols.") + )] #[schema(min_length = 12, max_length = 40)] pub password: String, } +fn validate_login(login: &str) -> Result<(), ValidationError> { + let invalid_chars = "&='+,<>"; + let invalid_double_period = ".."; + + if login.chars().any(|c| invalid_chars.contains(c)) || login.contains(invalid_double_period) { + return Err(ValidationError::new( + "Rules for login: Login can contain letters (a-z), numbers (0-9), and periods (.), + and cannot contain any of the following characters: & = ' + , < > or multiple periods (.)", + )); + } + + Ok(()) +} + +fn validate_password(password: &str) -> Result<(), ValidationError> { + let mut has_digit = false; + let mut has_special_char = false; + + for c in password.chars() { + if !has_digit && c.is_ascii_digit() { + has_digit = true; + } else if c.is_ascii_punctuation() || c.is_ascii_whitespace() { + has_special_char = true; + } + + if has_digit && has_special_char { + return Ok(()); + } + } + + Err(ValidationError::new( + "Rules for password: Minimum length of 12 characters and maximum length of 40 characters and a combination of + letters, numbers, and symbols.", + )) +} + impl Debug for RegistrationRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("RegistrationRequest") diff --git a/src/api/origin/create/error.rs b/src/api/origin/create/error.rs index 8d4ace7..4c1654c 100644 --- a/src/api/origin/create/error.rs +++ b/src/api/origin/create/error.rs @@ -24,7 +24,6 @@ impl IntoResponse for AddOriginError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/origin/delete/error.rs b/src/api/origin/delete/error.rs index 24b1a7d..e436ff3 100644 --- a/src/api/origin/delete/error.rs +++ b/src/api/origin/delete/error.rs @@ -28,7 +28,6 @@ impl IntoResponse for DeleteOriginError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/origin/list/error.rs b/src/api/origin/list/error.rs index 850bf8b..8aedb45 100644 --- a/src/api/origin/list/error.rs +++ b/src/api/origin/list/error.rs @@ -24,7 +24,6 @@ impl IntoResponse for ListOriginsError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/origin/purge/error.rs b/src/api/origin/purge/error.rs index 3d92d06..6bd6f62 100644 --- a/src/api/origin/purge/error.rs +++ b/src/api/origin/purge/error.rs @@ -24,7 +24,6 @@ impl IntoResponse for DeleteOriginsError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/origin/retrieve/error.rs b/src/api/origin/retrieve/error.rs index b807ef4..0a5d730 100644 --- a/src/api/origin/retrieve/error.rs +++ b/src/api/origin/retrieve/error.rs @@ -24,7 +24,6 @@ impl IntoResponse for GetOriginError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/site/disable/error.rs b/src/api/site/disable/error.rs index 00b4d3b..058cd28 100644 --- a/src/api/site/disable/error.rs +++ b/src/api/site/disable/error.rs @@ -27,7 +27,6 @@ impl IntoResponse for DisableError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/site/download/error.rs b/src/api/site/download/error.rs index 68c03d2..2acb2d6 100644 --- a/src/api/site/download/error.rs +++ b/src/api/site/download/error.rs @@ -32,7 +32,6 @@ impl IntoResponse for DownloadError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/site/enable/error.rs b/src/api/site/enable/error.rs index 76b3f9d..4c32ed4 100644 --- a/src/api/site/enable/error.rs +++ b/src/api/site/enable/error.rs @@ -28,7 +28,6 @@ impl IntoResponse for EnableError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/site/page/error.rs b/src/api/site/page/error.rs index 9c491cf..e2dfa3e 100644 --- a/src/api/site/page/error.rs +++ b/src/api/site/page/error.rs @@ -28,7 +28,6 @@ impl IntoResponse for PageError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/site/teardown/error.rs b/src/api/site/teardown/error.rs index d55e5a3..c754e8a 100644 --- a/src/api/site/teardown/error.rs +++ b/src/api/site/teardown/error.rs @@ -28,7 +28,6 @@ impl IntoResponse for TeardownError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/site/upload/error.rs b/src/api/site/upload/error.rs index 8feb853..62d9273 100644 --- a/src/api/site/upload/error.rs +++ b/src/api/site/upload/error.rs @@ -34,7 +34,6 @@ impl IntoResponse for UploadError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/extractors/auth.rs b/src/extractors/auth.rs index 15184d0..79d97c3 100644 --- a/src/extractors/auth.rs +++ b/src/extractors/auth.rs @@ -61,7 +61,6 @@ impl IntoResponse for AuthError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/extractors/guards/upload.rs b/src/extractors/guards/upload.rs index 364d8e1..b2dee44 100644 --- a/src/extractors/guards/upload.rs +++ b/src/extractors/guards/upload.rs @@ -49,7 +49,6 @@ impl IntoResponse for GuardError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/extractors/subdomain.rs b/src/extractors/subdomain.rs index 780876e..917cd2c 100644 --- a/src/extractors/subdomain.rs +++ b/src/extractors/subdomain.rs @@ -41,7 +41,6 @@ impl IntoResponse for SubdomainError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/extractors/subdomain_name.rs b/src/extractors/subdomain_name.rs index b17324d..b3621c2 100644 --- a/src/extractors/subdomain_name.rs +++ b/src/extractors/subdomain_name.rs @@ -32,7 +32,6 @@ impl IntoResponse for SubdomainNameError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/extractors/subdomain_owned.rs b/src/extractors/subdomain_owned.rs index f55d951..f6889b8 100644 --- a/src/extractors/subdomain_owned.rs +++ b/src/extractors/subdomain_owned.rs @@ -41,7 +41,6 @@ impl IntoResponse for SubdomainOwnedError { let reason = self.to_string(); let status_code: StatusCode = self.into(); - tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/lib.rs b/src/lib.rs index 3120d51..f58974a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ use configuration::{reader::ConfigurationReader, *}; use futures::StreamExt; use migration::{Migrator, MigratorTrait}; use origin::service::Service as CorsService; -use sea_orm::{ActiveModelTrait, ConnectOptions, Database, DbErr, IntoActiveModel}; +use sea_orm::{ConnectOptions, Database, DbErr}; use serde::{Deserialize, Serialize}; use services::*; use site::service::Service as SiteService; @@ -165,15 +165,6 @@ pub async fn app() -> Result<(Router, Arc), AppCreationError> { |cause| tracing::warn!(%cause, "Failed to remove file with path : {}", file.real_path), ) .ok(); - - let file_id = file.id; - file.into_active_model() - .delete(state_for_file_deletion_task.connection()) - .await - .inspect_err( - |cause| tracing::warn!(%cause, %file_id, "Failed to remove file from database"), - ) - .ok(); } } } @@ -206,12 +197,6 @@ pub async fn app() -> Result<(Router, Arc), AppCreationError> { }; async move { - //? If header was not provided - //? Allow as it probably management tool - if task.subdomain.is_empty() { - return true; - } - if cors_task_sender .send(task) .await @@ -247,10 +232,10 @@ pub async fn app() -> Result<(Router, Arc), AppCreationError> { let mut app = Router::new() .merge(openapi) + .nest("/api", api::router()) .route("/*path", get(api::site::page::handler::implementation)) .route("/", get(api::site::page::handler::redirect::implementation)) .layer(cors_layer) - .nest("/api", api::router()) .layer(tracing_layer) .layer(TimeoutLayer::new(Duration::from_secs(10))) .with_state(state.clone()); diff --git a/src/services/archive/service.rs b/src/services/archive/service.rs index 70c8aa0..da255f1 100644 --- a/src/services/archive/service.rs +++ b/src/services/archive/service.rs @@ -44,11 +44,11 @@ impl Service { .filename() .as_str() .inspect_err(|cause| tracing::warn!(%cause, "Failed to convert entry filepath to str"))?; - PathBuf::from(entry_filename) + PathBuf::from(entry_filename).components().skip(1).collect::() }; tracing::trace!(?path, "Entry filepath was successfully retrieved"); - //? Generating filename for enty + //? Generating filename for entry // Just random to prevent collisions let u1 = Uuid::new_v4(); let u2 = Uuid::new_v4();