Menu
Commit ab4282ef
ab4282efdc91026beb4921d684f283d908f1b5de by Claude Code
- CHANGELOG.md +5 -0
- Cargo.lock +8 -8
- Cargo.toml +1 -1
- VERSION +1 -1
- crates/akurai-control/src/listener.rs +77 -3
- crates/akurai-node/src/main.rs +3 -1
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 91e5a3c..72a9718 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,11 @@ All notable changes to this project are documented here. Format: Keep a Changelo
## [Unreleased]
+## [0.0.8] - 2026-06-28
+
+- Require per-user VPN node networks
+
+
## [0.0.7] - 2026-06-28
- Add host-only node install defaults
diff --git a/Cargo.lock b/Cargo.lock
index 99f63fb..55e9106 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,47 +4,47 @@ version = 4
[[package]]
name = "akurai-admin"
-version = "0.0.6"
+version = "0.0.7"
dependencies = [
"akurai-common",
]
[[package]]
name = "akurai-common"
-version = "0.0.6"
+version = "0.0.7"
[[package]]
name = "akurai-control"
-version = "0.0.6"
+version = "0.0.7"
dependencies = [
"akurai-common",
]
[[package]]
name = "akurai-crypto"
-version = "0.0.6"
+version = "0.0.7"
[[package]]
name = "akurai-dns"
-version = "0.0.6"
+version = "0.0.7"
dependencies = [
"akurai-common",
]
[[package]]
name = "akurai-node"
-version = "0.0.6"
+version = "0.0.7"
dependencies = [
"akurai-common",
]
[[package]]
name = "akurai-relay"
-version = "0.0.6"
+version = "0.0.7"
[[package]]
name = "akurai-transport"
-version = "0.0.6"
+version = "0.0.7"
dependencies = [
"akurai-crypto",
]
diff --git a/Cargo.toml b/Cargo.toml
index 80b046d..d94d552 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,7 +14,7 @@ members = ["crates/akurai-common", "crates/akurai-crypto", "crates/akurai-transp
# official RFC test vectors. The hand-rolled-crypto risk is accepted deliberately.
[workspace.package]
-version = "0.0.7"
+version = "0.0.8"
edition = "2021"
license = "MIT"
repository = "https://github.com/olibuijr/AkurAI-VPN"
diff --git a/VERSION b/VERSION
index 5a5831a..d169b2f 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.0.7
+0.0.8
diff --git a/crates/akurai-control/src/listener.rs b/crates/akurai-control/src/listener.rs
index b3bb877..8c11451 100644
--- a/crates/akurai-control/src/listener.rs
+++ b/crates/akurai-control/src/listener.rs
@@ -445,18 +445,25 @@ fn handle_dashboard(req: &Request, state: &SharedState) -> Response {
let endpoints = state
.lock()
.ok()
- .map(|s| s.endpoints.clone())
+ .map(|s| {
+ s.endpoints
+ .iter()
+ .filter(|e| e.added_by == user.user.email)
+ .cloned()
+ .collect::<Vec<_>>()
+ })
.unwrap_or_default();
Response::ok_html(render_dashboard(&user.user, &user.csrf_token, &endpoints))
}
-fn handle_list_endpoints(_user: &AuthSession, state: &SharedState) -> Response {
+fn handle_list_endpoints(session: &AuthSession, state: &SharedState) -> Response {
let json = state
.lock()
.ok()
.map(|s| {
s.endpoints
.iter()
+ .filter(|e| e.added_by == session.user.email)
.map(|e| e.to_json())
.collect::<Vec<_>>()
.join(",")
@@ -569,7 +576,8 @@ fn delete_endpoint(
Err(_) => return Response::error_html("Internal state lock error"),
};
let before = st.endpoints.len();
- st.endpoints.retain(|e| e.id != id);
+ st.endpoints
+ .retain(|e| !(e.id == id && e.added_by == session.user.email));
if st.endpoints.len() == before {
return Response::not_found();
}
@@ -826,4 +834,70 @@ mod tests {
let html = render_dashboard(&user, "csrf-value", &[]);
assert!(html.contains(r#"name="csrf_token" value="csrf-value""#));
}
+
+ #[test]
+ fn endpoint_list_is_scoped_to_authenticated_user() {
+ let state = crate::state::new_shared();
+ {
+ let mut st = state.lock().unwrap();
+ st.endpoints.push(crate::vpn_endpoint::VpnEndpoint {
+ id: "mine".to_string(),
+ name: "midget".to_string(),
+ public_key: "pk1".to_string(),
+ endpoint_addr: String::new(),
+ allowed_ips: Vec::new(),
+ added_by: "user@example.com".to_string(),
+ added_at: 1,
+ });
+ st.endpoints.push(crate::vpn_endpoint::VpnEndpoint {
+ id: "other".to_string(),
+ name: "other-host".to_string(),
+ public_key: "pk2".to_string(),
+ endpoint_addr: String::new(),
+ allowed_ips: Vec::new(),
+ added_by: "other@example.com".to_string(),
+ added_at: 1,
+ });
+ }
+ let session = AuthSession {
+ user: AuthUser {
+ sub: "sub".to_string(),
+ email: "user@example.com".to_string(),
+ name: "User".to_string(),
+ },
+ csrf_token: "csrf".to_string(),
+ };
+ let response = handle_list_endpoints(&session, &state);
+ assert!(response.body.contains("mine"));
+ assert!(!response.body.contains("other"));
+ }
+
+ #[test]
+ fn delete_endpoint_cannot_remove_another_users_node() {
+ let state = crate::state::new_shared();
+ {
+ let mut st = state.lock().unwrap();
+ st.endpoints.push(crate::vpn_endpoint::VpnEndpoint {
+ id: "other".to_string(),
+ name: "other-host".to_string(),
+ public_key: "pk2".to_string(),
+ endpoint_addr: String::new(),
+ allowed_ips: Vec::new(),
+ added_by: "other@example.com".to_string(),
+ added_at: 1,
+ });
+ }
+ let session = AuthSession {
+ user: AuthUser {
+ sub: "sub".to_string(),
+ email: "user@example.com".to_string(),
+ name: "User".to_string(),
+ },
+ csrf_token: "csrf".to_string(),
+ };
+ let req = request(&[], "csrf_token=csrf");
+ let response = delete_endpoint("other", &session, &req, &state);
+ assert_eq!(response.status, "404 Not Found");
+ assert_eq!(state.lock().unwrap().endpoints.len(), 1);
+ }
}
diff --git a/crates/akurai-node/src/main.rs b/crates/akurai-node/src/main.rs
index 5c2ba2b..4a668bd 100644
--- a/crates/akurai-node/src/main.rs
+++ b/crates/akurai-node/src/main.rs
@@ -64,7 +64,9 @@ fn install(args: &[String]) -> Result<(), NodeError> {
let current_exe = std::env::current_exe()?;
let installed_exe = dirs.bin.join(NAME);
- fs::copy(¤t_exe, &installed_exe)?;
+ if current_exe.canonicalize().ok() != installed_exe.canonicalize().ok() {
+ fs::copy(¤t_exe, &installed_exe)?;
+ }
if !dirs.config_file.exists() {
let hostname = std::env::var("HOSTNAME")