use std::{net::SocketAddr, sync::Arc}; use axum::{ extract::{Query, State}, http::StatusCode, response::IntoResponse, routing::{get, post}, Form, Json, Router, }; use color_eyre::eyre::Context; use secrecy::ExposeSecret; use serde::{Deserialize, Serialize}; use tracing::{event, instrument, Level}; use crate::auth::{ AuthenticationError, ServerPadlockGenerator, UserAuthenticator, UserServerKeyGenerator, }; use crate::secrets::{Password, ServerHash, UserToken}; #[derive(Debug, Clone)] struct AppState { user_authenticator: Arc, server_padlock_generator: Arc, user_server_key_generator: Arc, } #[instrument] pub async fn run( listen: SocketAddr, user_authenticator: Arc, server_padlock_generator: Arc, user_server_key_generator: Arc, ) -> color_eyre::Result<()> { let app_state = AppState { user_authenticator, server_padlock_generator, user_server_key_generator, }; let app = Router::new() .route("/tls-check/success", get(|| async { "OK" })) .route("/api-login", post(api_login)) .route( "/generate-user-server-key-2", post(generate_user_server_key_2), ) .route( "/generate-server-padlock-2", post(generate_server_padlock_2), ) .with_state(app_state); let listener = tokio::net::TcpListener::bind(listen) .await .context(format!("Failed to listen on {listen}"))?; axum::serve(listener, app).await?; Ok(()) } #[derive(Debug, Clone, PartialEq, Eq, Serialize)] struct ApiError { #[serde(skip_serializing)] status: StatusCode, error: String, message: String, } impl From for ApiError { fn from(err: AuthenticationError) -> Self { Self { status: StatusCode::UNAUTHORIZED, error: "authentication-failed".to_owned(), message: err.to_string(), } } } impl IntoResponse for ApiError { fn into_response(self) -> axum::response::Response { (self.status, Json(self)).into_response() } } type ApiResult = Result; fn default_api_version() -> String { "4".to_owned() } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ApiVersion { #[serde(default = "default_api_version")] api_version: String, } #[derive(Debug, Clone, Deserialize)] struct LoginRequest { username: String, password: Password, } #[derive(Debug, Clone, Serialize)] struct LoginResponse { username: String, token: String, } #[instrument] async fn api_login( State(AppState { user_authenticator, .. }): State, Query(ApiVersion { api_version }): Query, Form(LoginRequest { username, password }): Form, ) -> ApiResult> { event!(Level::INFO, "Generating user key"); let (username, user_token) = user_authenticator .get_user_token(&username, &password) .await?; Ok(Json(LoginResponse { username, token: user_token.0.expose_secret().to_owned(), })) } #[derive(Debug, Clone, Deserialize)] struct UserServerKeyRequest { username: String, token: UserToken, server_hash: ServerHash, } #[derive(Debug, Clone, Serialize)] struct UserServerKeyResponse { server_key: String, server_key_timestamp: String, } #[instrument] async fn generate_user_server_key_2( State(AppState { user_server_key_generator, .. }): State, Query(ApiVersion { api_version }): Query, Form(UserServerKeyRequest { username, token, server_hash, }): Form, ) -> ApiResult> { event!(Level::INFO, "Creating user_server_key"); let (server_key, server_key_timestamp) = user_server_key_generator .generate_user_server_key(&username, &token, &server_hash) .await?; Ok(Json(UserServerKeyResponse { server_key: server_key.0.expose_secret().to_owned(), server_key_timestamp, })) } #[derive(Serialize)] struct ServerPadlockResponse { server_hash: ServerHash, server_padlock: String, } #[instrument] async fn generate_server_padlock_2( State(AppState { server_padlock_generator, .. }): State, Query(ApiVersion { api_version }): Query, ) -> ApiResult> { event!(Level::INFO, "Creating server padlock"); let server_hash = server_padlock_generator .generate_hash() .await .map_err(AuthenticationError::from)?; let server_padlock = server_padlock_generator .generate_padlock(&server_hash) .await .map_err(AuthenticationError::from)?; Ok(Json(ServerPadlockResponse { server_hash, server_padlock: server_padlock.0.expose_secret().to_owned(), })) }