feat(server): add tls/ssl support
Added support for https server.
This commit is contained in:
parent
f440f30ea8
commit
0794bea31d
|
@ -10,6 +10,10 @@
|
||||||
*.db-shm
|
*.db-shm
|
||||||
*.db-wal
|
*.db-wal
|
||||||
|
|
||||||
|
# Tls
|
||||||
|
cert.key
|
||||||
|
cert.pem
|
||||||
|
|
||||||
# IDE configs
|
# IDE configs
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
|
|
@ -67,6 +67,12 @@ version = "1.0.65"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
|
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arc-swap"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-compression"
|
name = "async-compression"
|
||||||
version = "0.3.14"
|
version = "0.3.14"
|
||||||
|
@ -155,6 +161,26 @@ dependencies = [
|
||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-server"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87ba6170b61f7b086609dabcae68d2e07352539c6ef04a7c82980bdfa01a159d"
|
||||||
|
dependencies = [
|
||||||
|
"arc-swap",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"hyper",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pemfile",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.66"
|
version = "0.3.66"
|
||||||
|
@ -627,6 +653,7 @@ version = "0.0.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
|
"axum-server",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"byte-unit",
|
"byte-unit",
|
||||||
"crypto-utils",
|
"crypto-utils",
|
||||||
|
|
|
@ -37,6 +37,7 @@ toml = "0.5"
|
||||||
|
|
||||||
# HTTP server
|
# HTTP server
|
||||||
axum = { version = "0.6.0-rc.2", features = ["http2", "multipart"] }
|
axum = { version = "0.6.0-rc.2", features = ["http2", "multipart"] }
|
||||||
|
axum-server = { version = "0.4", features = ["tls-rustls"] }
|
||||||
tower-http = { version = "0.3", features = ["full"] }
|
tower-http = { version = "0.3", features = ["full"] }
|
||||||
hyper = { version = "0.14", features = ["full"] }
|
hyper = { version = "0.14", features = ["full"] }
|
||||||
byte-unit = "4.0.14"
|
byte-unit = "4.0.14"
|
||||||
|
|
19
README.md
19
README.md
|
@ -13,3 +13,22 @@
|
||||||
|
|
||||||
<a href="https://documenter.getpostman.com/view/23280189/VVk9dwRk"><img src="https://img.shields.io/badge/API_Docs-887BB0?style=flat-square&labelColor=555555&logo=postman"></a>
|
<a href="https://documenter.getpostman.com/view/23280189/VVk9dwRk"><img src="https://img.shields.io/badge/API_Docs-887BB0?style=flat-square&labelColor=555555&logo=postman"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
### 👨💻 Compile server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can run server using command `./target/release/homedisk`.
|
||||||
|
|
||||||
|
### 🔒 Generate development TLS certificate
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate private key
|
||||||
|
openssl genrsa -out cert.key 204
|
||||||
|
# Generate certificate
|
||||||
|
openssl req -new -x509 -key cert.key -out cert.pem -days 365
|
||||||
|
```
|
||||||
|
|
22
config.toml
22
config.toml
|
@ -1,20 +1,14 @@
|
||||||
[http]
|
[http]
|
||||||
# HTTP host
|
|
||||||
host = "0.0.0.0"
|
host = "0.0.0.0"
|
||||||
# HTTP port
|
httpPort = 8080 # http server port (recommended 80)
|
||||||
port = 8080
|
httpsPort = 8443 # https server port (recommended 443)
|
||||||
# Cors domains
|
cors = [ "localhost:8000" ] # CORS domains
|
||||||
cors = [
|
tlsCert = "./cert.pem" # TLS certificate file
|
||||||
"127.0.0.1:8000",
|
tlsKey = "./cert.key" # TLS key file
|
||||||
"localhost:8000",
|
|
||||||
]
|
|
||||||
|
|
||||||
[jwt]
|
[jwt]
|
||||||
# JWT Secret string (used to sign tokens)
|
secret = "secret key used to sign tokens" # jsonwebtoken secret string used to sign tokens
|
||||||
secret = "secret key used to sign tokens"
|
expires = 24 # token expiration time in hours (default one day)
|
||||||
# Token expiration time in hours
|
|
||||||
expires = 24 # one day
|
|
||||||
|
|
||||||
[storage]
|
[storage]
|
||||||
# Directory where user files will be stored
|
path = "/home/homedisk" # path to directory where user files will be stored
|
||||||
path = "/home/homedisk"
|
|
||||||
|
|
|
@ -2,18 +2,32 @@ mod api;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use axum::{http::HeaderValue, routing::get, Extension, Router, Server};
|
use axum::{
|
||||||
use tower_http::cors::{AllowOrigin, CorsLayer};
|
extract::Host,
|
||||||
use tracing::info;
|
handler::HandlerWithoutStateExt,
|
||||||
|
http::{HeaderValue, StatusCode, Uri},
|
||||||
|
response::Redirect,
|
||||||
|
routing::get,
|
||||||
|
Extension, Router,
|
||||||
|
};
|
||||||
|
use axum_server::tls_rustls::RustlsConfig;
|
||||||
|
use tower_http::{
|
||||||
|
cors::{AllowOrigin, CorsLayer},
|
||||||
|
BoxError,
|
||||||
|
};
|
||||||
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use crate::{config::Config, database::Database};
|
use crate::{config::Config, database::Database};
|
||||||
|
|
||||||
pub async fn start_server(config: Config, db: Database) -> anyhow::Result<()> {
|
pub async fn start_server(config: Config, db: Database) -> anyhow::Result<()> {
|
||||||
info!(
|
let host = format!("{}:{}", config.http.host, config.http.https_port);
|
||||||
"🚀 Server has launched on http://{}:{}",
|
|
||||||
config.http.host, config.http.port
|
tokio::spawn(redirect_http_to_https(config.clone()));
|
||||||
);
|
|
||||||
|
info!("🚀 Server has launched on https://{host}");
|
||||||
|
|
||||||
// change the type from Vec<String> to Vec<HeaderValue> so that the http server can correctly detect CORS hosts
|
// change the type from Vec<String> to Vec<HeaderValue> so that the http server can correctly detect CORS hosts
|
||||||
let origins = config
|
let origins = config
|
||||||
|
@ -23,7 +37,12 @@ pub async fn start_server(config: Config, db: Database) -> anyhow::Result<()> {
|
||||||
.map(|e| e.parse().expect("Failed to parse CORS hosts"))
|
.map(|e| e.parse().expect("Failed to parse CORS hosts"))
|
||||||
.collect::<Vec<HeaderValue>>();
|
.collect::<Vec<HeaderValue>>();
|
||||||
|
|
||||||
let host = format!("{}:{}", config.http.host, config.http.port);
|
let tls_config = RustlsConfig::from_pem_file(
|
||||||
|
PathBuf::from("").join("").join(&config.http.tls_cert),
|
||||||
|
PathBuf::from("").join("").join(&config.http.tls_key),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.nest("/api", api::app())
|
.nest("/api", api::app())
|
||||||
|
@ -32,9 +51,48 @@ pub async fn start_server(config: Config, db: Database) -> anyhow::Result<()> {
|
||||||
.layer(Extension(config))
|
.layer(Extension(config))
|
||||||
.layer(Extension(db));
|
.layer(Extension(db));
|
||||||
|
|
||||||
Server::bind(&host.parse()?)
|
axum_server::bind_rustls(host.parse()?, tls_config)
|
||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Err(anyhow!("Server unexpected stopped!"))
|
Err(anyhow!("Server unexpected stopped!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn redirect_http_to_https(config: Config) {
|
||||||
|
fn make_https(host: String, uri: Uri, config: Config) -> Result<Uri, BoxError> {
|
||||||
|
let mut parts = uri.into_parts();
|
||||||
|
|
||||||
|
parts.scheme = Some(axum::http::uri::Scheme::HTTPS);
|
||||||
|
|
||||||
|
if parts.path_and_query.is_none() {
|
||||||
|
parts.path_and_query = Some("/".parse().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let https_host = host.replace(
|
||||||
|
&config.http.http_port.to_string(),
|
||||||
|
&config.http.https_port.to_string(),
|
||||||
|
);
|
||||||
|
parts.authority = Some(https_host.parse()?);
|
||||||
|
|
||||||
|
Ok(Uri::from_parts(parts)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
let host = format!("{}:{}", config.http.host, config.http.http_port);
|
||||||
|
|
||||||
|
let redirect = move |Host(host): Host, uri: Uri| async move {
|
||||||
|
match make_https(host, uri, config) {
|
||||||
|
Ok(uri) => Ok(Redirect::permanent(&uri.to_string())),
|
||||||
|
Err(error) => {
|
||||||
|
tracing::warn!(%error, "Failed to convert URI to HTTPS");
|
||||||
|
Err(StatusCode::BAD_REQUEST)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("🚀 Http redirect listening on http://{host}");
|
||||||
|
|
||||||
|
axum::Server::bind(&host.parse().unwrap())
|
||||||
|
.serve(redirect.into_make_service())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
|
@ -4,41 +4,32 @@ use std::fs;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Settings configuration.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// HTTP Settings.
|
|
||||||
pub http: ConfigHTTP,
|
pub http: ConfigHTTP,
|
||||||
/// Json Web Token Settings.
|
|
||||||
pub jwt: ConfigJWT,
|
pub jwt: ConfigJWT,
|
||||||
/// Storage Settings.
|
|
||||||
pub storage: ConfigStorage,
|
pub storage: ConfigStorage,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// HTTP Settings.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ConfigHTTP {
|
pub struct ConfigHTTP {
|
||||||
/// HTTP Host.
|
|
||||||
pub host: String,
|
pub host: String,
|
||||||
/// Port HTTP Port.
|
pub http_port: u16,
|
||||||
pub port: u16,
|
pub https_port: u16,
|
||||||
/// [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) Domains (e.g ["site1.example.com", "site2.example.com"]).
|
|
||||||
pub cors: Vec<String>,
|
pub cors: Vec<String>,
|
||||||
|
pub tls_cert: String,
|
||||||
|
pub tls_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Json Web Token Settings.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ConfigJWT {
|
pub struct ConfigJWT {
|
||||||
/// JWT Secret string (used to sign tokens).
|
|
||||||
pub secret: String,
|
pub secret: String,
|
||||||
/// Token expiration time in hours.
|
|
||||||
pub expires: i64,
|
pub expires: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Storage Settings.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ConfigStorage {
|
pub struct ConfigStorage {
|
||||||
/// Directory where user files will be stored.
|
|
||||||
pub path: String,
|
pub path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue