2020-11-22 04:03:56 +00:00
|
|
|
use anyhow::Result;
|
|
|
|
use serde::{Serialize, de::DeserializeOwned};
|
|
|
|
|
2020-11-26 18:50:46 +00:00
|
|
|
#[cfg(feature = "dashmap")]
|
|
|
|
use dashmap::DashMap;
|
|
|
|
#[cfg(not(feature = "dashmap"))]
|
|
|
|
use std::collections::HashMap;
|
|
|
|
#[cfg(not(feature = "dashmap"))]
|
|
|
|
use std::sync::RwLock;
|
|
|
|
|
2020-11-22 07:56:41 +00:00
|
|
|
use std::future::Future;
|
|
|
|
|
2020-11-22 05:43:15 +00:00
|
|
|
use crate::{Document, Request, Response};
|
|
|
|
use crate::types::document::HeadingLevel;
|
2020-11-26 18:50:46 +00:00
|
|
|
use crate::user_management::{
|
|
|
|
User,
|
|
|
|
RegisteredUser,
|
|
|
|
UserManagerError,
|
|
|
|
user::NotSignedInUser,
|
|
|
|
};
|
2020-11-22 04:03:56 +00:00
|
|
|
|
2020-11-22 06:05:34 +00:00
|
|
|
/// Import this trait to use [`add_um_routes()`](Self::add_um_routes())
|
2020-11-22 04:03:56 +00:00
|
|
|
pub trait UserManagementRoutes: private::Sealed {
|
2020-11-22 06:05:34 +00:00
|
|
|
/// Add pre-configured routes to the serve to handle authentication
|
|
|
|
///
|
|
|
|
/// Specifically, the following routes are added:
|
|
|
|
/// * `/account`, the main settings & login page
|
|
|
|
/// * `/account/askcert`, a page which always prompts for a certificate
|
|
|
|
/// * `/account/register`, for users to register a new account
|
|
|
|
/// * `/account/login`, for users to link their certificate to an existing account
|
|
|
|
/// * `/account/password`, to change the user's password
|
|
|
|
///
|
|
|
|
/// If this method is used, no more routes should be added under `/account`. If you
|
|
|
|
/// would like to direct a user to login from your application, you should send them
|
|
|
|
/// to `/account`, which will start the login/registration flow.
|
|
|
|
///
|
|
|
|
/// The `redir` argument allows you to specify the point that users will be directed
|
|
|
|
/// to return to once their account has been created.
|
2020-11-26 18:50:46 +00:00
|
|
|
fn add_um_routes<UserData: Serialize + DeserializeOwned + Default + 'static>(self) -> Self;
|
2020-11-22 07:56:41 +00:00
|
|
|
|
|
|
|
/// Add a special route that requires users to be logged in
|
|
|
|
///
|
|
|
|
/// In addition to the normal [`Request`], your handler will recieve a copy of the
|
|
|
|
/// [`RegisteredUser`] for the current user. If a user tries to connect to the page
|
|
|
|
/// without logging in, they will be prompted to register or link an account.
|
|
|
|
///
|
|
|
|
/// To use this method, ensure that [`add_um_routes()`](Self::add_um_routes()) has
|
|
|
|
/// also been called.
|
|
|
|
fn add_authenticated_route<UserData, Handler, F>(
|
|
|
|
self,
|
|
|
|
path: &'static str,
|
|
|
|
handler: Handler,
|
|
|
|
) -> Self
|
|
|
|
where
|
|
|
|
UserData: Serialize + DeserializeOwned + 'static + Send + Sync,
|
2020-11-23 01:35:47 +00:00
|
|
|
Handler: Clone + Send + Sync + 'static + Fn(Request, RegisteredUser<UserData>) -> F,
|
2020-11-22 07:56:41 +00:00
|
|
|
F: Send + Sync + 'static + Future<Output = Result<Response>>;
|
2020-11-23 01:56:15 +00:00
|
|
|
|
|
|
|
/// Add a special route that requires users to be logged in AND takes input
|
|
|
|
///
|
|
|
|
/// Like with [`add_authenticated_route()`](Self::add_authenticated_route()), this
|
|
|
|
/// prompts the user to log in if they haven't already, but additionally prompts the
|
|
|
|
/// user for input before running the handler with both the user object and the input
|
|
|
|
/// they provided.
|
|
|
|
///
|
|
|
|
/// To a user, this might look something like this:
|
|
|
|
/// * Click a link to `/your/route`
|
|
|
|
/// * See a screen asking you to sign in or create an account
|
|
|
|
/// * Create a new account, and return to the app.
|
|
|
|
/// * Now, clicking the link shows the prompt provided.
|
|
|
|
/// * After entering some value, the user receives the response from the handler.
|
|
|
|
///
|
|
|
|
/// For a user whose already logged in, this will just look like a normal input route,
|
|
|
|
/// where they enter some query and see a page. This method just takes the burden of
|
|
|
|
/// having to check if the user sent a query string and respond with an INPUT response
|
|
|
|
/// if not.
|
|
|
|
///
|
|
|
|
/// To use this method, ensure that [`add_um_routes()`](Self::add_um_routes()) has
|
|
|
|
/// also been called.
|
|
|
|
fn add_authenticated_input_route<UserData, Handler, F>(
|
|
|
|
self,
|
|
|
|
path: &'static str,
|
|
|
|
prompt: &'static str,
|
|
|
|
handler: Handler,
|
|
|
|
) -> Self
|
|
|
|
where
|
|
|
|
UserData: Serialize + DeserializeOwned + 'static + Send + Sync,
|
|
|
|
Handler: Clone + Send + Sync + 'static + Fn(Request, RegisteredUser<UserData>, String) -> F,
|
|
|
|
F: Send + Sync + 'static + Future<Output = Result<Response>>;
|
2020-11-22 04:03:56 +00:00
|
|
|
}
|
|
|
|
|
2020-12-01 19:43:15 +00:00
|
|
|
impl UserManagementRoutes for crate::Server {
|
2020-11-22 06:05:34 +00:00
|
|
|
/// Add pre-configured routes to the serve to handle authentication
|
|
|
|
///
|
2020-11-22 07:56:41 +00:00
|
|
|
/// See [`UserManagementRoutes::add_um_routes()`]
|
2020-11-26 18:50:46 +00:00
|
|
|
fn add_um_routes<UserData: Serialize + DeserializeOwned + Default + 'static>(self) -> Self {
|
2020-11-28 00:50:01 +00:00
|
|
|
let clients_page = Response::success_gemini(include_str!("pages/clients.gmi"));
|
|
|
|
|
2020-11-23 03:24:36 +00:00
|
|
|
#[allow(unused_mut)]
|
2020-11-26 18:50:46 +00:00
|
|
|
let mut modified_self = self.add_route("/account", handle_base::<UserData>)
|
|
|
|
.add_route("/account/askcert", handle_ask_cert::<UserData>)
|
2020-11-28 00:50:01 +00:00
|
|
|
.add_route("/account/register", handle_register::<UserData>)
|
|
|
|
.add_route("/account/clients", clients_page);
|
2020-11-23 03:24:36 +00:00
|
|
|
|
|
|
|
#[cfg(feature = "user_management_advanced")] {
|
|
|
|
modified_self = modified_self
|
2020-11-26 18:50:46 +00:00
|
|
|
.add_route("/account/login", handle_login::<UserData>)
|
2020-11-23 03:24:36 +00:00
|
|
|
.add_route("/account/password", handle_password::<UserData>);
|
|
|
|
}
|
|
|
|
|
|
|
|
modified_self
|
2020-11-22 04:03:56 +00:00
|
|
|
}
|
2020-11-22 07:56:41 +00:00
|
|
|
|
|
|
|
/// Add a special route that requires users to be logged in
|
|
|
|
///
|
|
|
|
/// See [`UserManagementRoutes::add_authenticated_route()`]
|
|
|
|
fn add_authenticated_route<UserData, Handler, F>(
|
|
|
|
self,
|
|
|
|
path: &'static str,
|
|
|
|
handler: Handler,
|
|
|
|
) -> Self
|
|
|
|
where
|
|
|
|
UserData: Serialize + DeserializeOwned + 'static + Send + Sync,
|
2020-11-23 01:35:47 +00:00
|
|
|
Handler: Clone + Send + Sync + 'static + Fn(Request, RegisteredUser<UserData>) -> F,
|
2020-11-22 07:56:41 +00:00
|
|
|
F: Send + Sync + 'static + Future<Output = Result<Response>>
|
|
|
|
{
|
2020-11-26 02:25:35 +00:00
|
|
|
self.add_route(path, move|request: Request| {
|
2020-11-22 07:56:41 +00:00
|
|
|
let handler = handler.clone();
|
|
|
|
async move {
|
2020-11-26 18:50:46 +00:00
|
|
|
let segments = request.path_segments();
|
|
|
|
let segments = segments.iter().map(String::as_ref).collect::<Vec<&str>>();
|
2020-11-22 07:56:41 +00:00
|
|
|
Ok(match request.user::<UserData>()? {
|
|
|
|
User::Unauthenticated => {
|
2020-11-26 18:50:46 +00:00
|
|
|
render_unauth_page(segments)
|
2020-11-22 07:56:41 +00:00
|
|
|
},
|
2020-11-26 18:50:46 +00:00
|
|
|
User::NotSignedIn(user) => {
|
|
|
|
save_redirect(&user, segments);
|
2020-11-22 07:56:41 +00:00
|
|
|
Response::success_gemini(NSI)
|
|
|
|
},
|
|
|
|
User::SignedIn(user) => {
|
|
|
|
(handler)(request, user).await?
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2020-11-23 01:56:15 +00:00
|
|
|
|
|
|
|
/// Add a special route that requires users to be logged in AND takes input
|
|
|
|
///
|
|
|
|
/// See [`UserManagementRoutes::add_authenticated_input_route()`]
|
|
|
|
fn add_authenticated_input_route<UserData, Handler, F>(
|
|
|
|
self,
|
|
|
|
path: &'static str,
|
|
|
|
prompt: &'static str,
|
|
|
|
handler: Handler,
|
|
|
|
) -> Self
|
|
|
|
where
|
|
|
|
UserData: Serialize + DeserializeOwned + 'static + Send + Sync,
|
|
|
|
Handler: Clone + Send + Sync + 'static + Fn(Request, RegisteredUser<UserData>, String) -> F,
|
|
|
|
F: Send + Sync + 'static + Future<Output = Result<Response>>
|
|
|
|
{
|
|
|
|
self.add_authenticated_route(path, move|request, user| {
|
|
|
|
let handler = handler.clone();
|
|
|
|
async move {
|
|
|
|
if let Some(input) = request.input().map(str::to_owned) {
|
|
|
|
(handler.clone())(request, user, input).await
|
|
|
|
} else {
|
2020-12-01 21:36:29 +00:00
|
|
|
Ok(Response::input(prompt))
|
2020-11-23 01:56:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2020-11-22 04:03:56 +00:00
|
|
|
}
|
|
|
|
|
2020-11-26 18:50:46 +00:00
|
|
|
#[cfg(feature = "user_management_advanced")]
|
|
|
|
const NSI: &str = include_str!("pages/nsi.gmi");
|
|
|
|
#[cfg(not(feature = "user_management_advanced"))]
|
|
|
|
const NSI: &str = include_str!("pages/nopass/nsi.gmi");
|
|
|
|
|
|
|
|
// TODO periodically clean these
|
|
|
|
#[cfg(feature = "dashmap")]
|
|
|
|
lazy_static::lazy_static! {
|
2020-12-01 22:38:26 +00:00
|
|
|
static ref PENDING_REDIRECTS: DashMap<[u8; 32], String> = Default::default();
|
2020-11-26 18:50:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(feature = "dashmap"))]
|
|
|
|
lazy_static::lazy_static! {
|
2020-12-01 19:43:15 +00:00
|
|
|
static ref PENDING_REDIRECTS: RwLock<HashMap<[u8; 32], String>> = Default::default();
|
2020-11-26 18:50:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn handle_base<UserData: Serialize + DeserializeOwned>(request: Request) -> Result<Response> {
|
|
|
|
let segments = request.trailing_segments().iter().map(String::as_str).collect::<Vec<&str>>();
|
2020-11-22 04:03:56 +00:00
|
|
|
Ok(match request.user::<UserData>()? {
|
|
|
|
User::Unauthenticated => {
|
2020-11-26 18:50:46 +00:00
|
|
|
render_unauth_page(segments)
|
2020-11-22 04:03:56 +00:00
|
|
|
},
|
2020-11-26 18:50:46 +00:00
|
|
|
User::NotSignedIn(usr) => {
|
|
|
|
save_redirect(&usr, segments);
|
2020-11-22 05:43:15 +00:00
|
|
|
Response::success_gemini(NSI)
|
2020-11-22 04:03:56 +00:00
|
|
|
},
|
|
|
|
User::SignedIn(user) => {
|
2020-11-26 18:50:46 +00:00
|
|
|
render_settings_menu(user)
|
2020-11-22 04:03:56 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-11-26 18:50:46 +00:00
|
|
|
async fn handle_ask_cert<UserData: Serialize + DeserializeOwned>(request: Request) -> Result<Response> {
|
2020-11-22 04:03:56 +00:00
|
|
|
Ok(match request.user::<UserData>()? {
|
|
|
|
User::Unauthenticated => {
|
2020-12-01 21:36:29 +00:00
|
|
|
Response::client_certificate_required("Please select a client certificate to proceed.")
|
2020-11-22 04:03:56 +00:00
|
|
|
},
|
2020-11-26 18:50:46 +00:00
|
|
|
User::NotSignedIn(nsi) => {
|
|
|
|
let segments = request.trailing_segments().iter().map(String::as_str).collect::<Vec<&str>>();
|
|
|
|
save_redirect(&nsi, segments);
|
2020-11-23 03:24:36 +00:00
|
|
|
#[cfg(feature = "user_management_advanced")] {
|
|
|
|
Response::success_gemini(include_str!("pages/askcert/success.gmi"))
|
|
|
|
}
|
|
|
|
#[cfg(not(feature = "user_management_advanced"))] {
|
|
|
|
Response::success_gemini(include_str!("pages/nopass/askcert/success.gmi"))
|
|
|
|
}
|
2020-11-22 04:03:56 +00:00
|
|
|
},
|
|
|
|
User::SignedIn(user) => {
|
|
|
|
Response::success_gemini(format!(
|
|
|
|
include_str!("pages/askcert/exists.gmi"),
|
|
|
|
username = user.username(),
|
2020-11-26 18:50:46 +00:00
|
|
|
redirect = get_redirect(&user),
|
2020-11-22 04:03:56 +00:00
|
|
|
))
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-11-26 18:50:46 +00:00
|
|
|
async fn handle_register<UserData: Serialize + DeserializeOwned + Default>(request: Request) -> Result<Response> {
|
2020-11-22 04:03:56 +00:00
|
|
|
Ok(match request.user::<UserData>()? {
|
|
|
|
User::Unauthenticated => {
|
2020-11-26 18:50:46 +00:00
|
|
|
render_unauth_page(&[""])
|
2020-11-22 04:03:56 +00:00
|
|
|
},
|
|
|
|
User::NotSignedIn(nsi) => {
|
|
|
|
if let Some(username) = request.input() {
|
|
|
|
match nsi.register::<UserData>(username.to_owned()) {
|
|
|
|
Err(UserManagerError::UsernameNotUnique) => {
|
2020-11-23 03:24:36 +00:00
|
|
|
#[cfg(feature = "user_management_advanced")] {
|
|
|
|
Response::success_gemini(format!(
|
|
|
|
include_str!("pages/register/exists.gmi"),
|
|
|
|
username = username,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
#[cfg(not(feature = "user_management_advanced"))] {
|
|
|
|
Response::success_gemini(format!(
|
|
|
|
include_str!("pages/register/exists.gmi"),
|
|
|
|
username = username,
|
|
|
|
))
|
|
|
|
}
|
2020-11-22 04:03:56 +00:00
|
|
|
},
|
2020-11-26 18:50:46 +00:00
|
|
|
Ok(user) => {
|
2020-11-23 03:24:36 +00:00
|
|
|
#[cfg(feature = "user_management_advanced")] {
|
|
|
|
Response::success_gemini(format!(
|
|
|
|
include_str!("pages/register/success.gmi"),
|
|
|
|
username = username,
|
2020-11-26 18:50:46 +00:00
|
|
|
redirect = get_redirect(&user),
|
2020-11-23 03:24:36 +00:00
|
|
|
))
|
|
|
|
}
|
|
|
|
#[cfg(not(feature = "user_management_advanced"))] {
|
|
|
|
Response::success_gemini(format!(
|
|
|
|
include_str!("pages/nopass/register/success.gmi"),
|
|
|
|
username = username,
|
2020-11-26 18:50:46 +00:00
|
|
|
redirect = get_redirect(&user),
|
2020-11-23 03:24:36 +00:00
|
|
|
))
|
|
|
|
}
|
2020-11-22 04:03:56 +00:00
|
|
|
},
|
|
|
|
Err(e) => return Err(e.into())
|
|
|
|
}
|
|
|
|
} else {
|
2020-12-01 21:36:29 +00:00
|
|
|
Response::input("Please pick a username")
|
2020-11-22 04:03:56 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
User::SignedIn(user) => {
|
2020-11-26 18:50:46 +00:00
|
|
|
render_settings_menu(user)
|
2020-11-22 04:03:56 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-11-23 03:24:36 +00:00
|
|
|
#[cfg(feature = "user_management_advanced")]
|
2020-11-26 18:50:46 +00:00
|
|
|
async fn handle_login<UserData: Serialize + DeserializeOwned + Default>(request: Request) -> Result<Response> {
|
2020-11-22 04:03:56 +00:00
|
|
|
Ok(match request.user::<UserData>()? {
|
|
|
|
User::Unauthenticated => {
|
2020-11-26 18:50:46 +00:00
|
|
|
render_unauth_page(&[""])
|
2020-11-22 04:03:56 +00:00
|
|
|
},
|
|
|
|
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,
|
|
|
|
))
|
|
|
|
},
|
2020-11-26 18:50:46 +00:00
|
|
|
Ok(Some(user)) => {
|
2020-11-22 04:03:56 +00:00
|
|
|
Response::success_gemini(format!(
|
|
|
|
include_str!("pages/login/success.gmi"),
|
|
|
|
username = username,
|
2020-11-26 18:50:46 +00:00
|
|
|
redirect = get_redirect(&user),
|
2020-11-22 04:03:56 +00:00
|
|
|
))
|
|
|
|
},
|
|
|
|
Err(e) => return Err(e.into()),
|
|
|
|
}
|
|
|
|
} else {
|
2020-12-01 21:36:29 +00:00
|
|
|
Response::sensitive_input("Please enter your password")
|
2020-11-22 04:03:56 +00:00
|
|
|
}
|
|
|
|
} else if let Some(username) = request.input() {
|
2020-12-01 21:36:29 +00:00
|
|
|
Response::redirect_temporary(
|
2020-11-22 04:03:56 +00:00
|
|
|
format!("/account/login/{}", username).as_str()
|
|
|
|
)
|
|
|
|
} else {
|
2020-12-01 21:36:29 +00:00
|
|
|
Response::input("Please enter your username")
|
2020-11-22 04:03:56 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
User::SignedIn(user) => {
|
2020-11-26 18:50:46 +00:00
|
|
|
render_settings_menu(user)
|
2020-11-22 04:03:56 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-11-23 03:24:36 +00:00
|
|
|
#[cfg(feature = "user_management_advanced")]
|
2020-11-22 05:43:15 +00:00
|
|
|
async fn handle_password<UserData: Serialize + DeserializeOwned + Default>(request: Request) -> Result<Response> {
|
|
|
|
Ok(match request.user::<UserData>()? {
|
|
|
|
User::Unauthenticated => {
|
2020-11-26 18:50:46 +00:00
|
|
|
render_unauth_page(&[""])
|
2020-11-22 05:43:15 +00:00
|
|
|
},
|
2020-11-26 18:50:46 +00:00
|
|
|
User::NotSignedIn(nsi) => {
|
|
|
|
save_redirect(&nsi, &[""]);
|
2020-11-22 05:43:15 +00:00
|
|
|
Response::success_gemini(NSI)
|
|
|
|
},
|
|
|
|
User::SignedIn(mut user) => {
|
|
|
|
if let Some(password) = request.input() {
|
|
|
|
user.set_password(password)?;
|
|
|
|
Response::success_gemini(include_str!("pages/password/success.gmi"))
|
|
|
|
} else {
|
2020-12-01 21:36:29 +00:00
|
|
|
Response::sensitive_input(
|
2020-11-22 05:43:15 +00:00
|
|
|
format!("Please enter a {}password",
|
|
|
|
if user.has_password() {
|
|
|
|
"new "
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
}
|
|
|
|
)
|
2020-12-01 21:36:29 +00:00
|
|
|
)
|
2020-11-22 05:43:15 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-11-22 04:03:56 +00:00
|
|
|
fn render_settings_menu<UserData: Serialize + DeserializeOwned>(
|
2020-11-26 18:50:46 +00:00
|
|
|
user: RegisteredUser<UserData>
|
2020-11-22 04:03:56 +00:00
|
|
|
) -> Response {
|
2020-11-23 03:24:36 +00:00
|
|
|
let mut document = Document::new();
|
|
|
|
document
|
2020-11-22 05:43:15 +00:00
|
|
|
.add_heading(HeadingLevel::H1, "User Settings")
|
|
|
|
.add_blank_line()
|
|
|
|
.add_text(&format!("Welcome {}!", user.username()))
|
|
|
|
.add_blank_line()
|
2020-11-26 18:50:46 +00:00
|
|
|
.add_link(get_redirect(&user).as_str(), "Back to the app")
|
2020-11-23 03:24:36 +00:00
|
|
|
.add_blank_line();
|
|
|
|
|
|
|
|
#[cfg(feature = "user_management_advanced")]
|
|
|
|
document
|
2020-11-22 05:43:15 +00:00
|
|
|
.add_text(
|
|
|
|
if user.has_password() {
|
|
|
|
concat!(
|
|
|
|
"You currently have a password set. This can be used to link any new",
|
|
|
|
" certificates or clients to your account. If you don't remember your",
|
|
|
|
" password, or would like to change it, you may do so here.",
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
concat!(
|
|
|
|
"You don't currently have a password set! Without a password, you cannot",
|
|
|
|
" link any new certificates to your account, and if you lose your current",
|
|
|
|
" client or certificate, you won't be able to recover your account.",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
.add_blank_line()
|
2020-11-23 03:24:36 +00:00
|
|
|
.add_link("/account/password", if user.has_password() { "Change password" } else { "Set password" });
|
|
|
|
|
|
|
|
document.into()
|
2020-11-22 04:03:56 +00:00
|
|
|
}
|
|
|
|
|
2020-11-26 18:50:46 +00:00
|
|
|
fn render_unauth_page<'a>(
|
|
|
|
redirect: impl AsRef<[&'a str]>,
|
|
|
|
) -> Response {
|
|
|
|
Response::success_gemini(format!(
|
|
|
|
include_str!("pages/unauth.gmi"),
|
|
|
|
redirect = redirect.as_ref().join("/"),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn save_redirect<'a>(
|
|
|
|
user: &NotSignedInUser,
|
|
|
|
redirect: impl AsRef<[&'a str]>,
|
|
|
|
) {
|
|
|
|
let mut redirect = redirect.as_ref().join("/");
|
|
|
|
redirect.insert(0, '/');
|
|
|
|
if redirect.len() > 1 {
|
|
|
|
#[cfg(feature = "dashmap")]
|
|
|
|
let ref_to_map = &*PENDING_REDIRECTS;
|
|
|
|
#[cfg(not(feature = "dashmap"))]
|
|
|
|
let mut ref_to_map = PENDING_REDIRECTS.write().unwrap();
|
|
|
|
|
2020-12-01 19:43:15 +00:00
|
|
|
debug!("Added \"{}\" as redirect for cert {:x?}", redirect, &user.certificate);
|
|
|
|
ref_to_map.insert(user.certificate, redirect);
|
2020-11-26 18:50:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_redirect<T: Serialize + DeserializeOwned>(user: &RegisteredUser<T>) -> String {
|
2020-12-01 19:43:15 +00:00
|
|
|
let cert = user.active_certificate().unwrap();
|
2020-11-26 18:50:46 +00:00
|
|
|
|
|
|
|
#[cfg(feature = "dashmap")]
|
2020-12-01 19:43:15 +00:00
|
|
|
let maybe_redir = PENDING_REDIRECTS.get(cert).map(|r| r.clone());
|
2020-11-26 18:50:46 +00:00
|
|
|
#[cfg(not(feature = "dashmap"))]
|
|
|
|
let ref_to_map = PENDING_REDIRECTS.read().unwrap();
|
|
|
|
#[cfg(not(feature = "dashmap"))]
|
2020-12-01 19:43:15 +00:00
|
|
|
let maybe_redir = ref_to_map.get(cert).cloned();
|
2020-11-26 18:50:46 +00:00
|
|
|
|
|
|
|
let redirect = maybe_redir.unwrap_or_else(||"/".to_string());
|
2020-12-01 19:43:15 +00:00
|
|
|
debug!("Accessed redirect to \"{}\" for cert {:x?}", redirect, cert);
|
2020-11-26 18:50:46 +00:00
|
|
|
redirect
|
|
|
|
}
|
|
|
|
|
2020-11-22 04:03:56 +00:00
|
|
|
mod private {
|
|
|
|
pub trait Sealed {}
|
2020-12-01 19:43:15 +00:00
|
|
|
impl Sealed for crate::Server {}
|
2020-11-22 04:03:56 +00:00
|
|
|
}
|