//! Authentication: first-boot setup, login, session, logout, disabling. mod common; use axum::http::StatusCode; use common::*; use serde_json::json; #[tokio::test] async fn first_boot_me_reports_first_boot() { let env = Env::new().await; let c = Client::new(env.app.clone()); let r = c.get("/api/auth/me").await; assert_eq!(r.status, StatusCode::OK); let j = r.json(); assert_eq!(j["first_boot"], true); assert!(j["user"].is_null()); assert_eq!(j["roots"], json!([])); } #[tokio::test] async fn setup_validates_input() { let env = Env::new().await; let c = Client::new(env.app.clone()); // Blank name. let r = c .post_json( "/api/auth/setup", &json!({ "name": " ", "password": "longenough1" }), ) .await; assert_eq!(r.status, StatusCode::BAD_REQUEST); // Name too long (65 chars). let r = c .post_json( "/api/auth/setup", &json!({ "name": "a".repeat(65), "password": "longenough1" }), ) .await; assert_eq!(r.status, StatusCode::BAD_REQUEST); // Password too short. let r = c .post_json( "/api/auth/setup", &json!({ "name": "admin", "password": "short" }), ) .await; assert_eq!(r.status, StatusCode::BAD_REQUEST); // Nothing was created. assert_eq!(c.get("/api/auth/me").await.json()["first_boot"], true); } #[tokio::test] async fn setup_creates_admin_and_sets_cookie() { let env = Env::new().await; let admin = env.admin().await; // /me reports the admin with the whole root (rw). let r = admin.get("/api/auth/me").await; assert_eq!(r.status, StatusCode::OK); let j = r.json(); assert_eq!(j["first_boot"], false); assert_eq!(j["user"]["name"], "admin"); assert_eq!(j["user"]["is_admin"], true); let roots = j["roots"].as_array().unwrap(); assert_eq!(roots.len(), 1); assert_eq!(roots[0]["path"], "."); assert_eq!(roots[0]["mode"], "rw"); // Setup is only available on first boot. let anon = Client::new(env.app.clone()); let r = anon .post_json( "/api/auth/setup", &json!({ "name": "x", "password": "longenough1" }), ) .await; assert_eq!(r.status, StatusCode::CONFLICT); } #[tokio::test] async fn login_flows() { let env = Env::new().await; let _admin = env.admin().await; let c = Client::new(env.app.clone()); let r = c .post_json( "/api/auth/login", &json!({ "name": "admin", "password": "wrongpass1" }), ) .await; assert_eq!(r.status, StatusCode::UNAUTHORIZED); let r = c .post_json( "/api/auth/login", &json!({ "name": "ghost", "password": "whatever1" }), ) .await; assert_eq!(r.status, StatusCode::UNAUTHORIZED); let r = c .post_json( "/api/auth/login", &json!({ "name": "admin", "password": "admin1234" }), ) .await; assert_eq!(r.status, StatusCode::OK); assert!(session_cookie(&r).is_some()); } #[tokio::test] async fn me_requires_valid_session() { let env = Env::new().await; let _admin = env.admin().await; // No cookie at all. let anon = Client::new(env.app.clone()); assert_eq!( anon.get("/api/auth/me").await.status, StatusCode::UNAUTHORIZED ); // Bogus session token. let mut bogus = Client::new(env.app.clone()); bogus.set_cookie("not-a-real-token"); assert_eq!( bogus.get("/api/auth/me").await.status, StatusCode::UNAUTHORIZED ); } #[tokio::test] async fn logout_invalidates_session() { let env = Env::new().await; let admin = env.admin().await; let r = admin.post_json("/api/auth/logout", &json!({})).await; assert_eq!(r.status, StatusCode::OK); // The Set-Cookie header clears the cookie. assert!(r .header("set-cookie") .unwrap() .starts_with("fbng_session=;")); // The old cookie no longer authenticates. assert_eq!( admin.get("/api/auth/me").await.status, StatusCode::UNAUTHORIZED ); } #[tokio::test] async fn disabled_user_loses_session_and_cannot_login() { let env = Env::new().await; let admin = env.admin().await; create_user(&admin, "bob", "bobpass123", &[("docs", "rw")]).await; let bob = login(&env, "bob", "bobpass123").await; assert_eq!(bob.get("/api/auth/me").await.status, StatusCode::OK); // Admin disables bob. let id = user_id(&admin, "bob").await; let r = admin .put_json( &format!("/api/admin/users/{id}"), &json!({ "active": false }), ) .await; assert_eq!(r.status, StatusCode::OK); // bob's live session is rejected… assert_eq!( bob.get("/api/auth/me").await.status, StatusCode::UNAUTHORIZED ); // …and he cannot log in again. let anon = Client::new(env.app.clone()); let r = anon .post_json( "/api/auth/login", &json!({ "name": "bob", "password": "bobpass123" }), ) .await; assert_eq!(r.status, StatusCode::UNAUTHORIZED); }