mirror of
https://github.com/clowzed/sero
synced 2026-03-14 20:55:50 +01:00
Compare commits
9 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
466dd6b3f0 |
||
|
|
782d8e4b05 |
||
|
|
7214330e53 |
||
|
|
a86f6ba0e7 |
||
|
|
b22e8817e1 | ||
|
|
ac8bcf4e2a | ||
|
|
6eb836ee19 | ||
|
|
aa27492f6f | ||
|
|
017ab86591 |
39 changed files with 251 additions and 1416 deletions
27
.github/workflows/dev.yml
vendored
Normal file
27
.github/workflows/dev.yml
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
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
|
||||
50
.github/workflows/release.yml
vendored
50
.github/workflows/release.yml
vendored
|
|
@ -104,3 +104,53 @@ 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
|
||||
|
|
|
|||
22
.github/workflows/test.yml
vendored
22
.github/workflows/test.yml
vendored
|
|
@ -46,24 +46,6 @@ 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
|
||||
|
||||
|
|
@ -77,14 +59,14 @@ jobs:
|
|||
- uses: "actions-rs/toolchain@v1"
|
||||
with:
|
||||
profile: "minimal"
|
||||
toolchain: "stable"
|
||||
toolchain: "nightly"
|
||||
override: true
|
||||
|
||||
- run: "rustup component add rustfmt"
|
||||
|
||||
- uses: "actions-rs/cargo@v1"
|
||||
with:
|
||||
command: "+nightly fmt"
|
||||
command: "fmt"
|
||||
args: "--all -- --check"
|
||||
|
||||
clippy:
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -4,3 +4,4 @@ sites-uploads
|
|||
logs
|
||||
test_upload_files
|
||||
.env
|
||||
openapi.json
|
||||
|
|
|
|||
46
Cargo.toml
46
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sero"
|
||||
version = "0.2.1"
|
||||
version = "0.2.8"
|
||||
edition = "2021"
|
||||
authors = ["clowzed <clowzed.work@gmail.com>"]
|
||||
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,5 +77,11 @@ members = [".", "entity", "migration"]
|
|||
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
axum-test = "15.2.0"
|
||||
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
strip = true
|
||||
opt-level = 3
|
||||
codegen-units = 1
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
69
docker-compose.dev.yml
Normal file
69
docker-compose.dev.yml
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
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:
|
||||
|
|
@ -4,7 +4,6 @@ services:
|
|||
database:
|
||||
image: postgres:16
|
||||
user: postgres
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_DB=sero
|
||||
|
|
@ -21,7 +20,6 @@ services:
|
|||
|
||||
proxy:
|
||||
image: nginx:alpine3.18-slim
|
||||
restart: always
|
||||
environment:
|
||||
- DOLLAR=$
|
||||
- SERVER_PORT=8080
|
||||
|
|
@ -37,20 +35,17 @@ services:
|
|||
- 80:80
|
||||
links:
|
||||
- server
|
||||
profiles:
|
||||
- donotstart
|
||||
depends_on:
|
||||
- server
|
||||
|
||||
server:
|
||||
image: clowzed/sero
|
||||
restart: always
|
||||
image: clowzed/sero:v0.2.7
|
||||
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
|
||||
|
|
@ -60,8 +55,8 @@ services:
|
|||
- MAX_SITES_PER_USER=100
|
||||
- MAX_BODY_LIMIT_SIZE=10000000 # 10mb
|
||||
- RUST_LOG=none,sero=trace
|
||||
# end of section
|
||||
- JWT_SECRET=mysuperstrongjwtscret
|
||||
# end of section
|
||||
- JWT_TTL_SECONDS=120
|
||||
- SQLX_LOGGING=true
|
||||
- UPLOAD_FOLDER=./sites-uploads
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
map $http_host $subdomain {
|
||||
~^(?<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 ~^(?<subdomain>\w*)\.${DOMAIN}.${ZONE}${DOLLAR};
|
||||
server_name ~^(?<subdomain>[a-zA-Z0-9-]+)\.${DOMAIN}\.${ZONE}${DOLLAR};
|
||||
|
||||
location / {
|
||||
if ($subdomain != "" && $http_x_subdomain = "") {
|
||||
proxy_set_header x-subdomain $subdomain;
|
||||
}
|
||||
proxy_pass http://${SERVER}:${SERVER_PORT}/;
|
||||
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};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1219
openapi.json
1219
openapi.json
File diff suppressed because it is too large
Load diff
34
readme.md
34
readme.md
|
|
@ -21,31 +21,23 @@
|
|||
</p>
|
||||
</p>
|
||||
|
||||
# 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
|
||||
|
||||
- [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)
|
||||
- [📖 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]("http://sero-docs.clowzed.ru") for fast installation.
|
||||
Read [docs here]("clowzed.github.io/sero-docs/") for fast installation.
|
||||
|
||||
## 🔧 Tools
|
||||
|
||||
|
|
@ -70,7 +62,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]` Server events with websocket
|
||||
- `[WIP]` SSE
|
||||
|
||||
## 🔌 Built With
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,15 @@
|
|||
use std::fmt::{self, Debug};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{self, Debug};
|
||||
use utoipa::{schema, ToSchema};
|
||||
use validator::{Validate, ValidationError};
|
||||
use validator::Validate;
|
||||
|
||||
#[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),
|
||||
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 (.)"
|
||||
))]
|
||||
#[validate(length(min = 5, max = 40))]
|
||||
#[schema(min_length = 5, max_length = 40)]
|
||||
pub login: String,
|
||||
|
||||
|
|
@ -27,53 +17,11 @@ pub struct LoginRequest {
|
|||
/// It must meet the following requirements:
|
||||
/// - Minimum length of 12 characters.
|
||||
/// - Maximum length of 40 characters.
|
||||
/// - 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.")
|
||||
)]
|
||||
#[validate(length(min = 12, max = 40))]
|
||||
#[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")
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,15 @@
|
|||
use std::fmt::{self, Debug};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{self, Debug};
|
||||
use utoipa::{schema, ToSchema};
|
||||
use validator::{Validate, ValidationError};
|
||||
use validator::Validate;
|
||||
|
||||
#[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),
|
||||
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 (.)"
|
||||
))]
|
||||
#[validate(length(min = 5, max = 40))]
|
||||
#[schema(min_length = 5, max_length = 40)]
|
||||
pub login: String,
|
||||
|
||||
|
|
@ -27,53 +17,11 @@ pub struct RegistrationRequest {
|
|||
/// It must meet the following requirements:
|
||||
/// - Minimum length of 12 characters.
|
||||
/// - Maximum length of 40 characters.
|
||||
/// - 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.")
|
||||
)]
|
||||
#[validate(length(min = 12, max = 40))]
|
||||
#[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")
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
src/lib.rs
19
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::{ConnectOptions, Database, DbErr};
|
||||
use sea_orm::{ActiveModelTrait, ConnectOptions, Database, DbErr, IntoActiveModel};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use services::*;
|
||||
use site::service::Service as SiteService;
|
||||
|
|
@ -165,6 +165,15 @@ pub async fn app() -> Result<(Router, Arc<State>), 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -197,6 +206,12 @@ pub async fn app() -> Result<(Router, Arc<State>), 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
|
||||
|
|
@ -232,10 +247,10 @@ pub async fn app() -> Result<(Router, Arc<State>), 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());
|
||||
|
|
|
|||
|
|
@ -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).components().skip(1).collect::<PathBuf>()
|
||||
PathBuf::from(entry_filename)
|
||||
};
|
||||
tracing::trace!(?path, "Entry filepath was successfully retrieved");
|
||||
|
||||
//? Generating filename for entry
|
||||
//? Generating filename for enty
|
||||
// Just random to prevent collisions
|
||||
let u1 = Uuid::new_v4();
|
||||
let u2 = Uuid::new_v4();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue