use std::sync::Arc;
use crate::middleware::PathMatcher;
#[derive(Clone)]
pub struct Routes {
inner: Arc<RoutesInner>,
}
struct RoutesInner {
login: String,
logout: String,
mfa: String,
default_redirect: String,
}
impl Routes {
pub fn new(prefix: &str, login: &str, mfa: &str, logout: &str) -> Self {
Self {
inner: Arc::new(RoutesInner {
login: create_uri(prefix, login),
logout: create_uri(prefix, logout),
mfa: create_uri(prefix, mfa),
default_redirect: normalize_uri_part("/"),
}),
}
}
pub fn set_default_redirect(self, redirect: &str) -> Self {
Self {
inner: Arc::new(RoutesInner {
login: self.inner.login.clone(),
logout: self.inner.logout.clone(),
mfa: self.inner.mfa.clone(),
default_redirect: normalize_uri_part(redirect),
}),
}
}
pub fn default_redirect(&self) -> &str {
&self.inner.default_redirect
}
pub fn login(&self) -> &str {
&self.inner.login
}
pub fn logout(&self) -> &str {
&self.inner.logout
}
pub fn mfa(&self) -> &str {
&self.inner.mfa
}
}
impl From<Routes> for PathMatcher {
fn from(value: Routes) -> Self {
PathMatcher::new(vec![value.login()], true)
}
}
impl Default for Routes {
fn default() -> Self {
Self {
inner: Arc::new(RoutesInner {
login: "/login".to_owned(),
logout: "/logout".to_owned(),
mfa: "/login/mfa".to_owned(),
default_redirect: normalize_uri_part("/"),
}),
}
}
}
fn create_uri(prefix: &str, endpoint: &str) -> String {
if prefix.trim() == "" || prefix.trim() == "/" {
normalize_uri_part(endpoint)
} else {
format!(
"{}{}",
normalize_uri_part(prefix),
normalize_uri_part(endpoint)
)
}
}
fn normalize_uri_part(part: &str) -> String {
let mut normalized = part.to_owned().to_lowercase();
if normalized.ends_with("/") && normalized.len() > 1 {
normalized = normalized[0..(part.len() - 1)].to_owned();
}
if !normalized.starts_with("/") {
normalized = format!("/{normalized}");
}
normalized
}
#[cfg(test)]
mod test {
use super::Routes;
use crate::{middleware::PathMatcher, session::config::normalize_uri_part};
#[test]
fn should_be_able_to_add_routes_to_path_matcher() {
let routes = Routes::new("", "/custom-login", "/custom-mfa", "/logout");
let mut path_matcher: PathMatcher = routes.into();
path_matcher.add(vec!["/public"]);
assert!(!path_matcher.matches("/custom-login"));
assert!(!path_matcher.matches("/public"));
}
#[test]
fn path_matcher_can_be_created_from_routes() {
let routes = Routes::new("", "/custom-login", "/custom-mfa", "/logout");
let path_matcher: PathMatcher = routes.into();
assert!(!path_matcher.matches("/custom-login"));
assert!(path_matcher.matches("/custom-mfa"));
assert!(path_matcher.matches("/logout"));
}
#[test]
fn should_ignore_empty_prefix() {
let routes = Routes::new("", "/login", "/mfa", "/logout");
assert_eq!(routes.login(), "/login");
}
#[test]
fn normalize_uri_part_test() {
assert_eq!("/login/mfa", normalize_uri_part("/login/mfa/"));
assert_eq!("/login/mfa", normalize_uri_part("login/mfa/"));
assert_eq!("/login/mfa", normalize_uri_part("login/mfa"));
assert_eq!("/", normalize_uri_part("/"));
assert_eq!("/", normalize_uri_part(""));
}
}