admin.rs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. use once_cell::sync::Lazy;
  2. use serde_json::Value;
  3. use std::process::Command;
  4. use rocket::http::{Cookie, Cookies, SameSite};
  5. use rocket::request::{self, FlashMessage, Form, FromRequest, Request};
  6. use rocket::response::{content::Html, Flash, Redirect};
  7. use rocket::{Outcome, Route};
  8. use rocket_contrib::json::Json;
  9. use crate::api::{ApiResult, EmptyResult, JsonResult};
  10. use crate::auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp};
  11. use crate::config::ConfigBuilder;
  12. use crate::db::{backup_database, models::*, DbConn};
  13. use crate::error::Error;
  14. use crate::mail;
  15. use crate::CONFIG;
  16. pub fn routes() -> Vec<Route> {
  17. if !CONFIG.disable_admin_token() && !CONFIG.is_admin_token_set() {
  18. return routes![admin_disabled];
  19. }
  20. routes![
  21. admin_login,
  22. get_users,
  23. post_admin_login,
  24. admin_page,
  25. invite_user,
  26. logout,
  27. delete_user,
  28. deauth_user,
  29. remove_2fa,
  30. update_revision_users,
  31. post_config,
  32. delete_config,
  33. backup_db,
  34. test_smtp,
  35. ]
  36. }
  37. static CAN_BACKUP: Lazy<bool> =
  38. Lazy::new(|| cfg!(feature = "sqlite") && Command::new("sqlite3").arg("-version").status().is_ok());
  39. #[get("/")]
  40. fn admin_disabled() -> &'static str {
  41. "The admin panel is disabled, please configure the 'ADMIN_TOKEN' variable to enable it"
  42. }
  43. const COOKIE_NAME: &str = "BWRS_ADMIN";
  44. const ADMIN_PATH: &str = "/admin";
  45. const BASE_TEMPLATE: &str = "admin/base";
  46. const VERSION: Option<&str> = option_env!("BWRS_VERSION");
  47. fn admin_path() -> String {
  48. format!("{}{}", CONFIG.domain_path(), ADMIN_PATH)
  49. }
  50. /// Used for `Location` response headers, which must specify an absolute URI
  51. /// (see https://tools.ietf.org/html/rfc2616#section-14.30).
  52. fn admin_url() -> String {
  53. format!("{}{}", CONFIG.domain(), ADMIN_PATH)
  54. }
  55. #[get("/", rank = 2)]
  56. fn admin_login(flash: Option<FlashMessage>) -> ApiResult<Html<String>> {
  57. // If there is an error, show it
  58. let msg = flash.map(|msg| format!("{}: {}", msg.name(), msg.msg()));
  59. let json = json!({"page_content": "admin/login", "version": VERSION, "error": msg, "urlpath": CONFIG.domain_path()});
  60. // Return the page
  61. let text = CONFIG.render_template(BASE_TEMPLATE, &json)?;
  62. Ok(Html(text))
  63. }
  64. #[derive(FromForm)]
  65. struct LoginForm {
  66. token: String,
  67. }
  68. #[post("/", data = "<data>")]
  69. fn post_admin_login(data: Form<LoginForm>, mut cookies: Cookies, ip: ClientIp) -> Result<Redirect, Flash<Redirect>> {
  70. let data = data.into_inner();
  71. // If the token is invalid, redirect to login page
  72. if !_validate_token(&data.token) {
  73. error!("Invalid admin token. IP: {}", ip.ip);
  74. Err(Flash::error(
  75. Redirect::to(admin_url()),
  76. "Invalid admin token, please try again.",
  77. ))
  78. } else {
  79. // If the token received is valid, generate JWT and save it as a cookie
  80. let claims = generate_admin_claims();
  81. let jwt = encode_jwt(&claims);
  82. let cookie = Cookie::build(COOKIE_NAME, jwt)
  83. .path(admin_path())
  84. .max_age(time::Duration::minutes(20))
  85. .same_site(SameSite::Strict)
  86. .http_only(true)
  87. .finish();
  88. cookies.add(cookie);
  89. Ok(Redirect::to(admin_url()))
  90. }
  91. }
  92. fn _validate_token(token: &str) -> bool {
  93. match CONFIG.admin_token().as_ref() {
  94. None => false,
  95. Some(t) => crate::crypto::ct_eq(t.trim(), token.trim()),
  96. }
  97. }
  98. #[derive(Serialize)]
  99. struct AdminTemplateData {
  100. page_content: String,
  101. version: Option<&'static str>,
  102. users: Vec<Value>,
  103. config: Value,
  104. can_backup: bool,
  105. logged_in: bool,
  106. urlpath: String,
  107. }
  108. impl AdminTemplateData {
  109. fn new(users: Vec<Value>) -> Self {
  110. Self {
  111. page_content: String::from("admin/page"),
  112. version: VERSION,
  113. users,
  114. config: CONFIG.prepare_json(),
  115. can_backup: *CAN_BACKUP,
  116. logged_in: true,
  117. urlpath: CONFIG.domain_path(),
  118. }
  119. }
  120. fn render(self) -> Result<String, Error> {
  121. CONFIG.render_template(BASE_TEMPLATE, &self)
  122. }
  123. }
  124. #[get("/", rank = 1)]
  125. fn admin_page(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
  126. let users = User::get_all(&conn);
  127. let users_json: Vec<Value> = users.iter().map(|u| u.to_json(&conn)).collect();
  128. let text = AdminTemplateData::new(users_json).render()?;
  129. Ok(Html(text))
  130. }
  131. #[derive(Deserialize, Debug)]
  132. #[allow(non_snake_case)]
  133. struct InviteData {
  134. email: String,
  135. }
  136. #[post("/invite", data = "<data>")]
  137. fn invite_user(data: Json<InviteData>, _token: AdminToken, conn: DbConn) -> EmptyResult {
  138. let data: InviteData = data.into_inner();
  139. let email = data.email.clone();
  140. if User::find_by_mail(&data.email, &conn).is_some() {
  141. err!("User already exists")
  142. }
  143. let mut user = User::new(email);
  144. user.save(&conn)?;
  145. if CONFIG.mail_enabled() {
  146. mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None)
  147. } else {
  148. let invitation = Invitation::new(data.email);
  149. invitation.save(&conn)
  150. }
  151. }
  152. #[post("/test/smtp", data = "<data>")]
  153. fn test_smtp(data: Json<InviteData>, _token: AdminToken) -> EmptyResult {
  154. let data: InviteData = data.into_inner();
  155. if CONFIG.mail_enabled() {
  156. mail::send_test(&data.email)
  157. } else {
  158. err!("Mail is not enabled")
  159. }
  160. }
  161. #[get("/logout")]
  162. fn logout(mut cookies: Cookies) -> Result<Redirect, ()> {
  163. cookies.remove(Cookie::named(COOKIE_NAME));
  164. Ok(Redirect::to(admin_url()))
  165. }
  166. #[get("/users")]
  167. fn get_users(_token: AdminToken, conn: DbConn) -> JsonResult {
  168. let users = User::get_all(&conn);
  169. let users_json: Vec<Value> = users.iter().map(|u| u.to_json(&conn)).collect();
  170. Ok(Json(Value::Array(users_json)))
  171. }
  172. #[post("/users/<uuid>/delete")]
  173. fn delete_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
  174. let user = match User::find_by_uuid(&uuid, &conn) {
  175. Some(user) => user,
  176. None => err!("User doesn't exist"),
  177. };
  178. user.delete(&conn)
  179. }
  180. #[post("/users/<uuid>/deauth")]
  181. fn deauth_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
  182. let mut user = match User::find_by_uuid(&uuid, &conn) {
  183. Some(user) => user,
  184. None => err!("User doesn't exist"),
  185. };
  186. Device::delete_all_by_user(&user.uuid, &conn)?;
  187. user.reset_security_stamp();
  188. user.save(&conn)
  189. }
  190. #[post("/users/<uuid>/remove-2fa")]
  191. fn remove_2fa(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
  192. let mut user = match User::find_by_uuid(&uuid, &conn) {
  193. Some(user) => user,
  194. None => err!("User doesn't exist"),
  195. };
  196. TwoFactor::delete_all_by_user(&user.uuid, &conn)?;
  197. user.totp_recover = None;
  198. user.save(&conn)
  199. }
  200. #[post("/users/update_revision")]
  201. fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult {
  202. User::update_all_revisions(&conn)
  203. }
  204. #[post("/config", data = "<data>")]
  205. fn post_config(data: Json<ConfigBuilder>, _token: AdminToken) -> EmptyResult {
  206. let data: ConfigBuilder = data.into_inner();
  207. CONFIG.update_config(data)
  208. }
  209. #[post("/config/delete")]
  210. fn delete_config(_token: AdminToken) -> EmptyResult {
  211. CONFIG.delete_user_config()
  212. }
  213. #[post("/config/backup_db")]
  214. fn backup_db(_token: AdminToken) -> EmptyResult {
  215. if *CAN_BACKUP {
  216. backup_database()
  217. } else {
  218. err!("Can't back up current DB (either it's not SQLite or the 'sqlite' binary is not present)");
  219. }
  220. }
  221. pub struct AdminToken {}
  222. impl<'a, 'r> FromRequest<'a, 'r> for AdminToken {
  223. type Error = &'static str;
  224. fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
  225. if CONFIG.disable_admin_token() {
  226. Outcome::Success(AdminToken {})
  227. } else {
  228. let mut cookies = request.cookies();
  229. let access_token = match cookies.get(COOKIE_NAME) {
  230. Some(cookie) => cookie.value(),
  231. None => return Outcome::Forward(()), // If there is no cookie, redirect to login
  232. };
  233. let ip = match request.guard::<ClientIp>() {
  234. Outcome::Success(ip) => ip.ip,
  235. _ => err_handler!("Error getting Client IP"),
  236. };
  237. if decode_admin(access_token).is_err() {
  238. // Remove admin cookie
  239. cookies.remove(Cookie::named(COOKIE_NAME));
  240. error!("Invalid or expired admin JWT. IP: {}.", ip);
  241. return Outcome::Forward(());
  242. }
  243. Outcome::Success(AdminToken {})
  244. }
  245. }
  246. }