Skip to content

Commit

Permalink
WIP: use a 401 catcher for admin page
Browse files Browse the repository at this point in the history
  • Loading branch information
stefan0xC committed Nov 17, 2022
1 parent 44ad2bc commit ec261a8
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 35 deletions.
74 changes: 40 additions & 34 deletions src/api/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ use std::env;
use rocket::serde::json::Json;
use rocket::{
form::Form,
http::{Cookie, CookieJar, SameSite, Status},
http::{Cookie, CookieJar, MediaType, SameSite, Status},
request::{self, FromRequest, Outcome, Request},
response::{content::RawHtml as Html, Redirect},
Route,
Catcher, Route,
};

use crate::{
Expand All @@ -24,6 +24,13 @@ use crate::{
},
CONFIG, VERSION,
};
pub fn catchers() -> Vec<Catcher> {
if !CONFIG.disable_admin_token() && !CONFIG.is_admin_token_set() {
catchers![]
} else {
catchers![unauthorized]
}
}

pub fn routes() -> Vec<Route> {
if !CONFIG.disable_admin_token() && !CONFIG.is_admin_token_set() {
Expand All @@ -32,8 +39,6 @@ pub fn routes() -> Vec<Route> {

routes![
admin_login,
admin_redirect,
post_unauthorized,
get_users_json,
get_user_json,
post_admin_login,
Expand All @@ -56,7 +61,6 @@ pub fn routes() -> Vec<Route> {
delete_organization,
diagnostics,
get_diagnostics_config,
get_diagnostics_fallback
]
}

Expand Down Expand Up @@ -144,15 +148,28 @@ fn admin_url(referer: Referer) -> String {

#[derive(Responder)]
enum AdminResponse {
#[response(status = 200)]
Ok(ApiResult<Html<String>>),
#[response(status = 401)]
Unauthorized(ApiResult<Html<String>>),
#[response(status = 429)]
TooManyRequests(ApiResult<Html<String>>),
}

#[get("/", rank = 2)]
#[catch(401)]
fn unauthorized(request: &Request<'_>) -> Result<Redirect,Error> {
if request.format() == Some(&MediaType::JSON) {
err_code!("Authorization failed.", Status::Unauthorized.code);
}
//TODO: make this work
let redirect = if let Ok(path) = request.segments::<std::path::PathBuf>(0..) {
format!("?redirect={}", path.display())
} else {
String::new()
};
debug!("{redirect}");
Ok(Redirect::to(format!("{}/login", admin_url(Referer(None)))))
}

#[get("/login")]
fn admin_login() -> ApiResult<Html<String>> {
render_admin_login(None)
}
Expand All @@ -172,33 +189,23 @@ fn render_admin_login(msg: Option<&str>) -> ApiResult<Html<String>> {
Ok(Html(text))
}

#[get("/<_..>", rank = 5)]
fn admin_redirect(referer: Referer) -> Redirect {
Redirect::temporary(admin_url(referer))
}

#[post("/<_..>", rank = 5)]
fn post_unauthorized() -> EmptyResult {
err_code!("Authorization failed.", Status::Unauthorized.code);
}

#[derive(FromForm)]
struct LoginForm {
token: String,
}

#[post("/", data = "<data>")]
fn post_admin_login(data: Form<LoginForm>, cookies: &CookieJar<'_>, ip: ClientIp) -> AdminResponse {
#[post("/login", data = "<data>")]
fn post_admin_login(data: Form<LoginForm>, cookies: &CookieJar<'_>, ip: ClientIp) -> Result<Redirect, AdminResponse> {
let data = data.into_inner();

if crate::ratelimit::check_limit_admin(&ip.ip).is_err() {
return AdminResponse::TooManyRequests(render_admin_login(Some("Too many requests, try again later.")));
return Err(AdminResponse::TooManyRequests(render_admin_login(Some("Too many requests, try again later."))));
}

// If the token is invalid, redirect to login page
if !_validate_token(&data.token) {
error!("Invalid admin token. IP: {}", ip.ip);
AdminResponse::Unauthorized(render_admin_login(Some("Invalid admin token, please try again.")))
Err(AdminResponse::Unauthorized(render_admin_login(Some("Invalid admin token, please try again."))))
} else {
// If the token received is valid, generate JWT and save it as a cookie
let claims = generate_admin_claims();
Expand All @@ -212,7 +219,8 @@ fn post_admin_login(data: Form<LoginForm>, cookies: &CookieJar<'_>, ip: ClientIp
.finish();

cookies.add(cookie);
AdminResponse::Ok(render_admin_page())
//AdminResponse::Ok(render_admin_page())
Ok(Redirect::to(admin_url(Referer(None))))
}
}

Expand Down Expand Up @@ -632,11 +640,6 @@ fn get_diagnostics_config(_token: AdminToken) -> Json<Value> {
Json(support_json)
}

#[get("/diagnostics/config", rank = 2)]
fn get_diagnostics_fallback() -> EmptyResult {
err_code!("Authorization failed.", Status::Unauthorized.code);
}

#[post("/config", data = "<data>")]
fn post_config(data: Json<ConfigBuilder>, _token: AdminToken) -> EmptyResult {
let data: ConfigBuilder = data.into_inner();
Expand All @@ -663,30 +666,33 @@ pub struct AdminToken {}
impl<'r> FromRequest<'r> for AdminToken {
type Error = &'static str;

async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
if CONFIG.disable_admin_token() {
Outcome::Success(AdminToken {})
Outcome::Success(Self {})
} else {
let cookies = request.cookies();

let access_token = match cookies.get(COOKIE_NAME) {
Some(cookie) => cookie.value(),
None => return Outcome::Forward(()), // If there is no cookie, redirect to login
None => return Outcome::Failure((Status::Unauthorized, "Unauthorized")),
};

let ip = match ClientIp::from_request(request).await {
Outcome::Success(ip) => ip.ip,
_ => err_handler!("Error getting Client IP"),
_ => {
error!(target: "auth", "Unauthorized Error: {}", "Error getting Client IP");
return Outcome::Failure((Status::Unauthorized, "Error getting Client IP"));
}
};

if decode_admin(access_token).is_err() {
// Remove admin cookie
cookies.remove(Cookie::build(COOKIE_NAME, "").path(admin_path()).finish());
error!("Invalid or expired admin JWT. IP: {}.", ip);
return Outcome::Forward(());
return Outcome::Failure((Status::Unauthorized, "Session expired"));
}

Outcome::Success(AdminToken {})
Outcome::Success(Self {})
}
}
}
1 change: 1 addition & 0 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use rocket::serde::json::Json;
use serde_json::Value;

pub use crate::api::{
admin::catchers as admin_catchers,
admin::routes as admin_routes,
core::catchers as core_catchers,
core::purge_sends,
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error>
.mount([basepath, "/notifications"].concat(), api::notifications_routes())
.register([basepath, "/"].concat(), api::web_catchers())
.register([basepath, "/api"].concat(), api::core_catchers())
.register([basepath, "/admin"].concat(), api::admin_catchers())
.manage(pool)
.manage(api::start_notification_server())
.attach(util::AppHeaders())
Expand Down
4 changes: 3 additions & 1 deletion src/static/templates/admin/diagnostics.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,9 @@
supportString += "* Reverse proxy and version: \n";
supportString += "* Other relevant information: \n";
let jsonResponse = await fetch('{{urlpath}}/admin/diagnostics/config');
let jsonResponse = await fetch('{{urlpath}}/admin/diagnostics/config', {
'headers': { 'Accept': 'application/json' }
});
if (!jsonResponse.ok) {
alert("Generation failed: " + jsonResponse.statusText);
throw new Error(jsonResponse);
Expand Down

0 comments on commit ec261a8

Please sign in to comment.