auth.rs
⎇
Raw
1//! Authentication: first-boot setup, login, session, logout, disabling.
2
3use std::sync::Arc;
4
5use crate::common::*;
6use axum::http::StatusCode;
7use serde_json::json;
8
9#[tokio::test]
10async fn first_boot_me_reports_first_boot() {
11 let env = Env::new().await;
12 let c = Client::new(env.app.clone());
13 let r = c.get("/api/auth/me").await;
14 assert_eq!(r.status, StatusCode::OK);
15 let j = r.json();
16 assert_eq!(j["first_boot"], true);
17 assert!(j["user"].is_null());
18 assert_eq!(j["roots"], json!([]));
19}
20
21#[tokio::test]
22async fn setup_validates_input() {
23 let env = Env::new().await;
24 let c = Client::new(env.app.clone());
25 // Blank name.
26 let r = c
27 .post_json(
28 "/api/auth/setup",
29 &json!({ "name": " ", "password": "longenough1" }),
30 )
31 .await;
32 assert_eq!(r.status, StatusCode::BAD_REQUEST);
33 // Name too long (65 chars).
34 let r = c
35 .post_json(
36 "/api/auth/setup",
37 &json!({ "name": "a".repeat(65), "password": "longenough1" }),
38 )
39 .await;
40 assert_eq!(r.status, StatusCode::BAD_REQUEST);
41 // Password too short.
42 let r = c
43 .post_json(
44 "/api/auth/setup",
45 &json!({ "name": "admin", "password": "short" }),
46 )
47 .await;
48 assert_eq!(r.status, StatusCode::BAD_REQUEST);
49 // Nothing was created.
50 assert_eq!(c.get("/api/auth/me").await.json()["first_boot"], true);
51}
52
53#[tokio::test]
54async fn setup_creates_admin_and_sets_cookie() {
55 let env = Env::new().await;
56 let admin = env.admin().await;
57
58 // /me reports the admin with the whole root (rw).
59 let r = admin.get("/api/auth/me").await;
60 assert_eq!(r.status, StatusCode::OK);
61 let j = r.json();
62 assert_eq!(j["first_boot"], false);
63 assert_eq!(j["user"]["name"], "admin");
64 assert_eq!(j["user"]["is_admin"], true);
65 let roots = j["roots"].as_array().unwrap();
66 assert_eq!(roots.len(), 1);
67 assert_eq!(roots[0]["path"], ".");
68 assert_eq!(roots[0]["mode"], "rw");
69 // The whole root is named after the root folder itself (`--root-name`
70 // overrides this; see `root_name_is_configurable`).
71 assert_eq!(roots[0]["name"], env.state.root_name);
72 assert_eq!(
73 roots[0]["name"],
74 env.root.path().file_name().unwrap().to_str().unwrap()
75 );
76
77 // Setup is only available on first boot.
78 let anon = Client::new(env.app.clone());
79 let r = anon
80 .post_json(
81 "/api/auth/setup",
82 &json!({ "name": "x", "password": "longenough1" }),
83 )
84 .await;
85 assert_eq!(r.status, StatusCode::CONFLICT);
86}
87
88#[tokio::test]
89async fn login_flows() {
90 let env = Env::new().await;
91 let _admin = env.admin().await;
92 let c = Client::new(env.app.clone());
93
94 let r = c
95 .post_json(
96 "/api/auth/login",
97 &json!({ "name": "admin", "password": "wrongpass1" }),
98 )
99 .await;
100 assert_eq!(r.status, StatusCode::UNAUTHORIZED);
101
102 let r = c
103 .post_json(
104 "/api/auth/login",
105 &json!({ "name": "ghost", "password": "whatever1" }),
106 )
107 .await;
108 assert_eq!(r.status, StatusCode::UNAUTHORIZED);
109
110 let r = c
111 .post_json(
112 "/api/auth/login",
113 &json!({ "name": "admin", "password": "admin1234" }),
114 )
115 .await;
116 assert_eq!(r.status, StatusCode::OK);
117 assert!(session_cookie(&r).is_some());
118}
119
120#[tokio::test]
121async fn me_requires_valid_session() {
122 let env = Env::new().await;
123 let _admin = env.admin().await;
124
125 // No cookie at all.
126 let anon = Client::new(env.app.clone());
127 assert_eq!(
128 anon.get("/api/auth/me").await.status,
129 StatusCode::UNAUTHORIZED
130 );
131
132 // Bogus session token.
133 let mut bogus = Client::new(env.app.clone());
134 bogus.set_cookie("not-a-real-token");
135 assert_eq!(
136 bogus.get("/api/auth/me").await.status,
137 StatusCode::UNAUTHORIZED
138 );
139}
140
141#[tokio::test]
142async fn logout_invalidates_session() {
143 let env = Env::new().await;
144 let admin = env.admin().await;
145 let r = admin.post_json("/api/auth/logout", &json!({})).await;
146 assert_eq!(r.status, StatusCode::OK);
147 // The Set-Cookie header clears the cookie.
148 assert!(
149 r.header("set-cookie")
150 .unwrap()
151 .starts_with("dovenest_session=;")
152 );
153 // The old cookie no longer authenticates.
154 assert_eq!(
155 admin.get("/api/auth/me").await.status,
156 StatusCode::UNAUTHORIZED
157 );
158}
159
160#[tokio::test]
161async fn disabled_user_loses_session_and_cannot_login() {
162 let env = Env::new().await;
163 let admin = env.admin().await;
164
165 create_user(&admin, "bob", "bobpass123", &[("docs", "rw")]).await;
166 let bob = login(&env, "bob", "bobpass123").await;
167 assert_eq!(bob.get("/api/auth/me").await.status, StatusCode::OK);
168
169 // Admin disables bob.
170 let id = user_id(&admin, "bob").await;
171 let r = admin
172 .put_json(
173 &format!("/api/admin/users/{id}"),
174 &json!({ "active": false }),
175 )
176 .await;
177 assert_eq!(r.status, StatusCode::OK);
178
179 // bob's live session is rejected…
180 assert_eq!(
181 bob.get("/api/auth/me").await.status,
182 StatusCode::UNAUTHORIZED
183 );
184 // …and he cannot log in again.
185 let anon = Client::new(env.app.clone());
186 let r = anon
187 .post_json(
188 "/api/auth/login",
189 &json!({ "name": "bob", "password": "bobpass123" }),
190 )
191 .await;
192 assert_eq!(r.status, StatusCode::UNAUTHORIZED);
193}
194
195#[tokio::test]
196async fn root_name_is_configurable() {
197 let mut env = Env::new().await;
198 // Rebuild the router with a custom root name (what `--root-name` /
199 // `DOVENEST_ROOT_NAME` sets on the state).
200 let state = Arc::new(server::error::AppState {
201 db: env.state.db.clone(),
202 root: env.state.root.clone(),
203 root_name: "Media".to_string(),
204 public_url: None,
205 thumbs: None,
206 });
207 env.app = server::api::router(state.clone());
208 env.state = state;
209 let admin = env.admin().await;
210 let j = admin.get("/api/auth/me").await.json();
211 assert_eq!(j["roots"][0]["name"], "Media");
212 // Subroots keep their own folder name.
213 create_user(&admin, "bob", "bobpass123", &[("docs/inner", "rw")]).await;
214 let bob = login(&env, "bob", "bobpass123").await;
215 assert_eq!(
216 bob.get("/api/auth/me").await.json()["roots"][0]["name"],
217 "inner"
218 );
219}
220
221#[tokio::test]
222async fn public_url_reaches_client() {
223 let mut env = Env::new().await;
224 let state = Arc::new(server::error::AppState {
225 db: env.state.db.clone(),
226 root: env.state.root.clone(),
227 root_name: env.state.root_name.clone(),
228 public_url: Some("https://files.example.com".parse().unwrap()),
229 thumbs: None,
230 });
231 env.app = server::api::router(state.clone());
232 env.state = state;
233 let admin = env.admin().await;
234 assert_eq!(
235 admin.get("/api/auth/me").await.json()["public_url"],
236 "https://files.example.com"
237 );
238}
239
240#[tokio::test]
241async fn default_root_is_validated_and_dropped_with_its_root() {
242 let env = Env::new().await;
243 let admin = env.admin().await;
244 create_user(
245 &admin,
246 "bob",
247 "bobpass123",
248 &[("docs", "rw"), ("src", "ro")],
249 )
250 .await;
251 let bob = login(&env, "bob", "bobpass123").await;
252 let me = bob.get("/api/auth/me").await.json();
253 assert_eq!(me["user"]["default_root_id"], json!(null));
254 let src_id = me["roots"][1]["id"].as_i64().unwrap();
255 let admin_root_id = admin.get("/api/auth/me").await.json()["roots"][0]["id"]
256 .as_i64()
257 .unwrap();
258
259 // A root of another user → 400.
260 let r = bob
261 .put_json("/api/auth/me", &json!({ "default_root_id": admin_root_id }))
262 .await;
263 assert_eq!(r.status, StatusCode::BAD_REQUEST, "{}", r.text());
264
265 // One of bob's own roots is echoed back.
266 let r = bob
267 .put_json("/api/auth/me", &json!({ "default_root_id": src_id }))
268 .await;
269 assert_eq!(r.status, StatusCode::OK, "{}", r.text());
270 assert_eq!(r.json()["user"]["default_root_id"], json!(src_id));
271 assert_eq!(
272 bob.get("/api/auth/me").await.json()["user"]["default_root_id"],
273 json!(src_id)
274 );
275
276 // The admin takes that root away: /me no longer names it.
277 let id = user_id(&admin, "bob").await;
278 let r = admin
279 .put_json(
280 &format!("/api/admin/users/{id}"),
281 &json!({ "roots": [{ "path": "docs", "mode": "rw" }] }),
282 )
283 .await;
284 assert_eq!(r.status, StatusCode::OK);
285 assert_eq!(
286 bob.get("/api/auth/me").await.json()["user"]["default_root_id"],
287 json!(null)
288 );
289
290 // null clears it explicitly.
291 let r = bob
292 .put_json("/api/auth/me", &json!({ "default_root_id": null }))
293 .await;
294 assert_eq!(r.status, StatusCode::OK);
295 assert_eq!(r.json()["user"]["default_root_id"], json!(null));
296}
297
298#[tokio::test]
299async fn week_start_defaults_to_monday_and_is_validated() {
300 let env = Env::new().await;
301 let admin = env.admin().await;
302 assert_eq!(
303 admin.get("/api/auth/me").await.json()["user"]["week_start"],
304 json!(1)
305 );
306 let r = admin
307 .put_json("/api/auth/me", &json!({ "week_start": 3 }))
308 .await;
309 assert_eq!(r.status, StatusCode::BAD_REQUEST, "{}", r.text());
310 let r = admin
311 .put_json("/api/auth/me", &json!({ "week_start": 6 }))
312 .await;
313 assert_eq!(r.status, StatusCode::OK, "{}", r.text());
314 assert_eq!(
315 admin.get("/api/auth/me").await.json()["user"]["week_start"],
316 json!(6)
317 );
318}
319