//! The controller holds the business logic: it is the only place that knows the //! rules of the app. It talks to the outside world through the repository //! traits, so it depends neither on axum nor on sqlx. //! //! Authorization is carried by the **type**, not by a check inside the //! handlers. Each level derefs into the one below, so a manager can do //! everything a logged in user can: //! //! ```text //! AnonAppController anybody, logged in or not //! └─ AppController a logged in user (the api extractor answers 401 without a session) //! ├─ ManagerAppController a member of one unit, for that unit //! └─ AdminAppController an admin //! ``` //! //! A handler that takes an `AppController` cannot be reached anonymously: there //! is no way to forget the check. use std::{ops::Deref, sync::Arc}; use thiserror::Error; use crate::core::{ controller::{ authn::AuthnControllerError, bikes::BikesControllerError, reservations::ReservationsControllerError, }, models::{unit::UnitId, user::User}, repositories::{DatabaseRepository, RepositoryError}, }; pub mod authn; pub mod bikes; pub mod reservations; pub mod users; /// Entry point of the business logic, for anybody. Cheap to clone: handlers get /// one per request. #[derive(Clone)] pub struct AnonAppController { pub(crate) db: Arc>, } impl AnonAppController { pub fn new(db: Arc>) -> Self { Self { db } } /// Called by the api extractor once the session has been resolved pub fn auth(self, user: User) -> AppController { AppController { inner: self, user } } } /// A logged in user. #[derive(Clone)] pub struct AppController { inner: AnonAppController, user: User, } impl Deref for AppController { type Target = AnonAppController; fn deref(&self) -> &Self::Target { &self.inner } } impl AppController { pub fn user(&self) -> &User { &self.user } /// An admin manages every unit: refusing them here would only produce /// surprising 403s on routes they are otherwise allowed to use. pub fn try_into_manager(self, unit: UnitId) -> Result { if self.user.admin || self.user.units.iter().any(|u| u.id == unit) { Ok(ManagerAppController { inner: self, unit }) } else { Err(ControllerError::ManagerAuthorizationError(unit)) } } pub fn try_into_admin(self) -> Result { if self.user.admin { Ok(AdminAppController { inner: self }) } else { Err(ControllerError::AdminAuthorizationError) } } } /// A member of `unit`, acting for that unit #[derive(Clone)] pub struct ManagerAppController { inner: AppController, pub(crate) unit: UnitId, } impl Deref for ManagerAppController { type Target = AppController; fn deref(&self) -> &Self::Target { &self.inner } } pub struct AdminAppController { inner: AppController, } impl Deref for AdminAppController { type Target = AppController; fn deref(&self) -> &Self::Target { &self.inner } } impl AdminAppController { pub fn into_manager(self, unit: UnitId) -> ManagerAppController { ManagerAppController { inner: self.inner, unit, } } } /// Every error the api may have to translate into a status code. /// Domain specific errors are nested, so each area keeps its own enum. #[derive(Error, Debug)] pub enum ControllerError { #[error("Internal error: {0}")] InternalError(String), #[error("Generic repository error")] RepositoryError(#[from] RepositoryError), #[error("Authorization denied: the user is not part of the unit {0:?}")] ManagerAuthorizationError(UnitId), #[error("Authorization denied: the user is not an admin")] AdminAuthorizationError, #[error("Object's unit modification is not allowed")] ImmutableUnitModificationError, #[error("Authentication error: {0}")] Authn(#[from] AuthnControllerError), #[error("Bike specific error: {0}")] Bike(#[from] BikesControllerError), #[error("Reservation specific error: {0}")] Reservation(#[from] ReservationsControllerError), } impl ControllerError { /// Handy in the handlers: `Err(err) if err.is_not_found() => 404` pub fn is_not_found(&self) -> bool { matches!( self, ControllerError::RepositoryError(RepositoryError::NotFound(_)) ) } }