ezidam: split home routes

This commit is contained in:
Philippe Loctaux 2023-04-05 18:09:32 +02:00
parent 92a08db8fe
commit cdcdba2b9a
8 changed files with 160 additions and 141 deletions

View file

@ -24,7 +24,7 @@ impl AdminMenu {
MainItem { MainItem {
id: AdminMenu::Exit.id(), id: AdminMenu::Exit.id(),
label: "Exit admin panel", label: "Exit admin panel",
link: uri!(routes::root::homepage).to_string(), link: uri!(routes::root::home::homepage).to_string(),
icon: Icon::Logout.svg, icon: Icon::Logout.svg,
sub: None, sub: None,
}, },

View file

@ -17,7 +17,7 @@ impl UserMenu {
vec![MainItem { vec![MainItem {
id: UserMenu::Home.id(), id: UserMenu::Home.id(),
label: "Home", label: "Home",
link: uri!(routes::root::homepage).to_string(), link: uri!(routes::root::home::homepage).to_string(),
icon: Icon::Home.svg, icon: Icon::Home.svg,
sub: None, sub: None,
}] }]

View file

@ -1,86 +1,26 @@
use super::prelude::*; use super::prelude::*;
use crate::tokens::{JWT_COOKIE_NAME, REFRESH_TOKEN_COOKIE_NAME}; use avatar::*;
use rocket::http::{Cookie, CookieJar}; use home::*;
use rocket::{get, post}; use logo::*;
use settings::Settings; use logout::*;
use users::User;
pub mod avatar;
pub mod home;
pub mod logo;
pub mod logout;
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![ routes![
logo, get_logo,
avatar, user_avatar,
homepage, homepage,
homepage_user, homepage_user,
homepage_redirect, homepage_redirect,
redirect_to_setup, redirect_to_setup,
logout, request_logout,
] ]
} }
#[get("/logo")]
async fn logo(mut db: Connection<Database>) -> Result<FileFromBytes> {
// Get settings
let settings = Settings::get(&mut *db).await?;
// HTTP response
Ok(FileFromBytes::from(settings.business_logo()))
}
#[cfg(test)]
mod test {
use crate::tests::*;
#[test]
fn logo() {
// Setup http server
let client = setup_rocket_testing();
// Make request
let response = client.get(uri!(super::logo)).dispatch();
assert_eq!(response.status(), Status::Ok);
// Assert size of logo
let logo_length = response.into_bytes().expect("bytes containing logo").len();
use settings::DEFAULT_BUSINESS_LOGO;
assert_eq!(
logo_length,
DEFAULT_BUSINESS_LOGO.len(),
"Invalid logo size in bytes, value was `{logo_length}`",
);
}
}
#[get("/avatar/<user_id>?<size>")]
async fn avatar(
mut db: Connection<Database>,
_user: JwtUser,
user_id: RocketUserID,
size: Option<u32>,
) -> Result<FileFromBytes> {
// Verify existence of user
User::get_by_login(&mut *db, user_id.0.as_ref())
.await?
.ok_or_else(|| Error::not_found(user_id.0.to_string()))?;
// Generate avatar
let avatar = task::spawn_blocking(move || {
let mut avatar = identicon_rs::Identicon::new(user_id.0);
// Set optional size
if let Some(size) = size {
avatar.set_scale(size)?;
}
avatar.export_png_data()
})
.await?;
// HTTP response
avatar
.map(FileFromBytes::from)
.map_err(Error::internal_server_error)
}
pub mod content { pub mod content {
use jwt::JwtClaims; use jwt::JwtClaims;
use rocket::serde::Serialize; use rocket::serde::Serialize;
@ -92,68 +32,3 @@ pub mod content {
pub user: JwtClaims, pub user: JwtClaims,
} }
} }
#[get("/")]
async fn redirect_to_setup(_setup: NeedSetup) -> Redirect {
Redirect::to(uri!(super::setup::setup))
}
#[get("/", rank = 2)]
async fn homepage(admin: JwtAdmin) -> Page {
Page::Homepage(content::Homepage { user: admin.0 })
}
#[get("/", rank = 3)]
async fn homepage_user(user: JwtUser) -> Page {
Page::Homepage(content::Homepage { user: user.0 })
}
#[get("/", rank = 4)]
async fn homepage_redirect() -> Redirect {
Redirect::to(uri!(super::oauth::authorize::authorize_ezidam))
}
#[post("/logout")]
async fn logout(
mut db: Connection<Database>,
refresh_token: RefreshToken,
cookie_jar: &CookieJar<'_>,
) -> Result<Redirect> {
let mut transaction = db.begin().await?;
let refresh_token = refresh_tokens::RefreshToken::get_one(&mut transaction, &refresh_token.0)
.await?
.ok_or_else(|| Error::not_found("Unknown refresh token"))?;
// Delete cookies
cookie_jar.remove(Cookie::named(JWT_COOKIE_NAME));
cookie_jar.remove(Cookie::named(REFRESH_TOKEN_COOKIE_NAME));
// If refresh token has already been used
if refresh_token.has_been_used() {
// Revoke all refresh tokens for user
refresh_tokens::RefreshToken::revoke_all_for_user(&mut transaction, refresh_token.user())
.await?;
transaction.commit().await?;
return Err(Error::forbidden("This refresh token has already been used"));
}
// If token has been revoked
if refresh_token.is_revoked() {
return Err(Error::forbidden("This refresh token has been revoked"));
}
// If token has expired
if refresh_token.has_expired() {
return Err(Error::forbidden("This refresh token has expired"));
}
// Revoke token
refresh_token.revoke(&mut transaction).await?;
transaction.commit().await?;
Ok(Redirect::to(uri!(homepage)))
}

