Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 33 additions & 5 deletions crates/defguard_common/src/db/models/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use url::Url;
use utoipa::ToSchema;
use uuid::Uuid;

use crate::{global_value, secret::SecretStringWrapper};
use crate::{db::Id, global_value, secret::SecretStringWrapper};

global_value!(SETTINGS, Option<Settings>, None, set_settings, get_settings);

Expand Down Expand Up @@ -81,6 +81,22 @@ impl LdapSyncStatus {
}
}

#[derive(Clone, Debug, Copy, Eq, PartialEq, Deserialize, Serialize, Default, Type, PartialOrd)]
#[sqlx(type_name = "initial_setup_step", rename_all = "snake_case")]
pub enum InitialSetupStep {
#[default]
Welcome,
AdminUser,
GeneralConfiguration,
Ca,
CaSummary,
// Adoption is not present, since the proxy is saved
// only after completing adoption step.
EdgeComponent,
Confirmation,
Finished,
}

#[derive(Clone, Deserialize, PartialEq, Patch, Serialize, Default)]
#[patch(attribute(derive(Deserialize, Serialize, Debug)))]
pub struct Settings {
Expand Down Expand Up @@ -156,6 +172,8 @@ pub struct Settings {
pub authentication_period_days: i32,
pub mfa_code_timeout_seconds: i32,
pub public_proxy_url: String,
pub initial_setup_step: InitialSetupStep,
pub default_admin_id: Option<Id>,
}

// Implement manually to avoid exposing the license key.
Expand Down Expand Up @@ -242,6 +260,9 @@ impl fmt::Debug for Settings {
&self.authentication_period_days,
)
.field("mfa_code_timeout_seconds", &self.mfa_code_timeout_seconds)
.field("public_proxy_url", &self.public_proxy_url)
.field("initial_setup_step", &self.initial_setup_step)
.field("default_admin_id", &self.default_admin_id)
.finish_non_exhaustive()
}
}
Expand Down Expand Up @@ -274,7 +295,8 @@ impl Settings {
openid_username_handling \"openid_username_handling: OpenIdUsernameHandling\", \
ca_key_der, ca_cert_der, ca_expiry, initial_setup_completed, \
defguard_url, default_admin_group_name, authentication_period_days, mfa_code_timeout_seconds, \
public_proxy_url \
public_proxy_url, initial_setup_step \"initial_setup_step: InitialSetupStep\", \
default_admin_id \
FROM \"settings\" WHERE id = 1",
)
.fetch_optional(executor)
Expand Down Expand Up @@ -360,7 +382,9 @@ impl Settings {
default_admin_group_name = $54, \
authentication_period_days = $55, \
mfa_code_timeout_seconds = $56, \
public_proxy_url = $57 \
public_proxy_url = $57, \
initial_setup_step = $58, \
default_admin_id = $59 \
WHERE id = 1",
self.openid_enabled,
self.wireguard_enabled,
Expand Down Expand Up @@ -418,7 +442,9 @@ impl Settings {
self.default_admin_group_name,
self.authentication_period_days,
self.mfa_code_timeout_seconds,
self.public_proxy_url
self.public_proxy_url,
&self.initial_setup_step as &InitialSetupStep,
self.default_admin_id,
)
.execute(executor)
.await?;
Expand Down Expand Up @@ -514,6 +540,7 @@ pub struct SettingsEssentials {
pub worker_enabled: bool,
pub openid_enabled: bool,
pub initial_setup_completed: bool,
pub initial_setup_step: InitialSetupStep,
}

impl SettingsEssentials {
Expand All @@ -524,7 +551,7 @@ impl SettingsEssentials {
query_as!(
SettingsEssentials,
"SELECT instance_name, main_logo_url, nav_logo_url, wireguard_enabled, \
webhooks_enabled, worker_enabled, openid_enabled, initial_setup_completed \
webhooks_enabled, worker_enabled, openid_enabled, initial_setup_completed, initial_setup_step \"initial_setup_step: InitialSetupStep\" \
FROM settings WHERE id = 1"
)
.fetch_one(executor)
Expand All @@ -543,6 +570,7 @@ impl From<Settings> for SettingsEssentials {
instance_name: settings.instance_name,
main_logo_url: settings.main_logo_url,
initial_setup_completed: settings.initial_setup_completed,
initial_setup_step: settings.initial_setup_step,
}
}
}
Expand Down
27 changes: 26 additions & 1 deletion crates/defguard_core/src/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use defguard_common::db::{
OAuth2Token, Session, SessionState, Settings,
group::{Group, Permission},
oauth2client::OAuth2Client,
settings::InitialSetupStep,
user::User,
},
};
Expand Down Expand Up @@ -220,8 +221,32 @@ where
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let settings = Settings::get_current_settings();
if !settings.initial_setup_completed {
return Ok(Self {});
// Allow unauthenticated access only up to the admin creation step.
if settings.initial_setup_step <= InitialSetupStep::AdminUser {
return Ok(Self {});
}
let session_info = SessionInfo::from_request_parts(parts, state).await?;
if !session_info.user.is_active {
return Err(WebError::Forbidden("user is disabled".into()));
}
if let Some(default_admin_id) = settings.default_admin_id {
if session_info.user.id == default_admin_id {
return Ok(Self {});
}
}
let pool = extract_pool(parts, state).await?;
let groups_with_permission =
Group::find_by_permission(&pool, Permission::IsAdmin).await?;
let group_names = groups_with_permission
.iter()
.map(|group| group.name.as_str())
.collect::<Vec<_>>();
if session_info.contains_any_group(&group_names) {
return Ok(Self {});
}
return Err(WebError::Forbidden("access denied".into()));
}

let session_info = SessionInfo::from_request_parts(parts, state).await?;
if !session_info.user.is_active {
return Err(WebError::Forbidden("user is disabled".into()));
Expand Down
17 changes: 16 additions & 1 deletion crates/defguard_core/src/handlers/component_setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ use defguard_common::{
auth::claims::Claims,
db::{
Id,
models::{Settings, gateway::Gateway, proxy::Proxy},
models::{
Settings,
gateway::Gateway,
proxy::Proxy,
settings::{InitialSetupStep, update_current_settings},
},
},
types::proxy::ProxyControlMessage,
};
Expand Down Expand Up @@ -561,6 +566,16 @@ pub async fn setup_proxy_tls_stream(

debug!("Edge proxy setup completed successfully");

let mut settings = Settings::get_current_settings();
if !settings.initial_setup_completed {
settings.initial_setup_step = InitialSetupStep::Confirmation;
if let Err(err) = update_current_settings(&pool, settings).await {
yield Ok(flow.error(&format!("Failed to update setup step in settings: {err}")));
return;
}
debug!("Initial setup step advanced to 'Finished'");
}

// Step 7: Done
yield Ok(flow.step(SetupStep::Done));
};
Expand Down
Loading
Loading