diff --git a/src/api/admin.rs b/src/api/admin.rs new file mode 100644 index 00000000..ab5b5610 --- /dev/null +++ b/src/api/admin.rs @@ -0,0 +1,90 @@ +use rocket_contrib::json::Json; +use serde_json::Value; + +use crate::db::models::*; +use crate::db::DbConn; + +use crate::api::{EmptyResult, JsonResult, JsonUpcase}; + +use rocket::{Route, Outcome}; +use rocket::request::{self, Request, FromRequest}; + +pub fn routes() -> Vec { + routes![ + get_users, + invite_user, + delete_user, + ] +} + +#[derive(Deserialize, Debug)] +#[allow(non_snake_case)] +struct InviteData { + Email: String, +} + +#[get("/users")] +fn get_users(_token: AdminToken, conn: DbConn) -> JsonResult { + let users = User::get_all(&conn); + let users_json: Vec = users.iter().map(|u| u.to_json(&conn)).collect(); + + Ok(Json(Value::Array(users_json))) +} + +#[post("/users", data="")] +fn invite_user(data: JsonUpcase, _token: AdminToken, conn: DbConn) -> EmptyResult { + let data: InviteData = data.into_inner().data; + + if User::find_by_mail(&data.Email, &conn).is_some() { + err!("User already exists") + } + + err!("Unimplemented") +} + +#[delete("/users/")] +fn delete_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { + let _user = match User::find_by_uuid(&uuid, &conn) { + Some(user) => user, + None => err!("User doesn't exist") + }; + + // TODO: Enable this once we have a more secure auth method + err!("Unimplemented") + /* + match user.delete(&conn) { + Ok(_) => Ok(()), + Err(e) => err!("Error deleting user", e) + } + */ +} + + +pub struct AdminToken {} + +impl<'a, 'r> FromRequest<'a, 'r> for AdminToken { + type Error = &'static str; + + fn from_request(request: &'a Request<'r>) -> request::Outcome { + // Get access_token + let access_token: &str = match request.headers().get_one("Authorization") { + Some(a) => match a.rsplit("Bearer ").next() { + Some(split) => split, + None => err_handler!("No access token provided"), + }, + None => err_handler!("No access token provided"), + }; + + // TODO: What authentication to use? + // Option 1: Make it a config option + // Option 2: Generate random token, and + // Option 2a: Send it to admin email, like upstream + // Option 2b: Print in console or save to data dir, so admin can check + + if access_token != "token123" { + err_handler!("Invalid admin token") + } + + Outcome::Success(AdminToken {}) + } +} \ No newline at end of file diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index a6e32c90..ef7c254d 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -77,7 +77,7 @@ struct GlobalDomain { Excluded: bool, } -const GLOBAL_DOMAINS: &str = include_str!("global_domains.json"); +const GLOBAL_DOMAINS: &str = include_str!("../../static/global_domains.json"); #[get("/settings/domains")] fn get_eq_domains(headers: Headers) -> JsonResult { diff --git a/src/api/mod.rs b/src/api/mod.rs index 9c8e49eb..3bb2605d 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,10 +1,12 @@ pub(crate) mod core; +mod admin; mod icons; mod identity; mod web; mod notifications; pub use self::core::routes as core_routes; +pub use self::admin::routes as admin_routes; pub use self::icons::routes as icons_routes; pub use self::identity::routes as identity_routes; pub use self::web::routes as web_routes; diff --git a/src/api/web.rs b/src/api/web.rs index bcf41bd7..6619a08d 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -13,7 +13,7 @@ use crate::CONFIG; pub fn routes() -> Vec { if CONFIG.web_vault_enabled { - routes![web_index, app_id, web_files, attachments, alive] + routes![web_index, app_id, web_files, admin_page, attachments, alive] } else { routes![attachments, alive] } @@ -41,6 +41,11 @@ fn app_id() -> WebHeaders>> { })))) } +#[get("/admin")] +fn admin_page() -> WebHeaders> { + WebHeaders(NamedFile::open("src/static/admin.html")) // TODO: Change this to embed the page in the binary +} + #[get("/", rank = 1)] // Only match this if the other routes don't match fn web_files(p: PathBuf) -> WebHeaders> { WebHeaders(NamedFile::open(Path::new(&CONFIG.web_vault_folder).join(p))) diff --git a/src/auth.rs b/src/auth.rs index 0e851aa6..bc67a23b 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -174,7 +174,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Headers { }; // Get access_token - let access_token: &str = match request.headers().get_one("Authorization") { + let access_token: &str = match headers.get_one("Authorization") { Some(a) => match a.rsplit("Bearer ").next() { Some(split) => split, None => err_handler!("No access token provided"), diff --git a/src/main.rs b/src/main.rs index 0f0b6f78..c8a95c33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,12 +24,10 @@ mod auth; mod mail; fn init_rocket() -> Rocket { - - // TODO: TO HIDE MOUNTING LOG, call ignite, set logging to disabled, call all the mounts, and then enable it again - rocket::ignite() .mount("/", api::web_routes()) .mount("/api", api::core_routes()) + .mount("/admin", api::admin_routes()) .mount("/identity", api::identity_routes()) .mount("/icons", api::icons_routes()) .mount("/notifications", api::notifications_routes()) diff --git a/src/static/admin.html b/src/static/admin.html new file mode 100644 index 00000000..a464b526 --- /dev/null +++ b/src/static/admin.html @@ -0,0 +1,127 @@ + + + + + + + + + Bitwarden_rs Admin Panel + + + + + + + + + + + + + +
+
+
+
Authentication key needed to continue
+ Please provide it below: + +
+ + +
+
+
+ +
+
Registered Users
+ +
+ + + Reload users + +
+ +
+ identicon +
+
+ Full Name + Delete User +
+ Email +
+
+
+ + + \ No newline at end of file diff --git a/src/api/core/global_domains.json b/src/static/global_domains.json similarity index 100% rename from src/api/core/global_domains.json rename to src/static/global_domains.json