View file

@ -0,0 +1,34 @@
use crate::routes::prelude::*;
use rocket::get;
use users::User;
#[get("/avatar/<user_id>?<size>")]
pub async fn user_avatar(
mut db: Connection<Database>,
_user: JwtUser,
user_id: RocketUserID,
size: Option<u32>,
) -> Result<FileFromBytes> {
// Verify existence of user
User::get_by_login(&mut *db, user_id.0.as_ref())
.await?
.ok_or_else(|| Error::not_found(user_id.0.to_string()))?;
// Generate avatar
let avatar = task::spawn_blocking(move || {
let mut avatar = identicon_rs::Identicon::new(user_id.0);
// Set optional size
if let Some(size) = size {
avatar.set_scale(size)?;
}
avatar.export_png_data()
})
.await?;
// HTTP response
avatar
.map(FileFromBytes::from)
.map_err(Error::internal_server_error)
}

View file

@ -0,0 +1,23 @@
use super::content;
use crate::routes::prelude::*;
use rocket::get;
#[get("/")]
pub async fn homepage(admin: JwtAdmin) -> Page {
Page::Homepage(content::Homepage { user: admin.0 })
}
#[get("/", rank = 2)]
pub async fn homepage_user(user: JwtUser) -> Page {
Page::Homepage(content::Homepage { user: user.0 })
}
#[get("/", rank = 3)]
pub async fn redirect_to_setup(_setup: NeedSetup) -> Redirect {
Redirect::to(uri!(crate::routes::setup::setup))
}
#[get("/", rank = 4)]
pub async fn homepage_redirect() -> Redirect {
Redirect::to(uri!(crate::routes::oauth::authorize::authorize_ezidam))
}

View file

@ -0,0 +1,36 @@
use crate::routes::prelude::*;
use rocket::get;
use settings::Settings;
#[get("/logo")]
pub async fn get_logo(mut db: Connection<Database>) -> Result<FileFromBytes> {
// Get settings
let settings = Settings::get(&mut *db).await?;
// HTTP response
Ok(FileFromBytes::from(settings.business_logo()))
}
#[cfg(test)]
mod test {
use crate::tests::*;
#[test]
fn logo() {
// Setup http server
let client = setup_rocket_testing();
// Make request
let response = client.get(uri!(super::logo)).dispatch();
assert_eq!(response.status(), Status::Ok);
// Assert size of logo
let logo_length = response.into_bytes().expect("bytes containing logo").len();
use settings::DEFAULT_BUSINESS_LOGO;
assert_eq!(
logo_length,
DEFAULT_BUSINESS_LOGO.len(),
"Invalid logo size in bytes, value was `{logo_length}`",
);
}
}

View file

@ -0,0 +1,49 @@
use crate::routes::prelude::*;
use crate::tokens::{JWT_COOKIE_NAME, REFRESH_TOKEN_COOKIE_NAME};
use rocket::http::{Cookie, CookieJar};
use rocket::post;
#[post("/logout")]
pub async fn request_logout(
mut db: Connection<Database>,
refresh_token: RefreshToken,
cookie_jar: &CookieJar<'_>,
) -> Result<Redirect> {
let mut transaction = db.begin().await?;
let refresh_token = refresh_tokens::RefreshToken::get_one(&mut transaction, &refresh_token.0)
.await?
.ok_or_else(|| Error::not_found("Unknown refresh token"))?;
// Delete cookies
cookie_jar.remove(Cookie::named(JWT_COOKIE_NAME));
cookie_jar.remove(Cookie::named(REFRESH_TOKEN_COOKIE_NAME));
// If refresh token has already been used
if refresh_token.has_been_used() {
// Revoke all refresh tokens for user
refresh_tokens::RefreshToken::revoke_all_for_user(&mut transaction, refresh_token.user())
.await?;
transaction.commit().await?;
return Err(Error::forbidden("This refresh token has already been used"));
}
// If token has been revoked
if refresh_token.is_revoked() {
return Err(Error::forbidden("This refresh token has been revoked"));
}
// If token has expired
if refresh_token.has_expired() {
return Err(Error::forbidden("This refresh token has expired"));
}
// Revoke token
refresh_token.revoke(&mut transaction).await?;
transaction.commit().await?;
Ok(Redirect::to(uri!(super::homepage)))
}

View file

@ -13,7 +13,7 @@ pub fn routes() -> Vec<Route> {
#[get("/setup")] #[get("/setup")]
async fn setup_completed(_setup: CompletedSetup) -> Redirect { async fn setup_completed(_setup: CompletedSetup) -> Redirect {
Redirect::to(uri!(super::root::homepage)) Redirect::to(uri!(super::root::home::homepage))
} }
#[get("/setup", rank = 2)] #[get("/setup", rank = 2)]
@ -100,7 +100,9 @@ async fn create_first_account(
transaction.commit().await?; transaction.commit().await?;
// Redirect to home // Redirect to home
Ok(Either::Left(Redirect::to(uri!(super::root::homepage)))) Ok(Either::Left(Redirect::to(uri!(
super::root::home::homepage
))))
} }
#[cfg(test)] #[cfg(test)]