feature/frontend-login #10
@@ -147,6 +147,7 @@ fn get_app_state(
|
|||||||
) -> AppState {
|
) -> AppState {
|
||||||
AppState {
|
AppState {
|
||||||
database_connection: db_connection.clone(),
|
database_connection: db_connection.clone(),
|
||||||
|
config: Arc::new(settings.clone()),
|
||||||
service: Arc::new(AppService {
|
service: Arc::new(AppService {
|
||||||
server_state: Arc::new(ServerStateService::new(db_connection.clone())),
|
server_state: Arc::new(ServerStateService::new(db_connection.clone())),
|
||||||
settings: Arc::new(SettingsService::new(db_connection.clone())),
|
settings: Arc::new(SettingsService::new(db_connection.clone())),
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ use tracing::{debug, error};
|
|||||||
pub trait FromConfig: Sized {
|
pub trait FromConfig: Sized {
|
||||||
fn from_config(config: &Config) -> Result<Self, String>;
|
fn from_config(config: &Config) -> Result<Self, String>;
|
||||||
fn validate(&self) -> Result<(), String>;
|
fn validate(&self) -> Result<(), String>;
|
||||||
|
#[cfg(test)]
|
||||||
|
fn mock() -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -40,6 +42,16 @@ impl FromConfig for ProgramSettings {
|
|||||||
self.auth.validate()?;
|
self.auth.validate()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn mock() -> Self {
|
||||||
|
ProgramSettings {
|
||||||
|
logging: logging::LoggingSettings::mock(),
|
||||||
|
database: database::DatabaseSettings::mock(),
|
||||||
|
server: server::ServerSettings::mock(),
|
||||||
|
auth: auth::AuthSettings::mock(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_program_settings() -> ProgramSettings {
|
pub fn get_program_settings() -> ProgramSettings {
|
||||||
|
|||||||
@@ -48,4 +48,13 @@ impl FromConfig for AuthSettings {
|
|||||||
fn validate(&self) -> Result<(), String> {
|
fn validate(&self) -> Result<(), String> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn mock() -> Self {
|
||||||
|
AuthSettings {
|
||||||
|
jwt_secret: Some("mock_jwt_secret".to_string()),
|
||||||
|
default_admin_username: Some("admin".to_string()),
|
||||||
|
default_admin_password: Some("password".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,4 +50,13 @@ impl FromConfig for DatabaseSettings {
|
|||||||
fn validate(&self) -> Result<(), String> {
|
fn validate(&self) -> Result<(), String> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn mock() -> Self {
|
||||||
|
DatabaseSettings {
|
||||||
|
url: "sqlite::memory:".to_string(),
|
||||||
|
max_connections: 5,
|
||||||
|
migrate_on_startup: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ pub(crate) const SERVER_ADDRESS_KEY: &str = "SERVER.ADDRESS";
|
|||||||
pub(crate) const SERVER_PORT_KEY: &str = "SERVER.PORT";
|
pub(crate) const SERVER_PORT_KEY: &str = "SERVER.PORT";
|
||||||
pub(crate) const SERVER_SERVE_OPENAPI_KEY: &str = "SERVER.SERVE_OPENAPI";
|
pub(crate) const SERVER_SERVE_OPENAPI_KEY: &str = "SERVER.SERVE_OPENAPI";
|
||||||
pub(crate) const SERVER_CORS_ALLOWED_ORIGINS_KEY: &str = "SERVER.CORS.ALLOWED_ORIGINS";
|
pub(crate) const SERVER_CORS_ALLOWED_ORIGINS_KEY: &str = "SERVER.CORS.ALLOWED_ORIGINS";
|
||||||
|
pub(crate) const SERVER_COOKIES_SECURE_KEY: &str = "SERVER.COOKIES.SECURE";
|
||||||
//
|
//
|
||||||
pub(crate) const DATABASE_URL_KEY: &str = "DATABASE.URL";
|
pub(crate) const DATABASE_URL_KEY: &str = "DATABASE.URL";
|
||||||
pub(crate) const DATABASE_MAX_CONNECTIONS_KEY: &str = "DATABASE.MAX_CONNECTIONS";
|
pub(crate) const DATABASE_MAX_CONNECTIONS_KEY: &str = "DATABASE.MAX_CONNECTIONS";
|
||||||
|
|||||||
@@ -49,4 +49,12 @@ impl FromConfig for LoggingSettings {
|
|||||||
fn validate(&self) -> Result<(), String> {
|
fn validate(&self) -> Result<(), String> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn mock() -> Self {
|
||||||
|
LoggingSettings {
|
||||||
|
level: Level::INFO,
|
||||||
|
utc: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::net::IpAddr;
|
|||||||
use config::{Config, ConfigError};
|
use config::{Config, ConfigError};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::configs::key::{SERVER_CORS_ALLOWED_ORIGINS_KEY, SERVER_SERVE_OPENAPI_KEY};
|
use crate::configs::key::{SERVER_COOKIES_SECURE_KEY, SERVER_CORS_ALLOWED_ORIGINS_KEY, SERVER_SERVE_OPENAPI_KEY};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
FromConfig,
|
FromConfig,
|
||||||
@@ -16,6 +16,7 @@ pub struct ServerSettings {
|
|||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub serve_openapi: bool,
|
pub serve_openapi: bool,
|
||||||
pub cors: CORSSettings,
|
pub cors: CORSSettings,
|
||||||
|
pub cookies: CookiesSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -23,6 +24,11 @@ pub struct CORSSettings {
|
|||||||
pub allowed_origins: Vec<String>,
|
pub allowed_origins: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CookiesSettings {
|
||||||
|
pub secure: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl FromConfig for ServerSettings {
|
impl FromConfig for ServerSettings {
|
||||||
fn from_config(_config: &Config) -> Result<Self, String> {
|
fn from_config(_config: &Config) -> Result<Self, String> {
|
||||||
Ok(ServerSettings {
|
Ok(ServerSettings {
|
||||||
@@ -81,6 +87,24 @@ impl FromConfig for ServerSettings {
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
cookies: CookiesSettings {
|
||||||
|
secure: _config
|
||||||
|
.get_bool(SERVER_COOKIES_SECURE_KEY)
|
||||||
|
.inspect(|is_secure| {
|
||||||
|
if !*is_secure {
|
||||||
|
warn!("Cookie 'secure' flag is disabled; this is not recommended in production environments.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
const DEFAULT_COOKIES_SECURE: bool = true;
|
||||||
|
warn!(
|
||||||
|
"{} not set or invalid in configuration, defaulting to {}. Error: {}",
|
||||||
|
SERVER_COOKIES_SECURE_KEY, DEFAULT_COOKIES_SECURE, err
|
||||||
|
);
|
||||||
|
DEFAULT_COOKIES_SECURE
|
||||||
|
}),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,4 +115,19 @@ impl FromConfig for ServerSettings {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn mock() -> Self {
|
||||||
|
ServerSettings {
|
||||||
|
address: "0.0.0.0".parse().unwrap(),
|
||||||
|
port: 8080,
|
||||||
|
serve_openapi: false,
|
||||||
|
cors: CORSSettings {
|
||||||
|
allowed_origins: vec![],
|
||||||
|
},
|
||||||
|
cookies: CookiesSettings {
|
||||||
|
secure: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ use axum::{Extension, Router};
|
|||||||
use migration::sea_orm::DatabaseConnection;
|
use migration::sea_orm::DatabaseConnection;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
configs::server::CORSSettings,
|
configs::{ProgramSettings, server::CORSSettings},
|
||||||
middlewares,
|
middlewares,
|
||||||
services::{
|
services::{
|
||||||
auth::{
|
auth::{
|
||||||
@@ -23,10 +23,9 @@ use crate::{
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
#[allow(dead_code)]
|
|
||||||
pub database_connection: Arc<DatabaseConnection>,
|
pub database_connection: Arc<DatabaseConnection>,
|
||||||
#[allow(dead_code)]
|
|
||||||
pub service: Arc<AppService>,
|
pub service: Arc<AppService>,
|
||||||
|
pub config: Arc<ProgramSettings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ServiceState<T> = Arc<T>;
|
pub type ServiceState<T> = Arc<T>;
|
||||||
|
|||||||
@@ -84,10 +84,15 @@ pub async fn login(State(state): State<Arc<AppState>>, Json(payload): Json<Value
|
|||||||
.header(
|
.header(
|
||||||
SET_COOKIE,
|
SET_COOKIE,
|
||||||
format!(
|
format!(
|
||||||
"{}={}; HttpOnly; Path=/; Max-Age={}; SameSite=Strict;",
|
"{}={}; HttpOnly; Path=/; Max-Age={}; SameSite=Strict;{}",
|
||||||
JWT_COOKIE_NAME,
|
JWT_COOKIE_NAME,
|
||||||
jwt,
|
jwt,
|
||||||
claims.exp - claims.iat
|
claims.exp - claims.iat,
|
||||||
|
if state.config.server.cookies.secure {
|
||||||
|
" Secure;"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.body(Body::from(()));
|
.body(Body::from(()));
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ pub async fn get_health_info(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use crate::configs::FromConfig;
|
||||||
use crate::{
|
use crate::{
|
||||||
routes::{AppState, api::health::state::HealthState},
|
routes::{AppState, api::health::state::HealthState},
|
||||||
services::{
|
services::{
|
||||||
@@ -112,6 +113,7 @@ mod test {
|
|||||||
|
|
||||||
let app_state = Arc::new(AppState {
|
let app_state = Arc::new(AppState {
|
||||||
database_connection: db.clone(),
|
database_connection: db.clone(),
|
||||||
|
config: Arc::new(crate::configs::ProgramSettings::mock()),
|
||||||
service: Arc::new(crate::routes::AppService {
|
service: Arc::new(crate::routes::AppService {
|
||||||
settings: Arc::new(SettingsService::new(db.clone())),
|
settings: Arc::new(SettingsService::new(db.clone())),
|
||||||
auth_state: crate::routes::AuthState {
|
auth_state: crate::routes::AuthState {
|
||||||
|
|||||||
Reference in New Issue
Block a user