feat: sync profile before connection and apply current policy to enrollment responses

Add applyCurrentPolicy function to resolve and apply policy destinations to enrollment responses with fallback to 172.16.10.0/24 when no destinations exist. Replace withDebugProfile calls with applyCurrentPolicy in GetLatestEnrollmentByUser and GetEnrollmentByDeviceID. Extract sync_current_session helper function to deduplicate profile sync logic between sync_profile and connect_tunnel commands. Update connect
This commit is contained in:
2026-03-18 08:56:59 +01:00
parent e3bd6d3b96
commit 137fb1d3e7
3 changed files with 111 additions and 78 deletions

View File

@@ -260,77 +260,18 @@ fn clear_session(app: AppHandle, state: State<'_, AppState>) -> Result<(), Strin
#[tauri::command]
async fn sync_profile(app: AppHandle, state: State<'_, AppState>) -> Result<EnrollmentResult, String> {
let existing = {
let session = state.session.lock().map_err(|_| "Unable to read client state".to_string())?;
session.clone().ok_or_else(|| "No enrolled profile is available yet".to_string())?
};
let client = Client::builder()
.use_rustls_tls()
.build()
.map_err(|err| err.to_string())?;
let response = client
.get(format!("{}/api/v1/me/profile", existing.server_url.trim_end_matches('/')))
.bearer_auth(&existing.access_token)
.send()
.await
.map_err(|err| format!("Profile sync failed: {}", err))?;
if !response.status().is_success() {
let status = response.status();
let body = response
.text()
.await
.unwrap_or_else(|_| "<unable to read response body>".into());
return Err(format!("Profile sync failed with status {}: {}", status, body));
}
let enroll = response
.json::<EnrollResponse>()
.await
.map_err(|err| format!("Unable to decode profile sync response: {}", err))?;
let profile_content = materialize_profile(&enroll.profile.content, &existing.private_key);
let profile_path = write_profile(&app, &profile_content)?;
let result = EnrollmentResult {
assigned_ip: enroll.peer.assigned_ip,
resources: enroll.resources.into_iter().map(|resource| resource.value).collect(),
profile_revision: enroll.peer.profile_revision,
gateway_endpoint: enroll.peer.gateway.endpoint,
profile_path: profile_path.display().to_string(),
last_sync_time: now_label(),
tunnel_strategy: tunnel_manager::current_tunnel_strategy().into(),
};
let session_state = SessionState {
access_token: existing.access_token,
refresh_token: existing.refresh_token,
server_url: existing.server_url,
profile_path: result.profile_path.clone(),
private_key: existing.private_key,
enrollment: result.clone(),
};
write_session_state(&app, &session_state)?;
let mut session = state.session.lock().map_err(|_| "Unable to store client state".to_string())?;
*session = Some(session_state);
drop(session);
let session_state = sync_current_session(&app).await?;
refresh_tray_menu(&app);
Ok(result)
Ok(session_state.enrollment)
}
#[tauri::command]
fn connect_tunnel(app: AppHandle, state: State<'_, AppState>) -> Result<(), String> {
let profile_path = {
let session = state.session.lock().map_err(|_| "Unable to read client state".to_string())?;
let session = session.as_ref().ok_or_else(|| "No enrolled profile is available yet".to_string())?;
session.profile_path.clone()
};
let result = tunnel_manager::connect(&app, std::path::Path::new(&profile_path));
async fn connect_tunnel(app: AppHandle) -> Result<EnrollmentResult, String> {
let session_state = sync_current_session(&app).await?;
let result = tunnel_manager::connect(&app, std::path::Path::new(&session_state.profile_path));
refresh_tray_menu(&app);
result
result?;
Ok(session_state.enrollment)
}
#[tauri::command]
@@ -464,6 +405,67 @@ fn current_metrics(app: &AppHandle) -> Result<TunnelMetrics, String> {
})
}
async fn sync_current_session(app: &AppHandle) -> Result<SessionState, String> {
let existing = {
let state = app.state::<AppState>();
let session = state.session.lock().map_err(|_| "Unable to read client state".to_string())?;
session.clone().ok_or_else(|| "No enrolled profile is available yet".to_string())?
};
let client = Client::builder()
.use_rustls_tls()
.build()
.map_err(|err| err.to_string())?;
let response = client
.get(format!("{}/api/v1/me/profile", existing.server_url.trim_end_matches('/')))
.bearer_auth(&existing.access_token)
.send()
.await
.map_err(|err| format!("Profile sync failed: {}", err))?;
if !response.status().is_success() {
let status = response.status();
let body = response
.text()
.await
.unwrap_or_else(|_| "<unable to read response body>".into());
return Err(format!("Profile sync failed with status {}: {}", status, body));
}
let enroll = response
.json::<EnrollResponse>()
.await
.map_err(|err| format!("Unable to decode profile sync response: {}", err))?;
let profile_content = materialize_profile(&enroll.profile.content, &existing.private_key);
let profile_path = write_profile(app, &profile_content)?;
let result = EnrollmentResult {
assigned_ip: enroll.peer.assigned_ip,
resources: enroll.resources.into_iter().map(|resource| resource.value).collect(),
profile_revision: enroll.peer.profile_revision,
gateway_endpoint: enroll.peer.gateway.endpoint,
profile_path: profile_path.display().to_string(),
last_sync_time: now_label(),
tunnel_strategy: tunnel_manager::current_tunnel_strategy().into(),
};
let session_state = SessionState {
access_token: existing.access_token,
refresh_token: existing.refresh_token,
server_url: existing.server_url,
profile_path: result.profile_path.clone(),
private_key: existing.private_key,
enrollment: result.clone(),
};
write_session_state(app, &session_state)?;
let state = app.state::<AppState>();
let mut session = state.session.lock().map_err(|_| "Unable to store client state".to_string())?;
*session = Some(session_state.clone());
Ok(session_state)
}
fn update_tray_menu(app: &AppHandle, metrics: TunnelMetrics) -> Result<(), String> {
let state = app.state::<AppState>();
let tray = state.tray.lock().map_err(|_| "Unable to update tray state".to_string())?;
@@ -510,15 +512,20 @@ fn toggle_tray_connection(app: &AppHandle) {
Err(_) => return,
};
let result = if metrics.active {
tunnel_manager::disconnect(app, std::path::Path::new(&profile_path))
} else {
tunnel_manager::connect(app, std::path::Path::new(&profile_path))
};
if result.is_ok() {
refresh_tray_menu(app);
if metrics.active {
if tunnel_manager::disconnect(app, std::path::Path::new(&profile_path)).is_ok() {
refresh_tray_menu(app);
}
return;
}
let app_handle = app.clone();
tauri::async_runtime::spawn(async move {
if let Ok(session_state) = sync_current_session(&app_handle).await {
let _ = tunnel_manager::connect(&app_handle, std::path::Path::new(&session_state.profile_path));
refresh_tray_menu(&app_handle);
}
});
}
fn restore_webview_window(window: &WebviewWindow) {