lldap/server/src/infra/tcp_server.rs
Valentin Tolmer 1a03346a38 server: refactor auth_service to use Results
This simplifies the flow, and gets rid of wrong clippy warnings about
missing awaits due to the instrumentation.
2022-06-30 17:14:13 +02:00

162 lines
5.5 KiB
Rust

use crate::{
domain::{
error::DomainError,
handler::{BackendHandler, LoginHandler},
opaque_handler::OpaqueHandler,
},
infra::{
auth_service,
configuration::{Configuration, MailOptions},
logging::CustomRootSpanBuilder,
tcp_backend_handler::*,
},
};
use actix_files::{Files, NamedFile};
use actix_http::HttpServiceBuilder;
use actix_server::ServerBuilder;
use actix_service::map_config;
use actix_web::{dev::AppConfig, web, App, HttpResponse};
use anyhow::{Context, Result};
use hmac::{Hmac, NewMac};
use sha2::Sha512;
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::RwLock;
use tracing::info;
async fn index() -> actix_web::Result<NamedFile> {
let mut path = PathBuf::new();
path.push("app");
path.push("index.html");
Ok(NamedFile::open(path)?)
}
#[derive(thiserror::Error, Debug)]
pub enum TcpError {
#[error("`{0}`")]
DomainError(#[from] DomainError),
#[error("Bad request: `{0}`")]
BadRequest(String),
#[error("Internal server error: `{0}`")]
InternalServerError(String),
#[error("Unauthorized: `{0}`")]
UnauthorizedError(String),
}
pub type TcpResult<T> = std::result::Result<T, TcpError>;
pub(crate) fn error_to_http_response(error: TcpError) -> HttpResponse {
match error {
TcpError::DomainError(ref de) => match de {
DomainError::AuthenticationError(_) | DomainError::AuthenticationProtocolError(_) => {
HttpResponse::Unauthorized()
}
DomainError::DatabaseError(_)
| DomainError::InternalError(_)
| DomainError::UnknownCryptoError(_) => HttpResponse::InternalServerError(),
DomainError::Base64DecodeError(_) | DomainError::BinarySerializationError(_) => {
HttpResponse::BadRequest()
}
},
TcpError::BadRequest(_) => HttpResponse::BadRequest(),
TcpError::InternalServerError(_) => HttpResponse::InternalServerError(),
TcpError::UnauthorizedError(_) => HttpResponse::Unauthorized(),
}
.body(error.to_string())
}
fn http_config<Backend>(
cfg: &mut web::ServiceConfig,
backend_handler: Backend,
jwt_secret: secstr::SecUtf8,
jwt_blacklist: HashSet<u64>,
server_url: String,
mail_options: MailOptions,
) where
Backend: TcpBackendHandler + BackendHandler + LoginHandler + OpaqueHandler + Sync + 'static,
{
cfg.app_data(web::Data::new(AppState::<Backend> {
backend_handler,
jwt_key: Hmac::new_varkey(jwt_secret.unsecure().as_bytes()).unwrap(),
jwt_blacklist: RwLock::new(jwt_blacklist),
server_url,
mail_options,
}))
.service(web::scope("/auth").configure(auth_service::configure_server::<Backend>))
// API endpoint.
.service(
web::scope("/api")
.wrap(auth_service::CookieToHeaderTranslatorFactory)
.configure(super::graphql::api::configure_endpoint::<Backend>),
)
// Serve the /pkg path with the compiled WASM app.
.service(Files::new("/pkg", "./app/pkg"))
// Serve static files
.service(Files::new("/static", "./app/static"))
// Serve static fonts
.service(Files::new("/static/fonts", "./app/static/fonts"))
// Default to serve index.html for unknown routes, to support routing.
.service(
web::scope("/")
.route("", web::get().to(index)) // this is necessary because the below doesn't match a request for "/"
.route(".*", web::get().to(index)),
);
}
pub(crate) struct AppState<Backend> {
pub backend_handler: Backend,
pub jwt_key: Hmac<Sha512>,
pub jwt_blacklist: RwLock<HashSet<u64>>,
pub server_url: String,
pub mail_options: MailOptions,
}
pub async fn build_tcp_server<Backend>(
config: &Configuration,
backend_handler: Backend,
server_builder: ServerBuilder,
) -> Result<ServerBuilder>
where
Backend: TcpBackendHandler + BackendHandler + LoginHandler + OpaqueHandler + Sync + 'static,
{
let jwt_secret = config.jwt_secret.clone();
let jwt_blacklist = backend_handler
.get_jwt_blacklist()
.await
.context("while getting the jwt blacklist")?;
let server_url = config.http_url.clone();
let mail_options = config.smtp_options.clone();
info!("Starting the API/web server on port {}", config.http_port);
server_builder
.bind("http", ("0.0.0.0", config.http_port), move || {
let backend_handler = backend_handler.clone();
let jwt_secret = jwt_secret.clone();
let jwt_blacklist = jwt_blacklist.clone();
let server_url = server_url.clone();
let mail_options = mail_options.clone();
HttpServiceBuilder::new()
.finish(map_config(
App::new()
.wrap(tracing_actix_web::TracingLogger::<CustomRootSpanBuilder>::new())
.configure(move |cfg| {
http_config(
cfg,
backend_handler,
jwt_secret,
jwt_blacklist,
server_url,
mail_options,
)
}),
|_| AppConfig::default(),
))
.tcp()
})
.with_context(|| {
format!(
"While bringing up the TCP server with port {}",
config.http_port
)
})
}