diff --git a/README.md b/README.md index 4eed7e2..97147b8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ You may also venture on your own, adapting the following instructions to your ne Set the following environment variables, via `.env` or your shell: - `DOCKER_DB_ROOT_PASSWORD` will be used as the password for the database root user. - `DATABASE_URL` is used for db connection. During development, this is `postgres://tau:tau@localhost:5432/tau`. -- `SECRET` will be used as high entropy data used for generating tokens. +- `FRONTEND_ORIGIN` will be used as an allowed [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin) for the purpose of [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS). Must be a valid URL. Start the database with `docker compose --profile dev (up -d/down)`. Run the migrations via sqlx-cli with `sqlx run migrate` or by other means. @@ -22,10 +22,11 @@ Compile and run the project with `cargo`. For deploying via docker, set the following environment variables: - `DOCKER_DB_PASSWORD` which will be used as the password for the backend's database access user. - `DOCKER_DB_ROOT_PASSWORD` will be used as the password for the database root user. -- `SECRET` will be used as high entropy data used for generating tokens. +- `FRONTEND_ORIGIN` will be used as an allowed [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin) for the purpose of [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS). Must be a valid URL (`http://localhost:3000` by default). Then, run `docker compose --profile prod`. ### Optional configuration +- `SECRET` will be used as additional high entropy data used for generating tokens. By default, tau uses system entropy and the current UNIX timestamp. - `PORT` will be used as the port the server listens on. The default is 2023. The following example `.env` file is geared for both scenarios: @@ -34,6 +35,7 @@ DATABASE_URL=postgres://tau:tau@localhost:5432/tau SECRET=CENTRUMRWLYSONOSTARPOZNANCDNSBCD4L52SPM DOCKER_DB_ROOT_PASSWORD=superdoopersecretpasswordthatcannotbeleaked DOCKER_DB_PASSWORD=wedoingsecurityinhere +FRONTEND_ORIGIN=https://example.com PORT=2019 ``` diff --git a/compose.yaml b/compose.yaml index d2a6596..3d9d5e6 100644 --- a/compose.yaml +++ b/compose.yaml @@ -13,6 +13,7 @@ services: - DATABASE_URL=postgres://tau:${DOCKER_DB_PASSWORD}@db-prod:5432/tau - PORT=${PORT} - SECRET=${SECRET} + - FRONTEND_ORIGIN=${FRONTEND_ORIGIN} depends_on: db-prod: condition: service_healthy @@ -49,6 +50,7 @@ services: - dbrootpassword environment: - POSTGRES_PASSWORD_FILE=/run/secrets/dbrootpassword + - FRONTEND_ORIGIN=http://localhost:3000 volumes: - dbdevdata:/var/lib/postgresql/data - ./dbinit-dev.sh:/docker-entrypoint-initdb.d/dbinit-dev.sh diff --git a/src/main.rs b/src/main.rs index 8c4c86f..ed40f43 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ use axum::Router; use tokio::net::TcpListener; use tower_cookies::CookieManagerLayer; -use tower_http::cors::{Any, CorsLayer}; use tracing::error; use users::infradmin::guarantee_infrastructure_admin_exists; @@ -25,7 +24,7 @@ async fn main() { let app = Router::new() .merge(routes::routes()) .with_state(state) - .layer(CorsLayer::new().allow_origin(Any).allow_methods(Any)) + .layer(setup::configure_cors()) .layer(CookieManagerLayer::new()); let addr = setup::get_socket_addr(); diff --git a/src/setup.rs b/src/setup.rs index 910fb28..c05978f 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,6 +1,8 @@ +use axum::http::{header::CONTENT_TYPE, HeaderValue, Method}; use sqlx::{Pool, Postgres}; use std::net::{Ipv4Addr, SocketAddrV4}; use tokio::net::TcpListener; +use tower_http::cors::CorsLayer; use tracing::{error, info, warn, Level}; use tracing_subscriber::FmtSubscriber; @@ -9,6 +11,7 @@ use crate::database; const CRYPTO_SECRET_CORRECT: &str = "Cryptographic SECRET is set."; const CRYPTO_SECRET_NOT_SET: &str = "Cryptographic SECRET is not set. This may lead to increased predictability in token generation."; const CRYPTO_SECRET_ERROR: &str = "Could not read SECRET. Is it valid UTF-8?"; +const FRONTEND_ORIGIN_NOT_SET: &str = "FRONTEND_ORIGIN is not set. Please provide a valid URL leading to an accepted origin."; pub fn initialise_logging() { let subscriber = FmtSubscriber::builder() @@ -94,3 +97,33 @@ pub fn check_secret_env_var() { }, } } + +pub fn configure_cors() -> CorsLayer { + let default_origin = "http://localhost:3000".to_owned(); + let result = std::env::var("FRONTEND_ORIGIN"); + + #[cfg(not(debug_assertions))] + if result.is_err() { + error!("{}", FRONTEND_ORIGIN_NOT_SET); + panic!(); + } + + let frontend_origin = result.unwrap_or(default_origin); + info!( + "FRONTEND_ORIGIN set to {}. Requests made from any other origins will be disallowed at browser level", + &frontend_origin + ); + let layer = CorsLayer::new() + .allow_origin(frontend_origin.parse::().unwrap()) + .allow_methods([ + Method::GET, + Method::POST, + Method::DELETE, + Method::PATCH, + Method::PUT, + ]) + .allow_headers([CONTENT_TYPE]) + .allow_credentials(true); + + return layer; +}