144 lines
5.4 KiB
Rust
144 lines
5.4 KiB
Rust
|
use anyhow::Result;
|
||
|
use tokio::net::ToSocketAddrs;
|
||
|
use serde::{Serialize, de::DeserializeOwned};
|
||
|
|
||
|
use crate::{Request, Response};
|
||
|
use crate::user_management::{User, RegisteredUser, UserManagerError};
|
||
|
|
||
|
const UNAUTH: &str = include_str!("pages/unauth.gmi");
|
||
|
|
||
|
pub trait UserManagementRoutes: private::Sealed {
|
||
|
fn add_um_routes<UserData: Serialize + DeserializeOwned + Default + 'static>(self, redir: &'static str) -> Self;
|
||
|
}
|
||
|
|
||
|
impl<A: ToSocketAddrs> UserManagementRoutes for crate::Builder<A> {
|
||
|
fn add_um_routes<UserData: Serialize + DeserializeOwned + Default + 'static>(self, redir: &'static str) -> Self {
|
||
|
self.add_route("/account", move|r|handle_base::<UserData>(r, redir))
|
||
|
.add_route("/account/askcert", move|r|handle_ask_cert::<UserData>(r, redir))
|
||
|
.add_route("/account/register", move|r|handle_register::<UserData>(r, redir))
|
||
|
.add_route("/account/login", move|r|handle_login::<UserData>(r, redir))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async fn handle_base<UserData: Serialize + DeserializeOwned>(request: Request, redirect: &'static str) -> Result<Response> {
|
||
|
Ok(match request.user::<UserData>()? {
|
||
|
User::Unauthenticated => {
|
||
|
Response::success_gemini(UNAUTH)
|
||
|
},
|
||
|
User::NotSignedIn(_) => {
|
||
|
Response::success_gemini(include_str!("pages/nsi.gmi"))
|
||
|
},
|
||
|
User::SignedIn(user) => {
|
||
|
render_settings_menu(user, redirect)
|
||
|
},
|
||
|
})
|
||
|
}
|
||
|
|
||
|
async fn handle_ask_cert<UserData: Serialize + DeserializeOwned>(request: Request, redirect: &'static str) -> Result<Response> {
|
||
|
Ok(match request.user::<UserData>()? {
|
||
|
User::Unauthenticated => {
|
||
|
Response::client_certificate_required()
|
||
|
},
|
||
|
User::NotSignedIn(_) => {
|
||
|
Response::success_gemini(include_str!("pages/askcert/success.gmi"))
|
||
|
},
|
||
|
User::SignedIn(user) => {
|
||
|
Response::success_gemini(format!(
|
||
|
include_str!("pages/askcert/exists.gmi"),
|
||
|
username = user.username(),
|
||
|
redirect = redirect,
|
||
|
))
|
||
|
},
|
||
|
})
|
||
|
}
|
||
|
|
||
|
async fn handle_register<UserData: Serialize + DeserializeOwned + Default>(request: Request, redirect: &'static str) -> Result<Response> {
|
||
|
Ok(match request.user::<UserData>()? {
|
||
|
User::Unauthenticated => {
|
||
|
Response::success_gemini(UNAUTH)
|
||
|
},
|
||
|
User::NotSignedIn(nsi) => {
|
||
|
if let Some(username) = request.input() {
|
||
|
match nsi.register::<UserData>(username.to_owned()) {
|
||
|
Err(UserManagerError::UsernameNotUnique) => {
|
||
|
Response::success_gemini(format!(
|
||
|
include_str!("pages/register/exists.gmi"),
|
||
|
username = username,
|
||
|
))
|
||
|
},
|
||
|
Ok(_) => {
|
||
|
Response::success_gemini(format!(
|
||
|
include_str!("pages/register/success.gmi"),
|
||
|
username = username,
|
||
|
redirect = redirect,
|
||
|
))
|
||
|
},
|
||
|
Err(e) => return Err(e.into())
|
||
|
}
|
||
|
} else {
|
||
|
Response::input_lossy("Please pick a username")
|
||
|
}
|
||
|
},
|
||
|
User::SignedIn(user) => {
|
||
|
render_settings_menu(user, redirect)
|
||
|
},
|
||
|
})
|
||
|
}
|
||
|
|
||
|
async fn handle_login<UserData: Serialize + DeserializeOwned + Default>(request: Request, redirect: &'static str) -> Result<Response> {
|
||
|
Ok(match request.user::<UserData>()? {
|
||
|
User::Unauthenticated => {
|
||
|
Response::success_gemini(UNAUTH)
|
||
|
},
|
||
|
User::NotSignedIn(nsi) => {
|
||
|
if let Some(username) = request.trailing_segments().get(0) {
|
||
|
if let Some(password) = request.input() {
|
||
|
match nsi.attach::<UserData>(username, Some(password.as_bytes())) {
|
||
|
Err(UserManagerError::PasswordNotSet) | Ok(None) => {
|
||
|
Response::success_gemini(format!(
|
||
|
include_str!("pages/login/wrong.gmi"),
|
||
|
username = username,
|
||
|
))
|
||
|
},
|
||
|
Ok(_) => {
|
||
|
Response::success_gemini(format!(
|
||
|
include_str!("pages/login/success.gmi"),
|
||
|
username = username,
|
||
|
redirect = redirect,
|
||
|
))
|
||
|
},
|
||
|
Err(e) => return Err(e.into()),
|
||
|
}
|
||
|
} else {
|
||
|
Response::input_lossy("Please enter your password")
|
||
|
}
|
||
|
} else if let Some(username) = request.input() {
|
||
|
Response::redirect_temporary_lossy(
|
||
|
format!("/account/login/{}", username).as_str()
|
||
|
)
|
||
|
} else {
|
||
|
Response::input_lossy("Please enter your username")
|
||
|
}
|
||
|
},
|
||
|
User::SignedIn(user) => {
|
||
|
render_settings_menu(user, redirect)
|
||
|
},
|
||
|
})
|
||
|
}
|
||
|
|
||
|
fn render_settings_menu<UserData: Serialize + DeserializeOwned>(
|
||
|
user: RegisteredUser<UserData>,
|
||
|
redirect: &str
|
||
|
) -> Response {
|
||
|
Response::success_gemini(format!(
|
||
|
include_str!("pages/settings.gmi"),
|
||
|
username = user.username(),
|
||
|
redirect = redirect,
|
||
|
))
|
||
|
}
|
||
|
|
||
|
mod private {
|
||
|
pub trait Sealed {}
|
||
|
impl<A> Sealed for crate::Builder<A> {}
|
||
|
}
|