feat: add client-side private key persistence and profile materialization

Store generated private key in SessionState and persist across enrollment and profile sync operations. Add materialize_profile helper that replaces placeholder tokens (__CLIENT_GENERATED_PRIVATE_KEY__ and __CLIENT_PRIVATE_KEY_REQUIRED__) with actual private key before writing profile to disk. Update enroll_device and sync_profile to materialize profile content with private key before writing.
This commit is contained in:
2026-03-18 07:19:10 +01:00
parent aef6bf998b
commit 21b7a140dd

View File

@@ -29,6 +29,7 @@ struct SessionState {
refresh_token: String,
server_url: String,
profile_path: String,
private_key: String,
enrollment: EnrollmentResult,
}
@@ -151,7 +152,7 @@ async fn enroll_device(
.await
.map_err(|err| format!("Unable to decode login response: {}", err))?;
let (_private_key, public_key) = generate_keypair();
let (private_key, public_key) = generate_keypair();
let enroll_response = client
.post(format!("{}/api/v1/devices/enroll", payload.server_url.trim_end_matches('/')))
.bearer_auth(&login.access_token)
@@ -181,7 +182,8 @@ async fn enroll_device(
.await
.map_err(|err| format!("Unable to decode enrollment response: {}", err))?;
let profile_path = write_profile(&app, &enroll.profile.content)?;
let profile_content = materialize_profile(&enroll.profile.content, &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(),
@@ -197,6 +199,7 @@ async fn enroll_device(
refresh_token: login.refresh_token,
server_url: payload.server_url,
profile_path: result.profile_path.clone(),
private_key,
enrollment: result.clone(),
};
@@ -266,7 +269,8 @@ async fn sync_profile(app: AppHandle, state: State<'_, AppState>) -> Result<Enro
.await
.map_err(|err| format!("Unable to decode profile sync response: {}", err))?;
let profile_path = write_profile(&app, &enroll.profile.content)?;
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(),
@@ -282,6 +286,7 @@ async fn sync_profile(app: AppHandle, state: State<'_, AppState>) -> Result<Enro
refresh_token: existing.refresh_token,
server_url: existing.server_url,
profile_path: result.profile_path.clone(),
private_key: existing.private_key,
enrollment: result.clone(),
};
@@ -330,6 +335,12 @@ fn build_fingerprint(server_url: &str, username: &str, public_key: &str) -> Stri
format!("nexavpn:{}:{}:{}", server_url, username, public_key)
}
fn materialize_profile(profile_content: &str, private_key: &str) -> String {
profile_content
.replace("__CLIENT_GENERATED_PRIVATE_KEY__", private_key)
.replace("__CLIENT_PRIVATE_KEY_REQUIRED__", private_key)
}
fn write_profile(app: &AppHandle, profile_content: &str) -> Result<PathBuf, String> {
let app_dir = ensure_app_dir(app)?;
let profile_path = app_dir.join(format!("{}.conf", PROFILE_NAME));