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