From a0c8fcb2693cc017d78e39a6e623d1c53e493a84 Mon Sep 17 00:00:00 2001 From: tv Date: Mon, 9 Nov 2026 22:23:52 +0000 Subject: access: a token with a limit changes no credentials A token's limit narrows every endpoint but those under /auth, which any registered user may call. Among them, PUT and DELETE /auth/keys/{id} change the keys the user logs in with, and POST /auth/logout ends every token of the user. With the token a dashboard hands to another member's (the member list, read, five minutes), its holder could add a key of its own and log in by it as the user, with everything the user may do. These now take a password or a token without a limit (Need::Credentials, refused where Caps::limited). A narrowed token still refreshes within its limit, says who it is, lists the user's keys and ends its own session (DELETE /auth/session): a dashboard opened from another member's logs out with it. --- src/api/access.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'src/api/access.rs') diff --git a/src/api/access.rs b/src/api/access.rs index 6d26139..6977a0a 100644 --- a/src/api/access.rs +++ b/src/api/access.rs @@ -252,8 +252,13 @@ enum Need { Store, /// Looking at this node (its figures, peers, dashboard): any capability. Observe, - /// Logging in, refreshing, asking who one is: any registered user. + /// Logging in, refreshing, asking who one is, ending one's session: any registered + /// user. Auth, + /// Changing what one logs in with (one's keys), or ending every token one has: a + /// user not narrowed by a token's limit. A token handed over to read the member list + /// for five minutes adds no key that logs in as its user. + Credentials, /// A right on protocol data. Protocol(Right, Vec), } @@ -271,6 +276,8 @@ fn needed(method: &Method, path: &str, node: &str) -> Need { let local = |right, name: &str| protocol(right, &[meta::LOCAL, node, name]); if under("/store") || under("/stream") { Need::Store + } else if (under("/auth/keys") && !looking) || path == "/auth/logout" { + Need::Credentials } else if under("/auth") { Need::Auth } else if (path == "/peers" && looking) || path == "/admin/stats" || under("/admin/dashboard") { @@ -307,6 +314,10 @@ fn needed(method: &Method, path: &str, node: &str) -> Need { fn allowed(need: Need, access: &Access) -> bool { match need { Need::Store | Need::Auth => true, + Need::Credentials => match access { + Access::Everything => true, + Access::Caps(caps) => !caps.limited(), + }, Need::Observe => match access { Access::Everything => true, Access::Caps(caps) => !caps.is_empty(), @@ -413,6 +424,17 @@ mod tests { assert!(allowed(need(&Method::GET, "/admin/stats"), &handed)); assert!(allowed(need(&Method::GET, "/peers"), &handed)); for (method, at) in [ + (Method::POST, "/auth/refresh"), + (Method::DELETE, "/auth/session"), + (Method::GET, "/auth/whoami"), + (Method::GET, "/auth/keys"), + ] { + assert!(allowed(need(&method, at), &handed), "{method} {at}"); + } + for (method, at) in [ + (Method::PUT, "/auth/keys/laptop"), + (Method::DELETE, "/auth/keys/laptop"), + (Method::POST, "/auth/logout"), (Method::DELETE, "/peers/continuum:00"), (Method::POST, "/admin/remakes"), (Method::GET, "/admin/document"), -- cgit v1.3.1