api_admin.rs
⎇
Raw
1//! Admin API: user management, lockout guards, server settings.
2
3mod common;
4
5use axum::http::StatusCode;
6use common::*;
7use serde_json::json;
8
9#[tokio::test]
10async fn admin_routes_require_admin() {
11 let env = Env::new().await;
12 let admin = env.admin().await;
13
14 // No session → 401.
15 let anon = Client::new(env.app.clone());
16 assert_eq!(anon.get("/api/admin/users").await.status, StatusCode::UNAUTHORIZED);
17 assert_eq!(anon.get("/api/admin/settings").await.status, StatusCode::UNAUTHORIZED);
18
19 // Non-admin session → 403.
20 create_user(&admin, "bob", "bobpass123", &[("docs", "rw")]).await;
21 let bob = login(&env, "bob", "bobpass123").await;
22 assert_eq!(bob.get("/api/admin/users").await.status, StatusCode::FORBIDDEN);
23 assert_eq!(
24 bob.put_json("/api/admin/settings", &json!({ "allow_writable_shares": true }))
25 .await
26 .status,
27 StatusCode::FORBIDDEN
28 );
29}
30
31#[tokio::test]
32async fn user_lifecycle() {
33 let env = Env::new().await;
34 let admin = env.admin().await;
35
36 // Create.
37 let j = create_user(&admin, "bob", "bobpass123", &[("docs", "rw"), ("src", "ro")]).await;
38 let bob_id = j["id"].as_i64().unwrap();
39 assert_eq!(j["name"], "bob");
40 assert_eq!(j["is_admin"], false);
41 assert_eq!(j["active"], true);
42 assert_eq!(j["roots"].as_array().unwrap().len(), 2);
43
44 // bob can log in and sees his roots.
45 let bob = login(&env, "bob", "bobpass123").await;
46 let me = bob.get("/api/auth/me").await.json();
47 let roots = me["roots"].as_array().unwrap();
48 assert_eq!(roots.len(), 2);
49
50 // Duplicate name (case-insensitive) → 409.
51 let r = admin
52 .post_json(
53 "/api/admin/users",
54 &json!({ "name": "BOB", "password": "whatever12", "roots": [] }),
55 )
56 .await;
57 assert_eq!(r.status, StatusCode::CONFLICT);
58 // Weak password / bad name → 400.
59 let r = admin
60 .post_json(
61 "/api/admin/users",
62 &json!({ "name": "carol", "password": "short", "roots": [] }),
63 )
64 .await;
65 assert_eq!(r.status, StatusCode::BAD_REQUEST);
66 let r = admin
67 .post_json(
68 "/api/admin/users",
69 &json!({ "name": " ", "password": "longenough1", "roots": [] }),
70 )
71 .await;
72 assert_eq!(r.status, StatusCode::BAD_REQUEST);
73 // Bad root paths → 400.
74 for bad in ["no-such-dir", "../escape", "config.json"] {
75 let r = admin
76 .post_json(
77 "/api/admin/users",
78 &json!({ "name": "dave", "password": "longenough1", "roots": [{ "path": bad, "mode": "rw" }] }),
79 )
80 .await;
81 assert_eq!(r.status, StatusCode::BAD_REQUEST, "root '{bad}' should be rejected");
82 }
83 // Bad mode → 400.
84 let r = admin
85 .post_json(
86 "/api/admin/users",
87 &json!({ "name": "dave", "password": "longenough1", "roots": [{ "path": "docs", "mode": "both" }] }),
88 )
89 .await;
90 assert_eq!(r.status, StatusCode::BAD_REQUEST);
91
92 // Update: password reset.
93 let r = admin
94 .put_json(&format!("/api/admin/users/{bob_id}"), &json!({ "password": "newpass123" }))
95 .await;
96 assert_eq!(r.status, StatusCode::OK);
97 assert!(login(&env, "bob", "newpass123").await.get("/api/auth/me").await.status == StatusCode::OK);
98 let anon = Client::new(env.app.clone());
99 assert_eq!(
100 anon.post_json("/api/auth/login", &json!({ "name": "bob", "password": "bobpass123" }))
101 .await
102 .status,
103 StatusCode::UNAUTHORIZED
104 );
105
106 // Update: replace roots.
107 let r = admin
108 .put_json(
109 &format!("/api/admin/users/{bob_id}"),
110 &json!({ "roots": [{ "path": "src", "mode": "rw" }] }),
111 )
112 .await;
113 assert_eq!(r.status, StatusCode::OK);
114 let updated = r.json();
115 let roots = updated["roots"].as_array().unwrap();
116 assert_eq!(roots.len(), 1);
117 assert_eq!(roots[0]["path"], "src");
118
119 // Update: make admin, then demote.
120 let r = admin
121 .put_json(&format!("/api/admin/users/{bob_id}"), &json!({ "is_admin": true }))
122 .await;
123 assert_eq!(r.json()["is_admin"], true);
124 // bob can now use the admin API.
125 let bob = login(&env, "bob", "newpass123").await;
126 assert_eq!(bob.get("/api/admin/users").await.status, StatusCode::OK);
127 let r = admin
128 .put_json(&format!("/api/admin/users/{bob_id}"), &json!({ "is_admin": false }))
129 .await;
130 assert_eq!(r.json()["is_admin"], false);
131
132 // Update: disable → re-enable.
133 let r = admin
134 .put_json(&format!("/api/admin/users/{bob_id}"), &json!({ "active": false }))
135 .await;
136 assert_eq!(r.json()["active"], false);
137 let anon = Client::new(env.app.clone());
138 assert_eq!(
139 anon.post_json("/api/auth/login", &json!({ "name": "bob", "password": "newpass123" }))
140 .await
141 .status,
142 StatusCode::UNAUTHORIZED
143 );
144 let r = admin
145 .put_json(&format!("/api/admin/users/{bob_id}"), &json!({ "active": true }))
146 .await;
147 assert_eq!(r.json()["active"], true);
148
149 // Delete.
150 let r = admin.delete(&format!("/api/admin/users/{bob_id}")).await;
151 assert_eq!(r.status, StatusCode::OK);
152 let r = admin.get("/api/admin/users").await;
153 let names: Vec<String> = r
154 .json()
155 .as_array()
156 .unwrap()
157 .iter()
158 .map(|u| u["name"].as_str().unwrap().to_string())
159 .collect();
160 assert_eq!(names, vec!["admin"]);
161 assert_eq!(
162 admin.delete(&format!("/api/admin/users/{bob_id}")).await.status,
163 StatusCode::NOT_FOUND
164 );
165}
166
167#[tokio::test]
168async fn lockout_guards() {
169 let env = Env::new().await;
170 let admin = env.admin().await;
171 let me = admin.get("/api/auth/me").await;
172 let my_id = me.json()["user"]["id"].as_i64().unwrap();
173
174 // Cannot demote yourself.
175 let r = admin
176 .put_json(&format!("/api/admin/users/{my_id}"), &json!({ "is_admin": false }))
177 .await;
178 assert_eq!(r.status, StatusCode::BAD_REQUEST);
179 // Cannot disable yourself.
180 let r = admin
181 .put_json(&format!("/api/admin/users/{my_id}"), &json!({ "active": false }))
182 .await;
183 assert_eq!(r.status, StatusCode::BAD_REQUEST);
184 // Cannot delete yourself.
185 let r = admin.delete(&format!("/api/admin/users/{my_id}")).await;
186 assert_eq!(r.status, StatusCode::BAD_REQUEST);
187 // You're still fine.
188 assert_eq!(admin.get("/api/auth/me").await.status, StatusCode::OK);
189
190 // Promote a second admin, so the first is no longer the "last" admin.
191 create_user(&admin, "eve", "evepass123", &[("docs", "rw")]).await;
192 let eve_id = user_id(&admin, "eve").await;
193 let r = admin
194 .put_json(&format!("/api/admin/users/{eve_id}"), &json!({ "is_admin": true }))
195 .await;
196 assert_eq!(r.status, StatusCode::OK);
197 // Now demoting eve is allowed (two active admins exist).
198 let r = admin
199 .put_json(&format!("/api/admin/users/{eve_id}"), &json!({ "is_admin": false }))
200 .await;
201 assert_eq!(r.status, StatusCode::OK);
202 assert_eq!(r.json()["is_admin"], false);
203 // But demoting the (now only) remaining admin via another account is
204 // blocked: promote eve again, demote admin would need admin to act on
205 // themselves — already covered. Instead: with exactly one active admin
206 // left (admin), demoting eve (not an admin anymore) is fine, and the
207 // last-admin guard still fires for admins. Re-promote eve and confirm
208 // the guard message when count == 1 after disabling admin — but admin
209 // can't disable self. So: two admins, disable one (allowed), then the
210 // remaining one is the last → deleting the *disabled* one is allowed.
211 let r = admin
212 .put_json(&format!("/api/admin/users/{eve_id}"), &json!({ "is_admin": true, "active": false }))
213 .await;
214 // eve was an admin but is now disabled → not counted. Deleting her:
215 // target.active == false → the guard (which requires active) does not
216 // fire → allowed.
217 assert_eq!(r.status, StatusCode::OK);
218 let r = admin.delete(&format!("/api/admin/users/{eve_id}")).await;
219 assert_eq!(r.status, StatusCode::OK);
220 // admin still works.
221 assert_eq!(admin.get("/api/auth/me").await.status, StatusCode::OK);
222}
223
224#[tokio::test]
225async fn settings_round_trip_visible_in_me() {
226 let env = Env::new().await;
227 let admin = env.admin().await;
228
229 let r = admin.get("/api/admin/settings").await;
230 assert_eq!(r.status, StatusCode::OK);
231 assert_eq!(r.json()["allow_writable_shares"], false);
232
233 let r = admin
234 .put_json("/api/admin/settings", &json!({ "allow_writable_shares": true }))
235 .await;
236 assert_eq!(r.json()["allow_writable_shares"], true);
237
238 // Reflected in /me for clients.
239 assert_eq!(
240 admin.get("/api/auth/me").await.json()["allow_writable_shares"],
241 true
242 );
243
244 let r = admin
245 .put_json("/api/admin/settings", &json!({ "allow_writable_shares": false }))
246 .await;
247 assert_eq!(r.json()["allow_writable_shares"], false);
248}
249
250#[tokio::test]
251async fn update_unknown_user_is_404() {
252 let env = Env::new().await;
253 let admin = env.admin().await;
254 let r = admin.put_json("/api/admin/users/9999", &json!({ "active": true })).await;
255 assert_eq!(r.status, StatusCode::NOT_FOUND);
256}
257